Compare commits

..

115 Commits

Author SHA1 Message Date
Kirill Chibisov
8f08fbae89 Winit version 0.29.15 2024-03-13 14:09:30 +04:00
Kirill Chibisov
10a492b5bc bugfix(x11): crash when querying xsettings prop fails
Don't crash when xsettings query fails with _present_ xsettings.

Closes: https://github.com/rust-windowing/winit/issues/3573
2024-03-13 14:09:30 +04:00
Kirill Chibisov
aaaf08972d Winit version 0.29.14 2024-03-06 13:12:25 +04:00
Kirill Chibisov
ba4660dade fix(xkb): text not being None when composing
When composing the text was not reset to `None` leading to input in
some applications e.g. alacritty.

Links: https://github.com/alacritty/alacritty/issues/7806
2024-03-06 13:12:25 +04:00
daxpedda
090800e6a6 Bump x11-dl to v2.19.1 2024-03-06 13:12:25 +04:00
Kirill Chibisov
70fc8f66e9 chore(wayland): don't reapply same cursor grab
Some compositors break when re-taking the same grab.

Closes: https://github.com/rust-windowing/winit/issues/3566
2024-03-06 13:12:25 +04:00
John Nunley
7601a1506d bugfix(x11): fix incorrect delta filtering
Invert the mouse delta filter, so it aligns with the intention of
filtering values lower than epsilon.

Signed-off-by: John Nunley <dev@notgull.net>
Closes: https://github.com/rust-windowing/winit/issues/3558
2024-03-06 13:12:25 +04:00
daxpedda
b1dad450ee Winit version 0.29.13 2024-03-02 12:56:01 +01:00
daxpedda
8d66df7f6f Web: fix crash with ControlFlow::Wait|WaitUntil 2024-03-02 12:56:01 +01:00
Kirill Chibisov
1c5fcf3309 Winit version 0.29.12 2024-03-01 14:05:35 +04:00
Kirill Chibisov
58c89c1ffc On X11, fix use after free during xinput2 processing
Fixes #3536.
2024-03-01 14:05:35 +04:00
John Nunley
2dae807c4f On X11, filter out tiny device mouse events
Usually, if mouse events are equal to (0, 0) we filter them out.
However, if the event is very close to zero it will still be given to
the user. In some cases this can be caused by bad float math on the X11
server side.

Fix it by filtering absolute values smaller than floating point epsilon.

Signed-off-by: John Nunley <dev@notgull.net>
Closes: #3500
2024-03-01 14:05:35 +04:00
Kirill Chibisov
8b6c8ef323 Winit version 0.29.11 2024-02-26 13:15:27 +04:00
Kirill Chibisov
2e63493776 On X11, replay modifiers consumed by XIM 2024-02-26 13:15:27 +04:00
daxpedda
d2acea95cc Fix nightly CI 2024-02-26 13:15:27 +04:00
Friz64
454dc56a6e Update documentation regarding set_cursor_position (#3521) 2024-02-26 13:15:27 +04:00
Kirill Chibisov
016fd47d0d On X11, force resend modifiers when focus changes
Given that `ModifiersChanged` is a window event, it means that clients
may track it for each window individually, thus not sending it between
focus changes may result in modifiers getting desynced on the consumer
side.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
8ce5f6ea41 Pin bumpalo to 3.14.0 on CI 2024-02-26 13:15:27 +04:00
Andriy
64084c5cf0 On Wayland, send DeviceEvent::Motion 2024-02-26 13:15:27 +04:00
Kirill Chibisov
96d29ab26c Fix warnings with latest nightly 2024-02-26 13:15:27 +04:00
Kirill Chibisov
3e42fa364c On Windows, fix nightly warnings 2024-02-26 13:15:27 +04:00
Kirill Chibisov
cb855b87cc On X11, use events modifiers to detect state
While there's a separate event to deliver modifiers for keyboard,
unfortunately, it's not even remotely reflects the modifiers state.

Thus use events along side regular modifier updates to correctly
detect the state. Also, apply the modifiers from the regular
key event by converting their state to xkb modifiers state.

Links: https://github.com/alacritty/alacritty/issues/7549
Closes: #3388
2024-02-26 13:15:27 +04:00
Mads Marquart
9c033ce101 Pin ahash in CI (#3498)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-02-26 13:15:27 +04:00
Kirill Chibisov
54ad02e4b9 On Wayland, update title from AboutToWait
Fixes #3472.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
00fe65630e On X11, don't require XIM to be present
In general, we may want to use xinput v2 for keyboard input in such
cases, so we have compose going, but for now just don't crash if
there's no XIM.
2024-02-26 13:15:27 +04:00
Jeremy Soller
961b675d34 On Orbital, implement various Window methods
Implement the following methods on the `Window`:
  - `Window::set_cursor_grab`.
  - `Window::set_cursor_visible`.
  - `Window::drag_window`.
  - `Window::drag_resize_window`.
  - `Window::set_transparent`.
  - `Window::set_visible`.
  - `Window::is_visible`.
  - `Window::set_resizable`.
  - `Window::is_resizable`.
  - `Window::set_maximized`.
  - `Window::is_maximized`.
  - `Window::set_decorations`.
  - `Window::is_decorated`.
  - `Window::set_window_level`.

To make locked pointer useful, the `DeviceEvent::MouseMotion`
event was also implemented.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
bcd2fba4a0 On X11, extract event handlers
Make code more clear wrt explicit returns during event handling,
which may lead to skipped IME event handling.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
0135fe51ae On X11, store window target on EventProcessor
Remove the redundant `Rc` to access the window target.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
ed600e415a On X11, don't require XSETTINGS
We could fail to setup property watcher and fail to start, thus
don't require XSETTINGS to work.

Fixes: df8805c0 (On X11, reload DPI on _XSETTINGS_SETTINGS)
2024-02-26 13:15:27 +04:00
Jeremy Soller
ad892c6949 On Orbital, map keys to NamedKey when possible 2024-02-26 13:15:27 +04:00
Jeremy Soller
a6b25643ad On Orbital, implement KeyEventExtModifiersSupplement
This also fixes `logical_key` and `text` not reported in `KeyEvent`.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
bc5b6ff8ce Fix compatibility with platforms without AtomicU64
Fixes #3456.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
ba6254bc25 Account for WAYLAND_SOCKET when detecting Wayland
Fixes #3459.
2024-02-26 13:15:27 +04:00
Amr Bashir
bd2d2760f0 On Windows, apply ScaleFactorChanged new size if different than OS
This fixes an issue when setting the position of the window on a new
monitor and immediately maximizing it

```rs
window.set_outer_position::<PhysicalPosition<u32>>((2000, 200).into());
window.set_maximized(true);
```

Due to the nature of the event loop, the requested position and
maximization state will apply correctly but due to the fact that the new
position is a different monitor, a `ScaleFactorChanged` is emitted
afterwards to the evenloop and a new size is set while the window is
still maximized which results in a window that has `WS_MAXIMZE` window
style but doesn't cover the whole monitor.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
3de08204d3 On Wayland, fix min/max inner size setting
The size is only applied on the next `wl_surface::commit` thus we
must trigger the redraw.
2024-02-26 13:15:27 +04:00
Kirill Chibisov
c9030f06c0 On Wayland, send Focused(false) once seats left
Given that we merge all the seats, we should consider that window
is not focused once all seats wl_keyboards are no longer present.

We use seats instead of keyboards to track focus to protect against
wl_keyboard::leave not being delivered when removing the seat
(usually it's not the case though).

Fixes: #3376
2024-02-26 13:15:27 +04:00
John Nunley
3035546b17 On X11, reload DPI on _XSETTINGS_SETTINGS
This also fixes the deadlock when such reload may happen.

Fixes: #3383
Signed-off-by: John Nunley <dev@notgull.net>
Signed-off-by: Kirill Chibisov <contact@kchibisov.com>
2024-02-26 13:15:27 +04:00
Kirill Chibisov
57cb3126d6 On Wayland, disable Occluded handling
Change in state requires a redraw, however drawing when getting
`Occluded` with vsync will block indefinitely, thus the event in
it's current state is rather useless.

To solve this issue winit needs a way to determine whether the user
paused/continued their render loop, so it can commit on their behalf.

This commit also forces redraw when getting configure.

Links: https://github.com/rust-windowing/winit/issues/3442
2024-02-26 13:15:27 +04:00
Ulrik de Muelenaere
221b2e71cd bugfix: Fix swapped instance and general class names on X11
This let statement swapped the two names, resulting in incorrect
behavior since commit d7ec899d. That commit did not actually introduce
the swap, but the previous code swapped it again before setting the
WM_CLASS property, so no issue was ever observed.

It also brings the documentation in line with the implementation since the
parent commit, and with the ICCCM standard, which states the following
about the WM_CLASS property [1]:

  The two strings, respectively, are:
  * A string that names the particular instance of the application [...]
  * A string that names the general class of applications [...]

[1] https://www.x.org/releases/current/doc/xorg-docs/icccm/icccm.html#WM_CLASS_Property
2024-02-26 13:15:27 +04:00
Diggory Hardy
0dc376a9ef Improve error when X11/Wayland is not present 2024-02-26 13:15:27 +04:00
Nick
85052c09bb Send the event before waking up the message pump. (#3418) 2024-02-26 13:15:27 +04:00
Mads Marquart
be63581654 Fix Android examples link in README (#3420) 2024-02-26 13:15:27 +04:00
白山風露
45b5f3b031 On Windows, set fullscreen/maximized creating window 2024-02-26 13:15:27 +04:00
Yuze Jiang
0270516067 On macOS, fix incorrect IME cursor rect origin
`window.set_ime_cursor_area` requires a position from the top left
corner according to the documentation. However, the NSRect's origin is
from bottom left. The y coordinate should correctly calculated for the
NSRect.
2024-02-26 13:15:27 +04:00
daxpedda
a90cd1c9ad Web: support Firefox privacy.resistFingerprinting (#3371) 2024-02-26 13:15:27 +04:00
daxpedda
dbeeaeffd9 Winit version 0.29.10 2024-01-15 12:59:15 +04:00
daxpedda
c4bfbbe417 ci: Fix dead code error on nightly
See rust-lang/rust#118297
2024-01-15 12:59:15 +04:00
daxpedda
978ec7dfec Web: increase cursor position accuracy (#3380) 2024-01-15 12:59:15 +04:00
daxpedda
87f44ecffb Web: account for canvas being focused already (#3369) 2024-01-15 12:59:15 +04:00
Kirill Chibisov
da82971f52 Winit version 0.29.9 2024-01-05 15:09:05 +04:00
Emil Ernerfeldt
324dd5fa86 On macOS, reported shifted key with shift+Ctrl/Cmd
Fixes #3078.
2024-01-05 15:09:05 +04:00
Kirill Chibisov
fdedda38d2 On X11, fix error propagation in EventLoop::new
Fixes #3350.
2024-01-05 15:09:05 +04:00
Kirill Chibisov
cf0a533461 Issue resize due to scale change on Wayland
This is a regression from 8f6de4ef.

Links: https://github.com/alacritty/alacritty/issues/7559
2024-01-05 15:09:05 +04:00
Kirill Chibisov
017ff26e7d On X11 and Wayland, fix numpad up being ArrowLeft
Links: https://github.com/alacritty/alacritty/issues/7533
2024-01-05 15:09:05 +04:00
Kirill Chibisov
6eb79f04c8 Winit version 0.29.8 2023-12-31 20:13:31 +04:00
Kirill Chibisov
2bf12c74dc On X11, fix IME input lagging behind
IME events and requests where drained on one-by-one basis, however
we should drain all of them at once and send to user.

Links: https://github.com/alacritty/alacritty/issues/7514
2023-12-31 20:13:31 +04:00
John Nunley
2998bbf7db On X11, cache the XRandR extension version 2023-12-31 20:13:31 +04:00
Kirill Chibisov
3f82a6a90d On X11, fix ModifiersChanged from xdotool
xdotool will update modifiers before Xkb will actually send event
updating them, thus the modifiers will be updating even before the
actual update, which is unfortunate.

Links: https://github.com/alacritty/alacritty/issues/7502
2023-12-31 20:13:31 +04:00
Kirill Chibisov
2e610111b0 On X11, update keymap on XkbMapNotify
This is required to handle xmodmap.

Fixes #3338.
2023-12-31 20:13:31 +04:00
Kirill Chibisov
63d52aae32 On Wayland, fix Window::request_inner_size during resize
The user may change the size during the on-going resize, meaning that
the size will desync with winit's internal loop which breaks viewporter
setup with fractional scaling.

Links: https://github.com/alacritty/alacritty/issues/7474
2023-12-31 20:13:31 +04:00
John Nunley
5b4f97edac On X11, query for higher Xrandr version
This appears to be the solution for the elusive #3335 issue. Previously,
in the Xlib backend, we used the "XRRQueryVersion" function to query for
the Xrandr version, and used that to determine whether we should use the
"GetScreenResources" call or the "GetScreenResourcesCurrent" call.

However, we passed the version "0, 0" into "XRRQueryVersion".
Previously with Xlib this wasn't a problem, as Xlib ignores the version
you pass in and substitutes it with the version of RandR it expects.

https://gitlab.freedesktop.org/xorg/lib/libxrandr/-/blob/master/src/Xrandr.c?ref_type=heads#L386-387

The way that "XRRQueryVersion" is implemented on the server end, it
compares the version passed into the request with the version supported
by the server. If the server's version is greater than the client
version, it just returns the client version. If the client's version is
greater, it passes the server's version. Since we were passing in "0, 0"
this means that the server returned "0, 0".

https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/randr/rrdispatch.c?ref_type=heads#L50-59

To determine whether we use "GetScreenResources" or
"GetScreenResourcesCurrent", we compare the version returned by the
server against "1, 3". Since we got "0, 0"- a version of XRandR so old
it doesn't even exist- we use "GetScreenResources".

The problem manifests in that "GetScreenResources" can take several
seconds to query the screen state based on the current hardware
configuration. On the other hand, "GetScreenResourcesCurrent" is fast;
it uses the server's hardware cache if it is available.

This problem is visible in XTrace. On the latest `master`:

```
000:<:00c2: 12: RANDR-Request(140,0): QueryVersion major-version=0 minor-version=0
000:>:00c2:32: Reply to QueryVersion: major-version=0 minor-version=0
000:<:00c3:  8: RANDR-Request(140,8): GetScreenResources window=0x0000076e
000:>:00c3:1600: Reply to GetScreenResources:
```

On the `v0.28.0` tag:

```
000:<:0019: 12: RANDR-Request(140,0): QueryVersion major-version=1 minor-version=6
000:>:0019:32: Reply to QueryVersion: major-version=1 minor-version=6
...later
000:<:002d:  8: RANDR-Request(140,25): GetScreenResourcesCurrent window=0x0000076e
000:>:002d:1600: Reply to GetScreenResourcesCurrent
```

This commit fixes this issue by requesting "1, 3" instead. This returns
the version we expect, where we can now use "GetScreenResourcesCurrent"
properly.

Fixes #3335

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-31 20:13:31 +04:00
Kirill Chibisov
9135eb4024 Winit version 0.29.7 2023-12-27 10:10:59 +04:00
John Nunley
23b3c127fd bugfix: Change value sent to X server during minimize
Closes #3327

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-27 10:10:59 +04:00
John Nunley
b343f45500 bugfix: Reload Xft database on DPI change
Closes #1228
2023-12-27 10:10:59 +04:00
Kirill Chibisov
572d61f9ba Winit version 0.29.6 2023-12-24 23:55:50 +04:00
Uli Schlachter
87fc19826b On X11, simplify available_monitors() impl
This code confused me. I tried to understand it. I tried to simplify it
while keeping the functional style. But in the end, this just seems too
complicated for its own good. Just doing the exact same thing with a
match statement and the question mark operator makes it sooo much more
obvious what is happening.

Signed-off-by: Uli Schlachter <psychon@znc.in>
2023-12-24 23:55:50 +04:00
Kirill Chibisov
11d1b7a980 Fix run_on_demand exiting on consequent call
Fixes #3284.
2023-12-24 23:55:50 +04:00
Kirill Chibisov
5ca810ba8f On Wayland, fix WindowEvent::Destroyed delivery 2023-12-24 23:55:50 +04:00
Alex Butler
2d1607b3f7 bugfix(rwh): Bump rwh_05 min version to 0.5.2
Correct min version to support "std" feature
2023-12-24 23:55:50 +04:00
Markus Siglreithmaier
a32e232020 On Windows, remove internal WindowWrapper (#3294)
HWND in windows-sys doesn't require a newtype wrapper for Send/Sync.
2023-12-24 23:55:50 +04:00
daxpedda
9b03bb7276 Fix some doc nits (#3274) 2023-12-24 23:55:50 +04:00
Markus Siglreithmaier
e39596151c On Windows, refactor dynamic function definitions and raw input keyboard handling (#3286) 2023-12-24 23:55:50 +04:00
daxpedda
5289b4f206 On Web, fix context menu not being disabled 2023-12-24 23:55:50 +04:00
Kirill Chibisov
380dc4c451 Winit version 0.29.5 2023-12-22 00:39:22 +04:00
wjian23
6fbdbce6dd On Windows, fix IME area not working 2023-12-22 00:39:22 +04:00
Kirill Chibisov
cafcaa2cdc On Wayland, ensure initial resize delivery
While we correctly configure the sizes, we also need to actually resize
the frame on initial configure and send geometry.

Fixes #3277.
2023-12-22 00:39:22 +04:00
Kirill Chibisov
e00204e626 On windows, remove empty file 2023-12-22 00:39:22 +04:00
Kirill Chibisov
a5b89bfe5a On Wayland, fix resize being sent on focus change
Fixes #3263.
2023-12-22 00:39:22 +04:00
Amr Bashir
44052a093e On Windows, fix set_fullscreen early return for Fullscreen::Borderless(None) 2023-12-22 00:39:22 +04:00
Friz64
40cee238e2 Update sctk-adwaita to 0.8 2023-12-22 00:39:22 +04:00
Héctor Ramón
3dc5c42387 Fix typo in get_xft_dpi 2023-12-22 00:39:22 +04:00
Marijn Suijten
8c4a6ddcb4 On Wayland, make wl_subcompositor protocol optional
This protocol is only used for (optional) Client Side Decorations
(where) the compositor still takes the burden of compositing various
window parts together, via subsurfaces that all belong to a single
window.

If this core protocol is not available, as is the case on gamescope,
disable CSD.
2023-12-22 00:39:22 +04:00
Uli Schlachter
5011a67f6d m: Update to x11rb 0.13.0
The only breaking change is that x11rb no longer reports an error when
querying the WmSizeHints of a window that does not have this property
set. For this reason, the return type of WmSizeHintsCookie::Reply()
changed from Result<WmSizeHints, SomeError> to
Result<Option<WmSizeHints>, SomeError>.

In update_normal_hints(), previously a cryptic error would be reported
to the caller. Instead, this now uses unwrap_or_default() to get a
WmSizeHints. All fields of WmSizeHints are Options, so this produces an
empty object.

resize_increments() queries a value from the window and returns an
Option. Previously, the error for "missing property" was turned into
None via .ok(). This commit adds a call to flatten() to also turn
"property not set" into None.

Finally, request_user_attention() queries a window's WmHints property
and updates one field of it. The code already uses unwrap_or_default()
to deal with missing properties, so just a call to flatten() is needed
to merge "missing property" and "error while querying" into one.

Other changes in x11rb do not seem to affect this crate.

x11rb's MSRV increased from 1.56 to 1.63, which is still below the MSRV
of this crate, which is 1.65.

Signed-off-by: Uli Schlachter <psychon@znc.in>
2023-12-22 00:39:22 +04:00
Fredrik Fornwall
d621ab5018 On Windows, avoid panic in video_modes() 2023-12-22 00:39:22 +04:00
Leon
966c033a6c Changes and improvements to the documentation (#3253)
* FEATURES.md improvements

* docs improvements

* typo fix: 'mean' -> 'main'

Co-authored-by: daxpedda <daxpedda@gmail.com>

---------

Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-12-22 00:39:22 +04:00
Xiaopeng Li
1681410ca8 fix refresh_rate_millihertz on macOS (#3254)
* fix refresh_rate_millihertz on macOS

* round after conversion to mHz

* add changelog entry
2023-12-22 00:39:22 +04:00
John Nunley
a82327c73f bugfix(x11): Use the right atom type in focus_window()
Closes #3248 by removing an Xlibism I forgot about

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-22 00:39:22 +04:00
John Nunley
e71f765dea bugfix(x11): Properly interpret float data in drag ops
Closes #3245

notgull forgot to properly interpret float data from the X server,
making him tonight's biggest loser.

Signed-off-by: John Nunley <dev@notgull.net>
2023-12-22 00:39:22 +04:00
Mads Marquart
0738528931 Remove unused .gitmodules 2023-12-22 00:39:22 +04:00
Emil Ernerfeldt
8119c72d64 On macOS, remove spurious error logging when handling Fn
Fixes #3246.
2023-12-22 00:39:22 +04:00
Kirill Chibisov
43f29f0481 Winit version 0.29.4 2023-11-24 18:13:46 +04:00
John Nunley
266219f27f On X11, reload DPI on PropertyChange
Signed-off-by: John Nunley <dev@notgull.net>
Fixes #1228.
2023-11-24 18:13:46 +04:00
Kirill Chibisov
7449534ba2 Fix infinite recursion in BadIcon reporting (#3237) 2023-11-24 18:13:46 +04:00
Mads Marquart
7aa202b872 Make Android docs build on docs.rs (#3236) 2023-11-24 18:13:46 +04:00
Arend van Beelen jr
06cec065d4 Fix crash when running iPad build on macOS 2023-11-24 18:13:46 +04:00
Kirill Chibisov
f968e64ac8 On macOS, fix assertion when pressing Fn key 2023-11-24 18:13:46 +04:00
Kirill Chibisov
a97309690e On Wayland, fix wl_surface being dropped first
The surface was automatically dropped due to new RAII type in SCTK
when dropping the Window, which was not the case at some point with
SCTK.

Thus destroying objects associated with it where causing issues
with some window managers.

Links: https://github.com/neovide/neovide/issues/2109
2023-11-24 18:13:46 +04:00
Olivier Goffart
dec45ce0ff On Windows, fix set_control_flow from `AboutToWait
In case the AboutToWait event sets the control flow to another value
it's not being used on this iteration.

Fixes #3215.
2023-11-24 18:13:46 +04:00
Nathan Lilienthal
f709ac667f On macOS, send a Resized event after ScaleFactorChanged
Fixes #3213.
2023-11-24 18:13:46 +04:00
Kirill Chibisov
ecbe04caa7 On X11, try alternative cursor icon names as well
This should cover more icons.
2023-11-24 18:13:46 +04:00
Marijn Suijten
7103514ae8 Disable default-features for the ndk crate
We decided to add `rwh_06` to the `default` list of features in the
`ndk` to [nudge users to upgrade], but this forces `winit` to always
(transitively) include `raw-window-handle 0.6` even if the user has
set a different `rwh_xx` feature on the `winit` crate.  `winit` already
forwards the respective `rwh_xx` feaure to the `ndk` crate anyway, so
this default should just be turned off.

At the time of writing this is the only `default` feature of the `ndk`.

Links: https://github.com/rust-mobile/ndk/pull/434#issuecomment-1752089087
2023-11-24 18:13:46 +04:00
DevJac
8b5aa33a88 Fix typo in pre_present_notify docs
Fix typo and other small grammar corrections.
2023-11-24 18:13:46 +04:00
Linda_pp
7a3b486965 Fix crash when minimizing example on Windows 2023-11-24 18:13:46 +04:00
Jasper Bekkers
fc9c78cb56 On Windows, fix MT safety when starting drag 2023-11-24 18:13:46 +04:00
Kirill Chibisov
525219716c Winit version 0.29.3 2023-10-28 20:55:35 +04:00
Kirill Chibisov
7f851fe433 Clarify scale_factor docs
Wayland scales each window individually, thus make it clear. Also
recommend against using the `MonitorHandle::scale_factor`.

Fixes #3183.
2023-10-28 20:55:35 +04:00
Kirill Chibisov
5dea2a4734 On macOS, add support for Window::set_blur 2023-10-28 20:55:35 +04:00
Kirill Chibisov
821fc63a9c On Windows, add support for Window::set_transparent 2023-10-28 20:55:35 +04:00
Kirill Chibisov
8e9a3d2dd3 On Wayland, improve initial user size handling
Keep the user provided size in the original values and convert only
when we're getting a `configure` event. On some compositors will
have a scale available, so it'll work, however with some we'll
still have old 'pick 1` as default.

Also configure_bounds when compositor tells the user to pick the size,
that will ensure that initial `with_inner_size` won't grow beyond the
working area.

Fixes #3187.
2023-10-28 20:55:35 +04:00
Kirill Chibisov
70a77b8534 On Wayland, fix RedrawRequsted loop
The `dirty` is never cleared when decorations are hidden without
`sctk-adwaita`.

Fixes #3177.
2023-10-28 20:55:35 +04:00
Marijn Suijten
a5bb6d67f7 On wasm, provide intradoc-link for spawn() function in EventLoop docs (#3178) 2023-10-28 20:55:35 +04:00
J-P Nurmi
33a2e4cebd On macOS, fix deadlock during nested event loops (e.g. rfd) 2023-10-28 20:55:35 +04:00
Kirill Chibisov
0ee26986d8 On Winows, Fix deedlock with WM_MOUSEMOVE
The lock was still present in `None` path.

Fixes: d37d1a03b(On Windows, fix deadlock during `Cursor{Enter,Leave}`)
2023-10-28 20:55:35 +04:00
Kirill Chibisov
ec41dddd0d Fix unused import warnings on nightly 2023-10-28 20:55:35 +04:00
Kirill Chibisov
7a872903a4 On Windows, fix deadlock during Cursor{Enter,Leave}
The lock was not released when calling back to the user.

Fixes #3171.
2023-10-28 20:55:35 +04:00
96 changed files with 5666 additions and 2776 deletions

View File

@@ -81,7 +81,7 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "deps/apk-builder"]
path = deps/apk-builder
url = https://github.com/rust-windowing/android-rs-glue

View File

@@ -11,6 +11,128 @@ Unreleased` header.
# Unreleased
# 0.29.15
- On X11, fix crash due to xsettings query on systems with incomplete xsettings.
# 0.29.14
- On X11/Wayland, fix `text` and `text_with_all_modifiers` not being `None` during compose.
- On Wayland, don't reapply cursor grab when unchanged.
- On X11, fix a bug where some mouse events would be unexpectedly filtered out.
# 0.29.13
- On Web, fix possible crash with `ControlFlow::Wait` and `ControlFlow::WaitUntil`.
# 0.29.12
- On X11, fix use after free during xinput2 handling.
- On X11, filter close to zero values in mouse device events
# 0.29.11
- On Wayland, fix DeviceEvent::Motion not being sent
- On X11, don't require XIM to run.
- On X11, fix xkb state not being updated correctly sometimes leading to wrong input.
- Fix compatibility with 32-bit platforms without 64-bit atomics.
- On macOS, fix incorrect IME cursor rect origin.
- On X11, fix swapped instance and general class names.
- On Windows, fixed a race condition when sending an event through the loop proxy.
- On Wayland, disable `Occluded` event handling.
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
- On Orbital, implement `KeyEventExtModifierSupplement`.
- On Orbital, map keys to `NamedKey` when possible.
- On Orbital, implement `set_cursor_grab`.
- On Orbital, implement `set_cursor_visible`.
- On Orbital, implement `drag_window`.
- On Orbital, implement `drag_resize_window`.
- On Orbital, implement `set_transparent`.
- On Orbital, implement `set_visible`.
- On Orbital, implement `is_visible`.
- On Orbital, implement `set_resizable`.
- On Orbital, implement `is_resizable`.
- On Orbital, implement `set_maximized`.
- On Orbital, implement `is_maximized`.
- On Orbital, implement `set_decorations`.
- On Orbital, implement `is_decorated`.
- On Orbital, implement `set_window_level`.
- On Orbital, emit `DeviceEvent::MouseMotion`.
- On Wayland, fix title in CSD not updated from `AboutToWait`.
# 0.29.10
- On Web, account for canvas being focused already before event loop starts.
- On Web, increase cursor position accuracy.
# 0.29.9
- On X11, fix `NotSupported` error not propagated when creating event loop.
- On Wayland, fix resize not issued when scale changes
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
# 0.29.8
- On X11, fix IME input lagging behind.
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
- On X11, fix keymap not updated from xmodmap.
- On X11, reduce the amount of time spent fetching screen resources.
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
- On Wayland, fix `Window::inner_size` not using the correct rounding.
# 0.29.7
- On X11, fix `Xft.dpi` reload during runtime.
- On X11, fix window minimize.
# 0.29.6
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
# 0.29.5
- On macOS, remove spurious error logging when handling `Fn`.
- On X11, fix an issue where floating point data from the server is
misinterpreted during a drag and drop operation.
- On X11, fix a bug where focusing the window would panic.
- On macOS, fix `refresh_rate_millihertz`.
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
- On X11, fix `Xft.dpi` detection from Xresources.
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
- On Wayland, fix resize being sent on focus change.
- On Windows, fix `set_ime_cursor_area`.
# 0.29.4
- Fix crash when running iOS app on macOS.
- On X11, check common alternative cursor names when loading cursor.
- On X11, reload the DPI after a property change event.
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key.
# 0.29.3
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
- On Wayland, ignore resize requests when the window is fully tiled.
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
- On Windows, add support for `Window::set_transparent`.
- On macOS, fix deadlock when entering a nested event loop from an event handler.
- On macOS, add support for `Window::set_blur`.
# 0.29.2
- **Breaking:** Bump MSRV from `1.60` to `1.65`.

View File

@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible:
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
@@ -21,7 +21,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.65.
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
@@ -46,27 +46,26 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
## Release process
Given that winit is a widely used library we should be able to make a patch
Given that winit is a widely used library, we should be able to make a patch
releases at any time we want without blocking the development of new features.
To achieve these goals, a new branch is created for every new release. Releases
and later patch releases are committed and tagged in this branch.
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially the version on the latest master is `0.1.0`
1. Initially, the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release
3. In the branch, the version is bumped to `v0.2.0`
4. The new commit in the branch is tagged `v0.2.0`
5. The version is pushed to crates.io
6. A GitHub release is created for the `v0.2.0` tag
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
When doing a patch release the process is similar:
1. Initially the version of the latest release is `0.2.0`
When doing a patch release, the process is similar:
1. Initially, the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 3-7 of the regular release example

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.29.2"
version = "0.29.15"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -13,7 +13,14 @@ categories = ["gui"]
rust-version = "1.65.0"
[package.metadata.docs.rs]
features = ["rwh_04", "rwh_05", "rwh_06", "serde"]
features = [
"rwh_04",
"rwh_05",
"rwh_06",
"serde",
# Enabled to get docs to compile
"android-native-activity",
]
default-target = "x86_64-unknown-linux-gnu"
# These are all tested in CI
targets = [
@@ -54,12 +61,12 @@ cfg_aliases = "0.1.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.0.0"
cursor-icon = "1.1.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
@@ -74,7 +81,7 @@ softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0"
ndk = "0.8.0"
ndk = { version = "0.8.0", default-features = false }
ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
@@ -153,14 +160,14 @@ memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.2"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }

View File

@@ -1,6 +1,6 @@
# Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling, and can
Winit aims to expose an interface that abstracts over window creation and input handling and can
be used to create both games and applications. It supports the following main graphical platforms:
- Desktop
- Windows 7+ (10+ is tested regularly)
@@ -45,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades
Some platform features could in theory be exposed across multiple platforms, but have not gone
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions gets deprecated and become permanently
If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API.
# Features
@@ -88,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows, and if applicable, captures the monitor for exclusive
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
@@ -105,7 +105,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
@@ -151,12 +151,12 @@ If your PR makes notable changes to Winit's features, please update this section
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferrring system gestures
* Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if systems preferred color scheme is "dark"
* Get if the systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@@ -166,7 +166,7 @@ If your PR makes notable changes to Winit's features, please update this section
Legend:
- ✔️: Works as intended
- ▢: Mostly works but some bugs are known
- ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status

View File

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

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.29.2"
winit = "0.29.15"
```
## [Documentation](https://docs.rs/winit)
@@ -26,7 +26,7 @@ Join us in any of these:
Winit is a window creation and management library. It can create windows and lets you handle
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
produced by window.
produced by the window.
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
show something on the window you need to use the platform-specific getters provided by winit, or
@@ -42,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
## MSRV Policy
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
@@ -53,12 +53,11 @@ min(sid, stable - 3)
```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in the
event of a major ecosystem shift or a security vulnerability.
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc
The exception to this is for the Android platform, where a higher Rust version
The exception is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this
@@ -86,7 +85,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For example code using Winit with WebAssembly, check out the [web example]. For
For the example code using Winit with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
@@ -109,14 +108,14 @@ glue crate (prior to `0.28` it used
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entrypoint. If Cargo resolves multiple versions they will
application's main entry point. If Cargo resolves multiple versions, they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29.2| `android-activity = "0.5"` |
| 0.29 | `android-activity = "0.5"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
@@ -127,7 +126,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
```toml
[lib]
@@ -135,14 +134,14 @@ name = "main"
crate-type = ["cdylib"]
```
All Android applications are based on an `Activity` subclass and the
All Android applications are based on an `Activity` subclass, and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
@@ -151,13 +150,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.2", features = [ "android-native-activity" ] }`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.15", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
@@ -173,7 +172,7 @@ If you encounter problems, you should try doing your initialization inside
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI related code, see issue [#1705]. You should consider creating your windows
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
inside `Event::Resumed`.
@@ -184,5 +183,5 @@ inside `Event::Resumed`.
#### Redox OS
Redox OS has some functionality not present yet, that will be implemented when
Redox OS has some functionality not yet present that will be implemented when
its orbital display server provides it.

View File

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

56
examples/focus.rs Normal file
View File

@@ -0,0 +1,56 @@
#![allow(clippy::single_match)]
//! Example for focusing a window.
use simple_logger::SimpleLogger;
#[cfg(not(wasm_platform))]
use std::time;
#[cfg(wasm_platform)]
use web_time as time;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
event_loop.run(move |event, elwt| {
match event {
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
// Timeout reached; focus the window.
println!("Re-focusing the window.");
deadline += time::Duration::from_secs(3);
window.focus_window();
}
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
}
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
})
}

View File

@@ -7,17 +7,30 @@
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
use winit::window::Window;
#[allow(unused_imports)]
pub use platform::cleanup_window;
pub use platform::fill_window;
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
mod platform {
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use softbuffer::{Context, Surface};
use winit::window::Window;
use winit::window::WindowId;
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
}
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
@@ -35,52 +48,69 @@ pub(super) fn fill_window(window: &Window) {
}
}
fn surface(&mut self, w: &Window) -> &mut Surface {
self.surfaces.entry(w.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, w) }
fn create_surface(&mut self, window: &Window) -> &mut Surface {
self.surfaces.entry(window.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, window) }
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &Window) {
self.surfaces.remove(&window.id());
}
}
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
pub fn fill_window(window: &Window) {
GC.with(|gc| {
let size = window.inner_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc
.get_or_insert_with(|| GraphicsContext::new(window))
.create_surface(window);
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818;
surface
.resize(width, height)
.expect("Failed to resize the softbuffer surface");
let mut buffer = surface
.buffer_mut()
.expect("Failed to get the softbuffer buffer");
buffer.fill(DARK_GRAY);
buffer
.present()
.expect("Failed to present the softbuffer buffer");
})
}
GC.with(|gc| {
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc
.get_or_insert_with(|| GraphicsContext::new(window))
.surface(window);
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818;
let size = window.inner_size();
surface
.resize(
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
)
.expect("Failed to resize the softbuffer surface");
let mut buffer = surface
.buffer_mut()
.expect("Failed to get the softbuffer buffer");
buffer.fill(DARK_GRAY);
buffer
.present()
.expect("Failed to present the softbuffer buffer");
})
#[allow(dead_code)]
pub fn cleanup_window(window: &Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
}
}
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
pub(super) fn fill_window(_window: &Window) {
// No-op on mobile platforms.
mod platform {
pub fn fill_window(_window: &winit::window::Window) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &winit::window::Window) {
// No-op on mobile platforms.
}
}

View File

@@ -7,7 +7,6 @@ pub fn main() {
#[cfg(wasm_platform)]
mod wasm {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
use winit::{
dpi::PhysicalSize,

View File

@@ -40,6 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
window_id,
} if window.id() == window_id => {
println!("--------------------------------------------------------- Window {idx} CloseRequested");
fill::cleanup_window(window);
app.window = None;
}
Event::AboutToWait => window.request_redraw(),

View File

@@ -4,13 +4,13 @@
//!
//! Modern computer screens don't have a consistent relationship between resolution and size.
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
//! desktop nor mobile screens have consistent resolutions within their own size classes - common
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
//! and beyond.
//!
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
@@ -25,12 +25,12 @@
//!
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
//!
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
//! than any DPI-dependent units.
//!
//! ### Position and Size types
@@ -42,11 +42,11 @@
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//!
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
//! will truncate the fractional part of the float, rather than properly round to the nearest
//! will truncate the fractional part of the float rather than properly round to the nearest
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem.
@@ -55,34 +55,35 @@
//!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
//! can be found by calling [`window.scale_factor()`].
//!
//! ## How is the scale factor calculated?
//!
//! Scale factor is calculated differently on different platforms:
//! The scale factor is calculated differently on different platforms:
//!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
//! display settings. While users are free to select any option they want, they're only given a
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! the specific value varies across devices.
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
//! but the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** Scale factor is suggested by the the compositor.
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
//! monitor scale factor may differ from the window scale factor.
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.

View File

@@ -574,7 +574,7 @@ pub enum WindowEvent {
/// ### Others
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **Android / Windows / Orbital:** Unsupported.
/// - **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

View File

@@ -11,7 +11,7 @@ use std::marker::PhantomData;
use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{error, fmt};
#[cfg(not(wasm_platform))]
@@ -224,14 +224,22 @@ impl<T> EventLoop<T> {
/// (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
/// Web applications are recommended to use
#[cfg_attr(
wasm_platform,
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
)]
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
/// [^1] 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"`.
///
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
/// [`run()`]: Self::run()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
#[inline]
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
@@ -451,16 +459,16 @@ pub enum DeviceEvents {
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial {
serial: u64,
serial: usize,
}
impl AsyncRequestSerial {
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
// 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.
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: We rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}

View File

@@ -49,11 +49,7 @@ impl fmt::Display for BadIcon {
}
}
impl Error for BadIcon {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self)
}
}
impl Error for BadIcon {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RgbaIcon {

View File

@@ -10,12 +10,12 @@
//! let event_loop = EventLoop::new().unwrap();
//! ```
//!
//! Once this is done there are two ways to create a [`Window`]:
//! Once this is done, there are two ways to create a [`Window`]:
//!
//! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//!
//! The first method is the simplest, and will give you default values for everything. The second
//! The first method is the simplest and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
//!
@@ -26,17 +26,37 @@
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
//!
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! You can retrieve events by calling [`EventLoop::run()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event`]`::`[`LoopExiting`].
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond compatibility reasons.
#![cfg_attr(
any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
),
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
)]
#![cfg_attr(
not(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
)),
doc = "`EventLoopExtPumpEvents::pump_events()`"
)]
//! [^1]. See that method's documentation for more reasons about why
//! it's discouraged beyond compatibility reasons.
//!
//!
//! ```no_run
@@ -72,9 +92,9 @@
//!
//! // Queue a RedrawRequested event.
//! //
//! // You only need to call this if you've determined that you need to redraw, in
//! // You only need to call this if you've determined that you need to redraw in
//! // applications which do not always need to. Applications that redraw continuously
//! // can just render here instead.
//! // can render here instead.
//! window.request_redraw();
//! },
//! Event::WindowEvent {
@@ -92,16 +112,16 @@
//! });
//! ```
//!
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`] to determine which [`Window`]
//! dispatched the event.
//!
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
@@ -110,9 +130,8 @@
//! window visible only once you're ready to render into it.
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`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
//! [`EventLoop::run()`]: event_loop::EventLoop::run
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
@@ -120,15 +139,14 @@
//! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build
//! [window_id_fn]: window::Window::id
//! [`Event`]: event::Event
//! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopExiting`]: event::Event::LoopExiting
//! [`platform`]: platform
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
@@ -165,13 +183,13 @@ pub mod window;
pub mod platform;
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
/// and `Sync`, since they always excute on a single thread.
/// and `Sync`, since they always execute on a single thread.
///
/// # Safety
///
/// Winit can run only one event loop at the time and the event loop itself is tied to some thread.
/// The objects could be send across the threads, but once passed to winit, they execute on the
/// mean thread if the platform demands it. Thus marking such objects as `Send + Sync` is safe.
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
/// The objects could be sent across the threads, but once passed to winit, they execute on the
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
#[doc(hidden)]
#[derive(Clone, Debug)]
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);

View File

@@ -138,14 +138,18 @@ impl MonitorHandle {
self.inner.refresh_rate_millihertz()
}
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`].
///
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0.
///
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()

View File

@@ -84,5 +84,10 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {
// We enable the `"native-activity"` feature just so that we can build the
// docs, but it'll be very confusing for users to see the docs with that
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
pub use android_activity::*;
}

View File

@@ -53,5 +53,13 @@ pub mod run_on_demand;
))]
pub mod pump_events;
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
orbital_platform,
docsrs
))]
pub mod modifier_supplement;
pub mod scancode;

View File

@@ -35,9 +35,9 @@ pub trait EventLoopExtRunOnDemand {
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to
/// 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).
/// - 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 `EventLoopExtWebSys::spawn()`[^1] 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
@@ -57,8 +57,13 @@ pub trait EventLoopExtRunOnDemand {
/// 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.
///
/// [`exit()`]: EventLoopWindowTarget::exit
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
#[cfg_attr(
not(wasm_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)]
///
/// [`exit()`]: EventLoopWindowTarget::exit()
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
@@ -71,6 +76,14 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler)
}
}
impl<T> EventLoopWindowTarget<T> {
/// Clear exit status.
pub(crate) fn clear_exit(&self) {
self.p.clear_exit()
}
}

View File

@@ -109,13 +109,28 @@ pub trait EventLoopExtWebSys {
/// Initializes the winit event loop.
///
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
/// satisfy its `!` return type.
/// Unlike
#[cfg_attr(
all(wasm_platform, target_feature = "exception-handling"),
doc = "`run()`"
)]
#[cfg_attr(
not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
///
#[cfg_attr(
not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]: EventLoop::run()"
)]
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);

View File

@@ -96,7 +96,7 @@ pub trait WindowBuilderExtX11 {
/// Build window with the given `general` and `instance` names.
///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)

View File

@@ -713,6 +713,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
self.exit.set(true)
}
pub(crate) fn clear_exit(&self) {
self.exit.set(false)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get()
}

View File

@@ -200,6 +200,10 @@ impl AppState {
)
}
fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched {
@@ -243,7 +247,7 @@ impl AppState {
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() {
if !self.has_launched() || self.has_terminated() {
return None;
}
@@ -390,7 +394,7 @@ impl AppState {
}
fn events_cleared_transition(&mut self) {
if !self.has_launched() {
if !self.has_launched() || self.has_terminated() {
return;
}
let (waiting_event_handler, old) = match self.take_state() {
@@ -586,6 +590,10 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
events: I,
) {
let mut this = AppState::get_mut(mtm);
if this.has_terminated() {
return;
}
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
@@ -737,7 +745,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() {
if !this.has_launched() || this.has_terminated() {
return;
}
match this.state_mut() {

View File

@@ -289,7 +289,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
kCFRunLoopExit => {} // may happen when running on macOS
_ => unreachable!(),
}
}
@@ -304,7 +304,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
kCFRunLoopExit => {} // may happen when running on macOS
_ => unreachable!(),
}
}

View File

@@ -1,7 +1,5 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::convert::TryInto;
use icrate::Foundation::{NSInteger, NSUInteger};
use objc2::encode::{Encode, Encoding};

View File

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

View File

@@ -0,0 +1,124 @@
//! XKB compose handling.
use std::env;
use std::ffi::CString;
use std::ops::Deref;
use std::os::unix::ffi::OsStringExt;
use std::ptr::NonNull;
use super::XkbContext;
use super::XKBCH;
use smol_str::SmolStr;
use xkbcommon_dl::{
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
xkb_compose_status, xkb_compose_table, xkb_keysym_t,
};
#[derive(Debug)]
pub struct XkbComposeTable {
table: NonNull<xkb_compose_table>,
}
impl XkbComposeTable {
pub fn new(context: &XkbContext) -> Option<Self> {
let locale = env::var_os("LC_ALL")
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LC_CTYPE"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LANG"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let table = unsafe {
(XKBCH.xkb_compose_table_new_from_locale)(
context.as_ptr(),
locale.as_ptr(),
xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
)
};
let table = NonNull::new(table)?;
Some(Self { table })
}
/// Create new state with the given compose table.
pub fn new_state(&self) -> Option<XkbComposeState> {
let state = unsafe {
(XKBCH.xkb_compose_state_new)(
self.table.as_ptr(),
xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
let state = NonNull::new(state)?;
Some(XkbComposeState { state })
}
}
impl Deref for XkbComposeTable {
type Target = NonNull<xkb_compose_table>;
fn deref(&self) -> &Self::Target {
&self.table
}
}
impl Drop for XkbComposeTable {
fn drop(&mut self) {
unsafe {
(XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
}
}
}
#[derive(Debug)]
pub struct XkbComposeState {
state: NonNull<xkb_compose_state>,
}
impl XkbComposeState {
pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
super::make_string_with(scratch_buffer, |ptr, len| unsafe {
(XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
})
}
#[inline]
pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
match feed_result {
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
ComposeStatus::Accepted(self.status())
}
}
}
#[inline]
pub fn reset(&mut self) {
unsafe {
(XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
}
}
#[inline]
pub fn status(&mut self) -> xkb_compose_status {
unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
}
}
impl Drop for XkbComposeState {
fn drop(&mut self) {
unsafe {
(XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
};
}
}
#[derive(Copy, Clone, Debug)]
pub enum ComposeStatus {
Accepted(xkb_compose_status),
Ignored,
None,
}

View File

@@ -1,6 +1,24 @@
//! Convert XKB keys to Winit keys.
//! XKB keymap.
use std::ffi::c_char;
use std::ops::Deref;
use std::ptr::{self, NonNull};
#[cfg(x11_platform)]
use x11_dl::xlib_xcb::xcb_connection_t;
#[cfg(wayland_platform)]
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
use xkb::XKB_MOD_INVALID;
use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
xkb_layout_index_t, xkb_mod_index_t,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
#[cfg(x11_platform)]
use crate::platform_impl::common::xkb::XKBXH;
use crate::platform_impl::common::xkb::{XkbContext, XKBH};
/// Map the raw X11-style keycode to the `KeyCode` enum.
///
@@ -504,7 +522,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::KP_F4 => NamedKey::F4,
keysyms::KP_Home => NamedKey::Home,
keysyms::KP_Left => NamedKey::ArrowLeft,
keysyms::KP_Up => NamedKey::ArrowLeft,
keysyms::KP_Up => NamedKey::ArrowUp,
keysyms::KP_Right => NamedKey::ArrowRight,
keysyms::KP_Down => NamedKey::ArrowDown,
// keysyms::KP_Prior => NamedKey::PageUp,
@@ -894,3 +912,140 @@ pub fn keysym_location(keysym: u32) -> KeyLocation {
_ => KeyLocation::Standard,
}
}
#[derive(Debug)]
pub struct XkbKeymap {
keymap: NonNull<xkb_keymap>,
_mods_indices: ModsIndices,
pub _core_keyboard_id: i32,
}
impl XkbKeymap {
#[cfg(wayland_platform)]
pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> {
let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? };
let keymap = unsafe {
let keymap = (XKBH.xkb_keymap_new_from_string)(
(*context).as_ptr(),
map.as_ptr() as *const _,
xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
NonNull::new(keymap)?
};
Some(Self::new_inner(keymap, 0))
}
#[cfg(x11_platform)]
pub fn from_x11_keymap(
context: &XkbContext,
xcb: *mut xcb_connection_t,
core_keyboard_id: i32,
) -> Option<Self> {
let keymap = unsafe {
(XKBXH.xkb_x11_keymap_new_from_device)(
context.as_ptr(),
xcb,
core_keyboard_id,
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
let keymap = NonNull::new(keymap)?;
Some(Self::new_inner(keymap, core_keyboard_id))
}
fn new_inner(keymap: NonNull<xkb_keymap>, _core_keyboard_id: i32) -> Self {
let mods_indices = ModsIndices {
shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT),
caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS),
ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL),
alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT),
num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM),
mod3: mod_index_for_name(keymap, b"Mod3\0"),
logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO),
mod5: mod_index_for_name(keymap, b"Mod5\0"),
};
Self {
keymap,
_mods_indices: mods_indices,
_core_keyboard_id,
}
}
#[cfg(x11_platform)]
pub fn mods_indices(&self) -> ModsIndices {
self._mods_indices
}
pub fn first_keysym_by_level(
&mut self,
layout: xkb_layout_index_t,
keycode: xkb_keycode_t,
) -> xkb_keysym_t {
unsafe {
let mut keysyms = ptr::null();
let count = (XKBH.xkb_keymap_key_get_syms_by_level)(
self.keymap.as_ptr(),
keycode,
layout,
// NOTE: The level should be zero to ignore modifiers.
0,
&mut keysyms,
);
if count == 1 {
*keysyms
} else {
0
}
}
}
/// Check whether the given key repeats.
pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool {
unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 }
}
}
impl Drop for XkbKeymap {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_keymap_unref)(self.keymap.as_ptr());
};
}
}
impl Deref for XkbKeymap {
type Target = NonNull<xkb_keymap>;
fn deref(&self) -> &Self::Target {
&self.keymap
}
}
/// Modifier index in the keymap.
#[derive(Default, Debug, Clone, Copy)]
pub struct ModsIndices {
pub shift: Option<xkb_mod_index_t>,
pub caps: Option<xkb_mod_index_t>,
pub ctrl: Option<xkb_mod_index_t>,
pub alt: Option<xkb_mod_index_t>,
pub num: Option<xkb_mod_index_t>,
pub mod3: Option<xkb_mod_index_t>,
pub logo: Option<xkb_mod_index_t>,
pub mod5: Option<xkb_mod_index_t>,
}
fn mod_index_for_name(keymap: NonNull<xkb_keymap>, name: &[u8]) -> Option<xkb_mod_index_t> {
unsafe {
let mod_index =
(XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char);
if mod_index == XKB_MOD_INVALID {
None
} else {
Some(mod_index)
}
}
}

View File

@@ -0,0 +1,465 @@
use std::ops::Deref;
use std::os::raw::c_char;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering};
use log::warn;
use once_cell::sync::Lazy;
use smol_str::SmolStr;
#[cfg(wayland_platform)]
use std::os::unix::io::OwnedFd;
use xkbcommon_dl::{
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
xkbcommon_handle, XkbCommon, XkbCommonCompose,
};
#[cfg(x11_platform)]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
use crate::event::ElementState;
use crate::event::KeyEvent;
use crate::keyboard::{Key, KeyLocation};
use crate::platform_impl::KeyEventExtra;
mod compose;
mod keymap;
mod state;
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
use keymap::XkbKeymap;
#[cfg(x11_platform)]
pub use keymap::raw_keycode_to_physicalkey;
pub use keymap::{physicalkey_to_scancode, scancode_to_keycode};
pub use state::XkbState;
// TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")]
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
#[inline(always)]
pub fn reset_dead_keys() {
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
}
#[derive(Debug)]
pub enum Error {
/// libxkbcommon is not available
XKBNotFound,
}
#[derive(Debug)]
pub struct Context {
// NOTE: field order matters.
#[cfg(x11_platform)]
pub core_keyboard_id: i32,
state: Option<XkbState>,
keymap: Option<XkbKeymap>,
compose_state1: Option<XkbComposeState>,
compose_state2: Option<XkbComposeState>,
_compose_table: Option<XkbComposeTable>,
context: XkbContext,
scratch_buffer: Vec<u8>,
}
impl Context {
pub fn new() -> Result<Self, Error> {
if xkb::xkbcommon_option().is_none() {
return Err(Error::XKBNotFound);
}
let context = XkbContext::new()?;
let mut compose_table = XkbComposeTable::new(&context);
let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
// Disable compose if anything compose related failed to initialize.
if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
compose_state2 = None;
compose_state1 = None;
compose_table = None;
}
Ok(Self {
state: None,
keymap: None,
compose_state1,
compose_state2,
#[cfg(x11_platform)]
core_keyboard_id: 0,
_compose_table: compose_table,
context,
scratch_buffer: Vec::with_capacity(8),
})
}
#[cfg(feature = "x11")]
pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
let result = unsafe {
(XKBXH.xkb_x11_setup_xkb_extension)(
xcb,
1,
2,
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
if result != 1 {
return Err(Error::XKBNotFound);
}
let mut this = Self::new()?;
this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
this.set_keymap_from_x11(xcb);
Ok(this)
}
pub fn state_mut(&mut self) -> Option<&mut XkbState> {
self.state.as_mut()
}
pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
self.keymap.as_mut()
}
#[cfg(wayland_platform)]
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
let keymap = XkbKeymap::from_fd(&self.context, fd, size);
let state = keymap.as_ref().and_then(XkbState::new_wayland);
if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
#[cfg(x11_platform)]
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
let state = keymap
.as_ref()
.and_then(|keymap| XkbState::new_x11(xcb, keymap));
if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
/// Key builder context with the user provided xkb state.
pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
let state = self.state.as_mut()?;
let keymap = self.keymap.as_mut()?;
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext {
state,
keymap,
compose_state1,
compose_state2,
scratch_buffer,
})
}
/// Key builder context with the user provided xkb state.
///
/// Should be used when the original context must not be altered.
#[cfg(x11_platform)]
pub fn key_context_with_state<'a>(
&'a mut self,
state: &'a mut XkbState,
) -> Option<KeyContext<'a>> {
let keymap = self.keymap.as_mut()?;
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext {
state,
keymap,
compose_state1,
compose_state2,
scratch_buffer,
})
}
}
pub struct KeyContext<'a> {
pub state: &'a mut XkbState,
pub keymap: &'a mut XkbKeymap,
compose_state1: Option<&'a mut XkbComposeState>,
compose_state2: Option<&'a mut XkbComposeState>,
scratch_buffer: &'a mut Vec<u8>,
}
impl<'a> KeyContext<'a> {
pub fn process_key_event(
&mut self,
keycode: u32,
state: ElementState,
repeat: bool,
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
let platform_specific = KeyEventExtra {
text_with_all_modifiers,
key_without_modifiers,
};
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
platform_specific,
}
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
self.scratch_buffer.clear();
self.scratch_buffer.reserve(8);
loop {
let bytes_written = unsafe {
(XKBH.xkb_keysym_to_utf8)(
keysym,
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
)
};
if bytes_written == 0 {
return None;
} else if bytes_written == -1 {
self.scratch_buffer.reserve(8);
} else {
unsafe {
self.scratch_buffer
.set_len(bytes_written.try_into().unwrap())
};
break;
}
}
// Remove the null-terminator
self.scratch_buffer.pop();
byte_slice_to_smol_str(self.scratch_buffer)
}
}
struct KeyEventResults<'a, 'b> {
context: &'a mut KeyContext<'b>,
keycode: u32,
keysym: u32,
compose: ComposeStatus,
}
impl<'a, 'b> KeyEventResults<'a, 'b> {
fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
let keysym = context.state.get_one_sym_raw(keycode);
let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
state.reset();
context.compose_state2.as_mut().unwrap().reset();
}
state.feed(keysym)
} else {
ComposeStatus::None
};
KeyEventResults {
context,
keycode,
keysym,
compose,
}
}
pub fn key(&mut self) -> (Key, KeyLocation) {
let (key, location) = match self.keysym_to_key(self.keysym) {
Ok(known) => return known,
Err(undefined) => undefined,
};
if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
let compose_state = self.context.compose_state2.as_mut().unwrap();
// When pressing a dead key twice, the non-combining variant of that character will
// be produced. Since this function only concerns itself with a single keypress, we
// simulate this double press here by feeding the keysym to the compose state
// twice.
compose_state.feed(self.keysym);
if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
// Extracting only a single `char` here *should* be fine, assuming that no
// dead key's non-combining variant ever occupies more than one `char`.
let text = compose_state.get_string(self.context.scratch_buffer);
let key = Key::Dead(text.and_then(|s| s.chars().next()));
(key, location)
} else {
(key, location)
}
} else {
let key = self
.composed_text()
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
.map(Key::Character)
.unwrap_or(key);
(key, location)
}
}
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
let layout = self.context.state.layout(self.keycode);
let keysym = self
.context
.keymap
.first_keysym_by_level(layout, self.keycode);
match self.keysym_to_key(keysym) {
Ok((key, location)) => (key, location),
Err((key, location)) => {
let key = self
.context
.keysym_to_utf8_raw(keysym)
.map(Key::Character)
.unwrap_or(key);
(key, location)
}
}
}
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = keymap::keysym_location(keysym);
let key = keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) {
Err((key, location))
} else {
Ok((key, location))
}
}
pub fn text(&mut self) -> Option<SmolStr> {
self.composed_text()
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
}
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,
Err(_) => self
.context
.state
.get_utf8_raw(self.keycode, self.context.scratch_buffer),
}
}
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
match self.compose {
ComposeStatus::Accepted(status) => match status {
xkb_compose_status::XKB_COMPOSE_COMPOSED => {
let state = self.context.compose_state1.as_mut().unwrap();
Ok(state.get_string(self.context.scratch_buffer))
}
xkb_compose_status::XKB_COMPOSE_COMPOSING
| xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None),
xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
},
_ => Err(()),
}
}
}
#[derive(Debug)]
pub struct XkbContext {
context: NonNull<xkb_context>,
}
impl XkbContext {
pub fn new() -> Result<Self, Error> {
let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
let context = match NonNull::new(context) {
Some(context) => context,
None => return Err(Error::XKBNotFound),
};
Ok(Self { context })
}
}
impl Drop for XkbContext {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_context_unref)(self.context.as_ptr());
}
}
}
impl Deref for XkbContext {
type Target = NonNull<xkb_context>;
fn deref(&self) -> &Self::Target {
&self.context
}
}
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
/// `xkb_state_key_get_utf8`.
fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
where
F: FnMut(*mut c_char, usize) -> i32,
{
let size = f(ptr::null_mut(), 0);
if size == 0 {
return None;
}
let size = usize::try_from(size).unwrap();
scratch_buffer.clear();
// The allocated buffer must include space for the null-terminator.
scratch_buffer.reserve(size + 1);
unsafe {
let written = f(
scratch_buffer.as_mut_ptr().cast(),
scratch_buffer.capacity(),
);
if usize::try_from(written).unwrap() != size {
// This will likely never happen.
return None;
}
scratch_buffer.set_len(size);
};
byte_slice_to_smol_str(scratch_buffer)
}
// NOTE: This is track_caller so we can have more informative line numbers when logging
#[track_caller]
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes)
.map(SmolStr::new)
.map_err(|e| {
log::warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes
)
})
.ok()
}

View File

@@ -0,0 +1,189 @@
//! XKB state.
use std::os::raw::c_char;
use std::ptr::NonNull;
use smol_str::SmolStr;
#[cfg(x11_platform)]
use x11_dl::xlib_xcb::xcb_connection_t;
use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
};
use crate::platform_impl::common::xkb::keymap::XkbKeymap;
#[cfg(x11_platform)]
use crate::platform_impl::common::xkb::XKBXH;
use crate::platform_impl::common::xkb::{make_string_with, XKBH};
#[derive(Debug)]
pub struct XkbState {
state: NonNull<xkb_state>,
modifiers: ModifiersState,
}
impl XkbState {
#[cfg(wayland_platform)]
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
Some(Self::new_inner(state))
}
#[cfg(x11_platform)]
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
let state = unsafe {
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
};
let state = NonNull::new(state)?;
Some(Self::new_inner(state))
}
fn new_inner(state: NonNull<xkb_state>) -> Self {
let modifiers = ModifiersState::default();
let mut this = Self { state, modifiers };
this.reload_modifiers();
this
}
pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t {
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) }
}
pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t {
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
}
#[cfg(x11_platform)]
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_DEPRESSED,
)
}
}
#[cfg(x11_platform)]
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_LATCHED,
)
}
}
#[cfg(x11_platform)]
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_LOCKED,
)
}
}
pub fn get_utf8_raw(
&mut self,
keycode: xkb_keycode_t,
scratch_buffer: &mut Vec<u8>,
) -> Option<SmolStr> {
make_string_with(scratch_buffer, |ptr, len| unsafe {
(XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len)
})
}
pub fn modifiers(&self) -> ModifiersState {
self.modifiers
}
pub fn update_modifiers(
&mut self,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
depressed_group: u32,
latched_group: u32,
locked_group: u32,
) {
let mask = unsafe {
(XKBH.xkb_state_update_mask)(
self.state.as_ptr(),
mods_depressed,
mods_latched,
mods_locked,
depressed_group,
latched_group,
locked_group,
)
};
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
// Effective value of mods have changed, we need to update our state.
self.reload_modifiers();
}
}
/// Reload the modifiers.
fn reload_modifiers(&mut self) {
self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL);
self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT);
self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT);
self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS);
self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO);
self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM);
}
/// Check if the modifier is active within xkb.
fn mod_name_is_active(&mut self, name: &[u8]) -> bool {
unsafe {
(XKBH.xkb_state_mod_name_is_active)(
self.state.as_ptr(),
name.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
}
}
}
impl Drop for XkbState {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_state_unref)(self.state.as_ptr());
}
}
}
/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is currently pressed, others are toggled
/// (like caps lock).
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ModifiersState {
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "shift" key
pub shift: bool,
/// The "Caps lock" key
pub caps_lock: bool,
/// The "logo" key
///
/// Also known as the "windows" key on most keyboards
pub logo: bool,
/// The "Num lock" key
pub num_lock: bool,
}
impl From<ModifiersState> for crate::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
let mut to_mods = crate::keyboard::ModifiersState::empty();
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
to_mods
}
}

View File

@@ -498,7 +498,7 @@ impl<'a> KeyEventResults<'a> {
}
}
fn physical_key(&mut self) -> PhysicalKey {
fn physical_key(&self) -> PhysicalKey {
keymap::raw_keycode_to_physicalkey(self.keycode)
}
@@ -553,6 +553,7 @@ impl<'a> KeyEventResults<'a> {
} else {
0
};
self.keysym_to_key(keysym)
.unwrap_or_else(|(key, location)| {
(
@@ -565,7 +566,7 @@ impl<'a> KeyEventResults<'a> {
})
}
fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = super::keymap::keysym_location(keysym);
let key = super::keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) {

View File

@@ -521,7 +521,7 @@ impl Window {
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb_state::reset_dead_keys()
common::xkb::reset_dead_keys()
}
#[inline]
@@ -659,11 +659,11 @@ impl KeyEventExtModifierSupplement for KeyEvent {
impl PhysicalKeyExtScancode for PhysicalKey {
fn from_scancode(scancode: u32) -> PhysicalKey {
common::keymap::scancode_to_keycode(scancode)
common::xkb::scancode_to_keycode(scancode)
}
fn to_scancode(self) -> Option<u32> {
common::keymap::physicalkey_to_scancode(self)
common::xkb::physicalkey_to_scancode(self)
}
}
@@ -757,8 +757,11 @@ impl<T: 'static> EventLoop<T> {
let backend = match (
attributes.forced_backend,
env::var("WAYLAND_DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
.ok()
.filter(|var| !var.is_empty())
.or_else(|| env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some(),
env::var("DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
@@ -772,10 +775,15 @@ impl<T: 'static> EventLoop<T> {
#[cfg(x11_platform)]
(None, _, true) => Backend::X,
// No backend is present.
_ => {
return Err(EventLoopError::Os(os_error!(OsError::Misc(
"neither WAYLAND_DISPLAY nor DISPLAY is set."
))));
(_, wayland_display, x11_display) => {
let msg = if wayland_display && !cfg!(wayland_platform) {
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland"
} else if x11_display && !cfg!(x11_platform) {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11"
} else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
};
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
}
};
@@ -784,7 +792,7 @@ impl<T: 'static> EventLoop<T> {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)]
Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
}
}
@@ -794,10 +802,10 @@ impl<T: 'static> EventLoop<T> {
}
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -918,6 +926,10 @@ impl<T> EventLoopWindowTarget<T> {
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
}
pub(crate) fn clear_exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit())
}
pub(crate) fn exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
}
@@ -926,10 +938,12 @@ impl<T> EventLoopWindowTarget<T> {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
}
#[allow(dead_code)]
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
#[allow(dead_code)]
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}

View File

@@ -10,13 +10,12 @@ use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use sctk::reexports::calloop;
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, QueueHandle};
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{
@@ -34,7 +33,7 @@ use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState;
use super::{DeviceId, WaylandError, WindowId};
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
@@ -356,15 +355,13 @@ impl<T: 'static> EventLoop<T> {
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| {
if compositor_update.scale_changed {
let (physical_size, scale_factor) = 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)
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
(size, scale_factor)
});
// Stash the old window size.
@@ -386,30 +383,32 @@ impl<T: 'static> EventLoop<T> {
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);
let new_logical_size: LogicalSize<f64> =
physical_size.to_logical(scale_factor);
window.request_inner_size(new_logical_size.into());
});
// Make it queue resize.
compositor_update.resized = true;
}
}
if let Some(size) = compositor_update.size.take() {
// NOTE: Rescale changed the physical size which winit operates in, thus we should
// resize.
if compositor_update.resized || compositor_update.scale_changed {
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.
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
// Mark the window as needed a redraw.
state
@@ -420,7 +419,7 @@ impl<T: 'static> EventLoop<T> {
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
size
});
callback(
@@ -466,45 +465,45 @@ impl<T: 'static> EventLoop<T> {
window_ids.extend(state.window_requests.get_mut().keys());
});
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
for window_id in window_ids.iter() {
let event = 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();
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 frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested
}
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));
return Some(WindowEvent::Destroyed);
}
let mut window = state
.windows
.get_mut()
.get_mut(window_id)
.unwrap()
.lock()
.unwrap();
if window.frame_callback_state() == FrameCallbackState::Requested {
return None;
}
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested.then_some(WindowEvent::RedrawRequested)
});
if request_redraw {
if let Some(event) = event {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::RedrawRequested,
window_id: crate::window::WindowId(*window_id),
event,
},
&self.window_target,
);
@@ -519,6 +518,42 @@ impl<T: 'static> EventLoop<T> {
// This is always the last event we dispatch before poll again
callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;
for window_id in window_ids.drain(..) {
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
Some(window) => {
let refresh = window.lock().unwrap().refresh_frame();
if refresh {
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
}
refresh
}
None => false,
});
}
// Wakeup event loop if needed.
//
// If the user draws from the `AboutToWait` this is likely not required, however
// we can't do much about it.
if wake_up {
match &self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
}
#[cfg(x11_platform)]
PlatformEventLoopWindowTarget::X(_) => unreachable!(),
}
}
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);
@@ -629,6 +664,34 @@ pub struct EventLoopWindowTarget<T> {
}
impl<T> EventLoopWindowTarget<T> {
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
@@ -656,10 +719,3 @@ impl<T> EventLoopWindowTarget<T> {
.into())
}
}
// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -9,6 +9,7 @@ use sctk::reexports::client::globals::{BindError, GlobalError};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
use crate::dpi::{LogicalSize, PhysicalSize};
pub use crate::platform_impl::platform::{OsError, WindowId};
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
@@ -76,3 +77,10 @@ impl DeviceId {
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.id().as_ptr() as u64)
}
/// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -4,7 +4,6 @@ use sctk::reexports::client::Proxy;
use sctk::output::OutputData;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::event_loop::ControlFlow;
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
use super::event_loop::EventLoopWindowTarget;
@@ -24,30 +23,6 @@ impl<T> EventLoopWindowTarget<T> {
// There's no primary monitor on Wayland.
None
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
}
#[derive(Clone, Debug)]

View File

@@ -17,7 +17,7 @@ use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
use crate::event::{ElementState, WindowEvent};
use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb_state::KbdState;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::seat::WinitSeatState;
use crate::platform_impl::wayland::state::WinitState;
@@ -43,14 +43,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
WlKeymapFormat::NoKeymap => {
warn!("non-xkb compatible keymap")
}
WlKeymapFormat::XkbV1 => unsafe {
seat_state
.keyboard_state
.as_mut()
.unwrap()
.xkb_state
.init_with_fd(fd, size as usize);
},
WlKeymapFormat::XkbV1 => {
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
context.set_keymap_from_fd(fd, size as usize);
}
_ => unreachable!(),
},
WEnum::Unknown(value) => {
@@ -61,8 +57,13 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let window_id = wayland::make_wid(&surface);
// Mark the window as focused.
match state.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().set_has_focus(true),
let was_unfocused = match state.windows.get_mut().get(&window_id) {
Some(window) => {
let mut window = window.lock().unwrap();
let was_unfocused = !window.has_focus();
window.add_seat_focus(data.seat.id());
was_unfocused
}
None => return,
};
@@ -73,13 +74,15 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
keyboard_state.loop_handle.remove(token);
}
// The keyboard focus is considered as general focus.
state
.events_sink
.push_window_event(WindowEvent::Focused(true), window_id);
*data.window_id.lock().unwrap() = Some(window_id);
// The keyboard focus is considered as general focus.
if was_unfocused {
state
.events_sink
.push_window_event(WindowEvent::Focused(true), window_id);
}
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
if std::mem::take(&mut seat_state.modifiers_pending) {
state.events_sink.push_window_event(
@@ -101,24 +104,30 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
// NOTE: The check whether the window exists is essential as we might get a
// nil surface, regardless of what protocol says.
match state.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().set_has_focus(false),
let focused = match state.windows.get_mut().get(&window_id) {
Some(window) => {
let mut window = window.lock().unwrap();
window.remove_seat_focus(&data.seat.id());
window.has_focus()
}
None => return,
};
// Notify that no modifiers are being pressed.
state.events_sink.push_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
window_id,
);
// We don't need to update it above, because the next `Enter` will overwrite
// anyway.
*data.window_id.lock().unwrap() = None;
state
.events_sink
.push_window_event(WindowEvent::Focused(false), window_id);
if !focused {
// Notify that no modifiers are being pressed.
state.events_sink.push_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
window_id,
);
state
.events_sink
.push_window_event(WindowEvent::Focused(false), window_id);
}
}
WlKeyboardEvent::Key {
key,
@@ -142,7 +151,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
RepeatInfo::Disable => return,
};
if !keyboard_state.xkb_state.key_repeats(key) {
if !keyboard_state
.xkb_context
.keymap_mut()
.unwrap()
.key_repeats(key)
{
return;
}
@@ -208,7 +222,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
if keyboard_state.repeat_info != RepeatInfo::Disable
&& keyboard_state.xkb_state.key_repeats(key)
&& keyboard_state
.xkb_context
.keymap_mut()
.unwrap()
.key_repeats(key)
&& Some(key) == keyboard_state.current_repeat
{
keyboard_state.current_repeat = None;
@@ -224,9 +242,14 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
group,
..
} => {
let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state;
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let xkb_state = match xkb_context.state_mut() {
Some(state) => state,
None => return,
};
xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group);
seat_state.modifiers = xkb_state.mods_state().into();
seat_state.modifiers = xkb_state.modifiers().into();
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
let window_id = match *data.window_id.lock().unwrap() {
@@ -272,7 +295,7 @@ pub struct KeyboardState {
pub loop_handle: LoopHandle<'static, WinitState>,
/// The state of the keyboard.
pub xkb_state: KbdState,
pub xkb_context: Context,
/// The information about the repeat rate obtained from the compositor.
pub repeat_info: RepeatInfo,
@@ -289,7 +312,7 @@ impl KeyboardState {
Self {
keyboard,
loop_handle,
xkb_state: KbdState::new().unwrap(),
xkb_context: Context::new().unwrap(),
repeat_info: RepeatInfo::default(),
repeat_token: None,
current_repeat: None,
@@ -372,16 +395,13 @@ fn key_input(
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
let event = keyboard_state
.xkb_state
.process_key_event(keycode, state, repeat);
event_sink.push_window_event(
WindowEvent::KeyboardInput {
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat);
let event = WindowEvent::KeyboardInput {
device_id,
event,
is_synthetic: false,
},
window_id,
);
};
event_sink.push_window_event(event, window_id);
}
}

View File

@@ -4,6 +4,7 @@ use std::sync::Arc;
use ahash::AHashMap;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
@@ -13,6 +14,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use crate::event::WindowEvent;
use crate::keyboard::ModifiersState;
use crate::platform_impl::wayland::state::WinitState;
@@ -143,6 +145,10 @@ impl SeatHandler for WinitState {
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();
}
match capability {
SeatCapability::Touch => {
if let Some(touch) = seat_state.touch.take() {
@@ -174,13 +180,10 @@ impl SeatHandler for WinitState {
}
SeatCapability::Keyboard => {
seat_state.keyboard_state = None;
self.on_keyboard_destroy(&seat.id());
}
_ => (),
}
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();
}
}
fn new_seat(
@@ -199,6 +202,21 @@ impl SeatHandler for WinitState {
seat: WlSeat,
) {
let _ = self.seats.remove(&seat.id());
self.on_keyboard_destroy(&seat.id());
}
}
impl WinitState {
fn on_keyboard_destroy(&mut self, seat: &ObjectId) {
for (window_id, window) in self.windows.get_mut() {
let mut window = window.lock().unwrap();
let had_focus = window.has_focus();
window.remove_seat_focus(seat);
if had_focus != window.has_focus() {
self.events_sink
.push_window_event(WindowEvent::Focused(false), *window_id);
}
}
}
}

View File

@@ -60,19 +60,34 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
if let zwp_relative_pointer_v1::Event::RelativeMotion {
dx_unaccel,
dy_unaccel,
..
} = event
{
state.events_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
super::DeviceId,
);
}
let (dx_unaccel, dy_unaccel) = match event {
zwp_relative_pointer_v1::Event::RelativeMotion {
dx_unaccel,
dy_unaccel,
..
} => (dx_unaccel, dy_unaccel),
_ => return,
};
state.events_sink.push_device_event(
DeviceEvent::Motion {
axis: 0,
value: dx_unaccel,
},
super::DeviceId,
);
state.events_sink.push_device_event(
DeviceEvent::Motion {
axis: 1,
value: dy_unaccel,
},
super::DeviceId,
);
state.events_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
super::DeviceId,
);
}
}

View File

@@ -22,21 +22,19 @@ use sctk::shell::WaylandSurface;
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;
use super::seat::{
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::output::MonitorHandle;
use crate::platform_impl::wayland::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
};
use super::types::kwin_blur::KWinBlurManager;
use super::types::wp_fractional_scaling::FractionalScalingManager;
use super::types::wp_viewporter::ViewporterState;
use super::types::xdg_activation::XdgActivationState;
use super::window::{WindowRequests, WindowState};
use super::{WaylandError, WindowId};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
use crate::platform_impl::wayland::{WaylandError, WindowId};
use crate::platform_impl::OsError;
/// Winit's Wayland state.
pub struct WinitState {
@@ -50,7 +48,7 @@ pub struct WinitState {
pub compositor_state: Arc<CompositorState>,
/// The state of the subcompositor.
pub subcompositor_state: Arc<SubcompositorState>,
pub subcompositor_state: Option<Arc<SubcompositorState>>,
/// The seat state responsible for all sorts of input.
pub seat_state: SeatState,
@@ -124,12 +122,17 @@ impl WinitState {
let registry_state = RegistryState::new(globals);
let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let subcompositor_state = SubcompositorState::bind(
let subcompositor_state = match SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
queue_handle,
)
.map_err(WaylandError::Bind)?;
) {
Ok(c) => Some(c),
Err(e) => {
warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
None
}
};
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
@@ -151,7 +154,7 @@ impl WinitState {
Ok(Self {
registry_state,
compositor_state: Arc::new(compositor_state),
subcompositor_state: Arc::new(subcompositor_state),
subcompositor_state: subcompositor_state.map(Arc::new),
output_state,
seat_state,
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
@@ -214,7 +217,7 @@ impl WinitState {
// Update the scale factor right away.
window.lock().unwrap().set_scale_factor(scale_factor);
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
self.window_compositor_updates[pos].scale_changed = true;
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
// Get the window, where the pointer resides right now.
let focused_window = match pointer.pointer().winit_data().focused_window() {
@@ -278,23 +281,26 @@ impl WindowHandler for WinitState {
};
// Populate the configure to the window.
//
// XXX the size on the window will be updated right before dispatching the size to the user.
let new_size = self
self.window_compositor_updates[pos].resized |= self
.windows
.get_mut()
.get_mut(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure(
configure,
&self.shm,
&self.subcompositor_state,
&mut self.events_sink,
);
.configure(configure, &self.shm, &self.subcompositor_state);
self.window_compositor_updates[pos].size = Some(new_size);
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the
// users, since it can break a lot of things, thus it'll ask users to redraw instead.
self.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
// Manually mark that we've got an event, since configure may not generate a resize.
self.dispatched_events = true;
}
}
@@ -388,10 +394,10 @@ pub struct WindowCompositorUpdate {
pub window_id: WindowId,
/// New window size.
pub size: Option<LogicalSize<u32>>,
pub resized: bool,
/// New scale factor.
pub scale_factor: Option<f64>,
pub scale_changed: bool,
/// Close the window.
pub close_window: bool,
@@ -401,8 +407,8 @@ impl WindowCompositorUpdate {
fn new(window_id: WindowId) -> Self {
Self {
window_id,
size: None,
scale_factor: None,
resized: false,
scale_changed: false,
close_window: false,
}
}

View File

@@ -3,7 +3,6 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use sctk::reexports::calloop;
use sctk::reexports::client::protocol::wl_display::WlDisplay;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
@@ -97,11 +96,9 @@ impl Window {
.map(|activation_state| activation_state.global().clone());
let display = event_loop_window_target.connection.display();
// XXX The initial scale factor must be 1, but it might cause sizing issues on HiDPI.
let size: LogicalSize<u32> = attributes
let size: Size = attributes
.inner_size
.map(|size| size.to_logical::<u32>(1.))
.unwrap_or((800, 600).into());
.unwrap_or(LogicalSize::new(800., 600.).into());
// We prefer server side decorations, however to not have decorations we ask for client
// side decorations instead.
@@ -141,7 +138,8 @@ impl Window {
// Set the window title.
window_state.set_title(attributes.title);
// Set the min and max sizes.
// Set the min and max sizes. We must set the hints upon creating a window, so
// we use the default `1.` scaling...
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
window_state.set_min_inner_size(min_size);
@@ -281,7 +279,7 @@ impl Window {
pub fn inner_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
window_state.inner_size().to_physical(scale_factor)
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
}
#[inline]
@@ -309,18 +307,15 @@ impl Window {
pub fn outer_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
window_state.outer_size().to_physical(scale_factor)
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
}
#[inline]
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));
let new_size = window_state.request_inner_size(size);
self.request_redraw();
Some(window_state.inner_size().to_physical(scale_factor))
Some(new_size)
}
/// Set the minimum inner size for the window.
@@ -331,7 +326,9 @@ impl Window {
self.window_state
.lock()
.unwrap()
.set_min_inner_size(min_size)
.set_min_inner_size(min_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
/// Set the maximum inner size for the window.
@@ -342,7 +339,9 @@ impl Window {
self.window_state
.lock()
.unwrap()
.set_max_inner_size(max_size)
.set_max_inner_size(max_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
#[inline]
@@ -391,7 +390,10 @@ impl Window {
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.window_state.lock().unwrap().set_resizable(resizable);
if self.window_state.lock().unwrap().set_resizable(resizable) {
// NOTE: Requires commit to be applied.
self.request_redraw();
}
}
#[inline]

View File

@@ -1,12 +1,13 @@
//! The state of the window, which is shared with the event-loop.
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use std::sync::{Arc, Weak};
use std::time::Duration;
use ahash::HashSet;
use log::{info, warn};
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
@@ -28,11 +29,9 @@ use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError};
use crate::event::WindowEvent;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::make_wid;
use crate::platform_impl::wayland::logical_to_physical_rounded;
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
@@ -55,9 +54,6 @@ pub struct WindowState {
/// The connection to Wayland server.
pub connection: Connection,
/// The underlying SCTK window.
pub window: ManuallyDrop<Window>,
/// The window frame, which is created from the configure request.
frame: Option<WinitFrame>,
@@ -91,8 +87,10 @@ pub struct WindowState {
/// Whether the frame is resizable.
resizable: bool,
/// Whether the window has focus.
has_focus: bool,
// NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new
// is created, since add/removed stuff could be delivered a bit out of order.
/// Seats that has keyboard focus on that window.
seat_focus: HashSet<ObjectId>,
/// The scale factor of the window.
scale_factor: f64,
@@ -133,6 +131,10 @@ pub struct WindowState {
/// sends `None` for the new size in the configure.
stateless_size: LogicalSize<u32>,
/// Initial window size provided by the user. Removed on the first
/// configure.
initial_size: Option<Size>,
/// The state of the frame callback.
frame_callback_state: FrameCallbackState,
@@ -145,6 +147,9 @@ pub struct WindowState {
///
/// The value is the serial of the event triggered moved.
has_pending_move: Option<u32>,
/// The underlying SCTK window.
pub window: Window,
}
impl WindowState {
@@ -153,7 +158,7 @@ impl WindowState {
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
size: LogicalSize<u32>,
initial_size: Size,
window: Window,
theme: Option<Theme>,
) -> Self {
@@ -181,7 +186,7 @@ impl WindowState {
fractional_scale,
frame: None,
frame_callback_state: FrameCallbackState::None,
has_focus: false,
seat_focus: Default::default(),
has_pending_move: None,
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
@@ -194,14 +199,15 @@ impl WindowState {
resizable: true,
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
size,
stateless_size: size,
size: initial_size.to_logical(1.),
stateless_size: initial_size.to_logical(1.),
initial_size: Some(initial_size),
text_inputs: Vec::new(),
theme,
title: String::default(),
transparent: false,
viewport,
window: ManuallyDrop::new(window),
window,
}
}
@@ -250,16 +256,26 @@ impl WindowState {
&mut self,
configure: WindowConfigure,
shm: &Shm,
subcompositor: &Arc<SubcompositorState>,
event_sink: &mut EventSink,
) -> LogicalSize<u32> {
if configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
&& !self.csd_fails
{
subcompositor: &Option<Arc<SubcompositorState>>,
) -> bool {
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
// should be delivered before the first configure, thus apply it to
// properly scale the physical sizes provided by the users.
if let Some(initial_size) = self.initial_size.take() {
self.size = initial_size.to_logical(self.scale_factor());
self.stateless_size = self.size;
}
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
&& !self.csd_fails
}) {
match WinitFrame::new(
&*self.window,
&self.window,
shm,
#[cfg(feature = "sctk-adwaita")]
self.compositor.clone(),
subcompositor.clone(),
self.queue_handle.clone(),
#[cfg(feature = "sctk-adwaita")]
@@ -284,50 +300,90 @@ impl WindowState {
let stateless = Self::is_stateless(&configure);
// Emit `Occluded` event on suspension change.
let occluded = configure.state.contains(XdgWindowState::SUSPENDED);
if self
.last_configure
.as_ref()
.map(|c| c.state.contains(XdgWindowState::SUSPENDED))
.unwrap_or(false)
!= occluded
{
let window_id = make_wid(self.window.wl_surface());
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
}
let new_size = if let Some(frame) = self.frame.as_mut() {
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
// Configure the window states.
frame.update_state(configure.state);
match configure.new_size {
(Some(width), Some(height)) => {
let (width, height) = frame.subtract_borders(width, height);
(
width.map(|w| w.get()).unwrap_or(1),
height.map(|h| h.get()).unwrap_or(1),
)
.into()
let width = width.map(|w| w.get()).unwrap_or(1);
let height = height.map(|h| h.get()).unwrap_or(1);
((width, height).into(), false)
}
(_, _) if stateless => self.stateless_size,
_ => self.size,
(_, _) if stateless => (self.stateless_size, true),
_ => (self.size, true),
}
} else {
match configure.new_size {
(Some(width), Some(height)) => (width.get(), height.get()).into(),
_ if stateless => self.stateless_size,
_ => self.size,
(Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
_ if stateless => (self.stateless_size, true),
_ => (self.size, true),
}
};
// XXX Set the configure before doing a resize.
// Apply configure bounds only when compositor let the user decide what size to pick.
if constrain {
let bounds = self.inner_size_bounds(&configure);
new_size.width = bounds
.0
.map(|bound_w| new_size.width.min(bound_w.get()))
.unwrap_or(new_size.width);
new_size.height = bounds
.1
.map(|bound_h| new_size.height.min(bound_h.get()))
.unwrap_or(new_size.height);
}
let new_state = configure.state;
let old_state = self
.last_configure
.as_ref()
.map(|configure| configure.state);
let state_change_requires_resize = old_state
.map(|old_state| {
!old_state
.symmetric_difference(new_state)
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
.is_empty()
})
// NOTE: `None` is present for the initial configure, thus we must always resize.
.unwrap_or(true);
// NOTE: Set the configure before doing a resize, since we query it during it.
self.last_configure = Some(configure);
// XXX Update the new size right away.
self.resize(new_size);
if state_change_requires_resize || new_size != self.inner_size() {
self.resize(new_size);
true
} else {
false
}
}
new_size
/// Compute the bounds for the inner size of the surface.
fn inner_size_bounds(
&self,
configure: &WindowConfigure,
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
let configure_bounds = match configure.suggested_bounds {
Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
None => (None, None),
};
if let Some(frame) = self.frame.as_ref() {
let (width, height) = frame.subtract_borders(
configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
);
(
configure_bounds.0.and(width),
configure_bounds.1.and(height),
)
} else {
configure_bounds
}
}
#[inline]
@@ -442,10 +498,12 @@ impl WindowState {
}
/// Set the resizable state on the window.
///
/// Returns `true` when the state was applied.
#[inline]
pub fn set_resizable(&mut self, resizable: bool) {
pub fn set_resizable(&mut self, resizable: bool) -> bool {
if self.resizable == resizable {
return;
return false;
}
self.resizable = resizable;
@@ -461,12 +519,14 @@ impl WindowState {
if let Some(frame) = self.frame.as_mut() {
frame.set_resizable(resizable);
}
true
}
/// Whether the window is focused.
/// Whether the window is focused by any seat.
#[inline]
pub fn has_focus(&self) -> bool {
self.has_focus
!self.seat_focus.is_empty()
}
/// Whether the IME is allowed.
@@ -537,7 +597,7 @@ impl WindowState {
/// Refresh the decorations frame if it's present returning whether the client should redraw.
pub fn refresh_frame(&mut self) -> bool {
if let Some(frame) = self.frame.as_mut() {
if frame.is_dirty() {
if !frame.is_hidden() && frame.is_dirty() {
return frame.draw();
}
}
@@ -568,8 +628,22 @@ impl WindowState {
}
}
/// Try to resize the window when the user can do so.
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
if self
.last_configure
.as_ref()
.map(Self::is_stateless)
.unwrap_or(true)
{
self.resize(inner_size.to_logical(self.scale_factor()))
}
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
}
/// Resize the window to the new inner size.
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
fn resize(&mut self, inner_size: LogicalSize<u32>) {
self.size = inner_size;
// Update the stateless size.
@@ -684,9 +758,14 @@ impl WindowState {
/// Set the cursor grabbing state on the top-level.
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
// Replace the user grabbing mode.
if self.cursor_grab_mode.user_grab_mode == mode {
return Ok(());
}
self.set_cursor_grab_inner(mode)?;
// Update user grab on success.
self.cursor_grab_mode.user_grab_mode = mode;
self.set_cursor_grab_inner(mode)
Ok(())
}
/// Reload the hints for minimum and maximum sizes.
@@ -814,12 +893,16 @@ impl WindowState {
}
}
/// Mark that the window has focus.
///
/// Should be used from routine that sends focused event.
/// Add seat focus for the window.
#[inline]
pub fn set_has_focus(&mut self, has_focus: bool) {
self.has_focus = has_focus;
pub fn add_seat_focus(&mut self, seat: ObjectId) {
self.seat_focus.insert(seat);
}
/// Remove seat focus from the window.
#[inline]
pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
self.seat_focus.remove(seat);
}
/// Returns `true` if the requested state was applied.
@@ -843,7 +926,7 @@ impl WindowState {
/// Set the IME position.
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
// XXX This won't fly unless user will have a way to request IME window per seat, since
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
// the ime windows will be overlapping, but winit doesn't expose API to specify for
// which seat we're setting IME position.
let (x, y) = (position.x as i32, position.y as i32);
@@ -874,7 +957,7 @@ impl WindowState {
pub fn set_scale_factor(&mut self, scale_factor: f64) {
self.scale_factor = scale_factor;
// XXX when fractional scaling is not used update the buffer scale.
// NOTE: When fractional scaling is not used update the buffer scale.
if self.fractional_scale.is_none() {
let _ = self.window.set_buffer_scale(self.scale_factor as _);
}
@@ -959,13 +1042,6 @@ impl WindowState {
impl Drop for WindowState {
fn drop(&mut self) {
let surface = self.window.wl_surface().clone();
unsafe {
ManuallyDrop::drop(&mut self.window);
}
// Cleanup objects.
if let Some(blur) = self.blur.take() {
blur.release();
}
@@ -978,7 +1054,8 @@ impl Drop for WindowState {
viewport.destroy();
}
surface.destroy();
// NOTE: the wl_surface used by the window is being cleaned up when
// dropping SCTK `Window`.
}
}
@@ -1028,7 +1105,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
}
}
// XXX rust doesn't allow from `Option`.
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
#[cfg(feature = "sctk-adwaita")]
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
match theme {

View File

@@ -6,7 +6,7 @@ macro_rules! atom_manager {
($($name:ident $(:$lit:literal)?),*) => {
x11rb::atom_manager! {
/// The atoms used by `winit`
pub(crate) Atoms: AtomsCookie {
pub Atoms: AtomsCookie {
$($name $(:$lit)?,)*
}
}
@@ -14,7 +14,7 @@ macro_rules! atom_manager {
/// Indices into the `Atoms` struct.
#[derive(Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
pub(crate) enum AtomName {
pub enum AtomName {
$($name,)*
}
@@ -100,7 +100,8 @@ atom_manager! {
_NET_FRAME_EXTENTS,
_NET_SUPPORTED,
_NET_SUPPORTING_WM_CHECK,
_XEMBED
_XEMBED,
_XSETTINGS_SETTINGS
}
impl Index<AtomName> for Atoms {

View File

@@ -41,7 +41,7 @@ impl From<io::Error> for DndDataParseError {
}
}
pub(crate) struct Dnd {
pub struct Dnd {
xconn: Arc<XConnection>,
// Populated by XdndEnter event handler
pub version: Option<c_long>,

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -79,9 +79,9 @@ pub(crate) unsafe fn set_destroy_callback(
#[allow(clippy::enum_variant_names)]
enum ReplaceImError {
// Boxed to prevent large error type
MethodOpenFailed(Box<PotentialInputMethods>),
ContextCreationFailed(ImeContextCreationError),
SetDestroyCallbackFailed(XError),
MethodOpenFailed(#[allow(dead_code)] Box<PotentialInputMethods>),
ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError),
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
}
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This

View File

@@ -159,9 +159,9 @@ impl InputMethodResult {
#[derive(Debug, Clone)]
enum GetXimServersError {
XError(XError),
GetPropertyError(util::GetPropertyError),
InvalidUtf8(IntoStringError),
XError(#[allow(dead_code)] XError),
GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
InvalidUtf8(#[allow(dead_code)] IntoStringError),
}
impl From<util::GetPropertyError> for GetXimServersError {

View File

@@ -48,7 +48,7 @@ pub enum ImeRequest {
pub(crate) enum ImeCreationError {
// Boxed to prevent large error type
OpenFailure(Box<PotentialInputMethods>),
SetDestroyCallbackFailed(XError),
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
}
pub(crate) struct Ime {

View File

@@ -1,5 +1,49 @@
#![cfg(x11_platform)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::os::raw::*;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use std::{ptr, slice, str};
pub use self::xdisplay::{XError, XNotSupported};
use calloop::generic::Generic;
use calloop::EventLoop as Loop;
use calloop::{ping::Ping, Readiness};
use libc::{setlocale, LC_CTYPE};
use log::warn;
use x11rb::connection::RequestConnection;
use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError};
use x11rb::protocol::xinput::{self, ConnectionExt as _};
use x11rb::protocol::xkb;
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::xcb_ffi::ReplyOrIdError;
use super::{ControlFlow, OsError};
use crate::{
error::{EventLoopError, OsError as RootOsError},
event::{Event, StartCause, WindowEvent},
event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform::pump_events::PumpStatus,
platform_impl::common::xkb::Context,
platform_impl::{
platform::{min_timeout, WindowId},
PlatformSpecificWindowBuilderAttributes,
},
window::WindowAttributes,
};
mod activation;
mod atoms;
mod dnd;
@@ -10,77 +54,23 @@ mod monitor;
pub mod util;
mod window;
mod xdisplay;
pub(crate) use self::{
monitor::{MonitorHandle, VideoMode},
window::UnownedWindow,
xdisplay::XConnection,
};
pub use self::xdisplay::{XError, XNotSupported};
use calloop::generic::Generic;
use calloop::EventLoop as Loop;
use calloop::{ping::Ping, Readiness};
use std::{
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
ffi::CStr,
fmt,
mem::MaybeUninit,
ops::Deref,
os::{
raw::*,
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
},
ptr,
rc::Rc,
slice, str,
sync::mpsc::{Receiver, Sender, TryRecvError},
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
mod xsettings;
use atoms::*;
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::{
connection::RequestConnection,
protocol::{
xinput::{self, ConnectionExt as _},
xkb,
xproto::{self, ConnectionExt as _},
},
};
use x11rb::{
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
xcb_ffi::ReplyOrIdError,
};
use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
};
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
use crate::{
error::{EventLoopError, OsError as RootOsError},
event::{Event, StartCause, WindowEvent},
event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform::pump_events::PumpStatus,
platform_impl::{
platform::{min_timeout, WindowId},
PlatformSpecificWindowBuilderAttributes,
},
window::WindowAttributes,
};
use dnd::{Dnd, DndState};
use event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN};
use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender};
pub(crate) use monitor::{MonitorHandle, VideoMode};
use window::UnownedWindow;
pub(crate) use xdisplay::XConnection;
// Xinput constants not defined in x11rb
const ALL_DEVICES: u16 = 0;
const ALL_MASTER_DEVICES: u16 = 1;
const ICONIC_STATE: u32 = 3;
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
type X11Source = Generic<BorrowedFd<'static>>;
@@ -150,7 +140,7 @@ pub struct EventLoopWindowTarget<T> {
control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>,
root: xproto::Window,
ime: RefCell<Ime>,
ime: Option<RefCell<Ime>>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: WakeSender<WindowId>,
activation_sender: WakeSender<ActivationToken>,
@@ -167,7 +157,6 @@ pub struct EventLoop<T: 'static> {
user_receiver: PeekableReceiver<T>,
activation_receiver: PeekableReceiver<ActivationToken>,
user_sender: Sender<T>,
target: Rc<RootELW<T>>,
/// The current state of the event loop.
state: EventLoopState,
@@ -228,13 +217,15 @@ impl<T: 'static> EventLoop<T> {
setlocale(LC_CTYPE, default_locale);
}
}
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
panic!("Failed to open input method: {state:#?}");
}
result.expect("Failed to set input method destruction callback")
});
let ime = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(state)) = ime.as_ref() {
warn!("Failed to open input method: {state:#?}");
} else if let Err(err) = ime.as_ref() {
warn!("Failed to set input method destruction callback: {err:?}");
}
let ime = ime.ok().map(RefCell::new);
let randr_event_offset = xconn
.select_xrandr_input(root)
@@ -298,8 +289,11 @@ impl<T: 'static> EventLoop<T> {
// Create a channel for sending user events.
let (user_sender, user_channel) = mpsc::channel();
let kb_state =
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
let xkb_context =
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
let mut xmodmap = util::ModifierKeymap::new();
xmodmap.reload_from_x_connection(&xconn);
let window_target = EventLoopWindowTarget {
ime,
@@ -326,32 +320,36 @@ impl<T: 'static> EventLoop<T> {
// Set initial device event filter.
window_target.update_listen_device_events(true);
let target = Rc::new(RootELW {
let root_window_target = RootELW {
p: super::EventLoopWindowTarget::X(window_target),
_marker: ::std::marker::PhantomData,
});
_marker: PhantomData,
};
let event_processor = EventProcessor {
target: target.clone(),
target: root_window_target,
dnd,
devices: Default::default(),
randr_event_offset,
ime_receiver,
ime_event_receiver,
xi2ext,
xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN),
xmodmap,
xkbext,
kb_state,
xkb_context,
num_touch: 0,
held_key_press: None,
first_touch: None,
active_window: None,
modifiers: Default::default(),
is_composing: false,
};
// Register for device hotplug events
// (The request buffer is flushed during `init_device`)
get_xtarget(&target)
.xconn
let xconn = &EventProcessor::window_target(&event_processor.target).xconn;
xconn
.select_xinput_events(
root,
ALL_DEVICES,
@@ -359,11 +357,12 @@ impl<T: 'static> EventLoop<T> {
)
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
get_xtarget(&target)
.xconn
xconn
.select_xkb_events(
0x100, // Use the "core keyboard device"
xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
xkb::EventType::NEW_KEYBOARD_NOTIFY
| xkb::EventType::MAP_NOTIFY
| xkb::EventType::STATE_NOTIFY,
)
.unwrap();
@@ -378,7 +377,6 @@ impl<T: 'static> EventLoop<T> {
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
target,
state: EventLoopState {
x11_readiness: Readiness::EMPTY,
},
@@ -395,7 +393,7 @@ impl<T: 'static> EventLoop<T> {
}
pub(crate) fn window_target(&self) -> &RootELW<T> {
&self.target
&self.event_processor.target
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
@@ -424,7 +422,7 @@ impl<T: 'static> EventLoop<T> {
// `run_on_demand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// X Server.
let wt = get_xtarget(&self.target);
let wt = EventProcessor::window_target(&self.event_processor.target);
wt.x_connection().sync_with_server().map_err(|x_err| {
EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err)))))
})?;
@@ -547,12 +545,12 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<T>, &RootELW<T>),
{
callback(crate::event::Event::NewEvents(cause), &self.target);
callback(Event::NewEvents(cause), &self.event_processor.target);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
callback(crate::event::Event::Resumed, &self.target);
callback(Event::Resumed, &self.event_processor.target);
}
// Process all pending events
@@ -567,16 +565,16 @@ impl<T: 'static> EventLoop<T> {
});
match token {
Some(Ok(token)) => callback(
crate::event::Event::WindowEvent {
Some(Ok(token)) => {
let event = Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: crate::event::WindowEvent::ActivationTokenDone {
event: WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
},
},
&self.target,
),
};
callback(event, &self.event_processor.target)
}
Some(Err(e)) => {
log::error!("Failed to get activation token: {}", e);
}
@@ -587,7 +585,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_receiver.try_recv() {
callback(crate::event::Event::UserEvent(event), &self.target);
callback(Event::UserEvent(event), &self.event_processor.target);
}
}
@@ -606,14 +604,14 @@ impl<T: 'static> EventLoop<T> {
window_id,
event: WindowEvent::RedrawRequested,
},
&self.target,
&self.event_processor.target,
);
}
}
// This is always the last event we dispatch before poll again
{
callback(crate::event::Event::AboutToWait, &self.target);
callback(Event::AboutToWait, &self.event_processor.target);
}
}
@@ -621,40 +619,44 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<T>, &RootELW<T>),
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
wt.redraw_sender.send(wid).unwrap();
} else {
callback(event, target);
}
});
self.event_processor
.process_event(&mut xev, |window_target, event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid).unwrap();
} else {
callback(event, window_target);
}
});
}
}
fn control_flow(&self) -> ControlFlow {
self.target.p.control_flow()
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.control_flow()
}
fn exiting(&self) -> bool {
self.target.p.exiting()
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.exiting()
}
fn set_exit_code(&self, code: i32) {
self.target.p.set_exit_code(code)
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.set_exit_code(code);
}
fn exit_code(&self) -> Option<i32> {
self.target.p.exit_code()
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.exit_code()
}
}
@@ -670,14 +672,6 @@ impl<T> AsRawFd for EventLoop<T> {
}
}
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
match target.p {
super::EventLoopWindowTarget::X(ref target) => target,
#[cfg(wayland_platform)]
_ => unreachable!(),
}
}
impl<T> EventLoopWindowTarget<T> {
/// Returns the `XConnection` of this events loop.
#[inline]
@@ -751,6 +745,10 @@ impl<T> EventLoopWindowTarget<T> {
self.exit.set(Some(0))
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
@@ -890,6 +888,12 @@ pub enum X11Error {
/// Could not find a matching X11 visual for this visualid
NoSuchVisual(xproto::Visualid),
/// Unable to parse xsettings.
XsettingsParse(xsettings::ParserError),
/// Failed to get property.
GetProperty(util::GetPropertyError),
}
impl fmt::Display for X11Error {
@@ -899,6 +903,7 @@ impl fmt::Display for X11Error {
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e),
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
X11Error::InvalidActivationToken(s) => write!(
@@ -914,6 +919,9 @@ impl fmt::Display for X11Error {
visualid
)
}
X11Error::XsettingsParse(err) => {
write!(f, "Failed to parse xsettings: {:?}", err)
}
}
}
}
@@ -982,8 +990,17 @@ impl From<ReplyOrIdError> for X11Error {
}
}
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
impl From<xsettings::ParserError> for X11Error {
fn from(value: xsettings::ParserError) -> Self {
Self::XsettingsParse(value)
}
}
impl From<util::GetPropertyError> for X11Error {
fn from(value: util::GetPropertyError) -> Self {
Self::GetProperty(value)
}
}
/// Type alias for a void cookie.
type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
@@ -1000,34 +1017,6 @@ impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
}
}
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to
/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed
struct GenericEventCookie<'a> {
xconn: &'a XConnection,
cookie: ffi::XGenericEventCookie,
}
impl<'a> GenericEventCookie<'a> {
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
unsafe {
let mut cookie: ffi::XGenericEventCookie = From::from(event);
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
Some(GenericEventCookie { xconn, cookie })
} else {
None
}
}
}
}
impl<'a> Drop for GenericEventCookie<'a> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
}
}
}
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
}
@@ -1036,7 +1025,7 @@ fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
}
#[derive(Debug)]
struct Device {
pub struct Device {
_name: String,
scroll_axes: Vec<(i32, ScrollAxis)>,
// For master devices, this is the paired device (pointer <-> keyboard).
@@ -1128,3 +1117,9 @@ impl Device {
}
}
}
/// Convert the raw X11 representation for a 32-bit floating point to a double.
#[inline]
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
(fp as f64) / ((1 << 16) as f64)
}

View File

@@ -212,7 +212,7 @@ impl XConnection {
return Ok(MonitorHandle::dummy());
}
let default = monitors.get(0).unwrap();
let default = monitors.first().unwrap();
let window_rect = match window_rect {
Some(rect) => rect,
@@ -234,7 +234,8 @@ impl XConnection {
fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
let root = self.default_root();
let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
let resources =
ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?;
// Pipeline all of the get-crtc requests.
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
@@ -284,22 +285,16 @@ impl XConnection {
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
let mut monitors_lock = self.monitor_handles.lock().unwrap();
(*monitors_lock)
.as_ref()
.cloned()
.map(Ok)
.or_else(|| {
self.query_monitor_list()
.map(|mon_list| {
let monitors = Some(mon_list);
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
})
.transpose()
})
.unwrap()
match *monitors_lock {
Some(ref monitors) => Ok(monitors.clone()),
None => {
let monitors = self.query_monitor_list()?;
if !DISABLE_MONITOR_LIST_CACHING {
*monitors_lock = Some(monitors.clone());
}
Ok(monitors)
}
}
}
#[inline]
@@ -329,7 +324,7 @@ impl XConnection {
}
}
pub(crate) struct ScreenResources {
pub struct ScreenResources {
/// List of attached modes.
modes: Vec<randr::ModeInfo>,
@@ -349,10 +344,9 @@ impl ScreenResources {
pub(crate) fn from_connection(
conn: &impl x11rb::connection::Connection,
root: &x11rb::protocol::xproto::Screen,
(major_version, minor_version): (u32, u32),
) -> Result<Self, X11Error> {
let version = conn.randr_query_version(0, 0)?.reply()?;
if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 {
if (major_version == 1 && minor_version >= 3) || major_version > 1 {
let reply = conn
.randr_get_screen_resources_current(root.root)?
.reply()?;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,55 @@
use std::ffi::c_int;
use std::sync::Arc;
use x11_dl::xlib::{self, XEvent, XGenericEventCookie};
use crate::platform_impl::x11::XConnection;
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure.
/// This is a wrapper to extract the cookie from a GenericEvent XEvent and release the cookie data
/// once it has been processed
pub struct GenericEventCookie {
cookie: XGenericEventCookie,
xconn: Arc<XConnection>,
}
impl GenericEventCookie {
pub fn from_event(xconn: Arc<XConnection>, event: XEvent) -> Option<GenericEventCookie> {
unsafe {
let mut cookie: XGenericEventCookie = From::from(event);
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == xlib::True {
Some(GenericEventCookie { cookie, xconn })
} else {
None
}
}
}
#[inline]
pub fn extension(&self) -> u8 {
self.cookie.extension as u8
}
#[inline]
pub fn evtype(&self) -> c_int {
self.cookie.evtype
}
/// Borrow inner event data as `&T`.
///
/// ## SAFETY
///
/// The caller must ensure that the event has the `T` inside of it.
#[inline]
pub unsafe fn as_event<T>(&self) -> &T {
unsafe { &*(self.cookie.data as *const _) }
}
}
impl Drop for GenericEventCookie {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
}
}
}

View File

@@ -1,4 +1,5 @@
use std::ffi::CString;
use std::iter;
use x11rb::connection::Connection;
@@ -56,10 +57,22 @@ impl XConnection {
None => return self.create_empty_cursor(),
};
let name = CString::new(cursor.name()).unwrap();
unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
let mut xcursor = 0;
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
let name = CString::new(name).unwrap();
xcursor = unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(
self.display,
name.as_ptr() as *const c_char,
)
};
if xcursor != 0 {
break;
}
}
xcursor
}
fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> {

View File

@@ -83,14 +83,6 @@ impl FrameExtents {
}
}
#[derive(Debug, Clone)]
pub struct LogicalFrameExtents {
pub left: f64,
pub right: f64,
pub top: f64,
pub bottom: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameExtentsHeuristicPath {
Supported,

View File

@@ -1,7 +1,14 @@
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
// *results may vary
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
};
mod client_msg;
pub mod cookie;
mod cursor;
mod geometry;
mod hint;
@@ -9,18 +16,14 @@ mod icon;
mod input;
pub mod keys;
pub(crate) mod memory;
mod mouse;
mod randr;
mod window_property;
mod wm;
mod xmodmap;
pub use self::{
client_msg::*, geometry::*, hint::*, icon::*, input::*, randr::*, window_property::*, wm::*,
};
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
geometry::*, hint::*, input::*, mouse::*, window_property::*, wm::*, xmodmap::ModifierKeymap,
};
use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};

View File

@@ -0,0 +1,52 @@
//! Utilities for handling mouse events.
/// Recorded mouse delta designed to filter out noise.
pub struct Delta<T> {
x: T,
y: T,
}
impl<T: Default> Default for Delta<T> {
fn default() -> Self {
Self {
x: Default::default(),
y: Default::default(),
}
}
}
impl<T: Default> Delta<T> {
pub(crate) fn set_x(&mut self, x: T) {
self.x = x;
}
pub(crate) fn set_y(&mut self, y: T) {
self.y = y;
}
}
macro_rules! consume {
($this:expr, $ty:ty) => {{
let this = $this;
let (x, y) = match (this.x.abs() < <$ty>::EPSILON, this.y.abs() < <$ty>::EPSILON) {
(true, true) => return None,
(false, true) => (this.x, 0.0),
(true, false) => (0.0, this.y),
(false, false) => (this.x, this.y),
};
Some((x, y))
}};
}
impl Delta<f32> {
pub(crate) fn consume(self) -> Option<(f32, f32)> {
consume!(self, f32)
}
}
impl Delta<f64> {
pub(crate) fn consume(self) -> Option<(f64, f64)> {
consume!(self, f64)
}
}

View File

@@ -37,10 +37,22 @@ pub fn calc_dpi_factor(
impl XConnection {
// Retrieve DPI from Xft.dpi property
pub fn get_xft_dpi(&self) -> Option<f64> {
// Try to get it from XSETTINGS first.
if let Some(xsettings_screen) = self.xsettings_screen() {
match self.xsettings_dpi(xsettings_screen) {
Ok(Some(dpi)) => return Some(dpi),
Ok(None) => {}
Err(err) => {
log::warn!("failed to fetch XSettings: {err}");
}
}
}
self.database()
.get_string("Xfi.dpi", "")
.get_string("Xft.dpi", "")
.and_then(|s| f64::from_str(s).ok())
}
pub fn get_output_info(
&self,
resources: &monitor::ScreenResources,

View File

@@ -1,13 +1,18 @@
use super::*;
use bytemuck::{NoUninit, Pod};
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use bytemuck::{NoUninit, Pod};
use x11rb::connection::Connection;
use x11rb::errors::ReplyError;
pub type Cardinal = u32;
use super::*;
pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
pub type Cardinal = u32;
#[derive(Debug, Clone)]
pub enum GetPropertyError {
X11rbError(Arc<ReplyError>),
@@ -15,12 +20,6 @@ pub enum GetPropertyError {
FormatMismatch(c_int),
}
impl<T: Into<ReplyError>> From<T> for GetPropertyError {
fn from(e: T) -> Self {
Self::X11rbError(Arc::new(e.into()))
}
}
impl GetPropertyError {
pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool {
if let GetPropertyError::TypeMismatch(actual_type) = *self {
@@ -31,6 +30,24 @@ impl GetPropertyError {
}
}
impl<T: Into<ReplyError>> From<T> for GetPropertyError {
fn from(e: T) -> Self {
Self::X11rbError(Arc::new(e.into()))
}
}
impl fmt::Display for GetPropertyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GetPropertyError::X11rbError(err) => err.fmt(f),
GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"),
GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"),
}
}
}
impl Error for GetPropertyError {}
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
// To test if `get_property` works correctly, set this to 1.
const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone!

View File

@@ -0,0 +1,56 @@
use std::collections::HashSet;
use std::slice;
use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap};
// Offsets within XModifierKeymap to each set of keycodes.
// We are only interested in Shift, Control, Alt, and Logo.
//
// There are 8 sets total. The order of keycode sets is:
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
//
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
const NUM_MODS: usize = 8;
/// Track which keys are modifiers, so we can properly replay them when they were filtered.
#[derive(Debug, Default)]
pub struct ModifierKeymap {
// Maps keycodes to modifiers
modifers: HashSet<XKeyCode>,
}
impl ModifierKeymap {
pub fn new() -> ModifierKeymap {
ModifierKeymap::default()
}
pub fn is_modifier(&self, keycode: XKeyCode) -> bool {
self.modifers.contains(&keycode)
}
pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) {
unsafe {
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
if keymap.is_null() {
return;
}
self.reset_from_x_keymap(&*keymap);
(xconn.xlib.XFreeModifiermap)(keymap);
}
}
fn reset_from_x_keymap(&mut self, keymap: &XModifierKeymap) {
let keys_per_mod = keymap.max_keypermod as usize;
let keys = unsafe {
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
};
self.modifers.clear();
for key in keys {
self.modifers.insert(*key);
}
}
}

View File

@@ -9,7 +9,7 @@ use std::{
use x11rb::{
connection::Connection,
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification},
protocol::{
randr,
shape::SK,
@@ -22,9 +22,13 @@ use x11rb::{
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, InnerSizeWriter, WindowEvent},
event_loop::AsyncRequestSerial,
platform_impl::{
x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error},
x11::{
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
X11Error,
},
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
},
@@ -114,7 +118,7 @@ impl SharedState {
unsafe impl Send for UnownedWindow {}
unsafe impl Sync for UnownedWindow {}
pub(crate) struct UnownedWindow {
pub struct UnownedWindow {
pub(crate) xconn: Arc<XConnection>, // never changes
xwindow: xproto::Window, // never changes
#[allow(dead_code)]
@@ -276,7 +280,8 @@ impl UnownedWindow {
| EventMask::KEYMAP_STATE
| EventMask::BUTTON_PRESS
| EventMask::BUTTON_RELEASE
| EventMask::POINTER_MOTION;
| EventMask::POINTER_MOTION
| EventMask::PROPERTY_CHANGE;
aux = aux.event_mask(event_mask).border_pixel(0);
@@ -391,7 +396,7 @@ impl UnownedWindow {
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
{
let (class, instance) = if let Some(name) = pl_attribs.name {
let (instance, class) = if let Some(name) = pl_attribs.name {
(name.instance, name.general)
} else {
let class = env::args_os()
@@ -536,9 +541,9 @@ impl UnownedWindow {
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
{
let result = event_loop
.ime
// Try to create input context for the window.
if let Some(ime) = event_loop.ime.as_ref() {
let result = ime
.borrow_mut()
.create_context(window.xwindow as ffi::Window, false);
leap!(result);
@@ -923,6 +928,51 @@ impl UnownedWindow {
})
}
/// Refresh the API for the given monitor.
#[inline]
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
mut callback: impl FnMut(Event<T>),
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
if monitor.name == new_monitor.name {
let (width, height) = self.inner_size_physical();
let (new_width, new_height) = self.adjust_for_dpi(
// If we couldn't determine the previous scale
// factor (e.g., because all monitors were closed
// before), just pick whatever the current monitor
// has set as a baseline.
maybe_prev_scale_factor.unwrap_or(monitor.scale_factor),
new_monitor.scale_factor,
width,
height,
&self.shared_state_lock(),
);
let window_id = crate::window::WindowId(self.id());
let old_inner_size = PhysicalSize::new(width, 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,
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();
self.request_inner_size_physical(new_width, new_height);
}
}
}
fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> {
let atoms = self.xconn.atoms();
@@ -937,7 +987,7 @@ impl UnownedWindow {
xproto::EventMask::SUBSTRUCTURE_REDIRECT
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
),
[WmHintsState::Iconic as u32, 0, 0, 0, 0],
[3u32, 0, 0, 0, 0],
)
} else {
self.xconn.send_client_msg(
@@ -1327,7 +1377,8 @@ impl UnownedWindow {
self.xwindow as xproto::Window,
xproto::AtomEnum::WM_NORMAL_HINTS,
)?
.reply()?;
.reply()?
.unwrap_or_default();
callback(&mut normal_hints);
normal_hints
.set(
@@ -1380,6 +1431,7 @@ impl UnownedWindow {
)
.ok()
.and_then(|cookie| cookie.reply().ok())
.flatten()
.and_then(|hints| hints.size_increment)
.map(|(width, height)| (width as u32, height as u32).into())
}
@@ -1692,8 +1744,8 @@ impl UnownedWindow {
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
),
[
(window.x as u32 + pointer.win_x as u32),
(window.y as u32 + pointer.win_y as u32),
(window.x as u32 + xinput_fp1616_to_float(pointer.win_x) as u32),
(window.y as u32 + xinput_fp1616_to_float(pointer.win_y) as u32),
action.try_into().unwrap(),
1, // Button 1
1,
@@ -1735,9 +1787,9 @@ impl UnownedWindow {
let state_type_atom = atoms[CARD32];
let is_minimized = if let Ok(state) =
self.xconn
.get_property(self.xwindow, state_atom, state_type_atom)
.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
{
state.contains(&(ffi::IconicState as c_ulong))
state.contains(&super::ICONIC_STATE)
} else {
false
};
@@ -1774,6 +1826,7 @@ impl UnownedWindow {
WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
.ok()
.and_then(|cookie| cookie.reply().ok())
.flatten()
.unwrap_or_default();
wm_hints.urgent = request_type.is_some();

View File

@@ -4,17 +4,25 @@ use std::{
fmt, ptr,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex,
Arc, Mutex, RwLock, RwLockReadGuard,
},
};
use crate::window::CursorIcon;
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
use x11rb::{
connection::Connection,
protocol::{
randr::ConnectionExt as _,
xproto::{self, ConnectionExt},
},
resource_manager,
xcb_ffi::XCBConnection,
};
/// A connection to an X server.
pub(crate) struct XConnection {
pub struct XConnection {
pub xlib: ffi::Xlib,
pub xcursor: ffi::Xcursor,
@@ -45,7 +53,13 @@ pub(crate) struct XConnection {
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
/// The resource database.
database: resource_manager::Database,
database: RwLock<resource_manager::Database>,
/// RandR version.
randr_version: (u32, u32),
/// Atom for the XSettings screen.
xsettings_screen: Option<xproto::Atom>,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
@@ -94,16 +108,28 @@ impl XConnection {
// Get the default screen.
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
// Fetch the atoms.
// Load the database.
let database = resource_manager::new_from_default(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Load the RandR version.
let randr_version = xcb
.randr_query_version(1, 3)
.expect("failed to request XRandR version")
.reply()
.expect("failed to query XRandR version");
let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen);
if xsettings_screen.is_none() {
log::warn!("error setting XSETTINGS; Xft options won't reload automatically")
}
// Fetch atoms.
let atoms = Atoms::new(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Load the database.
let database = resource_manager::new_from_default(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
Ok(XConnection {
xlib,
xcursor,
@@ -115,11 +141,44 @@ impl XConnection {
timestamp: AtomicU32::new(0),
latest_error: Mutex::new(None),
monitor_handles: Mutex::new(None),
database,
database: RwLock::new(database),
cursor_cache: Default::default(),
randr_version: (randr_version.major_version, randr_version.minor_version),
xsettings_screen,
})
}
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.ok()?
.reply()
.ok()?
.atom;
// Get PropertyNotify events from the XSETTINGS window.
// TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
// in order to accomodate for a changed window here.
let selector_window = xcb
.get_selection_owner(xsettings_screen)
.ok()?
.reply()
.ok()?
.owner;
xcb.change_window_attributes(
selector_window,
&xproto::ChangeWindowAttributesAux::new()
.event_mask(xproto::EventMask::PROPERTY_CHANGE),
)
.ok()?
.check()
.ok()?;
Some(xsettings_screen)
}
/// Checks whether an error has been triggered by the previous function calls.
#[inline]
pub fn check_errors(&self) -> Result<(), XError> {
@@ -131,6 +190,11 @@ impl XConnection {
}
}
#[inline]
pub fn randr_version(&self) -> (u32, u32) {
self.randr_version
}
/// Get the underlying XCB connection.
#[inline]
pub fn xcb_connection(&self) -> &XCBConnection {
@@ -159,8 +223,16 @@ impl XConnection {
/// Get the resource database.
#[inline]
pub fn database(&self) -> &resource_manager::Database {
&self.database
pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> {
self.database.read().unwrap_or_else(|e| e.into_inner())
}
/// Reload the resource database.
#[inline]
pub fn reload_database(&self) -> Result<(), super::X11Error> {
let database = resource_manager::new_from_default(self.xcb_connection())?;
*self.database.write().unwrap_or_else(|e| e.into_inner()) = database;
Ok(())
}
/// Get the latest timestamp.
@@ -192,6 +264,12 @@ impl XConnection {
}
}
}
/// Get the atom for Xsettings.
#[inline]
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
self.xsettings_screen
}
}
impl fmt::Debug for XConnection {

View File

@@ -0,0 +1,343 @@
//! Parser for the xsettings data format.
//!
//! Some of this code is referenced from [here].
//!
//! [here]: https://github.com/derat/xsettingsd
use std::iter;
use std::num::NonZeroUsize;
use x11rb::protocol::xproto::{self, ConnectionExt};
use super::{atoms::*, XConnection};
type Result<T> = core::result::Result<T, ParserError>;
const DPI_NAME: &[u8] = b"Xft/DPI";
const DPI_MULTIPLIER: f64 = 1024.0;
const LITTLE_ENDIAN: u8 = b'l';
const BIG_ENDIAN: u8 = b'B';
impl XConnection {
/// Get the DPI from XSettings.
pub(crate) fn xsettings_dpi(
&self,
xsettings_screen: xproto::Atom,
) -> core::result::Result<Option<f64>, super::X11Error> {
let atoms = self.atoms();
// Get the current owner of the screen's settings.
let owner = self
.xcb_connection()
.get_selection_owner(xsettings_screen)?
.reply()?;
// Read the _XSETTINGS_SETTINGS property.
let data: Vec<u8> = self.get_property(
owner.owner,
atoms[_XSETTINGS_SETTINGS],
atoms[_XSETTINGS_SETTINGS],
)?;
// Parse the property.
let dpi_setting = read_settings(&data)?
.find(|res| res.as_ref().map_or(true, |s| s.name == DPI_NAME))
.transpose()?;
if let Some(dpi_setting) = dpi_setting {
let base_dpi = match dpi_setting.data {
SettingData::Integer(dpi) => dpi as f64,
SettingData::String(_) => {
return Err(ParserError::BadType(SettingType::String).into())
}
SettingData::Color(_) => {
return Err(ParserError::BadType(SettingType::Color).into())
}
};
Ok(Some(base_dpi / DPI_MULTIPLIER))
} else {
Ok(None)
}
}
}
/// Read over the settings in the block of data.
fn read_settings(data: &[u8]) -> Result<impl Iterator<Item = Result<Setting<'_>>> + '_> {
// Create a parser. This automatically parses the first 8 bytes for metadata.
let mut parser = Parser::new(data)?;
// Read the total number of settings.
let total_settings = parser.i32()?;
// Iterate over the settings.
let iter = iter::repeat_with(move || Setting::parse(&mut parser)).take(total_settings as usize);
Ok(iter)
}
/// A setting in the settings list.
struct Setting<'a> {
/// The name of the setting.
name: &'a [u8],
/// The data contained in the setting.
data: SettingData<'a>,
}
/// The data contained in a setting.
enum SettingData<'a> {
Integer(i32),
String(#[allow(dead_code)] &'a [u8]),
Color(#[allow(dead_code)] [i16; 4]),
}
impl<'a> Setting<'a> {
/// Parse a new `SettingData`.
fn parse(parser: &mut Parser<'a>) -> Result<Self> {
// Read the type.
let ty: SettingType = parser.i8()?.try_into()?;
// Read another byte of padding.
parser.advance(1)?;
// Read the name of the setting.
let name_len = parser.i16()?;
let name = parser.advance(name_len as usize)?;
parser.pad(name.len(), 4)?;
// Ignore the serial number.
parser.advance(4)?;
let data = match ty {
SettingType::Integer => {
// Read a 32-bit integer.
SettingData::Integer(parser.i32()?)
}
SettingType::String => {
// Read the data.
let data_len = parser.i32()?;
let data = parser.advance(data_len as usize)?;
parser.pad(data.len(), 4)?;
SettingData::String(data)
}
SettingType::Color => {
// Read i16's of color.
let (red, blue, green, alpha) =
(parser.i16()?, parser.i16()?, parser.i16()?, parser.i16()?);
SettingData::Color([red, blue, green, alpha])
}
};
Ok(Setting { name, data })
}
}
#[derive(Debug)]
pub enum SettingType {
Integer = 0,
String = 1,
Color = 2,
}
impl TryFrom<i8> for SettingType {
type Error = ParserError;
fn try_from(value: i8) -> Result<Self> {
Ok(match value {
0 => Self::Integer,
1 => Self::String,
2 => Self::Color,
x => return Err(ParserError::InvalidType(x)),
})
}
}
/// Parser for the incoming byte stream.
struct Parser<'a> {
bytes: &'a [u8],
endianness: Endianness,
}
impl<'a> Parser<'a> {
/// Create a new parser.
fn new(bytes: &'a [u8]) -> Result<Self> {
let (endianness, bytes) = bytes
.split_first()
.ok_or_else(|| ParserError::ran_out(1, 0))?;
let endianness = match *endianness {
BIG_ENDIAN => Endianness::Big,
LITTLE_ENDIAN => Endianness::Little,
_ => Endianness::native(),
};
Ok(Self {
// Ignore three bytes of padding and the four-byte serial.
bytes: bytes
.get(7..)
.ok_or_else(|| ParserError::ran_out(7, bytes.len()))?,
endianness,
})
}
/// Get a slice of bytes.
fn advance(&mut self, n: usize) -> Result<&'a [u8]> {
if n == 0 {
return Ok(&[]);
}
if n > self.bytes.len() {
Err(ParserError::ran_out(n, self.bytes.len()))
} else {
let (part, rem) = self.bytes.split_at(n);
self.bytes = rem;
Ok(part)
}
}
/// Skip some padding.
fn pad(&mut self, size: usize, pad: usize) -> Result<()> {
let advance = (pad - (size % pad)) % pad;
self.advance(advance)?;
Ok(())
}
/// Get a single byte.
fn i8(&mut self) -> Result<i8> {
self.advance(1).map(|s| s[0] as i8)
}
/// Get two bytes.
fn i16(&mut self) -> Result<i16> {
self.advance(2).map(|s| {
let bytes: &[u8; 2] = s.try_into().unwrap();
match self.endianness {
Endianness::Big => i16::from_be_bytes(*bytes),
Endianness::Little => i16::from_le_bytes(*bytes),
}
})
}
/// Get four bytes.
fn i32(&mut self) -> Result<i32> {
self.advance(4).map(|s| {
let bytes: &[u8; 4] = s.try_into().unwrap();
match self.endianness {
Endianness::Big => i32::from_be_bytes(*bytes),
Endianness::Little => i32::from_le_bytes(*bytes),
}
})
}
}
/// Endianness of the incoming data.
enum Endianness {
Little,
Big,
}
impl Endianness {
#[cfg(target_endian = "little")]
fn native() -> Self {
Endianness::Little
}
#[cfg(target_endian = "big")]
fn native() -> Self {
Endianness::Big
}
}
/// Parser errors.
#[derive(Debug)]
pub enum ParserError {
/// Ran out of bytes.
NoMoreBytes {
expected: NonZeroUsize,
found: usize,
},
/// Invalid type.
InvalidType(i8),
/// Bad setting type.
BadType(SettingType),
}
impl ParserError {
fn ran_out(expected: usize, found: usize) -> ParserError {
let expected = NonZeroUsize::new(expected).unwrap();
Self::NoMoreBytes { expected, found }
}
}
#[cfg(test)]
mod tests {
//! Tests for the XSETTINGS parser.
use super::*;
const XSETTINGS: &str = include_str!("tests/xsettings.dat");
#[test]
fn empty() {
let err = match read_settings(&[]) {
Ok(_) => panic!(),
Err(err) => err,
};
match err {
ParserError::NoMoreBytes { expected, found } => {
assert_eq!(expected.get(), 1);
assert_eq!(found, 0);
}
_ => panic!(),
}
}
#[test]
fn parse_xsettings() {
let data = XSETTINGS
.trim()
.split(',')
.map(|tok| {
let val = tok.strip_prefix("0x").unwrap();
u8::from_str_radix(val, 16).unwrap()
})
.collect::<Vec<_>>();
let settings = read_settings(&data)
.unwrap()
.collect::<Result<Vec<_>>>()
.unwrap();
let dpi = settings.iter().find(|s| s.name == b"Xft/DPI").unwrap();
assert_int(&dpi.data, 96 * 1024);
let hinting = settings.iter().find(|s| s.name == b"Xft/Hinting").unwrap();
assert_int(&hinting.data, 1);
let rgba = settings.iter().find(|s| s.name == b"Xft/RGBA").unwrap();
assert_string(&rgba.data, "rgb");
let lcd = settings
.iter()
.find(|s| s.name == b"Xft/Lcdfilter")
.unwrap();
assert_string(&lcd.data, "lcddefault");
}
fn assert_string(dat: &SettingData<'_>, s: &str) {
match dat {
SettingData::String(left) => assert_eq!(*left, s.as_bytes()),
_ => panic!("invalid data type"),
}
}
fn assert_int(dat: &SettingData<'_>, i: i32) {
match dat {
SettingData::Integer(left) => assert_eq!(*left, i),
_ => panic!("invalid data type"),
}
}
}

View File

@@ -209,6 +209,10 @@ impl Handler {
self.exit.store(true, Ordering::Relaxed)
}
pub fn clear_exit(&self) {
self.exit.store(false, Ordering::Relaxed)
}
pub fn exiting(&self) -> bool {
self.exit.load(Ordering::Relaxed)
}
@@ -329,7 +333,7 @@ impl Handler {
) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent {
let scale_factor_changed_event = Event::WindowEvent {
window_id: WindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
@@ -337,13 +341,19 @@ impl Handler {
},
};
callback.handle_nonuser_event(event);
callback.handle_nonuser_event(scale_factor_changed_event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
let resized_event = Event::WindowEvent {
window_id: WindowId(window.id()),
event: WindowEvent::Resized(physical_size),
};
callback.handle_nonuser_event(resized_event);
}
}
}
@@ -428,6 +438,10 @@ impl AppState {
HANDLER.exit()
}
pub fn clear_exit() {
HANDLER.clear_exit()
}
pub fn exiting() -> bool {
HANDLER.exiting()
}
@@ -494,9 +508,9 @@ impl AppState {
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking()
|| HANDLER.get_in_callback()
|| !HANDLER.have_callback()
|| !HANDLER.is_running()
|| HANDLER.get_in_callback()
{
return;
}
@@ -601,9 +615,9 @@ impl AppState {
// XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're
// about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking()
|| HANDLER.get_in_callback()
|| !HANDLER.have_callback()
|| !HANDLER.is_running()
|| HANDLER.get_in_callback()
{
return;
}

View File

@@ -36,6 +36,9 @@ extern_methods!(
#[method(frame)]
pub(crate) fn frame(&self) -> NSRect;
#[method(windowNumber)]
pub(crate) fn windowNumber(&self) -> NSInteger;
#[method(backingScaleFactor)]
pub(crate) fn backingScaleFactor(&self) -> CGFloat;

View File

@@ -39,6 +39,7 @@ impl KeyEventExtModifierSupplement for KeyEvent {
}
}
/// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key {
let mut string = [0; 16];
let input_source;
@@ -88,13 +89,16 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
return Key::Unidentified(NativeKey::MacOS(scancode));
}
if result_len == 0 {
log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length.");
// This is fine - not all keys have text representation.
// For instance, users that have mapped the `Fn` key to toggle
// keyboard layouts will hit this code path.
return Key::Unidentified(NativeKey::MacOS(scancode));
}
let chars = String::from_utf16_lossy(&string[0..result_len as usize]);
Key::Character(SmolStr::new(chars))
}
// Ignores all modifiers except for SHIFT (yes, even ALT is ignored).
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
let string = ns_event
.charactersIgnoringModifiers()
@@ -124,6 +128,13 @@ pub(crate) fn create_key_event(
let mut physical_key =
key_override.unwrap_or_else(|| PhysicalKey::from_scancode(scancode as u32));
// NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance:
// * Pressing the A key: logical key should be "a"
// * Pressing SHIFT A: logical key should be "A"
// * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best.
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
None
} else {
@@ -144,25 +155,31 @@ pub(crate) fn create_key_event(
let key_from_code = code_to_key(physical_key, scancode);
let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) {
// `get_modifierless_char/key_without_modifiers` ignores ALL modifiers.
let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = NSEvent::modifierFlags(ns_event);
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let has_cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let logical_key = match text_with_all_modifiers.as_ref() {
// Only checking for ctrl here, not checking for alt because we DO want to
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
// include its effect in the key. For example if -on the Germay layout- one
// presses alt+8, the logical key should be "{"
// Also not checking if this is a release event because then this issue would
// still affect the key release.
Some(text) if !has_ctrl => Key::Character(text.clone()),
_ => {
let modifierless_chars = match key_without_modifiers.as_ref() {
Key::Character(ch) => ch,
_ => "",
};
get_logical_key_char(ns_event, modifierless_chars)
Some(text) if !has_ctrl && !has_cmd => {
// Character heeding both SHIFT and ALT.
Key::Character(text.clone())
}
_ => match key_without_modifiers.as_ref() {
// Character heeding just SHIFT, ignoring ALT.
Key::Character(ch) => get_logical_key_char(ns_event, ch),
// Character ignoring ALL modifiers.
_ => key_without_modifiers.clone(),
},
};
(logical_key, key_without_modifiers)

View File

@@ -116,6 +116,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
AppState::exit()
}
pub(crate) fn clear_exit(&self) {
AppState::clear_exit()
}
pub(crate) fn exiting(&self) -> bool {
AppState::exiting()
}

View File

@@ -11,6 +11,7 @@ use core_graphics::{
base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef},
};
use objc2::{ffi::NSInteger, runtime::AnyObject};
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
@@ -113,6 +114,14 @@ extern "C" {
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
connection_id: *mut AnyObject,
window_id: NSInteger,
radius: i64,
) -> i32;
}
mod core_video {

View File

@@ -7,7 +7,9 @@ use core_foundation::{
base::{CFRelease, TCFType},
string::CFString,
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
use objc2::rc::Id;
use super::appkit::NSScreen;
@@ -216,6 +218,12 @@ impl MonitorHandle {
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
unsafe {
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
if refresh_rate > 0.0 {
return Some((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
!= ffi::kCVReturnSuccess

View File

@@ -1,5 +1,4 @@
use std::{
self,
ffi::c_void,
panic::{AssertUnwindSafe, UnwindSafe},
ptr,

View File

@@ -74,8 +74,8 @@ enum ImeState {
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq)]
struct ModLocationMask: u8 {
const LEFT = 1;
const RIGHT = 2;
const LEFT = 0b0001;
const RIGHT = 0b0010;
}
}
impl ModLocationMask {
@@ -88,13 +88,13 @@ impl ModLocationMask {
}
}
fn key_to_modifier(key: &Key) -> ModifiersState {
fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
match key {
Key::Named(NamedKey::Alt) => ModifiersState::ALT,
Key::Named(NamedKey::Control) => ModifiersState::CONTROL,
Key::Named(NamedKey::Super) => ModifiersState::SUPER,
Key::Named(NamedKey::Shift) => ModifiersState::SHIFT,
_ => unreachable!(),
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
_ => None,
}
}
@@ -410,9 +410,9 @@ declare_class!(
let content_rect = window.contentRectForFrameRect(window.frame());
let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
let x = base_x + self.state.ime_position.get().x;
let y = base_y - self.state.ime_position.get().y;
let LogicalSize { width, height } = self.state.ime_size.get();
let x = base_x + self.state.ime_position.get().x;
let y = base_y - self.state.ime_position.get().y - height;
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
}
@@ -924,91 +924,96 @@ impl WinitView {
// event, thus we can't generate regular presses based on that. The `ModifiersChanged`
// later will work though, since the flags are attached to the event and contain valid
// information.
if is_flags_changed_event && ns_event.key_code() != 0 {
let scancode = ns_event.key_code();
let physical_key = PhysicalKey::from_scancode(scancode as u32);
'send_event: {
if is_flags_changed_event && ns_event.key_code() != 0 {
let scancode = ns_event.key_code();
let physical_key = PhysicalKey::from_scancode(scancode as u32);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
let event_modifier = key_to_modifier(&key);
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location);
let key = code_to_key(physical_key, scancode);
// Ignore processing of unkown modifiers because we can't determine whether
// it was pressed or release reliably.
let Some(event_modifier) = key_to_modifier(&key) else {
break 'send_event;
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state
.entry(key)
.or_insert(ModLocationMask::empty());
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state
.entry(key)
.or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
// There is no API for getting whether the button was pressed or released
// during this event. For this reason we have to do a bit of magic below
// to come up with a good guess whether this key was pressed or released.
// (This is not trivial because there are multiple buttons that may affect
// the same modifier)
if !is_active {
event.state = Released;
if phys_mod.contains(ModLocationMask::LEFT) {
let mut event = event.clone();
event.location = KeyLocation::Left;
event.physical_key = get_left_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
if phys_mod.contains(ModLocationMask::RIGHT) {
event.location = KeyLocation::Right;
event.physical_key = get_right_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
*phys_mod = ModLocationMask::empty();
} else {
// is_active
if *phys_mod == location_mask {
// Here we hit a contradiction:
// The modifier state was "changed" to active,
// yet the only pressed modifier key was the one that we
// just got a change event for.
// This seemingly means that the only pressed modifier is now released,
// but at the same time the modifier became active.
//
// But this scenario is possible if we released modifiers
// while the application was not in focus. (Because we don't
// get informed of modifier key events while the application
// is not focused)
// In this case we prioritize the information
// about the current modifier state which means
// that the button was pressed.
event.state = Pressed;
// There is no API for getting whether the button was pressed or released
// during this event. For this reason we have to do a bit of magic below
// to come up with a good guess whether this key was pressed or released.
// (This is not trivial because there are multiple buttons that may affect
// the same modifier)
if !is_active {
event.state = Released;
if phys_mod.contains(ModLocationMask::LEFT) {
let mut event = event.clone();
event.location = KeyLocation::Left;
event.physical_key = get_left_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
if phys_mod.contains(ModLocationMask::RIGHT) {
event.location = KeyLocation::Right;
event.physical_key = get_right_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
*phys_mod = ModLocationMask::empty();
} else {
phys_mod.toggle(location_mask);
let is_pressed = phys_mod.contains(location_mask);
event.state = if is_pressed { Pressed } else { Released };
if *phys_mod == location_mask {
// Here we hit a contradiction:
// The modifier state was "changed" to active,
// yet the only pressed modifier key was the one that we
// just got a change event for.
// This seemingly means that the only pressed modifier is now released,
// but at the same time the modifier became active.
//
// But this scenario is possible if we released modifiers
// while the application was not in focus. (Because we don't
// get informed of modifier key events while the application
// is not focused)
// In this case we prioritize the information
// about the current modifier state which means
// that the button was pressed.
event.state = Pressed;
} else {
phys_mod.toggle(location_mask);
let is_pressed = phys_mod.contains(location_mask);
event.state = if is_pressed { Pressed } else { Released };
}
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
drop(phys_mod_state);
drop(phys_mod_state);
for event in events {
self.queue_event(event);
for event in events {
self.queue_event(event);
}
}
}

View File

@@ -46,6 +46,8 @@ use super::appkit::{
NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility,
};
use super::ffi::CGSMainConnectionID;
use super::ffi::CGSSetWindowBackgroundBlurRadius;
pub(crate) struct Window {
window: MainThreadBound<Id<WinitWindow>>,
@@ -494,6 +496,10 @@ impl WinitWindow {
this.setBackgroundColor(&NSColor::clear());
}
if attrs.blur {
this.set_blur(attrs.blur);
}
if let Some(dim) = attrs.min_inner_size {
this.set_min_inner_size(Some(dim));
}
@@ -582,7 +588,15 @@ impl WinitWindow {
self.setOpaque(!transparent)
}
pub fn set_blur(&self, _blur: bool) {}
pub fn set_blur(&self, blur: bool) {
// NOTE: in general we want to specify the blur radius, but the choice of 80
// should be a reasonable default.
let radius = if blur { 80 } else { 0 };
let window_number = self.windowNumber();
unsafe {
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius);
}
}
pub fn set_visible(&self, visible: bool) {
match visible {

View File

@@ -8,17 +8,18 @@ use std::{
};
use orbclient::{
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MoveEvent, QuitEvent,
ResizeEvent, ScrollEvent, TextInputEvent,
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
};
use smol_str::SmolStr;
use crate::{
error::EventLoopError,
event::{self, Ime, Modifiers, StartCause},
event_loop::{self, ControlFlow, DeviceEvents},
keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
PhysicalKey,
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey,
NativeKeyCode, PhysicalKey,
},
window::WindowId as RootWindowId,
};
@@ -28,90 +29,107 @@ use super::{
RedoxSocket, TimeSocket, WindowId, WindowProperties,
};
fn convert_scancode(scancode: u8) -> PhysicalKey {
PhysicalKey::Code(match scancode {
orbclient::K_A => KeyCode::KeyA,
orbclient::K_B => KeyCode::KeyB,
orbclient::K_C => KeyCode::KeyC,
orbclient::K_D => KeyCode::KeyD,
orbclient::K_E => KeyCode::KeyE,
orbclient::K_F => KeyCode::KeyF,
orbclient::K_G => KeyCode::KeyG,
orbclient::K_H => KeyCode::KeyH,
orbclient::K_I => KeyCode::KeyI,
orbclient::K_J => KeyCode::KeyJ,
orbclient::K_K => KeyCode::KeyK,
orbclient::K_L => KeyCode::KeyL,
orbclient::K_M => KeyCode::KeyM,
orbclient::K_N => KeyCode::KeyN,
orbclient::K_O => KeyCode::KeyO,
orbclient::K_P => KeyCode::KeyP,
orbclient::K_Q => KeyCode::KeyQ,
orbclient::K_R => KeyCode::KeyR,
orbclient::K_S => KeyCode::KeyS,
orbclient::K_T => KeyCode::KeyT,
orbclient::K_U => KeyCode::KeyU,
orbclient::K_V => KeyCode::KeyV,
orbclient::K_W => KeyCode::KeyW,
orbclient::K_X => KeyCode::KeyX,
orbclient::K_Y => KeyCode::KeyY,
orbclient::K_Z => KeyCode::KeyZ,
orbclient::K_0 => KeyCode::Digit0,
orbclient::K_1 => KeyCode::Digit1,
orbclient::K_2 => KeyCode::Digit2,
orbclient::K_3 => KeyCode::Digit3,
orbclient::K_4 => KeyCode::Digit4,
orbclient::K_5 => KeyCode::Digit5,
orbclient::K_6 => KeyCode::Digit6,
orbclient::K_7 => KeyCode::Digit7,
orbclient::K_8 => KeyCode::Digit8,
orbclient::K_9 => KeyCode::Digit9,
fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
// Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html
let (key_code, named_key_opt) = match scancode {
orbclient::K_A => (KeyCode::KeyA, None),
orbclient::K_B => (KeyCode::KeyB, None),
orbclient::K_C => (KeyCode::KeyC, None),
orbclient::K_D => (KeyCode::KeyD, None),
orbclient::K_E => (KeyCode::KeyE, None),
orbclient::K_F => (KeyCode::KeyF, None),
orbclient::K_G => (KeyCode::KeyG, None),
orbclient::K_H => (KeyCode::KeyH, None),
orbclient::K_I => (KeyCode::KeyI, None),
orbclient::K_J => (KeyCode::KeyJ, None),
orbclient::K_K => (KeyCode::KeyK, None),
orbclient::K_L => (KeyCode::KeyL, None),
orbclient::K_M => (KeyCode::KeyM, None),
orbclient::K_N => (KeyCode::KeyN, None),
orbclient::K_O => (KeyCode::KeyO, None),
orbclient::K_P => (KeyCode::KeyP, None),
orbclient::K_Q => (KeyCode::KeyQ, None),
orbclient::K_R => (KeyCode::KeyR, None),
orbclient::K_S => (KeyCode::KeyS, None),
orbclient::K_T => (KeyCode::KeyT, None),
orbclient::K_U => (KeyCode::KeyU, None),
orbclient::K_V => (KeyCode::KeyV, None),
orbclient::K_W => (KeyCode::KeyW, None),
orbclient::K_X => (KeyCode::KeyX, None),
orbclient::K_Y => (KeyCode::KeyY, None),
orbclient::K_Z => (KeyCode::KeyZ, None),
orbclient::K_0 => (KeyCode::Digit0, None),
orbclient::K_1 => (KeyCode::Digit1, None),
orbclient::K_2 => (KeyCode::Digit2, None),
orbclient::K_3 => (KeyCode::Digit3, None),
orbclient::K_4 => (KeyCode::Digit4, None),
orbclient::K_5 => (KeyCode::Digit5, None),
orbclient::K_6 => (KeyCode::Digit6, None),
orbclient::K_7 => (KeyCode::Digit7, None),
orbclient::K_8 => (KeyCode::Digit8, None),
orbclient::K_9 => (KeyCode::Digit9, None),
orbclient::K_TICK => KeyCode::Backquote,
orbclient::K_MINUS => KeyCode::Minus,
orbclient::K_EQUALS => KeyCode::Equal,
orbclient::K_BACKSLASH => KeyCode::Backslash,
orbclient::K_BRACE_OPEN => KeyCode::BracketLeft,
orbclient::K_BRACE_CLOSE => KeyCode::BracketRight,
orbclient::K_SEMICOLON => KeyCode::Semicolon,
orbclient::K_QUOTE => KeyCode::Quote,
orbclient::K_COMMA => KeyCode::Comma,
orbclient::K_PERIOD => KeyCode::Period,
orbclient::K_SLASH => KeyCode::Slash,
orbclient::K_BKSP => KeyCode::Backspace,
orbclient::K_SPACE => KeyCode::Space,
orbclient::K_TAB => KeyCode::Tab,
//orbclient::K_CAPS => KeyCode::CAPS,
orbclient::K_LEFT_SHIFT => KeyCode::ShiftLeft,
orbclient::K_RIGHT_SHIFT => KeyCode::ShiftRight,
orbclient::K_CTRL => KeyCode::ControlLeft,
orbclient::K_ALT => KeyCode::AltLeft,
orbclient::K_ENTER => KeyCode::Enter,
orbclient::K_ESC => KeyCode::Escape,
orbclient::K_F1 => KeyCode::F1,
orbclient::K_F2 => KeyCode::F2,
orbclient::K_F3 => KeyCode::F3,
orbclient::K_F4 => KeyCode::F4,
orbclient::K_F5 => KeyCode::F5,
orbclient::K_F6 => KeyCode::F6,
orbclient::K_F7 => KeyCode::F7,
orbclient::K_F8 => KeyCode::F8,
orbclient::K_F9 => KeyCode::F9,
orbclient::K_F10 => KeyCode::F10,
orbclient::K_HOME => KeyCode::Home,
orbclient::K_UP => KeyCode::ArrowUp,
orbclient::K_PGUP => KeyCode::PageUp,
orbclient::K_LEFT => KeyCode::ArrowLeft,
orbclient::K_RIGHT => KeyCode::ArrowRight,
orbclient::K_END => KeyCode::End,
orbclient::K_DOWN => KeyCode::ArrowDown,
orbclient::K_PGDN => KeyCode::PageDown,
orbclient::K_DEL => KeyCode::Delete,
orbclient::K_F11 => KeyCode::F11,
orbclient::K_F12 => KeyCode::F12,
orbclient::K_ALT => (KeyCode::AltLeft, Some(NamedKey::Alt)),
orbclient::K_ALT_GR => (KeyCode::AltRight, Some(NamedKey::AltGraph)),
orbclient::K_BACKSLASH => (KeyCode::Backslash, None),
orbclient::K_BKSP => (KeyCode::Backspace, Some(NamedKey::Backspace)),
orbclient::K_BRACE_CLOSE => (KeyCode::BracketRight, None),
orbclient::K_BRACE_OPEN => (KeyCode::BracketLeft, None),
orbclient::K_CAPS => (KeyCode::CapsLock, Some(NamedKey::CapsLock)),
orbclient::K_COMMA => (KeyCode::Comma, None),
orbclient::K_CTRL => (KeyCode::ControlLeft, Some(NamedKey::Control)),
orbclient::K_DEL => (KeyCode::Delete, Some(NamedKey::Delete)),
orbclient::K_DOWN => (KeyCode::ArrowDown, Some(NamedKey::ArrowDown)),
orbclient::K_END => (KeyCode::End, Some(NamedKey::End)),
orbclient::K_ENTER => (KeyCode::Enter, Some(NamedKey::Enter)),
orbclient::K_EQUALS => (KeyCode::Equal, None),
orbclient::K_ESC => (KeyCode::Escape, Some(NamedKey::Escape)),
orbclient::K_F1 => (KeyCode::F1, Some(NamedKey::F1)),
orbclient::K_F2 => (KeyCode::F2, Some(NamedKey::F2)),
orbclient::K_F3 => (KeyCode::F3, Some(NamedKey::F3)),
orbclient::K_F4 => (KeyCode::F4, Some(NamedKey::F4)),
orbclient::K_F5 => (KeyCode::F5, Some(NamedKey::F5)),
orbclient::K_F6 => (KeyCode::F6, Some(NamedKey::F6)),
orbclient::K_F7 => (KeyCode::F7, Some(NamedKey::F7)),
orbclient::K_F8 => (KeyCode::F8, Some(NamedKey::F8)),
orbclient::K_F9 => (KeyCode::F9, Some(NamedKey::F9)),
orbclient::K_F10 => (KeyCode::F10, Some(NamedKey::F10)),
orbclient::K_F11 => (KeyCode::F11, Some(NamedKey::F11)),
orbclient::K_F12 => (KeyCode::F12, Some(NamedKey::F12)),
orbclient::K_HOME => (KeyCode::Home, Some(NamedKey::Home)),
orbclient::K_LEFT => (KeyCode::ArrowLeft, Some(NamedKey::ArrowLeft)),
orbclient::K_LEFT_SHIFT => (KeyCode::ShiftLeft, Some(NamedKey::Shift)),
orbclient::K_MINUS => (KeyCode::Minus, None),
orbclient::K_NUM_0 => (KeyCode::Numpad0, None),
orbclient::K_NUM_1 => (KeyCode::Numpad1, None),
orbclient::K_NUM_2 => (KeyCode::Numpad2, None),
orbclient::K_NUM_3 => (KeyCode::Numpad3, None),
orbclient::K_NUM_4 => (KeyCode::Numpad4, None),
orbclient::K_NUM_5 => (KeyCode::Numpad5, None),
orbclient::K_NUM_6 => (KeyCode::Numpad6, None),
orbclient::K_NUM_7 => (KeyCode::Numpad7, None),
orbclient::K_NUM_8 => (KeyCode::Numpad8, None),
orbclient::K_NUM_9 => (KeyCode::Numpad9, None),
orbclient::K_PERIOD => (KeyCode::Period, None),
orbclient::K_PGDN => (KeyCode::PageDown, Some(NamedKey::PageDown)),
orbclient::K_PGUP => (KeyCode::PageUp, Some(NamedKey::PageUp)),
orbclient::K_QUOTE => (KeyCode::Quote, None),
orbclient::K_RIGHT => (KeyCode::ArrowRight, Some(NamedKey::ArrowRight)),
orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)),
orbclient::K_SEMICOLON => (KeyCode::Semicolon, None),
orbclient::K_SLASH => (KeyCode::Slash, None),
orbclient::K_SPACE => (KeyCode::Space, Some(NamedKey::Space)),
orbclient::K_SUPER => (KeyCode::SuperLeft, Some(NamedKey::Super)),
orbclient::K_TAB => (KeyCode::Tab, Some(NamedKey::Tab)),
orbclient::K_TICK => (KeyCode::Backquote, None),
orbclient::K_UP => (KeyCode::ArrowUp, Some(NamedKey::ArrowUp)),
orbclient::K_VOLUME_DOWN => (KeyCode::AudioVolumeDown, Some(NamedKey::AudioVolumeDown)),
orbclient::K_VOLUME_TOGGLE => (KeyCode::AudioVolumeMute, Some(NamedKey::AudioVolumeMute)),
orbclient::K_VOLUME_UP => (KeyCode::AudioVolumeUp, Some(NamedKey::AudioVolumeUp)),
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
})
_ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None),
};
(PhysicalKey::Code(key_code), named_key_opt)
}
fn element_state(pressed: bool) -> event::ElementState {
@@ -153,6 +171,22 @@ struct EventState {
}
impl EventState {
fn character_all_modifiers(&self, character: char) -> char {
// Modify character if Ctrl is pressed
#[allow(clippy::collapsible_if)]
if self.keyboard.contains(KeyboardModifierState::LCTRL)
|| self.keyboard.contains(KeyboardModifierState::RCTRL)
{
if character.is_ascii_lowercase() {
return ((character as u8 - b'a') + 1) as char;
}
//TODO: more control key variants?
}
// Return character as-is if no special handling required
character
}
fn key(&mut self, key: PhysicalKey, pressed: bool) {
let code = match key {
PhysicalKey::Code(code) => code,
@@ -333,39 +367,75 @@ impl<T: 'static> EventLoop<T> {
{
match event_option {
EventOption::Key(KeyEvent {
character: _,
character,
scancode,
pressed,
}) => {
if scancode != 0 {
let physical_key = convert_scancode(scancode);
let modifiers_before = event_state.keyboard;
event_state.key(physical_key, pressed);
// Convert scancode
let (physical_key, named_key_opt) = convert_scancode(scancode);
// Get previous modifiers and update modifiers based on physical key
let modifiers_before = event_state.keyboard;
event_state.key(physical_key, pressed);
// Default to unidentified key with no text
let mut logical_key = Key::Unidentified(NativeKey::Unidentified);
let mut key_without_modifiers = logical_key.clone();
let mut text = None;
let mut text_with_all_modifiers = None;
// Set key and text based on character
if character != '\0' {
let mut tmp = [0u8; 4];
let character_str = character.encode_utf8(&mut tmp);
// The key with Shift and Caps Lock applied (but not Ctrl)
logical_key = Key::Character(character_str.into());
// The key without Shift or Caps Lock applied
key_without_modifiers =
Key::Character(SmolStr::from_iter(character.to_lowercase()));
if pressed {
// The key with Shift and Caps Lock applied (but not Ctrl)
text = Some(character_str.into());
// The key with Shift, Caps Lock, and Ctrl applied
let character_all_modifiers =
event_state.character_all_modifiers(character);
text_with_all_modifiers =
Some(character_all_modifiers.encode_utf8(&mut tmp).into())
}
};
// Override key if a named key was found (this is to allow Enter to replace '\n')
if let Some(named_key) = named_key_opt {
logical_key = Key::Named(named_key);
key_without_modifiers = logical_key.clone();
}
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
},
is_synthetic: false,
},
});
// If the state of the modifiers has changed, send the event.
if modifiers_before != event_state.keyboard {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key: Key::Unidentified(NativeKey::Unidentified),
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
});
// If the state of the modifiers has changed, send the event.
if modifiers_before != event_state.keyboard {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
})
}
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
})
}
}
EventOption::TextInput(TextInputEvent { character }) => {
@@ -387,6 +457,14 @@ impl<T: 'static> EventLoop<T> {
},
});
}
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
event_handler(event::Event::DeviceEvent {
device_id: event::DeviceId(DeviceId),
event: event::DeviceEvent::MouseMotion {
delta: (dx as f64, dy as f64),
},
});
}
EventOption::Button(ButtonEvent {
left,
middle,
@@ -440,7 +518,7 @@ impl<T: 'static> EventLoop<T> {
// Acknowledge resize after event loop.
event_state.resize_opt = Some((width, height));
}
//TODO: Clipboard
//TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
if entered {
event_handler(event::Event::WindowEvent {

View File

@@ -4,7 +4,12 @@ use std::fmt::{self, Display, Formatter};
use std::str;
use std::sync::Arc;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use smol_str::SmolStr;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
keyboard::Key,
};
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
mod event_loop;
@@ -259,5 +264,8 @@ impl VideoMode {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub key_without_modifiers: Key,
pub text_with_all_modifiers: Option<SmolStr>,
}

View File

@@ -12,8 +12,8 @@ use crate::{
};
use super::{
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
TimeSocket, WindowId, WindowProperties,
EventLoopWindowTarget, MonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
RedoxSocket, TimeSocket, WindowId, WindowProperties,
};
// These values match the values uses in the `window_new` function in orbital:
@@ -21,7 +21,9 @@ use super::{
const ORBITAL_FLAG_ASYNC: char = 'a';
const ORBITAL_FLAG_BACK: char = 'b';
const ORBITAL_FLAG_FRONT: char = 'f';
const ORBITAL_FLAG_HIDDEN: char = 'h';
const ORBITAL_FLAG_BORDERLESS: char = 'l';
const ORBITAL_FLAG_MAXIMIZED: char = 'm';
const ORBITAL_FLAG_RESIZABLE: char = 'r';
const ORBITAL_FLAG_TRANSPARENT: char = 't';
@@ -58,11 +60,15 @@ impl Window {
// Async by default.
let mut flag_str = ORBITAL_FLAG_ASYNC.to_string();
if attrs.maximized {
flag_str.push(ORBITAL_FLAG_MAXIMIZED);
}
if attrs.resizable {
flag_str.push(ORBITAL_FLAG_RESIZABLE);
}
//TODO: maximized, fullscreen, visible
//TODO: fullscreen
if attrs.transparent {
flag_str.push(ORBITAL_FLAG_TRANSPARENT);
@@ -72,6 +78,10 @@ impl Window {
flag_str.push(ORBITAL_FLAG_BORDERLESS);
}
if !attrs.visible {
flag_str.push(ORBITAL_FLAG_HIDDEN);
}
match attrs.window_level {
window::WindowLevel::AlwaysOnBottom => {
flag_str.push(ORBITAL_FLAG_BACK);
@@ -130,6 +140,23 @@ impl Window {
f(self)
}
fn get_flag(&self, flag: char) -> Result<bool, error::ExternalError> {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
let properties = WindowProperties::new(path);
Ok(properties.flags.contains(flag))
}
fn set_flag(&self, flag: char, value: bool) -> Result<(), error::ExternalError> {
self.window_socket
.write(format!("F,{flag},{}", if value { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId {
@@ -255,17 +282,21 @@ impl Window {
}
#[inline]
pub fn set_transparent(&self, _transparent: bool) {}
pub fn set_transparent(&self, transparent: bool) {
let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent);
}
#[inline]
pub fn set_blur(&self, _blur: bool) {}
#[inline]
pub fn set_visible(&self, _visibility: bool) {}
pub fn set_visible(&self, visible: bool) {
let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible);
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
None
Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false))
}
#[inline]
@@ -277,17 +308,13 @@ impl Window {
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
#[inline]
pub fn set_resizable(&self, _resizeable: bool) {}
pub fn set_resizable(&self, resizeable: bool) {
let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable);
}
#[inline]
pub fn is_resizable(&self) -> bool {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.expect("failed to read properties");
let properties = WindowProperties::new(path);
properties.flags.contains(ORBITAL_FLAG_RESIZABLE)
self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false)
}
#[inline]
@@ -299,11 +326,13 @@ impl Window {
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {}
pub fn set_maximized(&self, maximized: bool) {
let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized);
}
#[inline]
pub fn is_maximized(&self) -> bool {
false
self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false)
}
#[inline]
@@ -315,21 +344,30 @@ impl Window {
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {}
#[inline]
pub fn is_decorated(&self) -> bool {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.expect("failed to read properties");
let properties = WindowProperties::new(path);
!properties.flags.contains(ORBITAL_FLAG_BORDERLESS)
pub fn set_decorations(&self, decorations: bool) {
let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations);
}
#[inline]
pub fn set_window_level(&self, _level: window::WindowLevel) {}
pub fn is_decorated(&self) -> bool {
!self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false)
}
#[inline]
pub fn set_window_level(&self, level: window::WindowLevel) {
match level {
window::WindowLevel::AlwaysOnBottom => {
let _ = self.set_flag(ORBITAL_FLAG_BACK, true);
}
window::WindowLevel::Normal => {
let _ = self.set_flag(ORBITAL_FLAG_BACK, false);
let _ = self.set_flag(ORBITAL_FLAG_FRONT, false);
}
window::WindowLevel::AlwaysOnTop => {
let _ = self.set_flag(ORBITAL_FLAG_FRONT, true);
}
}
}
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
@@ -360,30 +398,58 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, _: window::CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
pub fn set_cursor_grab(
&self,
mode: window::CursorGrabMode,
) -> Result<(), error::ExternalError> {
let (grab, relative) = match mode {
window::CursorGrabMode::None => (false, false),
window::CursorGrabMode::Confined => (true, false),
window::CursorGrabMode::Locked => (true, true),
};
self.window_socket
.write(format!("M,G,{}", if grab { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
self.window_socket
.write(format!("M,R,{}", if relative { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]
pub fn set_cursor_visible(&self, _: bool) {}
pub fn set_cursor_visible(&self, visible: bool) {
let _ = self
.window_socket
.write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes());
}
#[inline]
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
self.window_socket
.write(b"D")
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]
pub fn drag_resize_window(
&self,
_direction: window::ResizeDirection,
direction: window::ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
let arg = match direction {
window::ResizeDirection::East => "R",
window::ResizeDirection::North => "T",
window::ResizeDirection::NorthEast => "T,R",
window::ResizeDirection::NorthWest => "T,L",
window::ResizeDirection::South => "B",
window::ResizeDirection::SouthEast => "B,R",
window::ResizeDirection::SouthWest => "B,L",
window::ResizeDirection::West => "L",
};
self.window_socket
.write(format!("D,{}", arg).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]

View File

@@ -14,7 +14,6 @@ use crate::window::WindowId;
use std::{
cell::{Cell, RefCell},
clone::Clone,
collections::{HashSet, VecDeque},
iter,
ops::Deref,

View File

@@ -1,10 +1,11 @@
use std::cell::{Cell, RefCell};
use std::clone::Clone;
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
use std::iter;
use std::marker::PhantomData;
use std::rc::{Rc, Weak};
use web_sys::Element;
use super::runner::{EventWrapper, Execution};
use super::{
super::{monitor::MonitorHandle, KeyEventExtra},
@@ -122,6 +123,25 @@ impl<T> EventLoopWindowTarget<T> {
}
});
// It is possible that at this point the canvas has
// been focused before the callback can be called.
let focused = canvas
.document()
.active_element()
.filter(|element| {
let canvas: &Element = canvas.raw();
element == canvas
})
.is_some();
if focused {
canvas.has_focus.set(true);
self.runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Focused(true),
})
}
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
canvas.on_keyboard_press(
@@ -649,6 +669,8 @@ impl<T> EventLoopWindowTarget<T> {
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
canvas.on_touch_end();
canvas.on_context_menu(prevent_default);
}
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {

View File

@@ -17,6 +17,9 @@
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
// compliant way.
// TODO: FP, remove when <https://github.com/rust-lang/rust/issues/121621> is fixed.
#![allow(unknown_lints, non_local_definitions)]
mod r#async;
mod device;
mod error;

View File

@@ -5,7 +5,8 @@ use std::sync::{Arc, Mutex};
use smol_str::SmolStr;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
PointerEvent, WheelEvent,
};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
@@ -42,6 +43,7 @@ pub struct Canvas {
on_intersect: Option<IntersectionObserverHandle>,
animation_frame_handler: AnimationFrameHandler,
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
}
pub struct Common {
@@ -152,6 +154,7 @@ impl Canvas {
on_intersect: None,
animation_frame_handler: AnimationFrameHandler::new(window),
on_touch_end: None,
on_context_menu: None,
})
}
@@ -446,6 +449,17 @@ impl Canvas {
self.on_touch_end = Some(self.common.add_transient_event("touchend", |_| {}));
}
pub(crate) fn on_context_menu(&mut self, prevent_default: bool) {
self.on_context_menu = Some(self.common.add_event(
"contextmenu",
move |event: PointerEvent| {
if prevent_default {
event.prevent_default();
}
},
));
}
pub fn request_fullscreen(&self) {
self.common.fullscreen_handler.request_fullscreen()
}
@@ -524,6 +538,7 @@ impl Canvas {
self.animation_frame_handler.cancel();
self.on_touch_end = None;
self.common.fullscreen_handler.cancel();
self.on_context_menu = None;
}
}

View File

@@ -4,7 +4,6 @@ use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
use once_cell::unsync::OnceCell;
use smol_str::SmolStr;
use std::convert::TryInto;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
@@ -81,9 +80,22 @@ impl MouseButton {
}
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
#[wasm_bindgen]
extern "C" {
type MouseEventExt;
#[wasm_bindgen(method, getter, js_name = offsetX)]
fn offset_x(this: &MouseEventExt) -> f64;
#[wasm_bindgen(method, getter, js_name = offsetY)]
fn offset_y(this: &MouseEventExt) -> f64;
}
let event: &MouseEventExt = event.unchecked_ref();
LogicalPosition {
x: event.offset_x() as f64,
y: event.offset_y() as f64,
x: event.offset_x(),
y: event.offset_y(),
}
}
@@ -244,7 +256,7 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEv
// See <https://github.com/rust-windowing/winit/issues/2875>.
pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
thread_local! {
static POINTER_RAW_SUPPORT: OnceCell<bool> = OnceCell::new();
static POINTER_RAW_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
POINTER_RAW_SUPPORT.with(|support| {
@@ -267,7 +279,7 @@ pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {
thread_local! {
static COALESCED_EVENTS_SUPPORT: OnceCell<bool> = OnceCell::new();
static COALESCED_EVENTS_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
COALESCED_EVENTS_SUPPORT.with(|support| {

View File

@@ -11,7 +11,7 @@ use web_sys::{Document, Element, HtmlCanvasElement};
use super::EventListenerHandle;
thread_local! {
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
pub struct FullscreenHandler {

View File

@@ -126,7 +126,7 @@ impl ResizeScaleInternal {
(-webkit-device-pixel-ratio: {current_scale})",
);
let mql = MediaQueryListHandle::new(window, &media_query, closure);
assert!(
debug_assert!(
mql.mql().matches(),
"created media query doesn't match, {current_scale} != {}",
super::scale_factor(window)

View File

@@ -117,9 +117,7 @@ impl Schedule {
let channel = MessageChannel::new().unwrap();
let closure = Closure::new(f);
let port_1 = channel.port1();
port_1
.add_event_listener_with_callback("message", closure.as_ref().unchecked_ref())
.expect("Failed to set message handler");
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
port_1.start();
let port_2 = channel.port2();
@@ -178,6 +176,7 @@ impl Drop for Schedule {
} => {
window.clear_timeout_with_handle(*handle);
port.close();
port.set_onmessage(None);
}
}
}
@@ -199,7 +198,7 @@ fn duration_millis_ceil(duration: Duration) -> u32 {
fn has_scheduler_support(window: &web_sys::Window) -> bool {
thread_local! {
static SCHEDULER_SUPPORT: OnceCell<bool> = OnceCell::new();
static SCHEDULER_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
SCHEDULER_SUPPORT.with(|support| {
@@ -221,7 +220,7 @@ fn has_scheduler_support(window: &web_sys::Window) -> bool {
fn has_idle_callback_support(window: &web_sys::Window) -> bool {
thread_local! {
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = OnceCell::new();
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
IDLE_CALLBACK_SUPPORT.with(|support| {

View File

@@ -9,8 +9,8 @@ use windows_sys::Win32::{
},
UI::{
HiDpi::{
DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, MDT_EFFECTIVE_DPI,
PROCESS_PER_MONITOR_DPI_AWARE,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE,
},
WindowsAndMessaging::IsProcessDPIAware,
},
@@ -21,8 +21,6 @@ use crate::platform_impl::platform::util::{
SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT,
};
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4;
pub fn become_dpi_aware() {
static ENABLE_DPI_AWARENESS: Once = Once::new();
ENABLE_DPI_AWARENESS.call_once(|| {

View File

@@ -21,7 +21,7 @@ use once_cell::sync::Lazy;
use windows_sys::Win32::{
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
Graphics::Gdi::{
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
@@ -35,13 +35,9 @@ use windows_sys::Win32::{
Input::{
Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW},
KeyboardAndMouse::{
MapVirtualKeyW, ReleaseCapture, SetCapture, TrackMouseEvent, MAPVK_VK_TO_VSC_EX,
TME_LEAVE, TRACKMOUSEEVENT, VK_NUMLOCK, VK_SHIFT,
},
Pointer::{
POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE, POINTER_INFO,
POINTER_PEN_INFO, POINTER_TOUCH_INFO,
ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT,
},
Pointer::{POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE},
Touch::{
CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE,
TOUCHEVENTF_UP, TOUCHINPUT,
@@ -54,20 +50,19 @@ use windows_sys::Win32::{
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos,
TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN,
PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE,
SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER,
WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY,
WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED,
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
WS_VISIBLE,
PT_TOUCH, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE,
WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP,
WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT,
WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
},
},
};
@@ -80,8 +75,8 @@ use crate::{
WindowEvent,
},
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
keyboard::{KeyCode, ModifiersState, PhysicalKey},
platform::{pump_events::PumpStatus, scancode::PhysicalKeyExtScancode},
keyboard::ModifiersState,
platform::pump_events::PumpStatus,
platform_impl::platform::{
dark_mode::try_theme,
dpi::{become_dpi_aware, dpi_to_scale_factor},
@@ -103,37 +98,6 @@ use self::runner::RunnerState;
use super::window::set_skip_taskbar;
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
pointerId: u32,
entriesCount: *mut u32,
pointerCount: *mut u32,
pointerInfo: *mut POINTER_INFO,
) -> BOOL;
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
type GetPointerDeviceRects = unsafe extern "system" fn(
device: HANDLE,
pointerDeviceRect: *mut RECT,
displayRect: *mut RECT,
) -> BOOL;
type GetPointerTouchInfo =
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
type GetPointerPenInfo =
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
pub(crate) struct WindowData<T: 'static> {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: EventLoopRunnerShared<T>,
@@ -356,19 +320,6 @@ impl<T: 'static> EventLoop<T> {
/// Wait for one message and dispatch it, optionally with a timeout
fn wait_and_dispatch_message(&mut self, timeout: Option<Duration>) {
let start = Instant::now();
let runner = &self.window_target.p.runner_shared;
let control_flow_timeout = match runner.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
}
};
let timeout = min_timeout(control_flow_timeout, timeout);
fn get_msg_with_timeout(msg: &mut MSG, timeout: Option<Duration>) -> PumpStatus {
unsafe {
// A timeout of None means wait indefinitely (so we don't need to call SetTimer)
@@ -404,6 +355,8 @@ impl<T: 'static> EventLoop<T> {
}
}
let runner = &self.window_target.p.runner_shared;
// We aim to be consistent with the MacOS backend which has a RunLoop
// observer that will dispatch AboutToWait when about to wait for
// events, and NewEvents after the RunLoop wakes up.
@@ -415,6 +368,16 @@ impl<T: 'static> EventLoop<T> {
//
runner.prepare_wait();
let control_flow_timeout = match runner.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
let start = Instant::now();
Some(wait_deadline.saturating_duration_since(start))
}
};
let timeout = min_timeout(control_flow_timeout, timeout);
// # Safety
// The Windows API has no documented requirement for bitwise
// initializing a `MSG` struct (it can be uninitialized memory for the C
@@ -567,6 +530,10 @@ impl<T> EventLoopWindowTarget<T> {
self.runner_shared.exit_code().is_some()
}
pub(crate) fn clear_exit(&self) {
self.runner_shared.clear_exit();
}
fn exit_code(&self) -> Option<i32> {
self.runner_shared.exit_code()
}
@@ -729,14 +696,13 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
unsafe {
if PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) != false.into() {
self.event_send.send(event).ok();
Ok(())
} else {
Err(EventLoopClosed(event))
}
}
self.event_send
.send(event)
.map(|result| {
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
result
})
.map_err(|e| EventLoopClosed(e.0))
}
}
@@ -1465,6 +1431,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true))
.ok();
drop(w);
userdata.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorEntered {
@@ -1487,6 +1454,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false))
.ok();
drop(w);
userdata.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorLeft {
@@ -1494,12 +1462,13 @@ unsafe fn public_window_callback_inner<T: 'static>(
},
});
}
PointerMoveKind::None => (),
PointerMoveKind::None => drop(w),
}
// handle spurious WM_MOUSEMOVE messages
// see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343
// and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html
let mut w = userdata.window_state_lock();
cursor_moved = w.mouse.last_position != Some(position);
w.mouse.last_position = Some(position);
}
@@ -1829,9 +1798,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
Some(SkipPointerFrameMessages),
Some(GetPointerDeviceRects),
) = (
*GET_POINTER_FRAME_INFO_HISTORY,
*SKIP_POINTER_FRAME_MESSAGES,
*GET_POINTER_DEVICE_RECTS,
*util::GET_POINTER_FRAME_INFO_HISTORY,
*util::SKIP_POINTER_FRAME_MESSAGES,
*util::GET_POINTER_DEVICE_RECTS,
) {
let pointer_id = super::loword(wparam as u32) as u32;
let mut entries_count = 0u32;
@@ -1913,7 +1882,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
let force = match pointer_info.pointerType {
PT_TOUCH => {
let mut touch_info = mem::MaybeUninit::uninit();
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
match unsafe {
GetPointerTouchInfo(
pointer_info.pointerId,
@@ -1929,7 +1898,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
}
PT_PEN => {
let mut pen_info = mem::MaybeUninit::uninit();
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
match unsafe {
GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr())
} {
@@ -2491,105 +2460,17 @@ unsafe fn handle_raw_input<T: 'static>(userdata: &ThreadMsgTargetData<T>, data:
return;
}
let state = if pressed { Pressed } else { Released };
let extension = {
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
0xE000
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
0xE100
} else {
0x0000
}
};
let scancode = if keyboard.MakeCode == 0 {
// In some cases (often with media keys) the device reports a scancode of 0 but a
// valid virtual key. In these cases we obtain the scancode from the virtual key.
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
} else {
keyboard.MakeCode | extension
};
if scancode == 0xE11D || scancode == 0xE02A {
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
// Ctrl+NumLock.
// This equvalence means that if the user presses Pause, the keyboard will emit two
// subsequent keypresses:
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
// 2, 0x0045 - Which on its own can be interpreted as Pause
//
// There's another combination which isn't quite an equivalence:
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
// PrtSc (print screen) produces the following sequence:
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
// its own it can be interpreted as PrtSc
//
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
// that there's going to be another event coming, from which we can extract the
// appropriate key.
// For more on this, read the article by Raymond Chen, titled:
// "Why does Ctrl+ScrollLock cancel dialogs?"
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
return;
if let Some(physical_key) = raw_input::get_keyboard_physical_key(keyboard) {
let state = if pressed { Pressed } else { Released };
userdata.send_event(Event::DeviceEvent {
device_id,
event: Key(RawKeyEvent {
physical_key,
state,
}),
});
}
let physical_key = if keyboard.VKey == VK_NUMLOCK {
// Historically, the NumLock and the Pause key were one and the same physical key.
// The user could trigger Pause by pressing Ctrl+NumLock.
// Now these are often physically separate and the two keys can be differentiated by
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
//
// However in this event, both keys are reported as 0x0045 even on modern hardware.
// Therefore we use the virtual key instead to determine whether it's a NumLock and
// set the KeyCode accordingly.
//
// For more on this, read the article by Raymond Chen, titled:
// "Why does Ctrl+ScrollLock cancel dialogs?"
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
PhysicalKey::Code(KeyCode::NumLock)
} else {
PhysicalKey::from_scancode(scancode as u32)
};
if keyboard.VKey == VK_SHIFT {
if let PhysicalKey::Code(code) = physical_key {
match code {
KeyCode::NumpadDecimal
| KeyCode::Numpad0
| KeyCode::Numpad1
| KeyCode::Numpad2
| KeyCode::Numpad3
| KeyCode::Numpad4
| KeyCode::Numpad5
| KeyCode::Numpad6
| KeyCode::Numpad7
| KeyCode::Numpad8
| KeyCode::Numpad9 => {
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
// wasn't active. The way this is exposed to applications by the system is that
// the application receives a fake key release event for the shift key at the
// moment when the numpad key is pressed, just before receiving the numpad key
// as well.
//
// The issue is that in the raw device event (here), the fake shift release
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
// have any information to tell whether it's the left shift or the right shift
// that needs to get the fake release (or press) event so we don't forward this
// event to the application at all.
//
// For more on this, read the article by Raymond Chen, titled:
// "The shift key overrides NumLock"
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
return;
}
_ => (),
}
}
}
userdata.send_event(Event::DeviceEvent {
device_id,
event: Key(RawKeyEvent {
physical_key,
state,
}),
});
}
}

View File

@@ -163,6 +163,10 @@ impl<T> EventLoopRunner<T> {
self.exit.get()
}
pub fn clear_exit(&self) {
self.exit.set(None);
}
pub fn should_buffer(&self) -> bool {
let handler = self.event_handler.take();
let should_buffer = handler.is_none();
@@ -399,23 +403,29 @@ impl<T> BufferedEvent<T> {
match self {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => {
let new_inner_size = Arc::new(Mutex::new(new_inner_size));
let user_new_innner_size = Arc::new(Mutex::new(new_inner_size));
dispatch(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&user_new_innner_size,
)),
},
});
let inner_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let inner_size = *user_new_innner_size.lock().unwrap();
let window_flags = unsafe {
let userdata =
get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData<T>;
(*userdata).window_state_lock().window_flags
};
window_flags.set_size((window_id.0).0, inner_size);
drop(user_new_innner_size);
if inner_size != new_inner_size {
let window_flags = unsafe {
let userdata =
get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData<T>;
(*userdata).window_state_lock().window_flags
};
window_flags.set_size((window_id.0).0, inner_size);
}
}
}
}

View File

@@ -10,9 +10,9 @@ use windows_sys::Win32::{
UI::{
Input::Ime::{
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
ImmSetCandidateWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM,
CFS_EXCLUDE, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN,
IACE_DEFAULT,
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM,
GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
},
WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED},
},
@@ -124,7 +124,7 @@ impl ImeContext {
left: x,
top: y,
right: x + width,
bottom: y - height,
bottom: y + height,
};
let candidate_form = CANDIDATEFORM {
dwIndex: 0,
@@ -132,8 +132,16 @@ impl ImeContext {
ptCurrentPos: POINT { x, y },
rcArea: rc_area,
};
let composition_form = COMPOSITIONFORM {
dwStyle: CFS_POINT,
ptCurrentPos: POINT { x, y: y + height },
rcArea: rc_area,
};
unsafe { ImmSetCandidateWindow(self.himc, &candidate_form) };
unsafe {
ImmSetCompositionWindow(self.himc, &composition_form);
ImmSetCandidateWindow(self.himc, &candidate_form);
}
}
pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) {

View File

@@ -52,12 +52,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
#[derive(Debug, Clone, Copy)]
pub struct Cursor(pub *const u16);
unsafe impl Send for Cursor {}
unsafe impl Sync for Cursor {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u32);

View File

@@ -230,37 +230,45 @@ impl MonitorHandle {
// EnumDisplaySettingsExW can return duplicate values (or some of the
// fields are probably changing, but we aren't looking at those fields
// anyway), so we're using a BTreeSet deduplicate
let mut modes = BTreeSet::new();
let mut i = 0;
let mut modes = BTreeSet::<RootVideoMode>::new();
let mod_map = |mode: RootVideoMode| mode.video_mode;
loop {
unsafe {
let monitor_info = get_monitor_info(self.0).unwrap();
let device_name = monitor_info.szDevice.as_ptr();
let mut mode: DEVMODEW = mem::zeroed();
mode.dmSize = mem::size_of_val(&mode) as u16;
if EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == false.into() {
break;
}
i += 1;
const REQUIRED_FIELDS: u32 =
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
// Use Ord impl of RootVideoMode
modes.insert(RootVideoMode {
video_mode: VideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight),
bit_depth: mode.dmBitsPerPel as u16,
refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
monitor: self.clone(),
native_video_mode: Box::new(mode),
},
});
let monitor_info = match get_monitor_info(self.0) {
Ok(monitor_info) => monitor_info,
Err(error) => {
log::warn!("Error from get_monitor_info: {error}");
return modes.into_iter().map(mod_map);
}
};
let device_name = monitor_info.szDevice.as_ptr();
let mut i = 0;
loop {
let mut mode: DEVMODEW = unsafe { mem::zeroed() };
mode.dmSize = mem::size_of_val(&mode) as u16;
if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() {
break;
}
const REQUIRED_FIELDS: u32 =
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
// Use Ord impl of RootVideoMode
modes.insert(RootVideoMode {
video_mode: VideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight),
bit_depth: mode.dmBitsPerPel as u16,
refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
monitor: self.clone(),
native_video_mode: Box::new(mode),
},
});
i += 1;
}
modes.into_iter().map(|mode| mode.video_mode)
modes.into_iter().map(mod_map)
}
}

View File

@@ -11,21 +11,29 @@ use windows_sys::Win32::{
UI::{
Input::{
GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList,
KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT},
RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST,
RAWINPUTHEADER, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO,
RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD,
RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
RAWINPUTHEADER, RAWKEYBOARD, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE,
RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID,
RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID,
RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
},
WindowsAndMessaging::{
RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN,
RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP,
RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN,
RI_MOUSE_BUTTON_5_UP,
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP,
RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN,
RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP,
RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP,
},
},
};
use crate::{event::ElementState, event_loop::DeviceEvents, platform_impl::platform::util};
use crate::{
event::ElementState,
event_loop::DeviceEvents,
keyboard::{KeyCode, PhysicalKey},
platform::scancode::PhysicalKeyExtScancode,
platform_impl::platform::util,
};
#[allow(dead_code)]
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
@@ -220,3 +228,99 @@ pub fn get_raw_mouse_button_state(button_flags: u32) -> [Option<ElementState>; 5
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP),
]
}
pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option<PhysicalKey> {
let extension = {
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
0xE000
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
0xE100
} else {
0x0000
}
};
let scancode = if keyboard.MakeCode == 0 {
// In some cases (often with media keys) the device reports a scancode of 0 but a
// valid virtual key. In these cases we obtain the scancode from the virtual key.
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
} else {
keyboard.MakeCode | extension
};
if scancode == 0xE11D || scancode == 0xE02A {
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
// Ctrl+NumLock.
// This equvalence means that if the user presses Pause, the keyboard will emit two
// subsequent keypresses:
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
// 2, 0x0045 - Which on its own can be interpreted as Pause
//
// There's another combination which isn't quite an equivalence:
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
// PrtSc (print screen) produces the following sequence:
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
// its own it can be interpreted as PrtSc
//
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
// that there's going to be another event coming, from which we can extract the
// appropriate key.
// For more on this, read the article by Raymond Chen, titled:
// "Why does Ctrl+ScrollLock cancel dialogs?"
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
return None;
}
let physical_key = if keyboard.VKey == VK_NUMLOCK {
// Historically, the NumLock and the Pause key were one and the same physical key.
// The user could trigger Pause by pressing Ctrl+NumLock.
// Now these are often physically separate and the two keys can be differentiated by
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
//
// However in this event, both keys are reported as 0x0045 even on modern hardware.
// Therefore we use the virtual key instead to determine whether it's a NumLock and
// set the KeyCode accordingly.
//
// For more on this, read the article by Raymond Chen, titled:
// "Why does Ctrl+ScrollLock cancel dialogs?"
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
PhysicalKey::Code(KeyCode::NumLock)
} else {
PhysicalKey::from_scancode(scancode as u32)
};
if keyboard.VKey == VK_SHIFT {
if let PhysicalKey::Code(code) = physical_key {
match code {
KeyCode::NumpadDecimal
| KeyCode::Numpad0
| KeyCode::Numpad1
| KeyCode::Numpad2
| KeyCode::Numpad3
| KeyCode::Numpad4
| KeyCode::Numpad5
| KeyCode::Numpad6
| KeyCode::Numpad7
| KeyCode::Numpad8
| KeyCode::Numpad9 => {
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
// wasn't active. The way this is exposed to applications by the system is that
// the application receives a fake key release event for the shift key at the
// moment when the numpad key is pressed, just before receiving the numpad key
// as well.
//
// The issue is that in the raw device event (here), the fake shift release
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
// have any information to tell whether it's the left shift or the right shift
// that needs to get the fake release (or press) event so we don't forward this
// event to the application at all.
//
// For more on this, read the article by Raymond Chen, titled:
// "The shift key overrides NumLock"
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
return None;
}
_ => (),
}
}
}
Some(physical_key)
}

View File

@@ -13,7 +13,7 @@ use once_cell::sync::Lazy;
use windows_sys::{
core::{HRESULT, PCWSTR},
Win32::{
Foundation::{BOOL, HMODULE, HWND, RECT},
Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT},
Graphics::Gdi::{ClientToScreen, HMONITOR},
System::{
LibraryLoader::{GetProcAddress, LoadLibraryA},
@@ -21,7 +21,10 @@ use windows_sys::{
},
UI::{
HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS},
Input::KeyboardAndMouse::GetActiveWindow,
Input::{
KeyboardAndMouse::GetActiveWindow,
Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO},
},
WindowsAndMessaging::{
ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement,
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS,
@@ -191,7 +194,9 @@ pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
}
}
// Helper function to dynamically load function pointer.
// Helper function to dynamically load function pointer as some functions
// may not be available on all Windows platforms supported by winit.
//
// `library` and `function` must be zero-terminated.
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
@@ -237,6 +242,26 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
dpi: u32,
) -> BOOL;
pub type GetPointerFrameInfoHistory = unsafe extern "system" fn(
pointerId: u32,
entriesCount: *mut u32,
pointerCount: *mut u32,
pointerInfo: *mut POINTER_INFO,
) -> BOOL;
pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
pub type GetPointerDeviceRects = unsafe extern "system" fn(
device: HANDLE,
pointerDeviceRect: *mut RECT,
displayRect: *mut RECT,
) -> BOOL;
pub type GetPointerTouchInfo =
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
pub type GetPointerPenInfo =
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
pub static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy<Option<AdjustWindowRectExForDpi>> =
@@ -251,3 +276,13 @@ pub static SET_PROCESS_DPI_AWARENESS: Lazy<Option<SetProcessDpiAwareness>> =
Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness));
pub static SET_PROCESS_DPI_AWARE: Lazy<Option<SetProcessDPIAware>> =
Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware));
pub static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
pub static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
pub static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
pub static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
pub static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));

View File

@@ -83,7 +83,7 @@ use crate::{
/// The Win32 implementation of the main `Window` object.
pub(crate) struct Window {
/// Main handle for the window.
window: WindowWrapper,
window: HWND,
/// The current window state.
window_state: Arc<Mutex<WindowState>>,
@@ -126,17 +126,26 @@ impl Window {
}
}
pub fn set_transparent(&self, _transparent: bool) {}
pub fn set_transparent(&self, transparent: bool) {
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::TRANSPARENT, transparent)
});
});
}
pub fn set_blur(&self, _blur: bool) {}
#[inline]
pub fn set_visible(&self, visible: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::VISIBLE, visible)
});
});
@@ -144,7 +153,7 @@ impl Window {
#[inline]
pub fn is_visible(&self) -> Option<bool> {
Some(unsafe { IsWindowVisible(self.window.0) == 1 })
Some(unsafe { IsWindowVisible(self.window) == 1 })
}
#[inline]
@@ -180,10 +189,10 @@ impl Window {
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
let window = self.window;
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
@@ -237,10 +246,10 @@ impl Window {
if physical_size != self.inner_size() {
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
let window = self.window;
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
@@ -275,12 +284,12 @@ impl Window {
#[inline]
pub fn set_resizable(&self, resizable: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::RESIZABLE, resizable)
});
});
@@ -294,12 +303,12 @@ impl Window {
#[inline]
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(
WindowFlags::MINIMIZABLE,
buttons.contains(WindowButtons::MINIMIZE),
@@ -333,14 +342,14 @@ impl Window {
/// Returns the `hwnd` of this window.
#[inline]
pub fn hwnd(&self) -> HWND {
self.window.0
self.window
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::Win32Handle::empty();
window_handle.hwnd = self.window.0 as *mut _;
window_handle.hwnd = self.window as *mut _;
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
window_handle.hinstance = hinstance as *mut _;
rwh_04::RawWindowHandle::Win32(window_handle)
@@ -350,7 +359,7 @@ impl Window {
#[inline]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::Win32WindowHandle::empty();
window_handle.hwnd = self.window.0 as *mut _;
window_handle.hwnd = self.window as *mut _;
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
window_handle.hinstance = hinstance as *mut _;
rwh_05::RawWindowHandle::Win32(window_handle)
@@ -367,8 +376,7 @@ impl Window {
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe {
// SAFETY: Handle will never be zero.
let window = self.window.0;
std::num::NonZeroIsize::new_unchecked(window)
std::num::NonZeroIsize::new_unchecked(self.window)
});
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
window_handle.hinstance = std::num::NonZeroIsize::new(hinstance);
@@ -404,7 +412,7 @@ impl Window {
}
};
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
@@ -414,7 +422,7 @@ impl Window {
.lock()
.unwrap()
.mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, confine))
.set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine))
.map_err(|e| ExternalError::Os(os_error!(e)));
let _ = tx.send(result);
});
@@ -423,7 +431,7 @@ impl Window {
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
@@ -433,7 +441,7 @@ impl Window {
.lock()
.unwrap()
.mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, !visible))
.set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible))
.map_err(|e| e.to_string());
let _ = tx.send(result);
});
@@ -463,27 +471,41 @@ impl Window {
}
unsafe fn handle_os_dragging(&self, wparam: WPARAM) {
let points = {
let mut pos = unsafe { mem::zeroed() };
unsafe { GetCursorPos(&mut pos) };
pos
};
let points = POINTS {
x: points.x as i16,
y: points.y as i16,
};
unsafe { ReleaseCapture() };
let window = self.window;
let window_state = self.window_state.clone();
self.window_state_lock().dragging = true;
self.thread_executor.execute_in_thread(move || {
{
let mut guard = window_state.lock().unwrap();
if !guard.dragging {
guard.dragging = true;
} else {
return;
}
}
unsafe {
PostMessageW(
self.hwnd(),
WM_NCLBUTTONDOWN,
wparam,
&points as *const _ as LPARAM,
)
};
let points = {
let mut pos = unsafe { mem::zeroed() };
unsafe { GetCursorPos(&mut pos) };
pos
};
let points = POINTS {
x: points.x as i16,
y: points.y as i16,
};
// ReleaseCapture needs to execute on the main thread
unsafe { ReleaseCapture() };
unsafe {
PostMessageW(
window,
WM_NCLBUTTONDOWN,
wparam,
&points as *const _ as LPARAM,
)
};
});
}
#[inline]
@@ -592,10 +614,10 @@ impl Window {
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
});
});
@@ -610,7 +632,7 @@ impl Window {
#[inline]
pub fn set_minimized(&self, minimized: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
let is_minimized = util::is_minimized(self.hwnd());
@@ -620,7 +642,7 @@ impl Window {
WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| {
f.set(WindowFlags::MINIMIZED, is_minimized)
});
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MINIMIZED, minimized)
});
});
@@ -633,12 +655,12 @@ impl Window {
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MAXIMIZED, maximized)
});
});
@@ -658,14 +680,24 @@ impl Window {
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
let mut window_state_lock = window_state.lock().unwrap();
let old_fullscreen = window_state_lock.fullscreen.clone();
if window_state_lock.fullscreen == fullscreen {
return;
match (&old_fullscreen, &fullscreen) {
// Return if we already are in the same fullscreen mode
_ if old_fullscreen == fullscreen => return,
// Return if saved Borderless(monitor) is the same as current monitor when requested fullscreen is Borderless(None)
(Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None)))
if *monitor == monitor::current_monitor(window) =>
{
return
}
_ => {}
}
window_state_lock.fullscreen = fullscreen.clone();
drop(window_state_lock);
@@ -728,7 +760,7 @@ impl Window {
}
// Update window style
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
@@ -744,7 +776,7 @@ impl Window {
// this needs to be called before the below fullscreen SetWindowPos as this itself
// will generate WM_SIZE messages of the old window size that can race with what we set below
unsafe {
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
taskbar_mark_fullscreen(window, fullscreen.is_some());
}
// Update window bounds
@@ -753,7 +785,7 @@ impl Window {
// Save window bounds before entering fullscreen
let placement = unsafe {
let mut placement = mem::zeroed();
GetWindowPlacement(window.0, &mut placement);
GetWindowPlacement(window, &mut placement);
placement
};
@@ -762,7 +794,7 @@ impl Window {
let monitor = match &fullscreen {
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
Fullscreen::Borderless(None) => monitor::current_monitor(window.0),
Fullscreen::Borderless(None) => monitor::current_monitor(window),
};
let position: (i32, i32) = monitor.position().into();
@@ -770,7 +802,7 @@ impl Window {
unsafe {
SetWindowPos(
window.0,
window,
0,
position.0,
position.1,
@@ -778,7 +810,7 @@ impl Window {
size.1 as i32,
SWP_ASYNCWINDOWPOS | SWP_NOZORDER,
);
InvalidateRgn(window.0, 0, false.into());
InvalidateRgn(window, 0, false.into());
}
}
None => {
@@ -786,8 +818,8 @@ impl Window {
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
drop(window_state_lock);
unsafe {
SetWindowPlacement(window.0, &placement);
InvalidateRgn(window.0, 0, false.into());
SetWindowPlacement(window, &placement);
InvalidateRgn(window, 0, false.into());
}
}
}
@@ -797,12 +829,12 @@ impl Window {
#[inline]
pub fn set_decorations(&self, decorations: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MARKER_DECORATIONS, decorations)
});
});
@@ -818,12 +850,12 @@ impl Window {
#[inline]
pub fn set_window_level(&self, level: WindowLevel) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(
WindowFlags::ALWAYS_ON_TOP,
level == WindowLevel::AlwaysOnTop,
@@ -872,21 +904,21 @@ impl Window {
#[inline]
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
let window = self.window.clone();
let window = self.window;
let state = self.window_state.clone();
self.thread_executor.execute_in_thread(move || unsafe {
let scale_factor = state.lock().unwrap().scale_factor;
ImeContext::current(window.0).set_ime_cursor_area(spot, size, scale_factor);
ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor);
});
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
let window = self.window.clone();
let window = self.window;
let state = self.window_state.clone();
self.thread_executor.execute_in_thread(move || unsafe {
state.lock().unwrap().ime_allowed = allowed;
ImeContext::set_ime_allowed(window.0, allowed);
ImeContext::set_ime_allowed(window, allowed);
})
}
@@ -895,14 +927,13 @@ impl Window {
#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let window = self.window.clone();
let window = self.window;
let active_window_handle = unsafe { GetActiveWindow() };
if window.0 == active_window_handle {
if window == active_window_handle {
return;
}
self.thread_executor.execute_in_thread(move || unsafe {
let _ = &window;
let (flags, count) = request_type
.map(|ty| match ty {
UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX),
@@ -912,7 +943,7 @@ impl Window {
let flash_info = FLASHWINFO {
cbSize: mem::size_of::<FLASHWINFO>() as u32,
hwnd: window.0,
hwnd: window,
dwFlags: flags,
uCount: count,
dwTimeout: 0,
@@ -923,7 +954,7 @@ impl Window {
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
try_theme(self.window.0, theme);
try_theme(self.window, theme);
}
#[inline]
@@ -938,9 +969,9 @@ impl Window {
}
pub fn title(&self) -> String {
let len = unsafe { GetWindowTextLengthW(self.window.0) } + 1;
let len = unsafe { GetWindowTextLengthW(self.window) } + 1;
let mut buf = vec![0; len as usize];
unsafe { GetWindowTextW(self.window.0, buf.as_mut_ptr(), len) };
unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) };
util::decode_wide(&buf).to_string_lossy().to_string()
}
@@ -952,12 +983,12 @@ impl Window {
#[inline]
pub fn set_undecorated_shadow(&self, shadow: bool) {
let window = self.window.clone();
let window = self.window;
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow)
});
});
@@ -965,15 +996,14 @@ impl Window {
#[inline]
pub fn focus_window(&self) {
let window = self.window.clone();
let window_flags = self.window_state_lock().window_flags();
let is_visible = window_flags.contains(WindowFlags::VISIBLE);
let is_minimized = util::is_minimized(self.hwnd());
let is_foreground = window.0 == unsafe { GetForegroundWindow() };
let is_foreground = self.window == unsafe { GetForegroundWindow() };
if is_visible && !is_minimized && !is_foreground {
unsafe { force_window_active(window.0) };
unsafe { force_window_active(self.window) };
}
}
@@ -1023,18 +1053,6 @@ impl Drop for Window {
}
}
/// A simple non-owning wrapper around a window.
#[doc(hidden)]
#[derive(Clone)]
pub struct WindowWrapper(HWND);
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
// For more info see:
// https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396
unsafe impl Sync for WindowWrapper {}
unsafe impl Send for WindowWrapper {}
pub(super) struct InitData<'a, T: 'static> {
// inputs
pub event_loop: &'a EventLoopWindowTarget<T>,
@@ -1082,7 +1100,7 @@ impl<'a, T: 'static> InitData<'a, T> {
unsafe { ImeContext::set_ime_allowed(window, false) };
Window {
window: WindowWrapper(window),
window,
window_state,
thread_executor: self.event_loop.create_thread_executor(),
}
@@ -1105,7 +1123,7 @@ impl<'a, T: 'static> InitData<'a, T> {
let file_drop_runner = self.event_loop.runner_shared.clone();
let file_drop_handler = FileDropHandler::new(
win.window.0,
win.window,
Box::new(move |event| {
if let Ok(e) = event.map_nonuser_event() {
file_drop_runner.send_event(e)
@@ -1117,7 +1135,7 @@ impl<'a, T: 'static> InitData<'a, T> {
unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void };
assert_eq!(
unsafe { RegisterDragDrop(win.window.0, handler_interface_ptr) },
unsafe { RegisterDragDrop(win.window, handler_interface_ptr) },
S_OK
);
Some(file_drop_handler)
@@ -1192,28 +1210,17 @@ impl<'a, T: 'static> InitData<'a, T> {
win.set_enabled_buttons(attributes.enabled_buttons);
if attributes.fullscreen.0.is_some() {
win.set_fullscreen(attributes.fullscreen.0.map(Into::into));
unsafe { force_window_active(win.window.0) };
} else {
let size = attributes
.inner_size
.unwrap_or_else(|| PhysicalSize::new(800, 600).into());
let max_size = attributes
.max_inner_size
.unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into());
let min_size = attributes
.min_inner_size
.unwrap_or_else(|| PhysicalSize::new(0, 0).into());
let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor());
win.request_inner_size(clamped_size);
if attributes.maximized {
// Need to set MAXIMIZED after setting `inner_size` as
// `Window::request_inner_size` changes MAXIMIZED to false.
win.set_maximized(true);
}
}
let size = attributes
.inner_size
.unwrap_or_else(|| PhysicalSize::new(800, 600).into());
let max_size = attributes
.max_inner_size
.unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into());
let min_size = attributes
.min_inner_size
.unwrap_or_else(|| PhysicalSize::new(0, 0).into());
let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor());
win.request_inner_size(clamped_size);
// let margins = MARGINS {
// cxLeftWidth: 1,
@@ -1294,6 +1301,9 @@ where
#[cfg(not(feature = "rwh_06"))]
let parent = fallback_parent();
let fullscreen = attributes.fullscreen.clone();
let maximized = attributes.maximized;
let mut initdata = InitData {
event_loop,
attributes,
@@ -1331,7 +1341,18 @@ where
// If the handle is non-null, then window creation must have succeeded, which means
// that we *must* have populated the `InitData.window` field.
Ok(initdata.window.unwrap())
let win = initdata.window.unwrap();
// Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx
// This is because if the size is changed in WM_CREATE, the restored size will be stored in that size.
if fullscreen.0.is_some() {
win.set_fullscreen(fullscreen.0.map(Into::into));
unsafe { force_window_active(win.window) };
} else if maximized {
win.set_maximized(true);
}
Ok(win)
}
unsafe fn register_window_class<T: 'static>(class_name: &[u16]) {
@@ -1357,7 +1378,7 @@ unsafe fn register_window_class<T: 'static>(class_name: &[u16]) {
unsafe { RegisterClassExW(&class) };
}
struct ComInitialized(*mut ());
struct ComInitialized(#[allow(dead_code)] *mut ());
impl Drop for ComInitialized {
fn drop(&mut self) {
unsafe { CoUninitialize() };

View File

@@ -543,14 +543,17 @@ impl Window {
self.window.maybe_wait_on_main(|w| WindowId(w.id()))
}
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](crate::dpi) module for more information.
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and
/// vice versa.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking [`WindowEvent::ScaleFactorChanged`] events is
/// the most robust way to track the DPI you need to use to draw.
///
/// This value may differ from [`MonitorHandle::scale_factor`].
///
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///
/// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
@@ -597,11 +600,11 @@ impl Window {
self.window.maybe_queue_on_main(|w| w.request_redraw())
}
/// Notify the windowing system that you're before presenting to the window.
/// Notify the windowing system before presenting to the window.
///
/// You should call this event after you've done drawing operations, but before you submit
/// You should call this event after your drawing operations, but before you submit
/// the buffer to the display or commit your drawings. Doing so will help winit to properly
/// schedule and do assumptions about its internal state. For example, it could properly
/// schedule and make assumptions about its internal state. For example, it could properly
/// throttle [`WindowEvent::RedrawRequested`].
///
/// ## Example
@@ -904,7 +907,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **Windows / Web / iOS / Android / Orbital:** Unsupported.
/// - **Web / iOS / Android:** Unsupported.
/// - **X11:** Can only be set while building the window, with [`WindowBuilder::with_transparent`].
#[inline]
pub fn set_transparent(&self, transparent: bool) {
@@ -918,7 +921,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **Android / iOS / macOS / X11 / Web / Windows:** Unsupported.
/// - **Android / iOS / X11 / Web / Windows:** Unsupported.
/// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
#[inline]
pub fn set_blur(&self, blur: bool) {
@@ -1037,7 +1040,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Orbital:** Unsupported.
/// - **iOS / Android / Web:** Unsupported.
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.window
@@ -1048,7 +1051,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Orbital:** Unsupported.
/// - **iOS / Android / Web:** Unsupported.
#[inline]
pub fn is_maximized(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_maximized())
@@ -1360,7 +1363,8 @@ impl Window {
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Wayland / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **Wayland**: Cursor must be in [`CursorGrabMode::Locked`].
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn set_cursor_position<P: Into<Position>>(&self, position: P) -> Result<(), ExternalError> {
let position = position.into();
@@ -1368,7 +1372,7 @@ impl Window {
.maybe_wait_on_main(|w| w.set_cursor_position(position))
}
/// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window.
/// Set grabbing [mode](CursorGrabMode) on the cursor preventing it from leaving the window.
///
/// # Example
///
@@ -1399,7 +1403,7 @@ impl Window {
/// - **Wayland:** The cursor is only hidden within the confines of the window.
/// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
/// outside of the window.
/// - **iOS / Android / Orbital:** Unsupported.
/// - **iOS / Android:** Unsupported.
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window
@@ -1416,7 +1420,7 @@ impl Window {
/// - **X11:** Un-grabs the cursor.
/// - **Wayland:** Requires the cursor to be inside the window to be dragged.
/// - **macOS:** May prevent the button release event to be triggered.
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window.maybe_wait_on_main(|w| w.drag_window())
@@ -1430,7 +1434,7 @@ impl Window {
/// ## Platform-specific
///
/// - **macOS:** Always returns an [`ExternalError::NotSupported`]
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window
@@ -1472,10 +1476,6 @@ impl Window {
/// Returns the monitor on which the window currently resides.
///
/// Returns `None` if current monitor can't be detected.
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
self.window
@@ -1486,10 +1486,6 @@ impl Window {
///
/// This is the same as [`EventLoopWindowTarget::available_monitors`], and is provided for convenience.
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
///
/// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
@@ -1508,8 +1504,7 @@ impl Window {
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
/// **Wayland:** Always returns `None`.
/// **Wayland / Web:** Always returns `None`.
///
/// [`EventLoopWindowTarget::primary_monitor`]: crate::event_loop::EventLoopWindowTarget::primary_monitor
#[inline]
@@ -1593,7 +1588,7 @@ pub enum CursorGrabMode {
/// ## Platform-specific
///
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
Confined,
/// The cursor is locked inside the window area to the certain position.
@@ -1604,7 +1599,7 @@ pub enum CursorGrabMode {
/// ## Platform-specific
///
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`].
Locked,
}