Compare commits

..

221 Commits

Author SHA1 Message Date
Mads Marquart
e66eba38f5 Add OwnedWindowHandle to avoid lifetime on WindowHandle<'_> 2023-12-24 00:46:13 +01:00
Mads Marquart
e9a25a4c91 Replace remaining AppKit bindings with icrate's (#3296)
* Use icrate's window structs and enums

* Properly implement protocols

* Use icrate's NSWindow

We were previously using undocumented methods on `NSWindowTabGroup`

* Use icrate's NSApplication

And clean up some doc comments regarding NSApplication
2023-12-23 23:07:55 +01:00
Mads Marquart
674657efb6 Partially replace custom AppKit bindings with icrate's autogenerated bindings (#2982)
* Refactor winit-specific cursor logic out of appkit module

* Add relevant AppKit features that we depend on

* Use icrate's NSImageRep and NSBitmapImageRep

* Use icrate's NSImage

* Use icrate's NSCursor

* Use icrate's NSAppearance

* Use icrate's NSScreen

* Use icrate's NSButton

* Use icrate's NSAppKitVersionNumber

* Use icrate's NSTextInputContext

* Use icrate's NSColor

* Use icrate's NSEvent

* Use icrate's NSMenu and NSMenuItem

* Use icrate's NSPasteboard

* Use icrate's NSResponder

* Use icrate's NSTextInputClient

* Use icrate's NSView
2023-12-23 20:58:38 +01:00
Mads Marquart
7d5bee767c Update objc2 and icrate versions (#3256) 2023-12-23 18:04:24 +01:00
Markus Siglreithmaier
745cfaab2c On Windows, remove internal WindowWrapper (#3294)
HWND in windows-sys doesn't require a newtype wrapper for Send/Sync.
2023-12-23 17:06:43 +01:00
daxpedda
a8f49dc8ef MacOS: cache custom cursors (#3291) 2023-12-23 16:34:32 +01:00
daxpedda
e5310ade08 Custom cursor improvements (#3292) 2023-12-23 16:12:29 +01:00
daxpedda
37946e0a3a Use std::cell::OnceCell (#3290) 2023-12-22 23:49:25 +01:00
daxpedda
86b737f5e7 Fix changelog (#3289) 2023-12-22 23:36:58 +01:00
daxpedda
e37585e5bc Bump MSRV to 1.70 (#3287) 2023-12-22 23:27:36 +01:00
Mads Marquart
4aeeb24745 Window handle: Return an error when not on main thread on macOS and iOS (#3288) 2023-12-22 23:18:35 +01:00
daxpedda
8cd3aaa8a2 On Web, use the new WebCanvasWindowHandle (#3270) 2023-12-22 22:33:50 +01:00
daxpedda
2c15de7cf9 Allow custom cursor caching (#3276) 2023-12-22 22:20:41 +01:00
daxpedda
0a7ea61834 Fix some doc nits (#3274) 2023-12-22 21:46:00 +01:00
Markus Siglreithmaier
4ee11018c2 On Windows, refactor dynamic function definitions and raw input keyboard handling (#3286) 2023-12-22 18:42:17 +01:00
daxpedda
4f669ebbd2 On Web, fix context menu not being disabled (#3282) 2023-12-22 00:11:36 +01:00
Kirill Chibisov
7761b2b16c Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-12-22 00:55:52 +04:00
daxpedda
ae41e3265f On Web, correctly mark breaking changes in the changelog 2023-12-22 00:39:06 +04:00
wjian23
8702a09333 On Windows, fix IME area not working 2023-12-21 23:44:30 +04:00
Kirill Chibisov
8b5c84f404 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-21 22:29:36 +04:00
Kirill Chibisov
a676d0018b On windows, remove empty file 2023-12-20 19:12:44 +04:00
Kirill Chibisov
04ca85a909 On Wayland, fix resize being sent on focus change
Fixes #3263.
2023-12-20 18:48:50 +04:00
daxpedda
cc33212479 On Web, fix setting cursor icon overriding cursor visibility (#3269) 2023-12-17 18:49:45 +01:00
daxpedda
f2c5127f27 Web: remove queuing fullscreen request (#3242) 2023-12-17 13:31:48 +01:00
Eero Lehtinen
af93167237 feat(all): Custom cursor images for all desktop platforms
There seems to be many PRs relating to this issue, but they don't include all
platforms and for some reason lost steam. This PR again tries to make this
feature happen, and does it for all desktop platforms (x11, wayland, macos,
windows, web).

I think the best user of this feature and the reason I'm doing this is Bevy and
game engines in general. There non laggy hardware cursors with custom images are
very important. Game devs also like their PNGs so supporting platform native
cursor files is not that important, but I guess could be added too.

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-12-16 12:02:17 -08:00
Amr Bashir
7f6b16a6af On Windows, fix set_fullscreen early return for Fullscreen::Borderless(None) 2023-12-16 20:34:18 +04:00
Friz64
bf5806a9b2 Update sctk-adwaita to 0.8 2023-12-16 20:16:39 +04:00
Héctor Ramón
2a9c593e01 Fix typo in get_xft_dpi 2023-12-15 16:12:50 +04:00
Marijn Suijten
becdd0dbd2 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-14 21:04:15 +04:00
Uli Schlachter
3eea505440 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-09 07:02:30 -08:00
Fredrik Fornwall
b863283c38 On Windows, avoid panic in video_modes() 2023-12-06 20:47:33 +04:00
Leon
73718c9f2f 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-03 18:39:08 +01:00
Xiaopeng Li
f735f028a1 fix refresh_rate_millihertz on macOS (#3254)
* fix refresh_rate_millihertz on macOS

* round after conversion to mHz

* add changelog entry
2023-12-01 15:52:16 +01:00
John Nunley
da947992ac 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-11-28 16:20:36 -08:00
John Nunley
e9784127df 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-11-28 15:08:14 -08:00
Mads Marquart
0be2bb0a8c Remove unused .gitmodules 2023-11-29 00:56:11 +04:00
OG
075996b1fa feat: macos services menu added (#3231) 2023-11-28 21:39:12 +01:00
Emil Ernerfeldt
a7241b3db3 On macOS, remove spurious error logging when handling Fn
Fixes #3246.
2023-11-28 23:19:16 +04:00
Kirill Chibisov
17296e9878 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-11-24 18:32:53 +04:00
John Nunley
b3c87caa7c On X11, reload DPI on PropertyChange
Signed-off-by: John Nunley <dev@notgull.net>
Fixes #1228.
2023-11-24 12:14:06 +04:00
Kirill Chibisov
81a1d9c396 Fix infinite recursion in BadIcon reporting (#3237) 2023-11-23 19:15:17 +01:00
Mads Marquart
5612626944 Make Android docs build on docs.rs (#3236) 2023-11-23 08:18:05 +01:00
Arend van Beelen jr
d3ca685b77 Fix crash when running iPad build on macOS 2023-11-22 16:14:51 +04:00
Kirill Chibisov
7bed5eecfd On macOS, fix assertion when pressing Fn key 2023-11-17 15:56:03 +04:00
Kirill Chibisov
14140607d1 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-11 20:35:30 +04:00
daxpedda
eab982c402 Web: forbid additional functions in favor of caching them (#3219) 2023-11-10 22:46:51 +01:00
Olivier Goffart
21701a33de 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-08 19:21:33 +04:00
Nathan Lilienthal
c89e6df758 On macOS, send a Resized event after ScaleFactorChanged
Fixes #3213.
2023-11-07 02:26:02 +04:00
Kirill Chibisov
e9210555c1 On X11, try alternative cursor icon names as well
This should cover more icons.
2023-11-04 15:19:15 +04:00
Marijn Suijten
0994b5ceb8 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-04 15:18:55 +04:00
DevJac
bcce5134e1 Fix typo in pre_present_notify docs
Fix typo and other small grammar corrections.
2023-11-02 01:07:35 +04:00
Linda_pp
d333dd8664 Fix crash when minimizing example on Windows 2023-10-31 19:21:36 +04:00
Jasper Bekkers
52af1b4a77 On Windows, fix MT safety when starting drag 2023-10-31 19:20:34 +04:00
Kirill Chibisov
3c9f9da19e Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-10-28 21:09:28 +04:00
Kirill Chibisov
075dfcea19 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 14:23:20 +04:00
Kirill Chibisov
92b7dcccc1 On macOS, add support for Window::set_blur 2023-10-28 14:22:10 +04:00
Kirill Chibisov
5a3be586f4 On Windows, add support for Window::set_transparent 2023-10-28 02:22:45 +04:00
Kirill Chibisov
12dbbf8012 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-27 00:56:23 +04:00
Kirill Chibisov
53ca5af48f On Wayland, fix RedrawRequsted loop
The `dirty` is never cleared when decorations are hidden without
`sctk-adwaita`.

Fixes #3177.
2023-10-25 20:59:39 +04:00
Marijn Suijten
c235bd154a On wasm, provide intradoc-link for spawn() function in EventLoop docs (#3178) 2023-10-25 17:42:51 +02:00
J-P Nurmi
f4e71a1d9c On macOS, fix deadlock during nested event loops (e.g. rfd) 2023-10-25 19:13:44 +04:00
Kirill Chibisov
62ed51a138 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-25 18:32:16 +04:00
Kirill Chibisov
b2a2ec91ae Fix unused import warnings on nightly 2023-10-25 15:58:31 +04:00
Kirill Chibisov
d37d1a03b2 On Windows, fix deadlock during Cursor{Enter,Leave}
The lock was not released when calling back to the user.

Fixes #3171.
2023-10-22 19:38:54 +04:00
Kirill Chibisov
772b21ce09 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-10-21 11:58:17 +04:00
Kirill Chibisov
2edcd09704 On X11, fix cursor_hittest not reloaded on Resize
The cursor hittest was not reloaded on window size changes, only
when `Window::request_inner_size` was called leading to regions
of the window being not clickable.

Also, don't try to apply hittest logic when user never requested a
hittest.

Links: https://github.com/alacritty/alacritty/pull/7220
2023-10-21 11:09:53 +04:00
Diggory Hardy
d35c3bea42 Fix rwhd_05 doc links 2023-10-21 08:13:58 +04:00
Valaphee The Meerkat
89a184ed84 feat(windows): Fix inconsistency in mouse button device events, add hwheel device event on Windows
While working with device events, I noticed that there was an inconsistency in the mouse button device events between Windows/X11 and for example web, because web uses the same ids/order as the MouseButton enum, and Windows/X11 are using the X11 ids, and hwheel device event was ignored on Windows.

Mouse button device events are now using the same order as the MouseButton enum, and I also added hwheel device events for Windows.
2023-10-20 10:03:05 -07:00
Kirill Chibisov
36d4907da8 On Windows, fix IME APIs MT-safety
Execute the calls to the IME from the main thread.

Fixes #3123.
2023-10-20 15:46:57 +04:00
Kirill Chibisov
98b3508aca On Windows, fix RedrawRequested delivery
When calling `Window::request_redraw` from the `RedrawRequested`
handler the `RedrawWindow` won't result in `WM_PAINT` being delivered
due since user callback is run before `DefWindowProcW` is called.

Track whether the user called `Window::request_redraw` and ask for
`RedrawWindow` after running the said function during `WM_PAINT`
handling.

Fixes #3150.
2023-10-20 14:52:01 +04:00
Diggory Hardy
c0db53a516 Implement Ord/PartialOrd for ModifiersState 2023-10-20 14:51:42 +04:00
Xiaopeng Li
52b7205b75 On Windows, fix invalid hmonitor panic
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-20 14:51:04 +04:00
Arend van Beelen jr
41dbbc27a0 On iOS, add configuration for status bar style
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-10-20 14:26:10 +04:00
Kirill Chibisov
c346fb7e61 On macOS, fix tabGroup misuse
The property is marked as `Weak`, however we used strong `Id`.

Links: https://github.com/alacritty/alacritty/issues/7249
2023-10-20 14:05:57 +04:00
Kirill Chibisov
6a041f84ba Remove garbage from README
The docs are in the src/lib.rs anyway and are present on docs.rs.
2023-10-19 19:25:30 +04:00
Diggory Hardy
acfeff5327 Revise Key and KeyCode enums
Split `Key` into clear categories, like `Named`, `Dead`, Character`, `Unidentified`
removing the `#[non_exhaustive]` from the `Key` itself.

Similar action was done for the `KeyCode`.

Fixes: #2995
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-19 18:27:49 +04:00
Kirill Chibisov
b9e1e96eaa Ensure that DISPLAY vars are non-empty before using
It's common to disable Wayland by `WAYLAND_DISPLAY= <application>`.
2023-10-19 17:03:15 +04:00
Kirill Chibisov
880238a24f Fix examples not render on Wayland
The `rwh_05` feature was not enabled.

Fixes: e41fac825c (Update to new raw-window-handle strategy)
2023-10-17 07:53:38 +04:00
YouKnow
f5b4d6938f On Windows, fix CursorEntered/CursorLeft not sent during mouse grab
Fixes #3153.
2023-10-17 07:23:22 +04:00
Kirill Chibisov
c65e2247a1 On macOS, fix globe key triggering assertion
Sometimes FlagsChanged events don't carry any KeyCode information, thus
we can't create a synthetic presses events for them.

However in such cases, modifiers information is still accurate, thus
propagate it.

Fixes #2872.
2023-10-17 05:59:48 +04:00
Kirill Chibisov
801fddbfcf Make WindowBuilder Send + Sync
Window builder is always accessed by winit on the thread event loop
is on, thus it's safe to mark the data it gets as `Send + Sync`.
Each unsafe object is marked individually as `Send + Sync` instead
of just implementing `Send` and `Sync` for the whole builder.
2023-10-17 04:54:12 +04:00
Kirill Chibisov
3ad64fb811 Remove resolved deny.toml entries 2023-10-17 04:35:14 +04:00
Marijn Suijten
9bf4493a21 Upgrade to ndk 0.8, ndk-sys 0.5 + android-activity 0.5 releases
Fixes #2905.
Co-authored-by: Robert Bragg <robert@sixbynine.org>
2023-10-17 04:08:33 +04:00
daxpedda
48f6582eb4 Web Async Rework (#3082) 2023-10-16 15:50:22 +02:00
Kirill Chibisov
ef34692148 Add a note on Window::request_redraw on Windows
Fixing this could require a massive rework to how redraw is handled
on windows to the point of removing `WM_PAINT`, since it's not reliable
by any means for our use case.

For now at least document that the API is broken. It was broken like
that for a long while.
2023-10-15 23:48:37 +04:00
Kirill Chibisov
c48116a8fd Implement AsFd/AsRawFd for EventLoop<T>
This should help other crates to integrate winit's event loop into
their bigger event loop without adding an extra thread.
2023-10-15 20:31:29 +04:00
John Nunley
b938fe9df5 Fix potentially unaligned references in X11 device
Fixes #3125
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-15 07:09:10 +04:00
Kirill Chibisov
b7e3649e8b Update SCTK to 0.18.0
The update is pretty minor, however we support now
`WindowEvent::Occluded` when xdg-shell v6 is available.

It also adds support for `Window::show_window_menu`.

Fixes #2927.
2023-10-15 06:49:57 +04:00
Kirill Chibisov
844269d017 Fix ndk deps versions 2023-10-15 06:39:18 +04:00
John Nunley
e41fac825c Update to new raw-window-handle strategy
Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: TornaxO7 <tornax@proton.me>
2023-10-15 06:07:39 +04:00
Ryan Hileman
bbeacc46d5 feat: Implement set_cursor_hittest for X11 2023-10-13 20:42:07 -07:00
Kirill Chibisov
61581ebb4f Fix CHANGELOG entry for Event::MemoryWarning
While the changelog entries for beta releases doesn't really matter. The
change wasn't marked as breaking, while it is.

Fixes: 93f1000a0 (Add Occluded and MemoryWarning events for iOS/Android)
2023-10-13 02:02:00 +04:00
François
93f1000a05 Add Occluded and MemoryWarning events for iOS/Android
Hook `Occluded` event to foreground/background evens on iOS.

This commit also enabled the `MemoryWarning` event, since it's
emitted from the windowing system.

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-13 00:42:09 +04:00
YouKnow
1ea41a2ee2 Add Window::show_window_menu
Add a method to request a system menu. The implementation
is provided only on Windows for now.

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-11 01:46:16 +04:00
daxpedda
42c9b7e40e Fix reset to Poll after the event loop starts 2023-10-11 00:46:08 +04:00
baneyue
0960635895 On Wayland, fix MonitorHandle position 2023-10-11 00:44:52 +04:00
Kirill Chibisov
789a497980 Remove obsolete docs about wayland CSD env variable
The env variable was removed a while ago, yet it was still present in
the user docs.
2023-10-10 09:23:37 +04:00
daxpedda
fac6110cb6 Web: fix ControlFlow::WaitUntil to never wake up **before** the given time (#3133) 2023-10-09 12:31:02 +02:00
Dmitry Sharshakov
0363be4776 Add Window::set_blur
Allow clients to request blur behind their window, implemented on
Wayland for now.
2023-10-08 23:53:15 +04:00
daxpedda
f5dd1c008c Web: remove unnecessary usage of once_cell::unsync::Lazy (#3134) 2023-10-08 02:00:51 +02:00
daxpedda
48a1e84906 Update Clippy to v1.73 (#3135) 2023-10-08 01:21:46 +02:00
epimeletes
ee0db52ac4 Rename run_ondemand to run_on_demand 2023-10-04 01:24:42 +04:00
Fredrik Fornwall
c7cf0cfd83 Make DeviceId contain device id's on Android 2023-10-04 01:23:18 +04:00
Mads Marquart
8393d98940 Link to areweguiyet.com and arewegameyet.rs for extra deps 2023-10-04 01:22:14 +04:00
Mads Marquart
af247eac0f X11: Add #[deny(unsafe_op_in_unsafe_fn)] (#3121)
* X11: Add #[deny(unsafe_op_in_unsafe_fn)]

* Enable #![deny(unsafe_op_in_unsafe_fn)] everywhere
2023-09-30 21:43:41 +02:00
Mads Marquart
b2b4564a5f Windows: Add #[deny(unsafe_op_in_unsafe_fn)] (#3070) 2023-09-29 16:07:44 +02:00
Mads Marquart
cb58c49a90 Bump version on master (#3119)
This commit does not represent a release and only synchronizes CHANGELOG from the latest release.
2023-09-28 01:03:38 +02:00
Neil Macneale V
ffb46dd61f Fix transparent windows on X11 2023-09-26 01:05:15 +04:00
Kirill Chibisov
8c8fb39fcd Remove DeviceEvent::Text event
The event is never constructed inside the winit.
2023-09-23 18:40:23 +04:00
lucasmerlin
2422ea39d0 Pass force on touch events on android 2023-09-22 23:44:39 +04:00
daxpedda
878d832d24 Make ControlFlow::Wait the default (#3106) 2023-09-22 21:27:11 +02:00
Kirill Chibisov
e2e01e1fc6 Remove old docs about EventLoop::run 2023-09-22 13:46:52 +04:00
Pavel Strakhov
c8b685ddbc On X11, fix WaitUntil and Poll behavior
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-09-20 15:15:28 +04:00
StarStarJ
f10ae52385 Implement PartialOrd and Ord for MouseButton 2023-09-17 13:51:03 +04:00
Kirill Chibisov
e731041c15 Correct Wayland section in dpi docs
Fixes #3100.
2023-09-16 18:14:46 +04:00
daxpedda
9df7fc47a1 Install cargo-apk with the stable toolchain 2023-09-16 16:17:53 +04:00
daxpedda
992aeb0ca0 Ignore foreign-types* duplicate deps on macOS
The dependency is duplicated due to examples, yet we still need to
exclude checking it.

Fixes #3093.
2023-09-16 14:54:49 +04:00
daxpedda
0caba93b51 Rename PollType to PollStrategy (#3089) 2023-09-08 18:39:23 +02:00
John Nunley
c00c1e9eb7 Add an MSRV policy to the README (#3046) 2023-09-08 17:34:55 +02:00
daxpedda
83950acd5a Add Window.requestIdleCallback() support (#3084) 2023-09-07 12:12:35 +02:00
Fredrik Fornwall
b99403b1b9 Correct set_exit() -> exit() in the changelog (#3088) 2023-09-07 11:52:53 +02:00
John Nunley
4f0ce7201d Revert select_xkb_events to its previous impl
The new implementation of select_xkb_events apparently misconfigures
the server. This commit does a temporary fix by just reverting it to its
previous implementation.

This is temporary until I can figure out what Xlib is doing behind the
scenes or until I read xkbproto.pdf.

Fixes: #3079
Signed-off-by: John Nunley <dev@notgull.net>
2023-09-07 10:25:52 +04:00
daxpedda
e648169861 Move ControlFlow to EventLoopWindowTarget
Fixes #3042.
2023-09-07 10:25:04 +04:00
John Nunley
8fdd81ecef Allow the user to force X11 under Wayland
Use forced backend over the env variables. 

Signed-off-by: John Nunley <dev@notgull.net>
Fixes: #3057
2023-09-04 01:24:05 +04:00
daxpedda
7a2a2341c2 Remove T from EventLoopTargetWindow (#3081)
Co-authored-by: nerditation <12248559+nerditation@users.noreply.github.com>
2023-09-03 02:26:53 +02:00
Kirill Chibisov
d68d9eab38 Mark startup_notify unsafe functions as safe
They are safe, since they use the rust `std::env` stuff. Making them
safe lets downstream to determine that `std::env` is used and not the
`libc` env manipulation routines, which are unsafe.
2023-09-02 02:05:56 +04:00
Mads Marquart
a06ea45c0f Slightly reduce number of cfgs (#3071)
* Make Linux platforms less dependent on the root monitor handle

* Add various functions to the Wayland platform to reduce cfgs

* Don't use a cfg in listen_device_events

* Don't use a cfg in set_content_protected

* Fix instance of a target_os cfg
2023-09-01 23:14:16 +02:00
Kirill Chibisov
67b041e231 On Wayland, fix TouchPhase::Canceled sent for Move
Fixes #3035.
2023-09-01 02:14:34 +04:00
Mads Marquart
6dfc78fb50 Make EventLoopWindowTarget independent of the user type on Orbital (#3055) 2023-08-30 16:43:28 +02:00
Mads Marquart
477619c0a7 Ensure that winit initializes NSApplication (#3069) 2023-08-30 15:19:30 +02:00
Mads Marquart
5f1a4b65ad Fix missing quote (#3068) 2023-08-30 13:59:23 +02:00
John Nunley
bb9b629bc3 Implement X11 extensions using x11rb instead of Xlib
Removes Xlib code by replacing it with the x11rb equivalent,
the commit handles xrandr, xinput, xinput2, and xkb.

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-30 01:01:25 +04:00
daxpedda
0c8cf94a70 Web: Fullscreen Overhaul (#3063) 2023-08-29 09:28:30 +02:00
daxpedda
1dfca5a395 Enable event propagation (#3062) 2023-08-28 19:18:10 +02:00
Mads Marquart
7541220a41 Fix macOS deminiaturize (#3054) 2023-08-27 17:35:45 +02:00
Kirill Chibisov
7e11912d22 Lock the cargo-apk deps on CI 2023-08-27 19:05:45 +04:00
Mads Marquart
86baa1c99a Make iOS fully thread safe (#3045)
* macOS & iOS: Refactor EventWrapper

* macOS & iOS: Make EventLoopWindowTarget independent of the user event

* iOS: Use MainThreadMarker instead of marking functions unsafe

* Make iOS thread safe
2023-08-27 17:04:39 +02:00
Mads Marquart
d9f04780cc Improve CI caching, and give each job names
This improves CI performance by around 20% (down from 10 to 8 minutes).
2023-08-27 18:25:32 +04:00
daxpedda
67d3fd28f7 Move Event::RedrawRequested to WindowEvent (#3049) 2023-08-27 16:15:09 +02:00
daxpedda
a3cba838ea On Web, never return a MonitorHandle (#3051) 2023-08-26 18:56:44 +02:00
daxpedda
48abf52aac Use setTimeout() trick instead of Window.requestIdleCallback() (#3044) 2023-08-25 21:40:21 +02:00
Mads Marquart
68ef9f707e Use frame instead of visibleRect (#3043) 2023-08-24 22:52:11 +02:00
Mads Marquart
9979441c82 Fix recent CI failures (#3041)
* Fix new clippy lints

* Fix nightly documentation warnings
2023-08-24 18:29:31 +02:00
Kirill Chibisov
309e6aa85a Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.

This is a follow up to beta yanking
2023-08-16 16:34:36 +04:00
Kirill Chibisov
8b8556798e Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-08-16 13:21:09 +04:00
Kirill Chibisov
2d96480a89 Use beta versions of android crates 2023-08-16 12:31:23 +04:00
Kirill Chibisov
6caff77abb Bump MSRV to 1.65 2023-08-16 12:10:38 +04:00
Mads Marquart
af6c343d0e Improve macOS/iOS/Web thread safety
Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-08-14 23:19:57 +04:00
Kirill Chibisov
119462795a Pin android-activity git dependency 2023-08-14 23:10:32 +04:00
Kirill Chibisov
f801c4a00b Reexport raw-window-handle in window module
We use raw-window-handle extensive in the public API as well as we
force the users to use it to get some essential data for interop, thus
reexport it.

Fixes: #2913.
2023-08-14 11:24:45 +04:00
Kirill Chibisov
f9758528f6 Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users it's not desirable.

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

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

Fixes: #500.
2023-08-13 23:20:09 +04:00
lucasmerlin
778d70c001 Fix touch force for Apple Pencil 2023-08-12 16:22:22 +04:00
John Nunley
dc973883c9 Add a way to embed the X11 window into another
Signed-off-by: John Nunley <dev@notgull.net>
Tested-by: Kirill Chibisov <contact@kchibisov.com>
2023-08-11 13:30:55 +04:00
Fredrik Fornwall
2233edb9a0 iOS: Use NSOperatingSystemVersion from icrate (#3019) 2023-08-09 15:57:20 +02:00
Robert Bragg
bd2f1e8312 Android: Support unicode character mapping + dead keys
Up until now the Android backend has been directly mapping key codes
which essentially just represent the "physical" cap of the key (quoted
since this also related to virtual keyboards).

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

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

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

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

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

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

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

Fixes: cad327755 (On Wayland, reduce amount of spurious wakeups)
2023-08-07 09:35:59 +04:00
Kirill Chibisov
5f7955cb2b On X11, set visual_id in raw-window-handle
Fixes #2681.
2023-08-06 06:07:19 +04:00
Kirill Chibisov
793c535b01 Revert "Propagate error from EventLoop creation" (#3010)
This reverts commit ed26dd58fd.
The patched was merged with a review by accident.
2023-08-06 06:07:01 +04:00
Kirill Chibisov
ed26dd58fd Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users ints not desirable.

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

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

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-06 01:58:23 +04:00
Kirill Chibisov
8100a6a584 Remove 'static requirement on run
There's no need to force the static on the users, given that internally
some backends were not using static in the first place.

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

Fixes #1387.
2023-07-31 00:39:01 +04:00
Tobias Hunger
2b2dd6b65d On Windows, keep window maximized when setting size bounds (#2899) 2023-07-29 16:22:28 +02:00
Géraud-Loup
75173118b0 On Windows, add option to customize window class name (#2978) 2023-07-29 15:39:23 +02:00
Mads Marquart
e33d2bee6c Update objc2 version (#2936)
* Upgrade to objc2 v0.4.0 and icrate v0.0.3

* Fix `touchBar` method

* Use ClassType::alloc

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

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

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

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

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

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

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

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

In practice most applications almost certainly shouldn't care about
AboutToWait because the frequency of event loop iterations is
essentially arbitrary and usually irrelevant.
2023-07-28 20:37:56 +04:00
Robert Bragg
935146d299 Rename LoopDestroyed to LoopExiting
Considering the possibility of re-running an event loop via run_ondemand
then it's more correct to say that the loop is about to exit without
assuming it's going to be destroyed.
2023-07-28 20:19:53 +04:00
François
755c533b08 iOS: Always set timer when polling to avoid slow waking (#2979)
* Always set timer when polling to avoid slow waking

* add comment and changelog

---------

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-07-28 17:52:24 +02:00
Robert Bragg
ae9b02e097 Add timeout argument to pump_events
This renames all internal implementations of pump_events_with_timeout
to pump_events and makes them public.

Since all platforms that support pump_events support timeouts there's
no need to have a separate API.
2023-07-28 03:04:32 +04:00
Robert Bragg
e6c7cc297d Windows: implement pump_events_with_timeout internally 2023-07-28 03:04:32 +04:00
Robert Bragg
b74cee8df1 MacOS: implement pump_events_with_timeout internally
This layers pump_events on a pump_events_with_timeout API, like we have
for Linux and Android.

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

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

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

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

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

This fixes the window_ondemand example - by ensuring the window from
the first loop really is destroyed before waiting for 5 seconds
and starting the second loop.
2023-07-28 03:04:32 +04:00
Robert Bragg
9e46dffcc5 Update CHANGELOG.md 2023-07-28 03:04:32 +04:00
Robert Bragg
7501039d57 Add examples/window_ondemand
A minimal example that shows an application running the event loop more
than once via `run_ondemand`

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

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

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

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

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

Fixes: #2709
2023-07-28 03:04:32 +04:00
Robert Bragg
a6f414d732 Remove EventLoopExtRunReturn 2023-07-28 03:04:32 +04:00
Robert Bragg
c47d0846fa Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
Wayland:

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

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

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

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

X11:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This updates the documentation for `request_redraw()` to reflect that we
no longer guarantee that `RedrawRequested` events must be dispatched
after `MainEventsCleared`.
2023-07-28 03:04:32 +04:00
Robert Bragg
f5e73b0af4 Android: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand 2023-07-28 03:04:32 +04:00
Robert Bragg
f40b5f0dad Add EventLoopExtPumpEvents and EventLoopExtRunOnDemand
This adds two new extensions for running a Winit event loop which will
replace `EventLoopExtRunReturn`

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

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

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

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

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

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

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

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

This patch makes no functional change, it only introduces these new
extensions so we can then handle adding platform-specific backends
in separate pull requests, so the work can be landed in stages.
2023-07-28 03:04:32 +04:00
daxpedda
c91402efb9 Correctly detect that we don't support Emscripten (#2971) 2023-07-22 18:31:40 +02:00
John Nunley
43acf7f42f Replace libc with rustix in some modules
Unfortunately this isn't a total removal, for two reasons:

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

First one we can likely resolve in the near future, not so sure about
the second one without using some weird pthreads trick.
2023-07-22 09:32:27 +00:00
Venceslas Duet
c62e64060b On Windows, add drag_resize_window method support (#2966) 2023-07-21 20:01:56 +02:00
Kirill Chibisov
f7a84a5b50 Add platform::startup_notify for Wayland/X11
The utils in this module should help the users to activate the windows
they create, as well as manage activation tokens environment variables.

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

Fixes #2279.

Co-authored-by: John Nunley <jtnunley01@gmail.com>
2023-07-20 13:16:51 +00:00
Venceslas Duet
89aa7cc06e On Wayland, fix Window::is_decorated with CSD 2023-07-18 11:57:33 +00:00
Kirill Chibisov
b166e1ff13 On Wayland, make the CSD frame double click reliable
It was discovered that on GNOME the click sometimes being swallowed
by the mutter's `wl_pointer::enter/leave` sequences. This was happening
due to `xdg_toplevel::move` making the pointer to leave the surface.

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

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

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

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
2023-07-13 06:52:34 +00:00
daxpedda
b63164645b On Web, add WindowBuilderExtWebSys::with_append() (#2953) 2023-07-12 17:11:52 +02:00
daxpedda
5b5ebc25d8 Improve documentation of WindowBuilderExtWebSys methods (#2952) 2023-07-12 17:11:17 +02:00
John Nunley
d7ec899d69 Replace parts of the Xlib backend with x11-rb 2023-07-12 07:59:12 +00:00
daxpedda
5379d60e4d On Web, remove Window::is_dark_mode() (#2951) 2023-07-11 18:26:32 +02:00
daxpedda
44e2f95331 Fix mentions of Wasm (#2950) 2023-07-11 18:26:00 +02:00
daxpedda
3b2d1a7643 On Web, implement and fix missing methods on Window(Builder) (#2949) 2023-07-11 13:14:40 +02:00
daxpedda
50b17a3907 Add Fullscreen API compatibility for Safari (#2948) 2023-07-11 00:34:02 +02:00
daxpedda
af26f01b95 On Web, cache commonly used values (#2947) 2023-07-11 00:11:06 +02:00
daxpedda
c4d70d75c1 Increase accuracy of various Web APIs (#2946) 2023-07-10 23:55:43 +02:00
daxpedda
db8de03142 Improve Web specific documentation for various APIs (#2941)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-07-10 12:50:28 +02:00
Kirill Chibisov
ff0ce9d065 Rename Window::set_inner_size to Window::request_inner_size
Some systems could resize the window immediately and we'd rather
inform the users right away if that was the case, so they could
create e.g. EGLSurface without waiting for resize, which is really
important for Wayland.

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

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

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

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

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

* Convert the rest of WinitView to use interior mutability

* Use interior mutability instead of a Mutex for the CursorState

* Use interior mutability in WinitWindowDelegate
2023-07-08 22:36:42 +03:00
daxpedda
4652d48105 Don't unnecessarily clone canvas on Web (#2934) 2023-07-08 17:05:05 +02:00
daxpedda
96c0b267e2 Fix typos on Web (#2933) 2023-07-08 16:47:31 +02:00
StarStarJ
81fd39485f Implement PartialOrd/Ord for KeyCode/NativeKeyCode 2023-07-01 19:07:35 +04:00
Kirill Chibisov
6178acede8 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-07-01 00:10:02 +04:00
104 changed files with 3440 additions and 3517 deletions

View File

@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.65.0']
toolchain: [stable, nightly, '1.70.0']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -43,10 +43,10 @@ jobs:
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.65.0'
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.69.0'
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
env:
@@ -120,7 +120,7 @@ jobs:
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS
- name: Run tests
@@ -129,7 +129,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
@@ -139,7 +139,7 @@ jobs:
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
@@ -148,7 +148,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS --features serde
# See restore step above

View File

@@ -11,37 +11,18 @@ Unreleased` header.
# Unreleased
# 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 Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- Add `Window::set_custom_cursor`
- Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
- 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
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
# 0.29.5

View File

@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.29.10"
version = "0.29.5"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -10,7 +10,7 @@ readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.65.0"
rust-version = "1.70.0"
[package.metadata.docs.rs]
features = [
@@ -66,7 +66,7 @@ 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.2", features = ["std"], optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5", 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"
@@ -86,13 +86,13 @@ ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.4.1"
objc2 = "0.5.0"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4"
version = "0.1.0"
features = [
"dispatch",
"Foundation",
@@ -105,10 +105,31 @@ features = [
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSNumber",
"AppKit",
"AppKit_NSAppearance",
"AppKit_NSApplication",
"AppKit_NSBitmapImageRep",
"AppKit_NSButton",
"AppKit_NSColor",
"AppKit_NSControl",
"AppKit_NSCursor",
"AppKit_NSEvent",
"AppKit_NSGraphicsContext",
"AppKit_NSImage",
"AppKit_NSImageRep",
"AppKit_NSMenu",
"AppKit_NSMenuItem",
"AppKit_NSPasteboard",
"AppKit_NSResponder",
"AppKit_NSScreen",
"AppKit_NSTextInputContext",
"AppKit_NSView",
"AppKit_NSWindow",
"AppKit_NSWindowTabGroup",
]
[target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.0.4"
version = "0.1.0"
features = [
"dispatch",
"Foundation",
@@ -179,6 +200,7 @@ version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'console',
'CssStyleDeclaration',
'Document',
@@ -190,6 +212,11 @@ features = [
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
@@ -199,6 +226,7 @@ features = [
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
@@ -206,7 +234,8 @@ features = [
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent'
'WheelEvent',
'Url',
]
[target.'cfg(target_family = "wasm")'.dependencies]

View File

@@ -106,6 +106,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **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 image**: Changing the cursor to your own image.
- **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.
@@ -206,6 +207,7 @@ Legend:
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.29.10"
winit = "0.29.5"
```
## [Documentation](https://docs.rs/winit)
@@ -42,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. 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
@@ -156,7 +156,7 @@ For more details, refer to these `android-activity` [example applications](https
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.10", features = [ "android-native-activity" ] }`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.5", 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).

View File

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

View File

@@ -17,7 +17,7 @@ fn main() -> Result<(), impl std::error::Error> {
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget},
raw_window_handle::HasRawWindowHandle,
raw_window_handle::HasWindowHandle,
window::{Window, WindowBuilder, WindowId},
};
@@ -26,14 +26,13 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.raw_window_handle().unwrap();
let parent = parent.window_handle().unwrap();
let mut builder = WindowBuilder::new()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
builder = unsafe { builder.with_parent_window(Some(parent)) };
builder = builder.with_parent_window(Some(parent));
let child_window = builder.build(event_loop).unwrap();
let id = child_window.id();

View File

@@ -0,0 +1,94 @@
#![allow(clippy::single_match, clippy::disallowed_methods)]
#[cfg(not(wasm_platform))]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{EventLoop, EventLoopWindowTarget},
keyboard::Key,
window::{CustomCursor, WindowBuilder},
};
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples();
let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16);
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
builder.build(window_target)
}
#[cfg(not(wasm_platform))]
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
#[cfg(not(wasm_platform))]
SimpleLogger::new()
.with_level(log::LevelFilter::Info)
.init()
.unwrap();
#[cfg(wasm_platform)]
console_log::init_with_level(log::Level::Debug).unwrap();
let event_loop = EventLoop::new().unwrap();
let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
let mut cursor_idx = 0;
let mut cursor_visible = true;
let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
];
event_loop.run(move |event, _elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: key,
..
},
..
} => match key.as_ref() {
Key::Character("1") => {
log::debug!("Setting cursor to {:?}", cursor_idx);
window.set_custom_cursor(&custom_cursors[cursor_idx]);
cursor_idx = (cursor_idx + 1) % 2;
}
Key::Character("2") => {
log::debug!("Setting cursor icon to default");
window.set_cursor_icon(Default::default());
}
Key::Character("3") => {
cursor_visible = !cursor_visible;
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
window.set_cursor_visible(cursor_visible);
}
_ => {}
},
WindowEvent::RedrawRequested => {
#[cfg(not(wasm_platform))]
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
#[cfg(not(wasm_platform))]
_elwt.exit();
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => {}
})
}

BIN
examples/data/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

BIN
examples/data/cross2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@@ -7,30 +7,17 @@
//! 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.
#[allow(unused_imports)]
pub use platform::cleanup_window;
pub use platform::fill_window;
use winit::window::Window;
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
mod platform {
pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use 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>>> = ManuallyDrop::new(RefCell::new(None));
}
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
@@ -48,69 +35,55 @@ mod platform {
}
}
fn create_surface(&mut self, window: &Window) -> &mut Surface {
self.surfaces.entry(window.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, window) }
fn surface(&mut self, w: &Window) -> &mut Surface {
self.surfaces.entry(w.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, w) }
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &Window) {
self.surfaces.remove(&window.id());
}
}
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");
})
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));
}
#[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);
}
});
}
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))
.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");
})
}
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
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.
}
pub(super) fn fill_window(_window: &Window) {
// No-op on mobile platforms.
}

View File

@@ -40,7 +40,6 @@ 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(),

278
src/cursor.rs Normal file
View File

@@ -0,0 +1,278 @@
use core::fmt;
use std::hash::Hasher;
use std::sync::Arc;
use std::{error::Error, hash::Hash};
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
/// Use a custom image as a cursor (mouse pointer).
///
/// ## Platform-specific
///
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
///
/// # Example
///
/// ```no_run
/// use winit::{
/// event::{Event, WindowEvent},
/// event_loop::{ControlFlow, EventLoop},
/// window::{CustomCursor, Window},
/// };
///
/// let mut event_loop = EventLoop::new().unwrap();
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
///
/// #[cfg(not(target_family = "wasm"))]
/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(target_family = "wasm")]
/// let builder = {
/// use winit::platform::web::CustomCursorExtWebSys;
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// };
///
/// let custom_cursor = builder.build(&event_loop);
///
/// let window = Window::new(&event_loop).unwrap();
/// window.set_custom_cursor(&custom_cursor);
/// ```
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone.
pub(crate) inner: PlatformCustomCursor,
}
impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
/// ## Platform-specific
///
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
/// which are async by nature.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorBuilder, BadImage> {
Ok(CustomCursorBuilder {
inner: PlatformCustomCursorBuilder::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?,
})
}
}
/// Builds a [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug)]
pub struct CustomCursorBuilder {
pub(crate) inner: PlatformCustomCursorBuilder,
}
impl CustomCursorBuilder {
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
CustomCursor {
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
}
}
}
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone)]
pub enum BadImage {
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
/// guarantee that the cursor will work, but should avoid many platform and device specific
/// limits.
TooLarge { width: u16, height: u16 },
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
ByteCountNotDivisibleBy4 { byte_count: usize },
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount {
width: u16,
height: u16,
width_x_height: u64,
pixel_count: u64,
},
/// Produced when the hotspot is outside the image bounds
HotspotOutOfBounds {
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
},
}
impl fmt::Display for BadImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadImage::TooLarge { width, height } => write!(f,
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
),
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
),
BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
),
BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
} => write!(f,
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
),
}
}
}
impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images.
#[derive(Debug)]
pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage);
#[allow(dead_code)]
impl OnlyCursorImageBuilder {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
}
}
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
#[derive(Debug, Clone)]
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
impl Hash for OnlyCursorImage {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for OnlyCursorImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for OnlyCursorImage {}
#[allow(dead_code)]
impl OnlyCursorImage {
fn build<T>(
builder: OnlyCursorImageBuilder,
_: &platform_impl::EventLoopWindowTarget<T>,
) -> Self {
Self(Arc::new(builder.0))
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) hotspot_x: u16,
pub(crate) hotspot_y: u16,
}
impl CursorImage {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
return Err(BadImage::TooLarge { width, height });
}
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadImage::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
let width_x_height = width as u64 * height as u64;
if pixel_count != width_x_height {
return Err(BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
});
}
if hotspot_x >= width || hotspot_y >= height {
return Err(BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
});
}
Ok(CursorImage {
rgba,
width,
height,
hotspot_x,
hotspot_y,
})
}
}
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct NoCustomCursor;
#[allow(dead_code)]
impl NoCustomCursor {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
}
fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> NoCustomCursor {
self
}
}

View File

@@ -172,6 +172,7 @@ extern crate bitflags;
pub mod dpi;
#[macro_use]
pub mod error;
mod cursor;
pub mod event;
pub mod event_loop;
mod icon;

View File

@@ -1,5 +1,6 @@
use std::os::raw::c_void;
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use crate::{
@@ -365,7 +366,9 @@ impl MonitorHandleExtMacOS for MonitorHandle {
}
fn ns_screen(&self) -> Option<*mut c_void> {
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| Id::as_ptr(&s) as _)
}
}

View File

@@ -155,19 +155,19 @@ pub trait EventLoopExtPumpEvents {
/// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop.
///
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
/// - **MacOS**: The implementation works in terms of stopping the global application
/// whenever the application `RunLoop` indicates that it is preparing to block
/// and wait for new events.
///
/// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApp` which aren't accessible to application
/// developers)
/// implementation details for `NSApplication` which aren't accessible to
/// application developers)
///
/// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApp` is stopped while outside of the Winit
/// it also means the `NSApplication` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApp` is global state.
/// because the `NSApplication` is global state.
///
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`

View File

@@ -76,14 +76,6 @@ 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

@@ -27,9 +27,12 @@
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use crate::cursor::CustomCursorBuilder;
use crate::event::Event;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::PlatformCustomCursorBuilder;
use crate::window::CustomCursor;
use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;
@@ -200,3 +203,24 @@ pub enum PollStrategy {
#[default]
Scheduler,
}
pub trait CustomCursorExtWebSys {
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder;
}
impl CustomCursorExtWebSys for CustomCursor {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
CustomCursorBuilder {
inner: PlatformCustomCursorBuilder::Url {
url,
hotspot_x,
hotspot_y,
},
}
}
}

View File

@@ -713,10 +713,6 @@ 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()
}
@@ -755,6 +751,18 @@ impl DeviceId {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowBuilderAttributes;
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
// Parent windows are currently unsupported, though owned window
// handles would be implementable.
Self {}
}
}
pub(crate) struct Window {
app: AndroidApp,
redraw_requester: RedrawRequester,
@@ -910,6 +918,8 @@ impl Window {
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
@@ -1035,6 +1045,8 @@ impl Display for OsError {
}
}
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]

View File

@@ -73,10 +73,12 @@ pub(crate) use self::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
window::{OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
use self::uikit::UIScreen;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -8,6 +8,7 @@ use std::{
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable;
use objc2::rc::Id;
use objc2::Message;
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
@@ -20,16 +21,15 @@ use crate::{
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self {
Self(
self.0
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
)
Self(MainThreadMarker::run_on_main(|mtm| {
MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)
}))
}
}
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -37,7 +37,7 @@ impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
}
}
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -45,7 +45,7 @@ impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
}
}
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoMode {
@@ -100,11 +100,9 @@ pub struct MonitorHandle {
impl Clone for MonitorHandle {
fn clone(&self) -> Self {
Self {
ui_screen: self
.ui_screen
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
}
MainThreadMarker::run_on_main(|mtm| Self {
ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm),
})
}
}
@@ -168,16 +166,16 @@ impl MonitorHandle {
}
pub fn name(&self) -> Option<String> {
self.ui_screen.get_on_main(|ui_screen, mtm| {
MainThreadMarker::run_on_main(|mtm| {
let main = UIScreen::main(mtm);
if *ui_screen == main {
if *self.ui_screen(mtm) == main {
Some("Primary".to_string())
} else if *ui_screen == main.mirroredScreen() {
} else if *self.ui_screen(mtm) == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &**ui_screen)
.position(|rhs| rhs == &**self.ui_screen(mtm))
.map(|idx| idx.to_string())
}
})
@@ -186,31 +184,32 @@ impl MonitorHandle {
pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
.get_on_main(|ui_screen| ui_screen.nativeBounds());
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
.get_on_main(|ui_screen| ui_screen.nativeBounds());
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
pub fn scale_factor(&self) -> f64 {
self.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(
self.ui_screen
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)),
)
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.ui_screen.get_on_main(|ui_screen, mtm| {
MainThreadMarker::run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm);
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = ui_screen
@@ -230,8 +229,12 @@ impl MonitorHandle {
}
pub fn preferred_video_mode(&self) -> VideoMode {
self.ui_screen.get_on_main(|ui_screen, mtm| {
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
MainThreadMarker::run_on_main(|mtm| {
VideoMode::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(),
mtm,
)
})
}
}

View File

@@ -1,12 +1,12 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::ptr::NonNull;
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::Id;
use objc2::runtime::AnyClass;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use objc2::{
declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType, DeclaredClass,
};
use super::app_state::{self, EventWrapper};
use super::uikit::{
@@ -37,6 +37,8 @@ declare_class!(
const NAME: &'static str = "WinitUIView";
}
impl DeclaredClass for WinitView {}
unsafe impl WinitView {
#[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) {
@@ -274,11 +276,7 @@ pub struct ViewControllerState {
}
declare_class!(
pub(crate) struct WinitViewController {
state: IvarDrop<Box<ViewControllerState>, "_state">,
}
mod view_controller_ivars;
pub(crate) struct WinitViewController;
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
@@ -287,28 +285,8 @@ declare_class!(
const NAME: &'static str = "WinitUIViewController";
}
unsafe impl WinitViewController {
#[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
// These are set in WinitViewController::new, it's just to set them
// to _something_.
Ivar::write(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
UIRectEdge::NONE,
),
}),
);
NonNull::from(this)
})
}
impl DeclaredClass for WinitViewController {
type Ivars = ViewControllerState;
}
unsafe impl WinitViewController {
@@ -319,27 +297,27 @@ declare_class!(
#[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
self.state.prefers_status_bar_hidden.get()
self.ivars().prefers_status_bar_hidden.get()
}
#[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.state.preferred_status_bar_style.get()
self.ivars().preferred_status_bar_style.get()
}
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.state.prefers_home_indicator_auto_hidden.get()
self.ivars().prefers_home_indicator_auto_hidden.get()
}
#[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.state.supported_orientations.get()
self.ivars().supported_orientations.get()
}
#[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.state
self.ivars()
.preferred_screen_edges_deferring_system_gestures
.get()
}
@@ -348,17 +326,17 @@ declare_class!(
impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.state.prefers_status_bar_hidden.set(val);
self.ivars().prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
self.state.preferred_status_bar_style.set(val);
self.ivars().preferred_status_bar_style.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.state.prefers_home_indicator_auto_hidden.set(val);
self.ivars().prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
@@ -368,7 +346,7 @@ impl WinitViewController {
}
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
self.state
self.ivars()
.preferred_screen_edges_deferring_system_gestures
.set(val);
let os_capabilities = app_state::os_capabilities();
@@ -401,7 +379,7 @@ impl WinitViewController {
| UIInterfaceOrientationMask::PortraitUpsideDown
}
};
self.state.supported_orientations.set(mask);
self.ivars().supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
}
@@ -411,7 +389,15 @@ impl WinitViewController {
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView,
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
// These are set properly below, we just to set them to something in the meantime.
let this = Self::alloc().set_ivars(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
@@ -446,6 +432,8 @@ declare_class!(
const NAME: &'static str = "WinitUIWindow";
}
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
fn become_key_window(&self) {
@@ -518,6 +506,8 @@ declare_class!(
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for WinitApplicationDelegate {}
// UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate {
#[method(application:didFinishLaunchingWithOptions:)]

View File

@@ -17,7 +17,7 @@ use crate::{
icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -25,6 +25,19 @@ use crate::{
},
};
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
// Parent windows are currently unsupported, though owned window
// handles would be implementable (would work similar to macOS).
warn!("parent windows are unsupported on iOS");
Self {}
}
}
pub struct Inner {
window: Id<WinitUIWindow>,
view_controller: Id<WinitViewController>,
@@ -177,6 +190,10 @@ impl Inner {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {
debug!("`Window::set_custom_cursor` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -355,14 +372,14 @@ impl Inner {
}
#[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
let mut window_handle = rwh_06::UiKitWindowHandle::new({
let ui_view = Id::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
});
window_handle.ui_view_controller =
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
rwh_06::RawWindowHandle::UiKit(window_handle)
}
#[cfg(feature = "rwh_06")]
@@ -516,7 +533,19 @@ impl Window {
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.get_on_main(|inner, _mtm| f(inner))
self.inner.get_on_main(|inner| f(inner))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.inner.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
}

View File

@@ -504,7 +504,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::ArrowUp,
keysyms::KP_Up => NamedKey::ArrowLeft,
keysyms::KP_Right => NamedKey::ArrowRight,
keysyms::KP_Down => NamedKey::ArrowDown,
// keysyms::KP_Prior => NamedKey::PageUp,

View File

@@ -40,6 +40,8 @@ pub use x11::XNotSupported;
#[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
pub(crate) use crate::cursor::OnlyCursorImage as PlatformCustomCursor;
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
@@ -139,6 +141,43 @@ impl fmt::Display for OsError {
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub(crate) enum OwnedWindowHandle {
#[cfg(x11_platform)]
X(x11rb::protocol::xproto::Window),
#[cfg(wayland_platform)]
Wayland,
}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
// TODO: Do we need to do something extra to extend the lifetime of
// the window lives beyond the passed-in handle?
match handle.as_raw() {
#[cfg(x11_platform)]
rwh_06::RawWindowHandle::Xlib(handle) => {
Self::X(handle.window as x11rb::protocol::xproto::Window)
}
#[cfg(x11_platform)]
rwh_06::RawWindowHandle::Xcb(handle) => Self::X(handle.window.get()),
#[cfg(wayland_platform)]
rwh_06::RawWindowHandle::Wayland(_handle) => {
// Wayland does not currently support parent windows, but it
// could support owned handles.
Self::Wayland
}
#[cfg(not(x11_platform))]
handle => panic!("invalid window handle {handle:?} on Wayland"),
#[cfg(not(wayland_platform))]
handle => panic!("invalid window handle {handle:?} on X11"),
#[cfg(all(x11_platform, wayland_platform))]
handle => panic!("invalid window handle {handle:?} on X11 or Wayland"),
}
}
}
pub(crate) enum Window {
#[cfg(x11_platform)]
X(x11::Window),
@@ -424,6 +463,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
@@ -784,7 +828,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 => EventLoop::new_x11_any_thread().map_err(Into::into),
Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
}
}
@@ -794,10 +838,10 @@ impl<T: 'static> EventLoop<T> {
}
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
Err(err) => return Err(err.clone()),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -918,10 +962,6 @@ 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())
}

View File

@@ -16,7 +16,7 @@ use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, QueueHandle};
use crate::dpi::LogicalSize;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{
@@ -34,7 +34,7 @@ use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState;
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
use super::{DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
@@ -356,13 +356,15 @@ impl<T: 'static> EventLoop<T> {
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if compositor_update.scale_changed {
let (physical_size, scale_factor) = self.with_state(|state| {
if let Some(scale_factor) = compositor_update.scale_factor {
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 size = logical_to_physical_rounded(window.inner_size(), scale_factor);
(size, scale_factor)
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
@@ -384,32 +386,30 @@ 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();
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;
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
// NOTE: Rescale changed the physical size which winit operates in, thus we should
// resize.
if compositor_update.resized || compositor_update.scale_changed {
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
let physical_size = logical_to_physical_rounded(size, scale_factor);
// TODO could probably bring back size reporting optimization.
// Mark the window as needed a redraw.
state
@@ -420,7 +420,7 @@ impl<T: 'static> EventLoop<T> {
.redraw_requested
.store(true, Ordering::Relaxed);
size
physical_size
});
callback(
@@ -467,44 +467,44 @@ impl<T: 'static> EventLoop<T> {
});
for window_id in window_ids.drain(..) {
let event = self.with_state(|state| {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
return Some(WindowEvent::Destroyed);
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
}
}
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 let Some(event) = event {
if request_redraw {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event,
event: WindowEvent::RedrawRequested,
},
&self.window_target,
);
@@ -629,34 +629,6 @@ 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) {}
@@ -684,3 +656,10 @@ 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,7 +9,6 @@ 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};
@@ -77,10 +76,3 @@ 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,6 +4,7 @@ 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;
@@ -23,6 +24,30 @@ 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

@@ -19,22 +19,25 @@ use sctk::seat::SeatState;
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
use sctk::shell::xdg::XdgShell;
use sctk::shell::WaylandSurface;
use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::output::MonitorHandle;
use crate::platform_impl::wayland::seat::{
use crate::dpi::LogicalSize;
use crate::platform_impl::OsError;
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
};
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;
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};
/// Winit's Wayland state.
pub struct WinitState {
@@ -56,6 +59,9 @@ pub struct WinitState {
/// The shm for software buffers, such as cursors.
pub shm: Shm,
/// The pool where custom cursors are allocated.
pub custom_cursor_pool: Arc<Mutex<SlotPool>>,
/// The XDG shell that is used for widnows.
pub xdg_shell: XdgShell,
@@ -151,13 +157,17 @@ impl WinitState {
(None, None)
};
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
Ok(Self {
registry_state,
compositor_state: Arc::new(compositor_state),
subcompositor_state: subcompositor_state.map(Arc::new),
output_state,
seat_state,
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
shm,
custom_cursor_pool,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
@@ -217,7 +227,7 @@ impl WinitState {
// Update the scale factor right away.
window.lock().unwrap().set_scale_factor(scale_factor);
self.window_compositor_updates[pos].scale_changed = true;
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
} 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() {
@@ -281,7 +291,9 @@ impl WindowHandler for WinitState {
};
// Populate the configure to the window.
self.window_compositor_updates[pos].resized |= self
//
// XXX the size on the window will be updated right before dispatching the size to the user.
let new_size = self
.windows
.get_mut()
.get_mut(&window_id)
@@ -294,6 +306,12 @@ impl WindowHandler for WinitState {
&self.subcompositor_state,
&mut self.events_sink,
);
// NOTE: Only update when the value is `Some` to not override consequent configures with
// the same sizes.
if new_size.is_some() {
self.window_compositor_updates[pos].size = new_size;
}
}
}
@@ -387,10 +405,10 @@ pub struct WindowCompositorUpdate {
pub window_id: WindowId,
/// New window size.
pub resized: bool,
pub size: Option<LogicalSize<u32>>,
/// New scale factor.
pub scale_changed: bool,
pub scale_factor: Option<f64>,
/// Close the window.
pub close_window: bool,
@@ -400,8 +418,8 @@ impl WindowCompositorUpdate {
fn new(window_id: WindowId) -> Self {
Self {
window_id,
resized: false,
scale_changed: false,
size: None,
scale_factor: None,
close_window: false,
}
}

View File

@@ -0,0 +1,56 @@
use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool};
use crate::cursor::CursorImage;
#[derive(Debug)]
pub enum SelectedCursor {
Named(CursorIcon),
Custom(CustomCursor),
}
impl Default for SelectedCursor {
fn default() -> Self {
Self::Named(Default::default())
}
}
#[derive(Debug)]
pub struct CustomCursor {
pub buffer: Buffer,
pub w: i32,
pub h: i32,
pub hotspot_x: i32,
pub hotspot_y: i32,
}
impl CustomCursor {
pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
let (buffer, canvas) = pool
.create_buffer(
image.width as i32,
image.height as i32,
4 * (image.width as i32),
Format::Argb8888,
)
.unwrap();
for (canvas_chunk, rgba_chunk) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4))
{
canvas_chunk[0] = rgba_chunk[2];
canvas_chunk[1] = rgba_chunk[1];
canvas_chunk[2] = rgba_chunk[0];
canvas_chunk[3] = rgba_chunk[3];
}
CustomCursor {
buffer,
w: image.width as i32,
h: image.height as i32,
hotspot_x: image.hotspot_x as i32,
hotspot_y: image.hotspot_y as i32,
}
}
}

View File

@@ -1,5 +1,6 @@
//! Wayland protocol implementation boilerplate.
pub mod cursor;
pub mod kwin_blur;
pub mod wp_fractional_scaling;
pub mod wp_viewporter;

View File

@@ -20,8 +20,8 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -280,7 +280,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();
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
window_state.inner_size().to_physical(scale_factor)
}
#[inline]
@@ -308,7 +308,7 @@ impl Window {
pub fn outer_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
window_state.outer_size().to_physical(scale_factor)
}
#[inline]
@@ -506,6 +506,14 @@ impl Window {
self.window_state.lock().unwrap().set_cursor(cursor);
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
self.window_state
.lock()
.unwrap()
.set_custom_cursor(&cursor.0);
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window_state

View File

@@ -1,7 +1,7 @@
//! The state of the window, which is shared with the event-loop.
use std::num::NonZeroU32;
use std::sync::{Arc, Weak};
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
use log::{info, warn};
@@ -18,21 +18,24 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
use sctk::compositor::{CompositorState, Region};
use sctk::seat::pointer::ThemedPointer;
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
use sctk::shell::xdg::XdgSurface;
use sctk::shell::WaylandSurface;
use sctk::shm::slot::SlotPool;
use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CursorImage;
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::types::cursor::{CustomCursor, SelectedCursor};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::{logical_to_physical_rounded, make_wid};
use crate::platform_impl::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
@@ -60,14 +63,16 @@ pub struct WindowState {
/// The `Shm` to set cursor.
pub shm: WlShm,
// A shared pool where to allocate custom cursors.
custom_cursor_pool: Arc<Mutex<SlotPool>>,
/// The last received configure.
pub last_configure: Option<WindowConfigure>,
/// The pointers observed on the window.
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
/// Cursor icon.
pub cursor_icon: CursorIcon,
selected_cursor: SelectedCursor,
/// Wether the cursor is visible.
pub cursor_visible: bool,
@@ -178,7 +183,7 @@ impl WindowState {
connection,
csd_fails: false,
cursor_grab_mode: GrabState::new(),
cursor_icon: CursorIcon::Default,
selected_cursor: Default::default(),
cursor_visible: true,
decorate: true,
fractional_scale,
@@ -197,6 +202,7 @@ impl WindowState {
resizable: true,
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
size: initial_size.to_logical(1.),
stateless_size: initial_size.to_logical(1.),
initial_size: Some(initial_size),
@@ -256,7 +262,7 @@ impl WindowState {
shm: &Shm,
subcompositor: &Option<Arc<SubcompositorState>>,
event_sink: &mut EventSink,
) -> bool {
) -> Option<LogicalSize<u32>> {
// 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.
@@ -368,9 +374,9 @@ impl WindowState {
if state_change_requires_resize || new_size != self.inner_size() {
self.resize(new_size);
true
Some(new_size)
} else {
false
None
}
}
@@ -616,7 +622,10 @@ impl WindowState {
/// Reload the cursor style on the given window.
pub fn reload_cursor_style(&mut self) {
if self.cursor_visible {
self.set_cursor(self.cursor_icon);
match &self.selected_cursor {
SelectedCursor::Named(icon) => self.set_cursor(*icon),
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
}
} else {
self.set_cursor_visible(self.cursor_visible);
}
@@ -647,7 +656,7 @@ impl WindowState {
self.resize(inner_size.to_logical(self.scale_factor()))
}
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
self.inner_size().to_physical(self.scale_factor())
}
/// Resize the window to the new inner size.
@@ -702,10 +711,8 @@ impl WindowState {
}
/// Set the cursor icon.
///
/// Providing `None` will hide the cursor.
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
self.cursor_icon = cursor_icon;
self.selected_cursor = SelectedCursor::Named(cursor_icon);
if !self.cursor_visible {
return;
@@ -718,6 +725,54 @@ impl WindowState {
})
}
/// Set the custom cursor icon.
pub(crate) fn set_custom_cursor(&mut self, cursor: &CursorImage) {
let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, cursor)
};
if self.cursor_visible {
self.apply_custom_cursor(&cursor);
}
self.selected_cursor = SelectedCursor::Custom(cursor);
}
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_poiner(|pointer, _| {
let surface = pointer.surface();
let scale = surface
.data::<SurfaceData>()
.unwrap()
.surface_data()
.scale_factor();
surface.set_buffer_scale(scale);
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
if surface.version() >= 4 {
surface.damage_buffer(0, 0, cursor.w, cursor.h);
} else {
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
}
surface.commit();
let serial = pointer
.pointer()
.data::<WinitPointerData>()
.and_then(|data| data.pointer_data().latest_enter_serial())
.unwrap();
pointer.pointer().set_cursor(
serial,
Some(surface),
cursor.hotspot_x / scale,
cursor.hotspot_y / scale,
);
});
}
/// Set maximum inner window size.
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
// Ensure that the window has the right minimum size.
@@ -852,7 +907,10 @@ impl WindowState {
self.cursor_visible = cursor_visible;
if self.cursor_visible {
self.set_cursor(self.cursor_icon);
match &self.selected_cursor {
SelectedCursor::Named(icon) => self.set_cursor(*icon),
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
}
} else {
for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();

View File

@@ -1,5 +1,5 @@
use std::{
cell::{Cell, RefCell},
cell::RefCell,
collections::HashMap,
os::raw::{c_char, c_int, c_long, c_ulong},
rc::Rc,
@@ -56,8 +56,6 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) first_touch: Option<u64>,
// Currently focused window belonging to this process
pub(super) active_window: Option<xproto::Window>,
/// Latest modifiers we've sent for the user to trigger change in event.
pub(super) modifiers: Cell<ModifiersState>,
pub(super) is_composing: bool,
}
@@ -996,7 +994,12 @@ impl<T: 'static> EventProcessor<T> {
let modifiers: crate::keyboard::ModifiersState =
self.kb_state.mods_state().into();
self.send_modifiers(modifiers, &mut callback);
if !modifiers.is_empty() {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(modifiers.into()),
});
}
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
@@ -1058,7 +1061,12 @@ impl<T: 'static> EventProcessor<T> {
// window regains focus.
self.held_key_press = None;
self.send_modifiers(ModifiersState::empty(), &mut callback);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(
ModifiersState::empty().into(),
),
});
if let Some(window) = self.with_window(window, Arc::clone) {
window.shared_state_lock().has_focus = false;
@@ -1272,14 +1280,8 @@ impl<T: 'static> EventProcessor<T> {
&& (keycodes_changed || geometry_changed)
{
unsafe { self.kb_state.init_with_x11_keymap() };
let modifiers = self.kb_state.mods_state();
self.send_modifiers(modifiers.into(), &mut callback);
}
}
ffi::XkbMapNotify => {
unsafe { self.kb_state.init_with_x11_keymap() };
self.send_modifiers(self.kb_state.mods_state().into(), &mut callback);
}
ffi::XkbStateNotify => {
let xev =
unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) };
@@ -1287,9 +1289,7 @@ impl<T: 'static> EventProcessor<T> {
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
// NOTE: Modifiers could update without a prior event updating them,
// thus diffing the state before and after is not reliable.
let prev_mods = self.kb_state.mods_state();
self.kb_state.update_modifiers(
xev.base_mods,
xev.latched_mods,
@@ -1298,8 +1298,17 @@ impl<T: 'static> EventProcessor<T> {
xev.latched_group as u32,
xev.locked_group as u32,
);
self.send_modifiers(self.kb_state.mods_state().into(), &mut callback);
let new_mods = self.kb_state.mods_state();
if prev_mods != new_mods {
if let Some(window) = self.active_window {
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::ModifiersChanged(
Into::<ModifiersState>::into(new_mods).into(),
),
});
}
}
}
_ => {}
}
@@ -1311,7 +1320,7 @@ impl<T: 'static> EventProcessor<T> {
}
// Handle IME requests.
while let Ok(request) = self.ime_receiver.try_recv() {
if let Ok(request) = self.ime_receiver.try_recv() {
let mut ime = wt.ime.borrow_mut();
match request {
ImeRequest::Position(window_id, x, y) => {
@@ -1323,64 +1332,48 @@ impl<T: 'static> EventProcessor<T> {
}
}
// Drain IME events.
while let Ok((window, event)) = self.ime_event_receiver.try_recv() {
let window_id = mkwid(window as xproto::Window);
match event {
ImeEvent::Enabled => {
let (window, event) = match self.ime_event_receiver.try_recv() {
Ok((window, event)) => (window as xproto::Window, event),
Err(_) => return,
};
match event {
ImeEvent::Enabled => {
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Enabled),
});
}
ImeEvent::Start => {
self.is_composing = true;
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)),
});
}
ImeEvent::Update(text, position) => {
if self.is_composing {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Ime(Ime::Enabled),
});
}
ImeEvent::Start => {
self.is_composing = true;
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)),
});
}
ImeEvent::Update(text, position) => {
if self.is_composing {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))),
});
}
}
ImeEvent::End => {
self.is_composing = false;
// Issue empty preedit on `Done`.
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
});
}
ImeEvent::Disabled => {
self.is_composing = false;
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Ime(Ime::Disabled),
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))),
});
}
}
}
}
/// Send modifiers for the active window.
///
/// The event won't be send when the `modifiers` match the previosly `sent` modifiers value.
fn send_modifiers<F: FnMut(Event<T>)>(&self, modifiers: ModifiersState, callback: &mut F) {
let window_id = match self.active_window {
Some(window) => mkwid(window),
None => return,
};
if self.modifiers.replace(modifiers) != modifiers {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(self.modifiers.get().into()),
});
ImeEvent::End => {
self.is_composing = false;
// Issue empty preedit on `Done`.
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
});
}
ImeEvent::Disabled => {
self.is_composing = false;
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Disabled),
});
}
}
}
@@ -1420,9 +1413,6 @@ impl<T: 'static> EventProcessor<T> {
F: FnMut(Event<T>),
{
let wt = get_xtarget(&self.target);
wt.xconn
.reload_database()
.expect("failed to reload Xft database");
// In the future, it would be quite easy to emit monitor hotplug events.
let prev_list = {

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(#[allow(dead_code)] Box<PotentialInputMethods>),
ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError),
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
MethodOpenFailed(Box<PotentialInputMethods>),
ContextCreationFailed(ImeContextCreationError),
SetDestroyCallbackFailed(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(#[allow(dead_code)] XError),
GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
InvalidUtf8(#[allow(dead_code)] IntoStringError),
XError(XError),
GetPropertyError(util::GetPropertyError),
InvalidUtf8(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(#[allow(dead_code)] XError),
SetDestroyCallbackFailed(XError),
}
pub(crate) struct Ime {

View File

@@ -346,7 +346,6 @@ impl<T: 'static> EventLoop<T> {
held_key_press: None,
first_touch: None,
active_window: None,
modifiers: Default::default(),
is_composing: false,
};
@@ -365,9 +364,7 @@ impl<T: 'static> EventLoop<T> {
.xconn
.select_xkb_events(
0x100, // Use the "core keyboard device"
xkb::EventType::NEW_KEYBOARD_NOTIFY
| xkb::EventType::MAP_NOTIFY
| xkb::EventType::STATE_NOTIFY,
xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
)
.unwrap();
@@ -755,10 +752,6 @@ 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()
}

View File

@@ -234,8 +234,7 @@ 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, self.randr_version())?;
let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
// Pipeline all of the get-crtc requests.
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
@@ -285,16 +284,22 @@ impl XConnection {
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
let mut monitors_lock = self.monitor_handles.lock().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)
}
}
(*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()
}
#[inline]
@@ -344,9 +349,10 @@ 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> {
if (major_version == 1 && minor_version >= 3) || major_version > 1 {
let version = conn.randr_query_version(0, 0)?.reply()?;
if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 {
let reply = conn
.randr_get_screen_resources_current(root.root)?
.reply()?;

View File

@@ -1,9 +1,8 @@
use std::ffi::CString;
use std::iter;
use std::{ffi::CString, iter, slice, sync::Arc};
use x11rb::connection::Connection;
use crate::window::CursorIcon;
use crate::{cursor::CursorImage, window::CursorIcon};
use super::*;
@@ -20,6 +19,11 @@ impl XConnection {
.expect("Failed to set cursor");
}
pub(crate) fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
self.update_cursor(window, cursor.inner.cursor)
.expect("Failed to set cursor");
}
fn create_empty_cursor(&self) -> ffi::Cursor {
let data = 0;
let pixmap = unsafe {
@@ -87,3 +91,74 @@ impl XConnection {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelectedCursor {
Custom(CustomCursor),
Named(CursorIcon),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomCursor {
inner: Arc<CustomCursorInner>,
}
impl CustomCursor {
pub(crate) unsafe fn new(xconn: &Arc<XConnection>, image: &CursorImage) -> Self {
unsafe {
let ximage =
(xconn.xcursor.XcursorImageCreate)(image.width as i32, image.height as i32);
if ximage.is_null() {
panic!("failed to allocate cursor image");
}
(*ximage).xhot = image.hotspot_x as u32;
(*ximage).yhot = image.hotspot_y as u32;
(*ximage).delay = 0;
let dst = slice::from_raw_parts_mut((*ximage).pixels, image.rgba.len() / 4);
for (dst, chunk) in dst.iter_mut().zip(image.rgba.chunks_exact(4)) {
*dst = (chunk[0] as u32) << 16
| (chunk[1] as u32) << 8
| (chunk[2] as u32)
| (chunk[3] as u32) << 24;
}
let cursor = (xconn.xcursor.XcursorImageLoadCursor)(xconn.display, ximage);
(xconn.xcursor.XcursorImageDestroy)(ximage);
Self {
inner: Arc::new(CustomCursorInner {
xconn: xconn.clone(),
cursor,
}),
}
}
}
}
#[derive(Debug)]
struct CustomCursorInner {
xconn: Arc<XConnection>,
cursor: ffi::Cursor,
}
impl Drop for CustomCursorInner {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor);
}
}
}
impl PartialEq for CustomCursorInner {
fn eq(&self, other: &Self) -> bool {
self.cursor == other.cursor
}
}
impl Eq for CustomCursorInner {}
impl Default for SelectedCursor {
fn default() -> Self {
SelectedCursor::Named(Default::default())
}
}

View File

@@ -13,7 +13,7 @@ mod randr;
mod window_property;
mod wm;
pub use self::{geometry::*, hint::*, input::*, window_property::*, wm::*};
pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm::*};
use std::{
mem::{self, MaybeUninit},

View File

@@ -7,9 +7,10 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};
use cursor_icon::CursorIcon;
use x11rb::{
connection::Connection,
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification},
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
protocol::{
randr,
shape::SK,
@@ -29,17 +30,20 @@ use crate::{
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
X11Error,
},
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
OwnedWindowHandle as PlatformOwnedWindowHandle,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowLevel,
},
};
use super::{
ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
ffi,
util::{self, CustomCursor, SelectedCursor},
CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
XConnection,
};
@@ -126,7 +130,7 @@ pub(crate) struct UnownedWindow {
root: xproto::Window, // never changes
#[allow(dead_code)]
screen_id: i32, // never changes
cursor: Mutex<CursorIcon>,
selected_cursor: Mutex<SelectedCursor>,
cursor_grabbed_mode: Mutex<CursorGrabMode>,
#[allow(clippy::mutex_atomic)]
cursor_visible: Mutex<bool>,
@@ -154,15 +158,12 @@ impl UnownedWindow {
) -> Result<UnownedWindow, RootOsError> {
let xconn = &event_loop.xconn;
let atoms = xconn.atoms();
#[cfg(feature = "rwh_06")]
let root = match window_attrs.parent_window.0 {
Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
let root = match window_attrs.parent_window {
Some(PlatformOwnedWindowHandle::X(handle)) => handle,
#[cfg(wayland_platform)]
Some(handle) => panic!("invalid window handle {handle:?} on X11"),
None => event_loop.root,
};
#[cfg(not(feature = "rwh_06"))]
let root = event_loop.root;
let mut monitors = leap!(xconn.available_monitors());
let guessed_monitor = if monitors.is_empty() {
@@ -355,7 +356,7 @@ impl UnownedWindow {
visual,
root,
screen_id,
cursor: Default::default(),
selected_cursor: Default::default(),
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
@@ -987,7 +988,7 @@ impl UnownedWindow {
xproto::EventMask::SUBSTRUCTURE_REDIRECT
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
),
[3u32, 0, 0, 0, 0],
[WmHintsState::Iconic as u32, 0, 0, 0, 0],
)
} else {
self.xconn.send_client_msg(
@@ -1535,13 +1536,29 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let old_cursor = replace(&mut *self.cursor.lock().unwrap(), cursor);
let old_cursor = replace(
&mut *self.selected_cursor.lock().unwrap(),
SelectedCursor::Named(cursor),
);
#[allow(clippy::mutex_atomic)]
if cursor != old_cursor && *self.cursor_visible.lock().unwrap() {
if SelectedCursor::Named(cursor) != old_cursor && *self.cursor_visible.lock().unwrap() {
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
}
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.0) };
#[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() {
self.xconn.set_custom_cursor(self.xwindow, &new_cursor);
}
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(new_cursor);
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
@@ -1628,13 +1645,23 @@ impl UnownedWindow {
return;
}
let cursor = if visible {
Some(*self.cursor.lock().unwrap())
Some((*self.selected_cursor.lock().unwrap()).clone())
} else {
None
};
*visible_lock = visible;
drop(visible_lock);
self.xconn.set_cursor_icon(self.xwindow, cursor);
match cursor {
Some(SelectedCursor::Custom(cursor)) => {
self.xconn.set_custom_cursor(self.xwindow, &cursor);
}
Some(SelectedCursor::Named(cursor)) => {
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
}
None => {
self.xconn.set_cursor_icon(self.xwindow, None);
}
}
}
#[inline]

View File

@@ -4,19 +4,14 @@ use std::{
fmt, ptr,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex, RwLock, RwLockReadGuard,
Arc, Mutex,
},
};
use crate::window::CursorIcon;
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
use x11rb::{
connection::Connection,
protocol::{randr::ConnectionExt as _, xproto},
resource_manager,
xcb_ffi::XCBConnection,
};
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
/// A connection to an X server.
pub(crate) struct XConnection {
@@ -50,10 +45,7 @@ pub(crate) struct XConnection {
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
/// The resource database.
database: RwLock<resource_manager::Database>,
/// RandR version.
randr_version: (u32, u32),
database: resource_manager::Database,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
@@ -112,13 +104,6 @@ impl XConnection {
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");
Ok(XConnection {
xlib,
xcursor,
@@ -130,9 +115,8 @@ impl XConnection {
timestamp: AtomicU32::new(0),
latest_error: Mutex::new(None),
monitor_handles: Mutex::new(None),
database: RwLock::new(database),
database,
cursor_cache: Default::default(),
randr_version: (randr_version.major_version, randr_version.minor_version),
})
}
@@ -147,11 +131,6 @@ 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 {
@@ -180,16 +159,8 @@ impl XConnection {
/// Get the resource database.
#[inline]
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(())
pub fn database(&self) -> &resource_manager::Database {
&self.database
}
/// Get the latest timestamp.

View File

@@ -1,23 +1,30 @@
#![allow(clippy::unnecessary_cast)]
use icrate::AppKit::{
NSApplication, NSEvent, NSEventModifierFlagCommand, NSEventTypeKeyUp, NSEventTypeLeftMouseDown,
NSEventTypeLeftMouseDragged, NSEventTypeLeftMouseUp, NSEventTypeMouseMoved,
NSEventTypeOtherMouseDown, NSEventTypeOtherMouseDragged, NSEventTypeOtherMouseUp,
NSEventTypeRightMouseDown, NSEventTypeRightMouseDragged, NSEventTypeRightMouseUp, NSResponder,
};
use icrate::Foundation::NSObject;
use objc2::{declare_class, msg_send, mutability, ClassType};
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use super::event::flags_contains;
use super::{app_state::AppState, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(super) struct WinitApplication;
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
impl DeclaredClass for WinitApplication {}
unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
@@ -27,13 +34,13 @@ declare_class!(
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = event.type_();
let modifier_flags = event.modifierFlags();
if event_type == NSEventType::NSKeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask)
let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventTypeKeyUp
&& flags_contains(modifier_flags, NSEventModifierFlagCommand)
{
if let Some(key_window) = self.keyWindow() {
unsafe { key_window.sendEvent(event) };
key_window.sendEvent(event);
}
} else {
maybe_dispatch_device_event(event);
@@ -44,14 +51,15 @@ declare_class!(
);
fn maybe_dispatch_device_event(event: &NSEvent) {
let event_type = event.type_();
let event_type = unsafe { event.r#type() };
#[allow(non_upper_case_globals)]
match event_type {
NSEventType::NSMouseMoved
| NSEventType::NSLeftMouseDragged
| NSEventType::NSOtherMouseDragged
| NSEventType::NSRightMouseDragged => {
let delta_x = event.deltaX() as f64;
let delta_y = event.deltaY() as f64;
NSEventTypeMouseMoved
| NSEventTypeLeftMouseDragged
| NSEventTypeOtherMouseDragged
| NSEventTypeRightMouseDragged => {
let delta_x = unsafe { event.deltaX() } as f64;
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 {
queue_device_event(DeviceEvent::Motion {
@@ -73,17 +81,15 @@ fn maybe_dispatch_device_event(event: &NSEvent) {
});
}
}
NSEventType::NSLeftMouseDown
| NSEventType::NSRightMouseDown
| NSEventType::NSOtherMouseDown => {
NSEventTypeLeftMouseDown | NSEventTypeRightMouseDown | NSEventTypeOtherMouseDown => {
queue_device_event(DeviceEvent::Button {
button: event.buttonNumber() as u32,
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Pressed,
});
}
NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => {
NSEventTypeLeftMouseUp | NSEventTypeRightMouseUp | NSEventTypeOtherMouseUp => {
queue_device_event(DeviceEvent::Button {
button: event.buttonNumber() as u32,
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Released,
});
}

View File

@@ -1,54 +1,41 @@
use std::ptr::NonNull;
use icrate::Foundation::NSObject;
use objc2::declare::{IvarBool, IvarEncode};
use icrate::AppKit::{NSApplicationActivationPolicy, NSApplicationDelegate};
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use super::app_state::AppState;
use super::appkit::NSApplicationActivationPolicy;
#[derive(Debug)]
pub(super) struct State {
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
}
declare_class!(
#[derive(Debug)]
pub(super) struct ApplicationDelegate {
activation_policy: IvarEncode<NSApplicationActivationPolicy, "_activation_policy">,
default_menu: IvarBool<"_default_menu">,
activate_ignoring_other_apps: IvarBool<"_activate_ignoring_other_apps">,
}
mod ivars;
pub(super) struct ApplicationDelegate;
unsafe impl ClassType for ApplicationDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplicationDelegate";
}
unsafe impl ApplicationDelegate {
#[method(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)]
unsafe fn init(
this: *mut Self,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
this.map(|this| {
*this.activation_policy = activation_policy;
*this.default_menu = default_menu;
*this.activate_ignoring_other_apps = activate_ignoring_other_apps;
NonNull::from(this)
})
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = State;
}
unsafe impl NSObjectProtocol for ApplicationDelegate {}
unsafe impl NSApplicationDelegate for ApplicationDelegate {
#[method(applicationDidFinishLaunching:)]
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationDidFinishLaunching:");
AppState::launched(
*self.activation_policy,
*self.default_menu,
*self.activate_ignoring_other_apps,
self.ivars().activation_policy,
self.ivars().default_menu,
self.ivars().activate_ignoring_other_apps,
);
}
@@ -63,17 +50,16 @@ declare_class!(
impl ApplicationDelegate {
pub(super) fn new(
mtm: MainThreadMarker,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Id<Self> {
unsafe {
msg_send_id![
Self::alloc(),
initWithActivationPolicy: activation_policy,
defaultMenu: default_menu,
activateIgnoringOtherApps: activate_ignoring_other_apps,
]
}
let this = mtm.alloc().set_ivars(State {
activation_policy,
default_menu,
activate_ignoring_other_apps,
});
unsafe { msg_send_id![super(this), init] }
}
}

View File

@@ -12,13 +12,14 @@ use std::{
};
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
use icrate::Foundation::{is_main_thread, NSSize};
use icrate::AppKit::{NSApplication, NSApplicationActivationPolicy};
use icrate::Foundation::{is_main_thread, MainThreadMarker, NSSize};
use objc2::rc::{autoreleasepool, Id};
use once_cell::sync::Lazy;
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
use super::{
event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow,
event::dummy_event, event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never,
window::WinitWindow,
};
use crate::{
dpi::PhysicalSize,
@@ -57,14 +58,14 @@ impl<T> EventLoopHandler<T> {
where
F: FnOnce(&mut EventLoopHandler<T>, RefMut<'_, dyn FnMut(Event<T>, &RootWindowTarget<T>)>),
{
// The `NSApp` and our `HANDLER` are global state and so it's possible that
// we could get a delegate callback after the application has exit an
// `NSApplication` and our `HANDLER` are global state and so it's possible
// that we could get a delegate callback after the application has exit an
// `EventLoop`. If the loop has been exit then our weak `self.callback`
// will fail to upgrade.
//
// We don't want to panic or output any verbose logging if we fail to
// upgrade the weak reference since it might be valid that the application
// re-starts the `NSApp` after exiting a Winit `EventLoop`
// re-starts the `NSApplication` after exiting a Winit `EventLoop`
if let Some(callback) = self.callback.upgrade() {
let callback = callback.borrow_mut();
(f)(self, callback);
@@ -144,9 +145,9 @@ impl Handler {
/// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called
///
/// NB: This is global / `NSApp` state and since the app will only be launched
/// once but an `EventLoop` may be run more than once then only the first
/// `EventLoop` will observe the `NSApp` before it is launched.
/// NB: This is global / `NSApplication` state and since the app will only
/// be launched once but an `EventLoop` may be run more than once then only
/// the first `EventLoop` will observe the application before it is launched.
fn is_launched(&self) -> bool {
self.launched.load(Ordering::Acquire)
}
@@ -158,8 +159,8 @@ impl Handler {
/// `true` if an `EventLoop` is currently running
///
/// NB: This is global / `NSApp` state and may persist beyond the lifetime of
/// a running `EventLoop`.
/// NB: This is global / `NSApplication` state and may persist beyond the
/// lifetime of a running `EventLoop`.
///
/// # Caveat
/// This is only intended to be called from the main thread
@@ -167,7 +168,7 @@ impl Handler {
self.running.load(Ordering::Relaxed)
}
/// Set when an `EventLoop` starts running, after the `NSApp` is launched
/// Set when an `EventLoop` starts running, after the `NSApplication` is launched
///
/// # Caveat
/// This is only intended to be called from the main thread
@@ -180,8 +181,8 @@ impl Handler {
/// Since an `EventLoop` may be run more than once we need make sure to reset the
/// `control_flow` state back to `Poll` each time the loop exits.
///
/// Note: that if the `NSApp` has been launched then that state is preserved, and we won't
/// need to re-launch the app if subsequent EventLoops are run.
/// Note: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
///
/// # Caveat
/// This is only intended to be called from the main thread
@@ -209,10 +210,6 @@ 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)
}
@@ -396,7 +393,7 @@ impl AppState {
}
// If `pump_events` is called to progress the event loop then we bootstrap the event
// loop via `[NSApp run]` but will use `CFRunLoopRunInMode` for subsequent calls to
// loop via `-[NSAppplication run]` but will use `CFRunLoopRunInMode` for subsequent calls to
// `pump_events`
pub fn request_stop_on_launch() {
HANDLER.request_stop_app_on_launch();
@@ -438,10 +435,6 @@ impl AppState {
HANDLER.exit()
}
pub fn clear_exit() {
HANDLER.clear_exit()
}
pub fn exiting() -> bool {
HANDLER.exiting()
}
@@ -467,13 +460,15 @@ impl AppState {
create_default_menu: bool,
activate_ignoring_other_apps: bool,
) {
let app = NSApp();
let mtm = MainThreadMarker::new().unwrap();
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(activation_policy);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(activate_ignoring_other_apps);
HANDLER.set_launched();
@@ -481,21 +476,22 @@ impl AppState {
if create_default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize();
menu::initialize(&app);
}
Self::start_running();
// If the `NSApp` is being launched via `EventLoop::pump_events()` then we'll
// If the application is being launched via `EventLoop::pump_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if HANDLER.should_stop_app_on_launch() {
// Note: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`[NSApp run]` effectively
// ignored the attempt to stop the RunLoop and re-started it.). So we
// return from `pump_events` by stopping the `NSApp`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
Self::stop();
}
}
@@ -597,11 +593,12 @@ impl AppState {
}
pub fn stop() {
let app = NSApp();
let mtm = MainThreadMarker::new().unwrap();
let app = NSApplication::sharedApplication(mtm);
autoreleasepool(|_| {
app.stop(None);
// To stop event loop immediately, we need to post some event here.
app.postEvent_atStart(&NSEvent::dummy(), true);
app.postEvent_atStart(&dummy_event().unwrap(), true);
});
}

View File

@@ -1,28 +0,0 @@
use icrate::Foundation::{NSArray, NSObject, NSString};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSAppearance;
unsafe impl ClassType for NSAppearance {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
type NSAppearanceName = NSString;
extern_methods!(
unsafe impl NSAppearance {
#[method_id(appearanceNamed:)]
pub fn appearanceNamed(name: &NSAppearanceName) -> Id<Self>;
#[method_id(bestMatchFromAppearancesWithNames:)]
pub fn bestMatchFromAppearancesWithNames(
&self,
appearances: &NSArray<NSAppearanceName>,
) -> Id<NSAppearanceName>;
}
);

View File

@@ -1,140 +0,0 @@
use icrate::Foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use objc2::{Encode, Encoding};
use super::{NSAppearance, NSEvent, NSMenu, NSResponder, NSWindow};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSApplication;
unsafe impl ClassType for NSApplication {
#[inherits(NSObject)]
type Super = NSResponder;
type Mutability = mutability::InteriorMutable;
}
);
pub(crate) fn NSApp() -> Id<NSApplication> {
// TODO: Only allow access from main thread
NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() })
}
extern_methods!(
unsafe impl NSApplication {
/// This can only be called on the main thread since it may initialize
/// the application and since it's parameters may be changed by the main
/// thread at any time (hence it is only safe to access on the main thread).
pub fn shared(_mtm: MainThreadMarker) -> Id<Self> {
let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] };
// SAFETY: `sharedApplication` always initializes the app if it isn't already
unsafe { app.unwrap_unchecked() }
}
#[method_id(currentEvent)]
pub fn currentEvent(&self) -> Option<Id<NSEvent>>;
#[method(postEvent:atStart:)]
pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool);
#[method(presentationOptions)]
pub fn presentationOptions(&self) -> NSApplicationPresentationOptions;
#[method_id(windows)]
pub fn windows(&self) -> Id<NSArray<NSWindow>>;
#[method_id(keyWindow)]
pub fn keyWindow(&self) -> Option<Id<NSWindow>>;
// TODO: NSApplicationDelegate
#[method(setDelegate:)]
pub fn setDelegate(&self, delegate: &AnyObject);
#[method(setPresentationOptions:)]
pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions);
#[method(hide:)]
pub fn hide(&self, sender: Option<&AnyObject>);
#[method(orderFrontCharacterPalette:)]
#[allow(dead_code)]
pub fn orderFrontCharacterPalette(&self, sender: Option<&AnyObject>);
#[method(hideOtherApplications:)]
pub fn hideOtherApplications(&self, sender: Option<&AnyObject>);
#[method(stop:)]
pub fn stop(&self, sender: Option<&AnyObject>);
#[method(activateIgnoringOtherApps:)]
pub fn activateIgnoringOtherApps(&self, ignore: bool);
#[method(requestUserAttention:)]
pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger;
#[method(setActivationPolicy:)]
pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool;
#[method(setMainMenu:)]
pub fn setMainMenu(&self, menu: &NSMenu);
#[method_id(effectiveAppearance)]
pub fn effectiveAppearance(&self) -> Id<NSAppearance>;
#[method(setAppearance:)]
pub fn setAppearance(&self, appearance: Option<&NSAppearance>);
#[method(run)]
pub unsafe fn run(&self);
}
);
#[allow(dead_code)]
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSApplicationActivationPolicy {
NSApplicationActivationPolicyRegular = 0,
NSApplicationActivationPolicyAccessory = 1,
NSApplicationActivationPolicyProhibited = 2,
NSApplicationActivationPolicyERROR = -1,
}
unsafe impl Encode for NSApplicationActivationPolicy {
const ENCODING: Encoding = NSInteger::ENCODING;
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct NSApplicationPresentationOptions: NSUInteger {
const NSApplicationPresentationDefault = 0;
const NSApplicationPresentationAutoHideDock = 1 << 0;
const NSApplicationPresentationHideDock = 1 << 1;
const NSApplicationPresentationAutoHideMenuBar = 1 << 2;
const NSApplicationPresentationHideMenuBar = 1 << 3;
const NSApplicationPresentationDisableAppleMenu = 1 << 4;
const NSApplicationPresentationDisableProcessSwitching = 1 << 5;
const NSApplicationPresentationDisableForceQuit = 1 << 6;
const NSApplicationPresentationDisableSessionTermination = 1 << 7;
const NSApplicationPresentationDisableHideApplication = 1 << 8;
const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9;
const NSApplicationPresentationFullScreen = 1 << 10;
const NSApplicationPresentationAutoHideToolbar = 1 << 11;
}
}
unsafe impl Encode for NSApplicationPresentationOptions {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSRequestUserAttentionType {
NSCriticalRequest = 0,
NSInformationalRequest = 10,
}
unsafe impl Encode for NSRequestUserAttentionType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -1,15 +0,0 @@
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
use super::{NSControl, NSResponder, NSView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSButton;
unsafe impl ClassType for NSButton {
#[inherits(NSView, NSResponder, NSObject)]
type Super = NSControl;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -1,28 +0,0 @@
use icrate::Foundation::NSObject;
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
/// An object that stores color data and sometimes opacity (alpha value).
///
/// <https://developer.apple.com/documentation/appkit/nscolor?language=objc>
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSColor;
unsafe impl ClassType for NSColor {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
// SAFETY: Documentation clearly states:
// > Color objects are immutable and thread-safe
unsafe impl Send for NSColor {}
unsafe impl Sync for NSColor {}
extern_methods!(
unsafe impl NSColor {
#[method_id(clearColor)]
pub fn clear() -> Id<Self>;
}
);

View File

@@ -1,25 +0,0 @@
use icrate::Foundation::NSObject;
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::{NSResponder, NSView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSControl;
unsafe impl ClassType for NSControl {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSControl {
#[method(setEnabled:)]
pub fn setEnabled(&self, enabled: bool);
#[method(isEnabled)]
pub fn isEnabled(&self) -> bool;
}
);

View File

@@ -1,241 +0,0 @@
use once_cell::sync::Lazy;
use icrate::ns_string;
use icrate::Foundation::{
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSString,
};
use objc2::rc::{DefaultId, Id};
use objc2::runtime::Sel;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
use super::NSImage;
use crate::window::CursorIcon;
extern_class!(
/// <https://developer.apple.com/documentation/appkit/nscursor?language=objc>
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSCursor;
unsafe impl ClassType for NSCursor {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
// SAFETY: NSCursor is immutable, stated here:
// https://developer.apple.com/documentation/appkit/nscursor/1527062-image?language=objc
unsafe impl Send for NSCursor {}
unsafe impl Sync for NSCursor {}
macro_rules! def_cursor {
{$(
$(#[$($m:meta)*])*
pub fn $name:ident();
)*} => {$(
$(#[$($m)*])*
pub fn $name() -> Id<Self> {
unsafe { msg_send_id![Self::class(), $name] }
}
)*};
}
macro_rules! def_undocumented_cursor {
{$(
$(#[$($m:meta)*])*
pub fn $name:ident();
)*} => {$(
$(#[$($m)*])*
pub fn $name() -> Id<Self> {
unsafe { Self::from_selector(sel!($name)).unwrap_or_else(|| Default::default()) }
}
)*};
}
extern_methods!(
/// Documented cursors
unsafe impl NSCursor {
def_cursor!(
pub fn arrowCursor();
pub fn pointingHandCursor();
pub fn openHandCursor();
pub fn closedHandCursor();
pub fn IBeamCursor();
pub fn IBeamCursorForVerticalLayout();
pub fn dragCopyCursor();
pub fn dragLinkCursor();
pub fn operationNotAllowedCursor();
pub fn contextualMenuCursor();
pub fn crosshairCursor();
pub fn resizeRightCursor();
pub fn resizeUpCursor();
pub fn resizeLeftCursor();
pub fn resizeDownCursor();
pub fn resizeLeftRightCursor();
pub fn resizeUpDownCursor();
);
// Creating cursors should be thread-safe, though using them for anything probably isn't.
pub fn new(image: &NSImage, hotSpot: NSPoint) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initWithImage: image, hotSpot: hotSpot] }
}
pub fn invisible() -> Id<Self> {
// 16x16 GIF data for invisible cursor
// You can reproduce this via ImageMagick.
// $ convert -size 16x16 xc:none cursor.gif
static CURSOR_BYTES: &[u8] = &[
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9,
0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
];
static CURSOR: Lazy<Id<NSCursor>> = Lazy::new(|| {
// TODO: Consider using `dataWithBytesNoCopy:`
let data = NSData::with_bytes(CURSOR_BYTES);
let image = NSImage::new_with_data(&data);
NSCursor::new(&image, NSPoint::new(0.0, 0.0))
});
CURSOR.clone()
}
}
/// Undocumented cursors
unsafe impl NSCursor {
#[method(respondsToSelector:)]
fn class_responds_to(sel: Sel) -> bool;
#[method_id(performSelector:)]
unsafe fn from_selector_unchecked(sel: Sel) -> Id<Self>;
unsafe fn from_selector(sel: Sel) -> Option<Id<Self>> {
if Self::class_responds_to(sel) {
Some(unsafe { Self::from_selector_unchecked(sel) })
} else {
warn!("Cursor `{:?}` appears to be invalid", sel);
None
}
}
def_undocumented_cursor!(
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
pub fn _helpCursor();
pub fn _zoomInCursor();
pub fn _zoomOutCursor();
pub fn _windowResizeNorthEastCursor();
pub fn _windowResizeNorthWestCursor();
pub fn _windowResizeSouthEastCursor();
pub fn _windowResizeSouthWestCursor();
pub fn _windowResizeNorthEastSouthWestCursor();
pub fn _windowResizeNorthWestSouthEastCursor();
// While these two are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// pub fn _moveCursor();
// pub fn _waitCursor();
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
pub fn busyButClickableCursor();
);
}
/// Webkit cursors
unsafe impl NSCursor {
// Note that loading `busybutclickable` with this code won't animate
// the frames; instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(name: &NSString) -> Id<Self> {
// Snatch a cursor from WebKit; They fit the style of the native
// cursors, and will seem completely standard to macOS users.
//
// https://stackoverflow.com/a/21786835/5435443
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors");
let cursor_path = root.stringByAppendingPathComponent(name);
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
let image = NSImage::new_by_referencing_file(&pdf_path);
// TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
x = unsafe { &*ptr }.as_cgfloat()
}
}
let mut y = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
y = unsafe { &*ptr }.as_cgfloat()
}
}
let hotspot = NSPoint::new(x, y);
Self::new(&image, hotspot)
}
pub fn moveCursor() -> Id<Self> {
unsafe { Self::load_webkit_cursor(ns_string!("move")) }
}
pub fn cellCursor() -> Id<Self> {
unsafe { Self::load_webkit_cursor(ns_string!("cell")) }
}
}
);
impl NSCursor {
pub fn from_icon(icon: CursorIcon) -> Id<Self> {
match icon {
CursorIcon::Default => Default::default(),
CursorIcon::Pointer => Self::pointingHandCursor(),
CursorIcon::Grab => Self::openHandCursor(),
CursorIcon::Grabbing => Self::closedHandCursor(),
CursorIcon::Text => Self::IBeamCursor(),
CursorIcon::VerticalText => Self::IBeamCursorForVerticalLayout(),
CursorIcon::Copy => Self::dragCopyCursor(),
CursorIcon::Alias => Self::dragLinkCursor(),
CursorIcon::NotAllowed | CursorIcon::NoDrop => Self::operationNotAllowedCursor(),
CursorIcon::ContextMenu => Self::contextualMenuCursor(),
CursorIcon::Crosshair => Self::crosshairCursor(),
CursorIcon::EResize => Self::resizeRightCursor(),
CursorIcon::NResize => Self::resizeUpCursor(),
CursorIcon::WResize => Self::resizeLeftCursor(),
CursorIcon::SResize => Self::resizeDownCursor(),
CursorIcon::EwResize | CursorIcon::ColResize => Self::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => Self::resizeUpDownCursor(),
CursorIcon::Help => Self::_helpCursor(),
CursorIcon::ZoomIn => Self::_zoomInCursor(),
CursorIcon::ZoomOut => Self::_zoomOutCursor(),
CursorIcon::NeResize => Self::_windowResizeNorthEastCursor(),
CursorIcon::NwResize => Self::_windowResizeNorthWestCursor(),
CursorIcon::SeResize => Self::_windowResizeSouthEastCursor(),
CursorIcon::SwResize => Self::_windowResizeSouthWestCursor(),
CursorIcon::NeswResize => Self::_windowResizeNorthEastSouthWestCursor(),
CursorIcon::NwseResize => Self::_windowResizeNorthWestSouthEastCursor(),
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => Self::busyButClickableCursor(),
CursorIcon::Move | CursorIcon::AllScroll => Self::moveCursor(),
CursorIcon::Cell => Self::cellCursor(),
_ => Default::default(),
}
}
}
impl DefaultId for NSCursor {
fn default_id() -> Id<Self> {
Self::arrowCursor()
}
}

View File

@@ -1,308 +0,0 @@
use std::os::raw::c_ushort;
use icrate::Foundation::{
CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger,
};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSEvent;
unsafe impl ClassType for NSEvent {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
// > Safely handled only on the same thread, whether that be the main thread
// > or a secondary thread; otherwise you run the risk of having events get
// > out of sequence.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123383>
extern_methods!(
unsafe impl NSEvent {
#[method_id(
otherEventWithType:
location:
modifierFlags:
timestamp:
windowNumber:
context:
subtype:
data1:
data2:
)]
unsafe fn otherEventWithType(
type_: NSEventType,
location: NSPoint,
flags: NSEventModifierFlags,
time: NSTimeInterval,
window_num: NSInteger,
context: Option<&NSObject>, // NSGraphicsContext
subtype: NSEventSubtype,
data1: NSInteger,
data2: NSInteger,
) -> Id<Self>;
pub fn dummy() -> Id<Self> {
unsafe {
Self::otherEventWithType(
NSEventType::NSApplicationDefined,
NSPoint::new(0.0, 0.0),
NSEventModifierFlags::empty(),
0.0,
0,
None,
NSEventSubtype::NSWindowExposedEventType,
0,
0,
)
}
}
#[method_id(
keyEventWithType:
location:
modifierFlags:
timestamp:
windowNumber:
context:
characters:
charactersIgnoringModifiers:
isARepeat:
keyCode:
)]
pub fn keyEventWithType(
type_: NSEventType,
location: NSPoint,
modifier_flags: NSEventModifierFlags,
timestamp: NSTimeInterval,
window_num: NSInteger,
context: Option<&NSObject>,
characters: &NSString,
characters_ignoring_modifiers: &NSString,
is_a_repeat: bool,
scancode: c_ushort,
) -> Id<Self>;
#[method(locationInWindow)]
pub fn locationInWindow(&self) -> NSPoint;
// TODO: MainThreadMarker
#[method(pressedMouseButtons)]
pub fn pressedMouseButtons() -> NSUInteger;
#[method(modifierFlags)]
pub fn modifierFlags(&self) -> NSEventModifierFlags;
#[method(type)]
pub fn type_(&self) -> NSEventType;
#[method(keyCode)]
pub fn key_code(&self) -> c_ushort;
#[method(magnification)]
pub fn magnification(&self) -> CGFloat;
#[method(phase)]
pub fn phase(&self) -> NSEventPhase;
#[method(momentumPhase)]
pub fn momentumPhase(&self) -> NSEventPhase;
#[method(deltaX)]
pub fn deltaX(&self) -> CGFloat;
#[method(deltaY)]
pub fn deltaY(&self) -> CGFloat;
#[method(buttonNumber)]
pub fn buttonNumber(&self) -> NSInteger;
#[method(scrollingDeltaX)]
pub fn scrollingDeltaX(&self) -> CGFloat;
#[method(scrollingDeltaY)]
pub fn scrollingDeltaY(&self) -> CGFloat;
#[method(hasPreciseScrollingDeltas)]
pub fn hasPreciseScrollingDeltas(&self) -> bool;
#[method(rotation)]
pub fn rotation(&self) -> f32;
#[method(pressure)]
pub fn pressure(&self) -> f32;
#[method(stage)]
pub fn stage(&self) -> NSInteger;
#[method(isARepeat)]
pub fn is_a_repeat(&self) -> bool;
#[method(windowNumber)]
pub fn window_number(&self) -> NSInteger;
#[method(timestamp)]
pub fn timestamp(&self) -> NSTimeInterval;
#[method_id(characters)]
pub fn characters(&self) -> Option<Id<NSString>>;
#[method_id(charactersIgnoringModifiers)]
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString>>;
pub fn lshift_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICELSHIFTKEYMASK != 0
}
pub fn rshift_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICERSHIFTKEYMASK != 0
}
pub fn lctrl_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICELCTLKEYMASK != 0
}
pub fn rctrl_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICERCTLKEYMASK != 0
}
pub fn lalt_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICELALTKEYMASK != 0
}
pub fn ralt_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICERALTKEYMASK != 0
}
pub fn lcmd_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICELCMDKEYMASK != 0
}
pub fn rcmd_pressed(&self) -> bool {
let raw_modifiers = self.modifierFlags().bits() as u32;
raw_modifiers & NX_DEVICERCMDKEYMASK != 0
}
}
);
unsafe impl NSCopying for NSEvent {}
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
const NX_DEVICELCTLKEYMASK: u32 = 0x00000001;
const NX_DEVICELSHIFTKEYMASK: u32 = 0x00000002;
const NX_DEVICERSHIFTKEYMASK: u32 = 0x00000004;
const NX_DEVICELCMDKEYMASK: u32 = 0x00000008;
const NX_DEVICERCMDKEYMASK: u32 = 0x00000010;
const NX_DEVICELALTKEYMASK: u32 = 0x00000020;
const NX_DEVICERALTKEYMASK: u32 = 0x00000040;
const NX_DEVICERCTLKEYMASK: u32 = 0x00002000;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NSEventModifierFlags: NSUInteger {
const NSAlphaShiftKeyMask = 1 << 16;
const NSShiftKeyMask = 1 << 17;
const NSControlKeyMask = 1 << 18;
const NSAlternateKeyMask = 1 << 19;
const NSCommandKeyMask = 1 << 20;
const NSNumericPadKeyMask = 1 << 21;
const NSHelpKeyMask = 1 << 22;
const NSFunctionKeyMask = 1 << 23;
const NSDeviceIndependentModifierFlagsMask = 0xffff0000;
}
}
unsafe impl Encode for NSEventModifierFlags {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NSEventPhase: NSUInteger {
const NSEventPhaseNone = 0;
const NSEventPhaseBegan = 0x1 << 0;
const NSEventPhaseStationary = 0x1 << 1;
const NSEventPhaseChanged = 0x1 << 2;
const NSEventPhaseEnded = 0x1 << 3;
const NSEventPhaseCancelled = 0x1 << 4;
const NSEventPhaseMayBegin = 0x1 << 5;
}
}
unsafe impl Encode for NSEventPhase {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(i16)] // short
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSEventSubtype {
// TODO: Not sure what these values are
// NSMouseEventSubtype = NX_SUBTYPE_DEFAULT,
// NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT,
// NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY
// NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH,
NSWindowExposedEventType = 0,
NSApplicationActivatedEventType = 1,
NSApplicationDeactivatedEventType = 2,
NSWindowMovedEventType = 4,
NSScreenChangedEventType = 8,
NSAWTEventType = 16,
}
unsafe impl Encode for NSEventSubtype {
const ENCODING: Encoding = i16::ENCODING;
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(usize)] // NSUInteger
pub enum NSEventType {
NSLeftMouseDown = 1,
NSLeftMouseUp = 2,
NSRightMouseDown = 3,
NSRightMouseUp = 4,
NSMouseMoved = 5,
NSLeftMouseDragged = 6,
NSRightMouseDragged = 7,
NSMouseEntered = 8,
NSMouseExited = 9,
NSKeyDown = 10,
NSKeyUp = 11,
NSFlagsChanged = 12,
NSAppKitDefined = 13,
NSSystemDefined = 14,
NSApplicationDefined = 15,
NSPeriodic = 16,
NSCursorUpdate = 17,
NSScrollWheel = 22,
NSTabletPoint = 23,
NSTabletProximity = 24,
NSOtherMouseDown = 25,
NSOtherMouseUp = 26,
NSOtherMouseDragged = 27,
NSEventTypeGesture = 29,
NSEventTypeMagnify = 30,
NSEventTypeSwipe = 31,
NSEventTypeRotate = 18,
NSEventTypeBeginGesture = 19,
NSEventTypeEndGesture = 20,
NSEventTypePressure = 34,
}
unsafe impl Encode for NSEventType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -1,36 +0,0 @@
use icrate::Foundation::{NSData, NSObject, NSString};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
extern_class!(
// TODO: Can this be mutable?
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSImage;
unsafe impl ClassType for NSImage {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
// Documented Thread-Unsafe, but:
// > One thread can create an NSImage object, draw to the image buffer,
// > and pass it off to the main thread for drawing. The underlying image
// > cache is shared among all threads.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728>
//
// So really only unsafe to mutate on several threads.
unsafe impl Send for NSImage {}
unsafe impl Sync for NSImage {}
extern_methods!(
unsafe impl NSImage {
pub fn new_by_referencing_file(path: &NSString) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initByReferencingFile: path] }
}
pub fn new_with_data(data: &NSData) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initWithData: data] }
}
}
);

View File

@@ -1,25 +0,0 @@
use icrate::Foundation::NSObject;
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::NSMenuItem;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSMenu;
unsafe impl ClassType for NSMenu {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSMenu {
#[method_id(new)]
pub fn new() -> Id<Self>;
#[method(addItem:)]
pub fn addItem(&self, item: &NSMenuItem);
}
);

View File

@@ -1,43 +0,0 @@
use icrate::Foundation::{NSObject, NSString};
use objc2::rc::Id;
use objc2::runtime::Sel;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{NSEventModifierFlags, NSMenu};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSMenuItem;
unsafe impl ClassType for NSMenuItem {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSMenuItem {
#[method_id(new)]
pub fn new() -> Id<Self>;
pub fn newWithTitle(title: &NSString, action: Sel, key_equivalent: &NSString) -> Id<Self> {
unsafe {
msg_send_id![
Self::alloc(),
initWithTitle: title,
action: action,
keyEquivalent: key_equivalent,
]
}
}
#[method_id(separatorItem)]
pub fn separatorItem() -> Id<Self>;
#[method(setKeyEquivalentModifierMask:)]
pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags);
#[method(setSubmenu:)]
pub fn setSubmenu(&self, submenu: &NSMenu);
}
);

View File

@@ -1,66 +0,0 @@
//! Safe bindings for the AppKit framework.
//!
//! These are split out from the rest of `winit` to make safety easier to review.
//! In the future, these should probably live in another crate like `cacao`.
//!
//! TODO: Main thread safety.
// Objective-C methods have different conventions, and it's much easier to
// understand if we just use the same names
#![allow(non_snake_case)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::enum_variant_names)]
#![allow(non_upper_case_globals)]
mod appearance;
mod application;
mod button;
mod color;
mod control;
mod cursor;
mod event;
mod image;
mod menu;
mod menu_item;
mod pasteboard;
mod responder;
mod screen;
mod tab_group;
mod text_input_client;
mod text_input_context;
mod version;
mod view;
mod window;
pub(crate) use self::appearance::NSAppearance;
pub(crate) use self::application::{
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
NSRequestUserAttentionType,
};
pub(crate) use self::button::NSButton;
pub(crate) use self::color::NSColor;
pub(crate) use self::control::NSControl;
pub(crate) use self::cursor::NSCursor;
#[allow(unused_imports)]
pub(crate) use self::event::{
NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType,
};
pub(crate) use self::image::NSImage;
pub(crate) use self::menu::NSMenu;
pub(crate) use self::menu_item::NSMenuItem;
pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType};
pub(crate) use self::responder::NSResponder;
#[allow(unused_imports)]
pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
pub(crate) use self::tab_group::NSWindowTabGroup;
pub(crate) use self::text_input_client::NSTextInputClient;
pub(crate) use self::text_input_context::NSTextInputContext;
pub(crate) use self::version::NSAppKitVersion;
pub(crate) use self::view::{NSTrackingRectTag, NSView};
pub(crate) use self::window::{
NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState,
NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode,
NSWindowTitleVisibility,
};
#[link(name = "AppKit", kind = "framework")]
extern "C" {}

View File

@@ -1,26 +0,0 @@
use icrate::Foundation::{NSObject, NSString};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSPasteboard;
unsafe impl ClassType for NSPasteboard {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSPasteboard {
#[method_id(propertyListForType:)]
pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id<NSObject>;
}
);
pub type NSPasteboardType = NSString;
extern "C" {
pub static NSFilenamesPboardType: &'static NSPasteboardType;
}

View File

@@ -1,23 +0,0 @@
use icrate::Foundation::{NSArray, NSObject};
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::NSEvent;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NSResponder;
unsafe impl ClassType for NSResponder {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
// Documented as "Thread-Unsafe".
extern_methods!(
unsafe impl NSResponder {
#[method(interpretKeyEvents:)]
pub(crate) unsafe fn interpretKeyEvents(&self, events: &NSArray<NSEvent>);
}
);

View File

@@ -1,65 +0,0 @@
use icrate::ns_string;
use icrate::Foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSScreen;
unsafe impl ClassType for NSScreen {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
// TODO: Main thread marker!
extern_methods!(
unsafe impl NSScreen {
/// The application object must have been created.
#[method_id(mainScreen)]
pub fn main() -> Option<Id<Self>>;
/// The application object must have been created.
#[method_id(screens)]
pub fn screens() -> Id<NSArray<Self>>;
#[method(frame)]
pub fn frame(&self) -> NSRect;
#[method(visibleFrame)]
pub fn visibleFrame(&self) -> NSRect;
#[method_id(deviceDescription)]
pub fn deviceDescription(&self) -> Id<NSDictionary<NSDeviceDescriptionKey, AnyObject>>;
pub fn display_id(&self) -> u32 {
let key = ns_string!("NSScreenNumber");
objc2::rc::autoreleasepool(|_| {
let device_description = self.deviceDescription();
// Retrieve the CGDirectDisplayID associated with this screen
//
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for `deviceDescription` for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description
.get(key)
.expect("failed getting screen display id from device description");
let obj: *const AnyObject = obj;
let obj: *const NSNumber = obj.cast();
let obj: &NSNumber = unsafe { &*obj };
obj.as_u32()
})
}
#[method(backingScaleFactor)]
pub fn backingScaleFactor(&self) -> CGFloat;
}
);
pub type NSDeviceDescriptionKey = NSString;

View File

@@ -1,31 +0,0 @@
use icrate::Foundation::{NSArray, NSObject};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::NSWindow;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSWindowTabGroup;
unsafe impl ClassType for NSWindowTabGroup {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSWindowTabGroup {
#[method(selectNextTab)]
pub fn selectNextTab(&self);
#[method(selectPreviousTab)]
pub fn selectPreviousTab(&self);
#[method_id(windows)]
pub fn tabbedWindows(&self) -> Option<Id<NSArray<NSWindow>>>;
#[method(setSelectedWindow:)]
pub fn setSelectedWindow(&self, window: &NSWindow);
}
);

View File

@@ -1,9 +0,0 @@
use objc2::{extern_protocol, ProtocolType};
extern_protocol!(
pub(crate) unsafe trait NSTextInputClient {
// TODO: Methods
}
unsafe impl ProtocolType for dyn NSTextInputClient {}
);

View File

@@ -1,29 +0,0 @@
use icrate::Foundation::{NSObject, NSString};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, mutability, ClassType};
type NSTextInputSourceIdentifier = NSString;
extern_class!(
/// Main-Thread-Only!
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSTextInputContext;
unsafe impl ClassType for NSTextInputContext {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSTextInputContext {
#[method(invalidateCharacterCoordinates)]
pub fn invalidateCharacterCoordinates(&self);
#[method(discardMarkedText)]
pub fn discardMarkedText(&self);
#[method_id(selectedKeyboardInputSource)]
pub fn selectedKeyboardInputSource(&self) -> Option<Id<NSTextInputSourceIdentifier>>;
}
);

View File

@@ -1,62 +0,0 @@
#[repr(transparent)]
#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
pub struct NSAppKitVersion(f64);
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
impl NSAppKitVersion {
pub fn current() -> Self {
extern "C" {
static NSAppKitVersionNumber: NSAppKitVersion;
}
unsafe { NSAppKitVersionNumber }
}
pub fn floor(self) -> Self {
Self(self.0.floor())
}
pub const NSAppKitVersionNumber10_0: Self = Self(577.0);
pub const NSAppKitVersionNumber10_1: Self = Self(620.0);
pub const NSAppKitVersionNumber10_2: Self = Self(663.0);
pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6);
pub const NSAppKitVersionNumber10_3: Self = Self(743.0);
pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14);
pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2);
pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24);
pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33);
pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36);
pub const NSAppKitVersionNumber10_4: Self = Self(824.0);
pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1);
pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23);
pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33);
pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41);
pub const NSAppKitVersionNumber10_5: Self = Self(949.0);
pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27);
pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33);
pub const NSAppKitVersionNumber10_6: Self = Self(1038.0);
pub const NSAppKitVersionNumber10_7: Self = Self(1138.0);
pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23);
pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32);
pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47);
pub const NSAppKitVersionNumber10_8: Self = Self(1187.0);
pub const NSAppKitVersionNumber10_9: Self = Self(1265.0);
pub const NSAppKitVersionNumber10_10: Self = Self(1343.0);
pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0);
pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0);
pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0);
pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0);
pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0);
pub const NSAppKitVersionNumber10_11: Self = Self(1404.0);
pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13);
pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34);
pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34);
pub const NSAppKitVersionNumber10_12: Self = Self(1504.0);
pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60);
pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76);
pub const NSAppKitVersionNumber10_13: Self = Self(1561.0);
pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1);
pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2);
pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4);
}

View File

@@ -1,95 +0,0 @@
use std::ffi::c_void;
use std::num::NonZeroIsize;
use std::ptr;
use icrate::Foundation::{NSObject, NSPoint, NSRect};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::{NSCursor, NSResponder, NSTextInputContext, NSWindow};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSView;
unsafe impl ClassType for NSView {
#[inherits(NSObject)]
type Super = NSResponder;
type Mutability = mutability::InteriorMutable;
}
);
// Documented as "Main Thread Only".
// > generally thread safe, although operations on views such as creating,
// > resizing, and moving should happen on the main thread.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
//
// > If you want to use a thread to draw to a view, bracket all drawing code
// > between the lockFocusIfCanDraw and unlockFocus methods of NSView.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB>
extern_methods!(
/// Getter methods
unsafe impl NSView {
#[method(frame)]
pub fn frame(&self) -> NSRect;
#[method(bounds)]
pub fn bounds(&self) -> NSRect;
#[method_id(inputContext)]
pub fn inputContext(
&self,
// _mtm: MainThreadMarker,
) -> Option<Id<NSTextInputContext>>;
#[method(hasMarkedText)]
pub fn hasMarkedText(&self) -> bool;
#[method(convertPoint:fromView:)]
pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint;
#[method_id(window)]
pub fn window(&self) -> Option<Id<NSWindow>>;
}
unsafe impl NSView {
#[method(setWantsBestResolutionOpenGLSurface:)]
pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool);
#[method(setWantsLayer:)]
pub fn setWantsLayer(&self, wants_layer: bool);
#[method(setPostsFrameChangedNotifications:)]
pub fn setPostsFrameChangedNotifications(&self, value: bool);
#[method(removeTrackingRect:)]
pub fn removeTrackingRect(&self, tag: NSTrackingRectTag);
#[method(addTrackingRect:owner:userData:assumeInside:)]
unsafe fn inner_addTrackingRect(
&self,
rect: NSRect,
owner: &AnyObject,
user_data: *mut c_void,
assume_inside: bool,
) -> Option<NSTrackingRectTag>;
pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag {
// SAFETY: The user data is NULL, so it is valid
unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) }
.expect("failed creating tracking rect")
}
#[method(addCursorRect:cursor:)]
// NSCursor safe to take by shared reference since it is already immutable
pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor);
#[method(setHidden:)]
pub fn setHidden(&self, hidden: bool);
}
);
/// <https://developer.apple.com/documentation/appkit/nstrackingrecttag?language=objc>
pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero!

View File

@@ -1,440 +0,0 @@
use icrate::Foundation::{
CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger,
};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::{
NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView, NSWindowTabGroup,
};
extern_class!(
/// Main-Thread-Only!
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NSWindow;
unsafe impl ClassType for NSWindow {
#[inherits(NSObject)]
type Super = NSResponder;
type Mutability = mutability::InteriorMutable;
}
);
// Documented as "Main Thread Only", but:
// > Thread safe in that you can create and manage them on a secondary thread.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123364>
//
// So could in theory be `Send`, and perhaps also `Sync` - but we would like
// interior mutability on windows, since that's just much easier, and in that
// case, they can't be!
extern_methods!(
unsafe impl NSWindow {
#[method(frame)]
pub(crate) fn frame(&self) -> NSRect;
#[method(windowNumber)]
pub(crate) fn windowNumber(&self) -> NSInteger;
#[method(backingScaleFactor)]
pub(crate) fn backingScaleFactor(&self) -> CGFloat;
#[method_id(contentView)]
pub(crate) fn contentView(&self) -> Id<NSView>;
#[method(setContentView:)]
pub(crate) fn setContentView(&self, view: &NSView);
#[method(setInitialFirstResponder:)]
pub(crate) fn setInitialFirstResponder(&self, view: &NSView);
#[method(makeFirstResponder:)]
#[must_use]
pub(crate) fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool;
#[method(contentRectForFrameRect:)]
pub(crate) fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect;
#[method_id(screen)]
pub(crate) fn screen(&self) -> Option<Id<NSScreen>>;
#[method(setContentSize:)]
pub(crate) fn setContentSize(&self, contentSize: NSSize);
#[method(setFrameTopLeftPoint:)]
pub(crate) fn setFrameTopLeftPoint(&self, point: NSPoint);
#[method(setMinSize:)]
pub(crate) fn setMinSize(&self, minSize: NSSize);
#[method(setMaxSize:)]
pub(crate) fn setMaxSize(&self, maxSize: NSSize);
#[method(setResizeIncrements:)]
pub(crate) fn setResizeIncrements(&self, increments: NSSize);
#[method(contentResizeIncrements)]
pub(crate) fn contentResizeIncrements(&self) -> NSSize;
#[method(setContentResizeIncrements:)]
pub(crate) fn setContentResizeIncrements(&self, increments: NSSize);
#[method(setFrame:display:)]
pub(crate) fn setFrame_display(&self, frameRect: NSRect, flag: bool);
#[method(setMovable:)]
pub(crate) fn setMovable(&self, movable: bool);
#[method(setSharingType:)]
pub(crate) fn setSharingType(&self, sharingType: NSWindowSharingType);
#[method(setTabbingMode:)]
pub(crate) fn setTabbingMode(&self, tabbingMode: NSWindowTabbingMode);
#[method(setOpaque:)]
pub(crate) fn setOpaque(&self, opaque: bool);
#[method(hasShadow)]
pub(crate) fn hasShadow(&self) -> bool;
#[method(setHasShadow:)]
pub(crate) fn setHasShadow(&self, has_shadow: bool);
#[method(setIgnoresMouseEvents:)]
pub(crate) fn setIgnoresMouseEvents(&self, ignores: bool);
#[method(setBackgroundColor:)]
pub(crate) fn setBackgroundColor(&self, color: &NSColor);
#[method(styleMask)]
pub(crate) fn styleMask(&self) -> NSWindowStyleMask;
#[method(setStyleMask:)]
pub(crate) fn setStyleMask(&self, mask: NSWindowStyleMask);
#[method(registerForDraggedTypes:)]
pub(crate) fn registerForDraggedTypes(&self, types: &NSArray<NSPasteboardType>);
#[method(makeKeyAndOrderFront:)]
pub(crate) fn makeKeyAndOrderFront(&self, sender: Option<&AnyObject>);
#[method(orderFront:)]
pub(crate) fn orderFront(&self, sender: Option<&AnyObject>);
#[method(miniaturize:)]
pub(crate) fn miniaturize(&self, sender: Option<&AnyObject>);
#[method(deminiaturize:)]
pub(crate) fn deminiaturize(&self, sender: Option<&AnyObject>);
#[method(toggleFullScreen:)]
pub(crate) fn toggleFullScreen(&self, sender: Option<&AnyObject>);
#[method(orderOut:)]
pub(crate) fn orderOut(&self, sender: Option<&AnyObject>);
#[method(zoom:)]
pub(crate) fn zoom(&self, sender: Option<&AnyObject>);
#[method(selectNextKeyView:)]
pub(crate) fn selectNextKeyView(&self, sender: Option<&AnyObject>);
#[method(selectPreviousKeyView:)]
pub(crate) fn selectPreviousKeyView(&self, sender: Option<&AnyObject>);
#[method_id(firstResponder)]
pub(crate) fn firstResponder(&self) -> Option<Id<NSResponder>>;
#[method_id(standardWindowButton:)]
pub(crate) fn standardWindowButton(&self, kind: NSWindowButton) -> Option<Id<NSButton>>;
#[method(setTitle:)]
pub(crate) fn setTitle(&self, title: &NSString);
#[method_id(title)]
pub(crate) fn title_(&self) -> Id<NSString>;
#[method(setReleasedWhenClosed:)]
pub(crate) fn setReleasedWhenClosed(&self, val: bool);
#[method(setAcceptsMouseMovedEvents:)]
pub(crate) fn setAcceptsMouseMovedEvents(&self, val: bool);
#[method(setTitlebarAppearsTransparent:)]
pub(crate) fn setTitlebarAppearsTransparent(&self, val: bool);
#[method(setTitleVisibility:)]
pub(crate) fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility);
#[method(setMovableByWindowBackground:)]
pub(crate) fn setMovableByWindowBackground(&self, val: bool);
#[method(setLevel:)]
pub(crate) fn setLevel(&self, level: NSWindowLevel);
#[method(setAllowsAutomaticWindowTabbing:)]
pub(crate) fn setAllowsAutomaticWindowTabbing(val: bool);
#[method(setTabbingIdentifier:)]
pub(crate) fn setTabbingIdentifier(&self, identifier: &NSString);
#[method(setDocumentEdited:)]
pub(crate) fn setDocumentEdited(&self, val: bool);
#[method(occlusionState)]
pub(crate) fn occlusionState(&self) -> NSWindowOcclusionState;
#[method(center)]
pub(crate) fn center(&self);
#[method(isResizable)]
pub(crate) fn isResizable(&self) -> bool;
#[method(isMiniaturizable)]
pub(crate) fn isMiniaturizable(&self) -> bool;
#[method(hasCloseBox)]
pub(crate) fn hasCloseBox(&self) -> bool;
#[method(isMiniaturized)]
pub(crate) fn isMiniaturized(&self) -> bool;
#[method(isVisible)]
pub(crate) fn isVisible(&self) -> bool;
#[method(isKeyWindow)]
pub(crate) fn isKeyWindow(&self) -> bool;
#[method(isZoomed)]
pub(crate) fn isZoomed(&self) -> bool;
#[method(allowsAutomaticWindowTabbing)]
pub(crate) fn allowsAutomaticWindowTabbing() -> bool;
#[method(selectNextTab)]
pub(crate) fn selectNextTab(&self);
#[method_id(tabbingIdentifier)]
pub(crate) fn tabbingIdentifier(&self) -> Id<NSString>;
#[method_id(tabGroup)]
pub(crate) fn tabGroup(&self) -> Option<Id<NSWindowTabGroup>>;
#[method(isDocumentEdited)]
pub(crate) fn isDocumentEdited(&self) -> bool;
#[method(close)]
pub(crate) fn close(&self);
#[method(performWindowDragWithEvent:)]
// TODO: Can this actually accept NULL?
pub(crate) fn performWindowDragWithEvent(&self, event: Option<&NSEvent>);
#[method(invalidateCursorRectsForView:)]
pub(crate) fn invalidateCursorRectsForView(&self, view: &NSView);
#[method(setDelegate:)]
pub(crate) fn setDelegate(&self, delegate: Option<&NSObject>);
#[method(sendEvent:)]
pub(crate) unsafe fn sendEvent(&self, event: &NSEvent);
#[method(addChildWindow:ordered:)]
pub(crate) unsafe fn addChildWindow(&self, child: &NSWindow, ordered: NSWindowOrderingMode);
}
);
#[allow(dead_code)]
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowTitleVisibility {
#[doc(alias = "NSWindowTitleVisible")]
Visible = 0,
#[doc(alias = "NSWindowTitleHidden")]
Hidden = 1,
}
unsafe impl Encode for NSWindowTitleVisibility {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowButton {
#[doc(alias = "NSWindowCloseButton")]
Close = 0,
#[doc(alias = "NSWindowMiniaturizeButton")]
Miniaturize = 1,
#[doc(alias = "NSWindowZoomButton")]
Zoom = 2,
#[doc(alias = "NSWindowToolbarButton")]
Toolbar = 3,
#[doc(alias = "NSWindowDocumentIconButton")]
DocumentIcon = 4,
#[doc(alias = "NSWindowDocumentVersionsButton")]
DocumentVersions = 6,
#[doc(alias = "NSWindowFullScreenButton")]
#[deprecated = "Deprecated since macOS 10.12"]
FullScreen = 7,
}
unsafe impl Encode for NSWindowButton {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
// CGWindowLevel.h
//
// Note: There are two different things at play in this header:
// `CGWindowLevel` and `CGWindowLevelKey`.
//
// It seems like there was a push towards using "key" values instead of the
// raw window level values, and then you were supposed to use
// `CGWindowLevelForKey` to get the actual level.
//
// But the values that `NSWindowLevel` has are compiled in, and as such has
// to remain ABI compatible, so they're safe for us to hardcode as well.
#[allow(dead_code)]
mod window_level {
const kCGNumReservedWindowLevels: i32 = 16;
const kCGNumReservedBaseWindowLevels: i32 = 5;
pub const kCGBaseWindowLevel: i32 = i32::MIN;
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
pub const kCGBackstopMenuLevel: i32 = -20;
pub const kCGNormalWindowLevel: i32 = 0;
pub const kCGFloatingWindowLevel: i32 = 3;
pub const kCGTornOffMenuWindowLevel: i32 = 3;
pub const kCGModalPanelWindowLevel: i32 = 8;
pub const kCGUtilityWindowLevel: i32 = 19;
pub const kCGDockWindowLevel: i32 = 20;
pub const kCGMainMenuWindowLevel: i32 = 24;
pub const kCGStatusWindowLevel: i32 = 25;
pub const kCGPopUpMenuWindowLevel: i32 = 101;
pub const kCGOverlayWindowLevel: i32 = 102;
pub const kCGHelpWindowLevel: i32 = 200;
pub const kCGDraggingWindowLevel: i32 = 500;
pub const kCGScreenSaverWindowLevel: i32 = 1000;
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
}
use window_level::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct NSWindowLevel(pub NSInteger);
#[allow(dead_code)]
impl NSWindowLevel {
#[doc(alias = "BelowNormalWindowLevel")]
pub const BELOW_NORMAL: Self = Self((kCGNormalWindowLevel - 1) as _);
#[doc(alias = "NSNormalWindowLevel")]
pub const Normal: Self = Self(kCGNormalWindowLevel as _);
#[doc(alias = "NSFloatingWindowLevel")]
pub const Floating: Self = Self(kCGFloatingWindowLevel as _);
#[doc(alias = "NSTornOffMenuWindowLevel")]
pub const TornOffMenu: Self = Self(kCGTornOffMenuWindowLevel as _);
#[doc(alias = "NSModalPanelWindowLevel")]
pub const ModalPanel: Self = Self(kCGModalPanelWindowLevel as _);
#[doc(alias = "NSMainMenuWindowLevel")]
pub const MainMenu: Self = Self(kCGMainMenuWindowLevel as _);
#[doc(alias = "NSStatusWindowLevel")]
pub const Status: Self = Self(kCGStatusWindowLevel as _);
#[doc(alias = "NSPopUpMenuWindowLevel")]
pub const PopUpMenu: Self = Self(kCGPopUpMenuWindowLevel as _);
#[doc(alias = "NSScreenSaverWindowLevel")]
pub const ScreenSaver: Self = Self(kCGScreenSaverWindowLevel as _);
}
unsafe impl Encode for NSWindowLevel {
const ENCODING: Encoding = NSInteger::ENCODING;
}
bitflags! {
#[derive(Clone, Copy)]
pub struct NSWindowOcclusionState: NSUInteger {
const NSWindowOcclusionStateVisible = 1 << 1;
}
}
unsafe impl Encode for NSWindowOcclusionState {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct NSWindowStyleMask: NSUInteger {
const NSBorderlessWindowMask = 0;
const NSTitledWindowMask = 1 << 0;
const NSClosableWindowMask = 1 << 1;
const NSMiniaturizableWindowMask = 1 << 2;
const NSResizableWindowMask = 1 << 3;
const NSTexturedBackgroundWindowMask = 1 << 8;
const NSUnifiedTitleAndToolbarWindowMask = 1 << 12;
const NSFullScreenWindowMask = 1 << 14;
const NSFullSizeContentViewWindowMask = 1 << 15;
}
}
unsafe impl Encode for NSWindowStyleMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSBackingStoreType {
NSBackingStoreRetained = 0,
NSBackingStoreNonretained = 1,
NSBackingStoreBuffered = 2,
}
unsafe impl Encode for NSBackingStoreType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowSharingType {
NSWindowSharingNone = 0,
NSWindowSharingReadOnly = 1,
NSWindowSharingReadWrite = 2,
}
unsafe impl Encode for NSWindowSharingType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowOrderingMode {
NSWindowAbove = 1,
NSWindowBelow = -1,
NSWindowOut = 0,
}
unsafe impl Encode for NSWindowOrderingMode {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowTabbingMode {
NSWindowTabbingModeAutomatic = 0,
NSWindowTabbingModeDisallowed = 2,
NSWindowTabbingModePreferred = 1,
}
unsafe impl Encode for NSWindowTabbingMode {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -0,0 +1,228 @@
use icrate::AppKit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use icrate::Foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString,
};
use objc2::rc::Id;
use objc2::runtime::Sel;
use objc2::{msg_send_id, sel, ClassType};
use once_cell::sync::Lazy;
use std::ffi::c_uchar;
use std::slice;
use super::EventLoopWindowTarget;
use crate::cursor::CursorImage;
use crate::cursor::OnlyCursorImageBuilder;
use crate::window::CursorIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Id<NSCursor>);
// SAFETY: NSCursor is immutable and thread-safe
// TODO(madsmtm): Put this logic in icrate itself
unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {}
impl CustomCursor {
pub(crate) fn build<T>(
cursor: OnlyCursorImageBuilder,
_: &EventLoopWindowTarget<T>,
) -> CustomCursor {
Self(cursor_from_image(&cursor.0))
}
}
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> {
let width = cursor.width;
let height = cursor.height;
let bitmap = unsafe {
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
NSBitmapImageRep::alloc(),
std::ptr::null_mut::<*mut c_uchar>(),
width as isize,
height as isize,
8,
4,
true,
false,
NSDeviceRGBColorSpace,
width as isize * 4,
32,
).unwrap()
};
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
bitmap_data.copy_from_slice(&cursor.rgba);
let image = unsafe {
NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()))
};
unsafe { image.addRepresentation(&bitmap) };
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
}
pub(crate) fn default_cursor() -> Id<NSCursor> {
NSCursor::arrowCursor()
}
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Id<NSCursor>> {
let cls = NSCursor::class();
if cls.responds_to(sel) {
let cursor: Id<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor)
} else {
warn!("cursor `{sel}` appears to be invalid");
None
}
}
macro_rules! def_undocumented_cursor {
{$(
$(#[$($m:meta)*])*
fn $name:ident();
)*} => {$(
$(#[$($m)*])*
#[allow(non_snake_case)]
fn $name() -> Id<NSCursor> {
unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) }
}
)*};
}
def_undocumented_cursor!(
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
fn _helpCursor();
fn _zoomInCursor();
fn _zoomOutCursor();
fn _windowResizeNorthEastCursor();
fn _windowResizeNorthWestCursor();
fn _windowResizeSouthEastCursor();
fn _windowResizeSouthWestCursor();
fn _windowResizeNorthEastSouthWestCursor();
fn _windowResizeNorthWestSouthEastCursor();
// While these two are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// pub fn _moveCursor();
// pub fn _waitCursor();
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
fn busyButClickableCursor();
);
// Note that loading `busybutclickable` with this code won't animate
// the frames; instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
// Snatch a cursor from WebKit; They fit the style of the native
// cursors, and will seem completely standard to macOS users.
//
// https://stackoverflow.com/a/21786835/5435443
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors");
let cursor_path = root.stringByAppendingPathComponent(name);
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap();
// TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
x = unsafe { &*ptr }.as_cgfloat()
}
}
let mut y = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
y = unsafe { &*ptr }.as_cgfloat()
}
}
let hotspot = NSPoint::new(x, y);
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
}
fn webkit_move() -> Id<NSCursor> {
unsafe { load_webkit_cursor(ns_string!("move")) }
}
fn webkit_cell() -> Id<NSCursor> {
unsafe { load_webkit_cursor(ns_string!("cell")) }
}
pub(crate) fn invisible_cursor() -> Id<NSCursor> {
// 16x16 GIF data for invisible cursor
// You can reproduce this via ImageMagick.
// $ convert -size 16x16 xc:none cursor.gif
static CURSOR_BYTES: &[u8] = &[
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F,
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
];
static CURSOR: Lazy<CustomCursor> = Lazy::new(|| {
// TODO: Consider using `dataWithBytesNoCopy:`
let data = NSData::with_bytes(CURSOR_BYTES);
let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap();
let hotspot = NSPoint::new(0.0, 0.0);
CustomCursor(NSCursor::initWithImage_hotSpot(
NSCursor::alloc(),
&image,
hotspot,
))
});
CURSOR.0.clone()
}
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Id<NSCursor> {
match icon {
CursorIcon::Default => default_cursor(),
CursorIcon::Pointer => NSCursor::pointingHandCursor(),
CursorIcon::Grab => NSCursor::openHandCursor(),
CursorIcon::Grabbing => NSCursor::closedHandCursor(),
CursorIcon::Text => NSCursor::IBeamCursor(),
CursorIcon::VerticalText => NSCursor::IBeamCursorForVerticalLayout(),
CursorIcon::Copy => NSCursor::dragCopyCursor(),
CursorIcon::Alias => NSCursor::dragLinkCursor(),
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
CursorIcon::Crosshair => NSCursor::crosshairCursor(),
CursorIcon::EResize => NSCursor::resizeRightCursor(),
CursorIcon::NResize => NSCursor::resizeUpCursor(),
CursorIcon::WResize => NSCursor::resizeLeftCursor(),
CursorIcon::SResize => NSCursor::resizeDownCursor(),
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),
CursorIcon::Move | CursorIcon::AllScroll => webkit_move(),
CursorIcon::Cell => webkit_cell(),
_ => default_cursor(),
}
}

View File

@@ -4,10 +4,15 @@ use core_foundation::{
base::CFRelease,
data::{CFDataGetBytePtr, CFDataRef},
};
use icrate::Foundation::MainThreadMarker;
use icrate::AppKit::{
NSEvent, NSEventModifierFlagCommand, NSEventModifierFlagControl, NSEventModifierFlagOption,
NSEventModifierFlagShift, NSEventModifierFlags, NSEventSubtypeWindowExposed,
NSEventTypeApplicationDefined,
};
use icrate::Foundation::{MainThreadMarker, NSPoint};
use objc2::rc::Id;
use smol_str::SmolStr;
use super::appkit::{NSEvent, NSEventModifierFlags};
use crate::{
event::{ElementState, KeyEvent, Modifiers},
keyboard::{
@@ -39,7 +44,6 @@ impl KeyEventExtModifierSupplement for KeyEvent {
}
}
/// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key {
let mut string = [0; 16];
let input_source;
@@ -98,10 +102,8 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
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()
let string = unsafe { ns_event.charactersIgnoringModifiers() }
.map(|s| s.to_string())
.unwrap_or_default();
if string.is_empty() {
@@ -124,22 +126,14 @@ pub(crate) fn create_key_event(
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = ns_event.key_code();
let scancode = unsafe { ns_event.keyCode() };
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 {
let characters = ns_event
.characters()
let characters = unsafe { ns_event.characters() }
.map(|s| s.to_string())
.unwrap_or_default();
if characters.is_empty() {
@@ -155,29 +149,21 @@ 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 modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = flags_contains(modifiers, NSEventModifierFlagControl);
let logical_key = match text_with_all_modifiers.as_ref() {
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
// Only checking for ctrl 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 && !has_cmd => {
// Character heeding both SHIFT and ALT.
Key::Character(text.clone())
}
Some(text) if !has_ctrl => 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.
// Don't try to get text for events which likely don't have it.
_ => key_without_modifiers.clone(),
},
};
@@ -328,42 +314,84 @@ pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey {
}
}
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
const NX_DEVICELCTLKEYMASK: NSEventModifierFlags = 0x00000001;
const NX_DEVICELSHIFTKEYMASK: NSEventModifierFlags = 0x00000002;
const NX_DEVICERSHIFTKEYMASK: NSEventModifierFlags = 0x00000004;
const NX_DEVICELCMDKEYMASK: NSEventModifierFlags = 0x00000008;
const NX_DEVICERCMDKEYMASK: NSEventModifierFlags = 0x00000010;
const NX_DEVICELALTKEYMASK: NSEventModifierFlags = 0x00000020;
const NX_DEVICERALTKEYMASK: NSEventModifierFlags = 0x00000040;
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = 0x00002000;
pub(super) fn flags_contains(flags: NSEventModifierFlags, value: NSEventModifierFlags) -> bool {
flags & value == value
}
pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICELALTKEYMASK)
}
pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICERALTKEYMASK)
}
pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let flags = event.modifierFlags();
let flags = unsafe { event.modifierFlags() };
let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty();
state.set(
ModifiersState::SHIFT,
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
flags_contains(flags, NSEventModifierFlagShift),
);
pressed_mods.set(
ModifiersKeys::LSHIFT,
flags_contains(flags, NX_DEVICELSHIFTKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RSHIFT,
flags_contains(flags, NX_DEVICERSHIFTKEYMASK),
);
pressed_mods.set(ModifiersKeys::LSHIFT, event.lshift_pressed());
pressed_mods.set(ModifiersKeys::RSHIFT, event.rshift_pressed());
state.set(
ModifiersState::CONTROL,
flags.contains(NSEventModifierFlags::NSControlKeyMask),
flags_contains(flags, NSEventModifierFlagControl),
);
pressed_mods.set(
ModifiersKeys::LCONTROL,
flags_contains(flags, NX_DEVICELCTLKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RCONTROL,
flags_contains(flags, NX_DEVICERCTLKEYMASK),
);
pressed_mods.set(ModifiersKeys::LCONTROL, event.lctrl_pressed());
pressed_mods.set(ModifiersKeys::RCONTROL, event.rctrl_pressed());
state.set(
ModifiersState::ALT,
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
flags_contains(flags, NSEventModifierFlagOption),
);
pressed_mods.set(
ModifiersKeys::LALT,
flags_contains(flags, NX_DEVICELALTKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RALT,
flags_contains(flags, NX_DEVICERALTKEYMASK),
);
pressed_mods.set(ModifiersKeys::LALT, event.lalt_pressed());
pressed_mods.set(ModifiersKeys::RALT, event.ralt_pressed());
state.set(
ModifiersState::SUPER,
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
flags_contains(flags, NSEventModifierFlagCommand),
);
pressed_mods.set(
ModifiersKeys::LSUPER,
flags_contains(flags, NX_DEVICELCMDKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RSUPER,
flags_contains(flags, NX_DEVICERCMDKEYMASK),
);
pressed_mods.set(ModifiersKeys::LSUPER, event.lcmd_pressed());
pressed_mods.set(ModifiersKeys::RSUPER, event.rcmd_pressed());
Modifiers {
state,
@@ -371,6 +399,22 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
}
}
pub(super) fn dummy_event() -> Option<Id<NSEvent>> {
unsafe {
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEventTypeApplicationDefined,
NSPoint::new(0.0, 0.0),
0, // Empty NSEventModifierFlags
0.0,
0,
None,
NSEventSubtypeWindowExposed,
0,
0,
)
}
}
impl PhysicalKeyExtScancode for PhysicalKey {
fn to_scancode(self) -> Option<u32> {
let code = match self {

View File

@@ -17,12 +17,18 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use icrate::Foundation::MainThreadMarker;
use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::NSObjectProtocol;
use icrate::AppKit::{
NSApplication, NSApplicationActivationPolicyAccessory, NSApplicationActivationPolicyProhibited,
NSApplicationActivationPolicyRegular, NSWindow,
};
use icrate::Foundation::{MainThreadMarker, NSObjectProtocol};
use objc2::{msg_send_id, ClassType};
use objc2::{
rc::{autoreleasepool, Id},
runtime::ProtocolObject,
};
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow};
use super::event::dummy_event;
use crate::{
error::EventLoopError,
event::Event,
@@ -116,10 +122,6 @@ impl<T: 'static> EventLoopWindowTarget<T> {
AppState::exit()
}
pub(crate) fn clear_exit(&self) {
AppState::clear_exit()
}
pub(crate) fn exiting(&self) -> bool {
AppState::exiting()
}
@@ -127,19 +129,19 @@ impl<T: 'static> EventLoopWindowTarget<T> {
impl<T> EventLoopWindowTarget<T> {
pub(crate) fn hide_application(&self) {
NSApplication::shared(self.mtm).hide(None)
NSApplication::sharedApplication(self.mtm).hide(None)
}
pub(crate) fn hide_other_applications(&self) {
NSApplication::shared(self.mtm).hideOtherApplications(None)
NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
}
pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
NSWindow::setAllowsAutomaticWindowTabbing(enabled)
NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
}
pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
NSWindow::allowsAutomaticWindowTabbing()
NSWindow::allowsAutomaticWindowTabbing(self.mtm)
}
}
@@ -200,20 +202,20 @@ impl<T> EventLoop<T> {
panic!("`winit` requires control over the principal class. You must create the event loop before other parts of your application initialize NSApplication");
}
use NSApplicationActivationPolicy::*;
let activation_policy = match attributes.activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
};
let delegate = ApplicationDelegate::new(
mtm,
activation_policy,
attributes.default_menu,
attributes.activate_ignoring_other_apps,
);
autoreleasepool(|_| {
app.setDelegate(&delegate);
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
});
let panic_info: Rc<PanicInfo> = Default::default();
@@ -311,7 +313,7 @@ impl<T> EventLoop<T> {
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the `NSApp` and saving the
// will lead to us stopping the `NSApplication` and saving the
// `PanicInfo` so that we can resume the unwind at a controlled,
// safe point in time.
if let Some(panic) = self.panic_info.take() {
@@ -359,8 +361,6 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback));
autoreleasepool(|_| {
let app = NSApp();
// A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback.
let weak_cb: Weak<_> = Rc::downgrade(&callback);
@@ -381,25 +381,24 @@ impl<T> EventLoop<T> {
// catch panics to make sure we can't unwind without clearing the set callback
// (which would leave the global `AppState` in an undefined, unsafe state)
let catch_result = catch_unwind(AssertUnwindSafe(|| {
// As a special case, if the `NSApp` hasn't been launched yet then we at least run
// As a special case, if the application hasn't been launched yet then we at least run
// the loop until it has fully launched.
if !AppState::is_launched() {
debug_assert!(!AppState::is_running());
AppState::request_stop_on_launch();
unsafe {
app.run();
self.app.run();
}
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application has launched
} else if !AppState::is_running() {
// Even though the NSApp may have been launched, it's possible we aren't running
// Even though the application may have been launched, it's possible we aren't running
// if the `EventLoop` was run before and has since exited. This indicates that
// we just starting to re-run the same `EventLoop` again.
AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
} else {
// Only run the NSApp for as long as the given `Duration` allows so we
// don't block the external loop.
// Only run for as long as the given `Duration` allows so we don't block the external loop.
match timeout {
Some(Duration::ZERO) => {
AppState::set_wait_timeout(None);
@@ -419,13 +418,13 @@ impl<T> EventLoop<T> {
}
AppState::set_stop_app_on_redraw_requested(true);
unsafe {
app.run();
self.app.run();
}
}
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the `NSApp` and saving the
// will lead to us stopping the application and saving the
// `PanicInfo` so that we can resume the unwind at a controlled,
// safe point in time.
if let Some(panic) = self.panic_info.take() {
@@ -463,6 +462,7 @@ impl<T> EventLoop<T> {
/// happens, stops the `sharedApplication`
#[inline]
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
mtm: MainThreadMarker,
panic_info: Weak<PanicInfo>,
f: F,
) -> Option<R> {
@@ -477,11 +477,11 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
let panic_info = panic_info.upgrade().unwrap();
panic_info.set_panic(e);
}
let app = NSApp();
let app = NSApplication::sharedApplication(mtm);
app.stop(None);
// Posting a dummy event to get `stop` to take effect immediately.
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
app.postEvent_atStart(&NSEvent::dummy(), true);
app.postEvent_atStart(&dummy_event().unwrap(), true);
None
}
}

View File

@@ -210,3 +210,45 @@ extern "C" {
unicodeString: *mut UniChar,
) -> OSStatus;
}
// CGWindowLevel.h
//
// Note: There are two different things at play in this header:
// `CGWindowLevel` and `CGWindowLevelKey`.
//
// It seems like there was a push towards using "key" values instead of the
// raw window level values, and then you were supposed to use
// `CGWindowLevelForKey` to get the actual level.
//
// But the values that `NSWindowLevel` has are compiled in, and as such has
// to remain ABI compatible, so they're safe for us to hardcode as well.
#[allow(dead_code, non_upper_case_globals)]
mod window_level {
const kCGNumReservedWindowLevels: i32 = 16;
const kCGNumReservedBaseWindowLevels: i32 = 5;
pub const kCGBaseWindowLevel: i32 = i32::MIN;
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
pub const kCGBackstopMenuLevel: i32 = -20;
pub const kCGNormalWindowLevel: i32 = 0;
pub const kCGFloatingWindowLevel: i32 = 3;
pub const kCGTornOffMenuWindowLevel: i32 = 3;
pub const kCGModalPanelWindowLevel: i32 = 8;
pub const kCGUtilityWindowLevel: i32 = 19;
pub const kCGDockWindowLevel: i32 = 20;
pub const kCGMainMenuWindowLevel: i32 = 24;
pub const kCGStatusWindowLevel: i32 = 25;
pub const kCGPopUpMenuWindowLevel: i32 = 101;
pub const kCGOverlayWindowLevel: i32 = 102;
pub const kCGHelpWindowLevel: i32 = 200;
pub const kCGDraggingWindowLevel: i32 = 500;
pub const kCGScreenSaverWindowLevel: i32 = 1000;
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
}
pub use window_level::*;

View File

@@ -1,36 +1,49 @@
use icrate::ns_string;
use icrate::Foundation::{NSProcessInfo, NSString};
use icrate::AppKit::{
NSApplication, NSEventModifierFlagCommand, NSEventModifierFlagOption, NSEventModifierFlags,
NSMenu, NSMenuItem,
};
use icrate::Foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
use objc2::rc::Id;
use objc2::runtime::Sel;
use objc2::sel;
use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem};
struct KeyEquivalent<'a> {
key: &'a NSString,
masks: Option<NSEventModifierFlags>,
}
pub fn initialize() {
let menubar = NSMenu::new();
let app_menu_item = NSMenuItem::new();
pub fn initialize(app: &NSApplication) {
let mtm = MainThreadMarker::from(app);
let menubar = NSMenu::new(mtm);
let app_menu_item = NSMenuItem::new(mtm);
menubar.addItem(&app_menu_item);
let app_menu = NSMenu::new();
let app_menu = NSMenu::new(mtm);
let process_name = NSProcessInfo::processInfo().processName();
// About menu item
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None);
let about_item = menu_item(
mtm,
&about_item_title,
Some(sel!(orderFrontStandardAboutPanel:)),
None,
);
// Services menu item
let services_menu = NSMenu::new(mtm);
let services_item = menu_item(mtm, ns_string!("Services"), None, None);
services_item.setSubmenu(Some(&services_menu));
// Seperator menu item
let sep_first = NSMenuItem::separatorItem();
let sep_first = NSMenuItem::separatorItem(mtm);
// Hide application menu item
let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name);
let hide_item = menu_item(
mtm,
&hide_item_title,
sel!(hide:),
Some(sel!(hide:)),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: None,
@@ -40,28 +53,33 @@ pub fn initialize() {
// Hide other applications menu item
let hide_others_item_title = ns_string!("Hide Others");
let hide_others_item = menu_item(
mtm,
hide_others_item_title,
sel!(hideOtherApplications:),
Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: Some(
NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask,
),
masks: Some(NSEventModifierFlagOption | NSEventModifierFlagCommand),
}),
);
// Show applications menu item
let show_all_item_title = ns_string!("Show All");
let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None);
let show_all_item = menu_item(
mtm,
show_all_item_title,
Some(sel!(unhideAllApplications:)),
None,
);
// Seperator menu item
let sep = NSMenuItem::separatorItem();
let sep = NSMenuItem::separatorItem(mtm);
// Quit application menu item
let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name);
let quit_item = menu_item(
mtm,
&quit_item_title,
sel!(terminate:),
Some(sel!(terminate:)),
Some(KeyEquivalent {
key: ns_string!("q"),
masks: None,
@@ -70,27 +88,31 @@ pub fn initialize() {
app_menu.addItem(&about_item);
app_menu.addItem(&sep_first);
app_menu.addItem(&services_item);
app_menu.addItem(&hide_item);
app_menu.addItem(&hide_others_item);
app_menu.addItem(&show_all_item);
app_menu.addItem(&sep);
app_menu.addItem(&quit_item);
app_menu_item.setSubmenu(&app_menu);
app_menu_item.setSubmenu(Some(&app_menu));
let app = NSApp();
app.setMainMenu(&menubar);
unsafe { app.setServicesMenu(Some(&services_menu)) };
app.setMainMenu(Some(&menubar));
}
fn menu_item(
mtm: MainThreadMarker,
title: &NSString,
selector: Sel,
selector: Option<Sel>,
key_equivalent: Option<KeyEquivalent<'_>>,
) -> Id<NSMenuItem> {
let (key, masks) = match key_equivalent {
Some(ke) => (ke.key, ke.masks),
None => (ns_string!(""), None),
};
let item = NSMenuItem::newWithTitle(title, selector, key);
let item = unsafe {
NSMenuItem::initWithTitle_action_keyEquivalent(mtm.alloc(), title, selector, key)
};
if let Some(masks) = masks {
item.setKeyEquivalentModifierMask(masks)
}

View File

@@ -4,7 +4,7 @@ mod util;
mod app;
mod app_delegate;
mod app_state;
mod appkit;
mod cursor;
mod event;
mod event_loop;
mod ffi;
@@ -27,7 +27,9 @@ pub(crate) use self::{
};
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::window::Window;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::window::{OwnedWindowHandle, Window};
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -10,9 +10,10 @@ use core_foundation::{
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
use objc2::rc::Id;
use icrate::AppKit::NSScreen;
use icrate::Foundation::{ns_string, MainThreadMarker, NSNumber};
use objc2::{rc::Id, runtime::AnyObject};
use super::appkit::NSScreen;
use super::ffi;
use crate::dpi::{PhysicalPosition, PhysicalSize};
@@ -210,10 +211,12 @@ impl MonitorHandle {
}
pub fn scale_factor(&self) -> f64 {
match self.ns_screen() {
Some(screen) => screen.backingScaleFactor() as f64,
None => 1.0, // default to 1.0 when we can't find the screen
}
MainThreadMarker::run_on_main(|mtm| {
match self.ns_screen(mtm) {
Some(screen) => screen.backingScaleFactor() as f64,
None => 1.0, // default to 1.0 when we can't find the screen
}
})
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
@@ -303,10 +306,10 @@ impl MonitorHandle {
}
}
pub(crate) fn ns_screen(&self) -> Option<Id<NSScreen>> {
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Id<NSScreen>> {
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
NSScreen::screens().into_iter().find(|screen| {
let other_native_id = screen.display_id();
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
let other_uuid = unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
};
@@ -314,3 +317,25 @@ impl MonitorHandle {
})
}
}
pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
let key = ns_string!("NSScreenNumber");
objc2::rc::autoreleasepool(|_| {
let device_description = screen.deviceDescription();
// Retrieve the CGDirectDisplayID associated with this screen
//
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for `deviceDescription` for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description
.get(key)
.expect("failed getting screen display id from device description");
let obj: *const AnyObject = obj;
let obj: *const NSNumber = obj.cast();
let obj: &NSNumber = unsafe { &*obj };
obj.as_u32()
})
}

View File

@@ -21,6 +21,7 @@ use core_foundation::runloop::{
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use icrate::Foundation::MainThreadMarker;
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where
@@ -36,7 +37,8 @@ where
// However we want to keep that weak reference around after the function.
std::mem::forget(info_from_raw);
stop_app_on_panic(Weak::clone(&panic_info), move || {
let mtm = MainThreadMarker::new().unwrap();
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
let _ = &panic_info;
f(panic_info.0)
});

View File

@@ -1,25 +1,27 @@
#![allow(clippy::unnecessary_cast)]
use std::boxed::Box;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::ptr::NonNull;
use std::ptr;
use icrate::Foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
use icrate::AppKit::{
NSApplication, NSCursor, NSEvent, NSEventPhaseBegan, NSEventPhaseCancelled,
NSEventPhaseChanged, NSEventPhaseEnded, NSEventPhaseMayBegin, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView,
};
use icrate::Foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize,
NSString, NSUInteger,
};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{Id, WeakId};
use objc2::runtime::{AnyObject, Sel};
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
use super::{
appkit::{
NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag,
NSView,
},
event::{code_to_key, code_to_location},
use objc2::{
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
};
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{code_to_key, code_to_location};
use super::event::{lalt_pressed, ralt_pressed};
use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{
@@ -49,7 +51,7 @@ impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
cursor: Default::default(),
cursor: default_cursor(),
}
}
}
@@ -140,93 +142,50 @@ pub struct ViewState {
marked_text: RefCell<Id<NSMutableAttributedString>>,
accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view
_ns_window: WeakId<WinitWindow>,
}
declare_class!(
#[derive(Debug)]
#[allow(non_snake_case)]
pub(super) struct WinitView {
// Weak reference because the window keeps a strong reference to the view
_ns_window: IvarDrop<Box<WeakId<WinitWindow>>, "__ns_window">,
state: IvarDrop<Box<ViewState>, "_state">,
}
mod ivars;
pub(super) struct WinitView;
unsafe impl ClassType for WinitView {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
unsafe impl WinitView {
#[method(initWithId:acceptsFirstMouse:)]
unsafe fn init_with_id(
this: *mut Self,
window: &WinitWindow,
accepts_first_mouse: bool,
) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
this.map(|this| {
let state = ViewState {
accepts_first_mouse,
..Default::default()
};
Ivar::write(
&mut this._ns_window,
Box::new(WeakId::new(&window.retain())),
);
Ivar::write(&mut this.state, Box::new(state));
this.setPostsFrameChangedNotifications(true);
let notification_center: &AnyObject =
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
// About frame change
let frame_did_change_notification_name =
NSString::from_str("NSViewFrameDidChangeNotification");
#[allow(clippy::let_unit_value)]
unsafe {
let _: () = msg_send![
notification_center,
addObserver: &*this,
selector: sel!(frameDidChange:),
name: &*frame_did_change_notification_name,
object: &*this,
];
}
*this.state.input_source.borrow_mut() = this.current_input_source();
NonNull::from(this)
})
}
impl DeclaredClass for WinitView {
type Ivars = ViewState;
}
unsafe impl WinitView {
#[method(viewDidMoveToWindow)]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.state.tracking_rect.take() {
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false);
self.state.tracking_rect.set(Some(tracking_rect));
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
}
#[method(frameDidChange:)]
fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:");
if let Some(tracking_rect) = self.state.tracking_rect.take() {
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false);
self.state.tracking_rect.set(Some(tracking_rect));
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
@@ -241,7 +200,7 @@ declare_class!(
trace_scope!("drawRect:");
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
if let Some(window) = self._ns_window.load() {
if let Some(window) = self.ivars()._ns_window.load() {
AppState::handle_redraw(WindowId(window.id()));
}
@@ -270,12 +229,12 @@ declare_class!(
fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects");
let bounds = self.bounds();
let cursor_state = self.state.cursor_state.borrow();
let cursor_state = self.ivars().cursor_state.borrow();
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
if cursor_state.visible {
self.addCursorRect(bounds, &cursor_state.cursor);
self.addCursorRect_cursor(bounds, &cursor_state.cursor);
} else {
self.addCursorRect(bounds, &NSCursor::invisible());
self.addCursorRect_cursor(bounds, &invisible_cursor());
}
}
}
@@ -284,13 +243,13 @@ declare_class!(
#[method(hasMarkedText)]
fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText");
self.state.marked_text.borrow().length() > 0
self.ivars().marked_text.borrow().length() > 0
}
#[method(markedRange)]
fn marked_range(&self) -> NSRange {
trace_scope!("markedRange");
let length = self.state.marked_text.borrow().length();
let length = self.ivars().marked_text.borrow().length();
if length > 0 {
NSRange::new(0, length)
} else {
@@ -333,19 +292,19 @@ declare_class!(
};
// Update marked text.
*self.state.marked_text.borrow_mut() = marked_text;
*self.ivars().marked_text.borrow_mut() = marked_text;
// Notify IME is active if application still doesn't know it.
if self.state.ime_state.get() == ImeState::Disabled {
*self.state.input_source.borrow_mut() = self.current_input_source();
if self.ivars().ime_state.get() == ImeState::Disabled {
*self.ivars().input_source.borrow_mut() = self.current_input_source();
self.queue_event(WindowEvent::Ime(Ime::Enabled));
}
if self.hasMarkedText() {
self.state.ime_state.set(ImeState::Preedit);
if unsafe { self.hasMarkedText() } {
self.ivars().ime_state.set(ImeState::Preedit);
} else {
// In case the preedit was cleared, set IME into the Ground state.
self.state.ime_state.set(ImeState::Ground);
self.ivars().ime_state.set(ImeState::Ground);
}
// Empty string basically means that there's no preedit, so indicate that by sending
@@ -363,15 +322,15 @@ declare_class!(
#[method(unmarkText)]
fn unmark_text(&self) {
trace_scope!("unmarkText");
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
let input_context = self.inputContext().expect("input context");
input_context.discardMarkedText();
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
if self.is_ime_enabled() {
// Leave the Preedit self.state
self.state.ime_state.set(ImeState::Ground);
// Leave the Preedit self.ivars()
self.ivars().ime_state.set(ImeState::Ground);
} else {
warn!("Expected to have IME enabled when receiving unmarkText");
}
@@ -410,9 +369,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.ivars().ime_position.get().x;
let y = base_y - self.ivars().ime_position.get().y;
let LogicalSize { width, height } = self.ivars().ime_size.get();
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
}
@@ -434,10 +393,10 @@ declare_class!(
let is_control = string.chars().next().map_or(false, |c| c.is_control());
// Commit only if we have marked text.
if self.hasMarkedText() && self.is_ime_enabled() && !is_control {
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.state.ime_state.set(ImeState::Commited);
self.ivars().ime_state.set(ImeState::Commited);
}
}
@@ -449,15 +408,15 @@ declare_class!(
// We shouldn't forward any character from just commited text, since we'll end up sending
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
// which is not desired given it was used to confirm IME input.
if self.state.ime_state.get() == ImeState::Commited {
if self.ivars().ime_state.get() == ImeState::Commited {
return;
}
self.state.forward_key_to_app.set(true);
self.ivars().forward_key_to_app.set(true);
if self.hasMarkedText() && self.state.ime_state.get() == ImeState::Preedit {
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit {
// Leave preedit so that we also report the key-up for this key.
self.state.ime_state.set(ImeState::Ground);
self.ivars().ime_state.set(ImeState::Ground);
}
}
}
@@ -467,19 +426,19 @@ declare_class!(
fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:");
{
let mut prev_input_source = self.state.input_source.borrow_mut();
let mut prev_input_source = self.ivars().input_source.borrow_mut();
let current_input_source = self.current_input_source();
if *prev_input_source != current_input_source && self.is_ime_enabled() {
*prev_input_source = current_input_source;
drop(prev_input_source);
self.state.ime_state.set(ImeState::Disabled);
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
}
// Get the characters from the event.
let old_ime_state = self.state.ime_state.get();
self.state.forward_key_to_app.set(false);
let old_ime_state = self.ivars().ime_state.get();
self.ivars().forward_key_to_app.set(false);
let event = replace_event(event, self.window().option_as_alt());
// The `interpretKeyEvents` function might call
@@ -488,32 +447,32 @@ declare_class!(
// we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application)
if self.state.ime_allowed.get() {
if self.ivars().ime_allowed.get() {
let events_for_nsview = NSArray::from_slice(&[&*event]);
unsafe { self.interpretKeyEvents(&events_for_nsview) };
// If the text was commited we must treat the next keyboard event as IME related.
if self.state.ime_state.get() == ImeState::Commited {
if self.ivars().ime_state.get() == ImeState::Commited {
// Remove any marked text, so normal input can continue.
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
}
}
self.update_modifiers(&event, false);
let had_ime_input = match self.state.ime_state.get() {
let had_ime_input = match self.ivars().ime_state.get() {
ImeState::Commited => {
// Allow normal input after the commit.
self.state.ime_state.set(ImeState::Ground);
self.ivars().ime_state.set(ImeState::Ground);
true
}
ImeState::Preedit => true,
// `key_down` could result in preedit clear, so compare old and current state.
_ => old_ime_state != self.state.ime_state.get(),
_ => old_ime_state != self.ivars().ime_state.get(),
};
if !had_ime_input || self.state.forward_key_to_app.get() {
let key_event = create_key_event(&event, true, event.is_a_repeat(), None);
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: key_event,
@@ -531,7 +490,7 @@ declare_class!(
// We want to send keyboard input when we are currently in the ground state.
if matches!(
self.state.ime_state.get(),
self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput {
@@ -575,14 +534,15 @@ declare_class!(
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[method(cancelOperation:)]
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:");
let event = NSApp()
let event = NSApplication::sharedApplication(mtm)
.currentEvent()
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, event.is_a_repeat(), None);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
@@ -679,8 +639,8 @@ declare_class!(
self.mouse_motion(event);
let delta = {
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
if event.hasPreciseScrollingDeltas() {
let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) };
if unsafe { event.hasPreciseScrollingDeltas() } {
let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor());
MouseScrollDelta::PixelDelta(delta)
} else {
@@ -692,18 +652,19 @@ declare_class!(
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
// report the touch phase.
let phase = match event.momentumPhase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } {
NSEventPhaseMayBegin | NSEventPhaseBegan => {
TouchPhase::Started
}
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
NSEventPhaseEnded | NSEventPhaseCancelled => {
TouchPhase::Ended
}
_ => match event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
_ => match unsafe { event.phase() } {
NSEventPhaseMayBegin | NSEventPhaseBegan => {
TouchPhase::Started
}
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
NSEventPhaseEnded | NSEventPhaseCancelled => {
TouchPhase::Ended
}
_ => TouchPhase::Moved,
@@ -724,17 +685,18 @@ declare_class!(
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
let phase = match event.phase() {
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } {
NSEventPhaseBegan => TouchPhase::Started,
NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhaseEnded => TouchPhase::Ended,
_ => return,
};
self.queue_event(WindowEvent::TouchpadMagnify {
device_id: DEVICE_ID,
delta: event.magnification(),
delta: unsafe { event.magnification() },
phase,
});
}
@@ -752,17 +714,18 @@ declare_class!(
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
let phase = match event.phase() {
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } {
NSEventPhaseBegan => TouchPhase::Started,
NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhaseEnded => TouchPhase::Ended,
_ => return,
};
self.queue_event(WindowEvent::TouchpadRotate {
device_id: DEVICE_ID,
delta: event.rotation(),
delta: unsafe { event.rotation() },
phase,
});
}
@@ -775,8 +738,8 @@ declare_class!(
self.queue_event(WindowEvent::TouchpadPressure {
device_id: DEVICE_ID,
pressure: event.pressure(),
stage: event.stage() as i64,
pressure: unsafe { event.pressure() },
stage: unsafe { event.stage() } as i64,
});
}
@@ -792,20 +755,41 @@ declare_class!(
#[method(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:");
self.state.accepts_first_mouse
self.ivars().accepts_first_mouse
}
}
);
impl WinitView {
pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id<Self> {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState {
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
..Default::default()
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true);
let notification_center: &AnyObject =
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
// About frame change
let frame_did_change_notification_name =
NSString::from_str("NSViewFrameDidChangeNotification");
#[allow(clippy::let_unit_value)]
unsafe {
msg_send_id![
Self::alloc(),
initWithId: window,
acceptsFirstMouse: accepts_first_mouse,
]
let _: () = msg_send![
notification_center,
addObserver: &*this,
selector: sel!(frameDidChange:),
name: &*frame_did_change_notification_name,
object: &*this,
];
}
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Id<WinitWindow> {
@@ -814,7 +798,10 @@ impl WinitView {
// (which is incompatible with `frameDidChange:`)
//
// unsafe { msg_send_id![self, window] }
self._ns_window.load().expect("view to have a window")
self.ivars()
._ns_window
.load()
.expect("view to have a window")
}
fn window_id(&self) -> WindowId {
@@ -842,7 +829,7 @@ impl WinitView {
}
fn is_ime_enabled(&self) -> bool {
!matches!(self.state.ime_state.get(), ImeState::Disabled)
!matches!(self.ivars().ime_state.get(), ImeState::Disabled)
}
fn current_input_source(&self) -> String {
@@ -854,7 +841,7 @@ impl WinitView {
}
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) {
let mut cursor_state = self.state.cursor_state.borrow_mut();
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
cursor_state.cursor = icon;
}
@@ -862,7 +849,7 @@ impl WinitView {
///
/// Returns whether the state changed.
pub(super) fn set_cursor_visible(&self, visible: bool) -> bool {
let mut cursor_state = self.state.cursor_state.borrow_mut();
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
if visible != cursor_state.visible {
cursor_state.visible = visible;
true
@@ -872,19 +859,19 @@ impl WinitView {
}
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
if self.state.ime_allowed.get() == ime_allowed {
if self.ivars().ime_allowed.get() == ime_allowed {
return;
}
self.state.ime_allowed.set(ime_allowed);
if self.state.ime_allowed.get() {
self.ivars().ime_allowed.set(ime_allowed);
if self.ivars().ime_allowed.get() {
return;
}
// Clear markedText
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
if self.state.ime_state.get() != ImeState::Disabled {
self.state.ime_state.set(ImeState::Disabled);
if self.ivars().ime_state.get() != ImeState::Disabled {
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
}
@@ -894,17 +881,17 @@ impl WinitView {
position: LogicalPosition<f64>,
size: LogicalSize<f64>,
) {
self.state.ime_position.set(position);
self.state.ime_size.set(size);
self.ivars().ime_position.set(position);
self.ivars().ime_size.set(size);
let input_context = self.inputContext().expect("input context");
input_context.invalidateCharacterCoordinates();
}
/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
pub(super) fn reset_modifiers(&self) {
if !self.state.modifiers.get().state().is_empty() {
self.state.modifiers.set(Modifiers::default());
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
if !self.ivars().modifiers.get().state().is_empty() {
self.ivars().modifiers.set(Modifiers::default());
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
}
}
@@ -913,8 +900,8 @@ impl WinitView {
use ElementState::{Pressed, Released};
let current_modifiers = event_mods(ns_event);
let prev_modifiers = self.state.modifiers.get();
self.state.modifiers.set(current_modifiers);
let prev_modifiers = self.ivars().modifiers.get();
self.ivars().modifiers.set(current_modifiers);
// This function was called form the flagsChanged event, which is triggered
// when the user presses/releases a modifier even if the same kind of modifier
@@ -925,8 +912,8 @@ impl WinitView {
// later will work though, since the flags are attached to the event and contain valid
// information.
'send_event: {
if is_flags_changed_event && ns_event.key_code() != 0 {
let scancode = ns_event.key_code();
if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = PhysicalKey::from_scancode(scancode as u32);
// We'll correct the `is_press` later.
@@ -943,7 +930,7 @@ impl WinitView {
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 mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state
.entry(key)
.or_insert(ModLocationMask::empty());
@@ -1021,7 +1008,7 @@ impl WinitView {
return;
}
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
}
fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
@@ -1037,7 +1024,7 @@ impl WinitView {
}
fn mouse_motion(&self, event: &NSEvent) {
let window_point = event.locationInWindow();
let window_point = unsafe { event.locationInWindow() };
let view_point = self.convertPoint_fromView(window_point, None);
let view_rect = self.frame();
@@ -1046,7 +1033,7 @@ impl WinitView {
|| view_point.x > view_rect.size.width
|| view_point.y > view_rect.size.height
{
let mouse_buttons_down = NSEvent::pressedMouseButtons();
let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() };
if mouse_buttons_down == 0 {
// Point is outside of the client area (view) and no buttons are pressed
return;
@@ -1073,7 +1060,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
// For the other events, it's always set to 0.
// MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons,
// but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications.
match event.buttonNumber() {
match unsafe { event.buttonNumber() } {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
@@ -1089,30 +1076,35 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> {
let ev_mods = event_mods(event).state;
let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
OptionAsAlt::Both if ev_mods.alt_key() => true,
_ => false,
} && !ev_mods.control_key()
&& !ev_mods.super_key();
if ignore_alt_characters {
let ns_chars = event
.charactersIgnoringModifiers()
.expect("expected characters to be non-null");
let ns_chars = unsafe {
event
.charactersIgnoringModifiers()
.expect("expected characters to be non-null")
};
NSEvent::keyEventWithType(
event.type_(),
event.locationInWindow(),
event.modifierFlags(),
event.timestamp(),
event.window_number(),
None,
&ns_chars,
&ns_chars,
event.is_a_repeat(),
event.key_code(),
)
unsafe {
NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
event.r#type(),
event.locationInWindow(),
event.modifierFlags(),
event.timestamp(),
event.windowNumber(),
None,
&ns_chars,
&ns_chars,
event.isARepeat(),
event.keyCode(),
)
.unwrap()
}
} else {
event.copy()
}

View File

@@ -3,8 +3,6 @@
use std::collections::VecDeque;
use std::f64;
use std::ops;
use std::os::raw::c_void;
use std::ptr::NonNull;
use std::sync::{Mutex, MutexGuard};
use crate::{
@@ -17,14 +15,13 @@ use crate::{
platform::macos::{OptionAsAlt, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
appkit::NSWindowOrderingMode,
event_loop::EventLoopWindowTarget,
ffi,
monitor::{self, MonitorHandle, VideoMode},
util,
view::WinitView,
window_delegate::WinitWindowDelegate,
Fullscreen, OsError,
Fullscreen, OsError, PlatformCustomCursor,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -32,22 +29,30 @@ use crate::{
},
};
use core_graphics::display::{CGDisplay, CGPoint};
use icrate::AppKit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
NSApplicationPresentationAutoHideDock, NSApplicationPresentationAutoHideMenuBar,
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSBackingStoreBuffered,
NSColor, NSCriticalRequest, NSFilenamesPboardType, NSInformationalRequest, NSResponder,
NSScreen, NSView, NSWindow, NSWindowAbove, NSWindowCloseButton, NSWindowFullScreenButton,
NSWindowLevel, NSWindowMiniaturizeButton, NSWindowSharingNone, NSWindowSharingReadOnly,
NSWindowStyleMask, NSWindowStyleMaskBorderless, NSWindowStyleMaskClosable,
NSWindowStyleMaskFullSizeContentView, NSWindowStyleMaskMiniaturizable,
NSWindowStyleMaskResizable, NSWindowStyleMaskTitled, NSWindowTabbingModePreferred,
NSWindowTitleHidden, NSWindowZoomButton,
};
use icrate::Foundation::{
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint,
NSRect, NSSize, NSString,
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSObject, NSPoint, NSRect,
NSSize, NSString,
};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{autoreleasepool, Id};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use super::appkit::{
NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen,
NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility,
};
use super::ffi::CGSMainConnectionID;
use super::ffi::CGSSetWindowBackgroundBlurRadius;
use super::cursor::cursor_from_icon;
use super::ffi::kCGFloatingWindowLevel;
use super::ffi::{kCGNormalWindowLevel, CGSMainConnectionID, CGSSetWindowBackgroundBlurRadius};
use super::monitor::get_display_id;
pub(crate) struct Window {
window: MainThreadBound<Id<WinitWindow>>,
@@ -58,7 +63,7 @@ pub(crate) struct Window {
impl Drop for Window {
fn drop(&mut self) {
self.window
.get_on_main(|window, _| autoreleasepool(|_| window.close()))
.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
@@ -70,7 +75,8 @@ impl Window {
) -> Result<Self, RootOsError> {
let mtm = MainThreadMarker::new()
.expect("windows can only be created on the main thread on macOS");
let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
let (window, _delegate) =
autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs, mtm))?;
Ok(Window {
window: MainThreadBound::new(window, mtm),
_delegate: MainThreadBound::new(_delegate, mtm),
@@ -86,7 +92,19 @@ impl Window {
&self,
f: impl FnOnce(&WinitWindow) -> R + Send,
) -> R {
self.window.get_on_main(|window, _mtm| f(window))
self.window.get_on_main(|window| f(window))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.window.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
}
@@ -111,6 +129,54 @@ impl From<u64> for WindowId {
}
}
#[derive(Debug)]
pub(crate) struct OwnedWindowHandle {
ns_view: MainThreadBound<Id<NSView>>,
}
impl Clone for OwnedWindowHandle {
fn clone(&self) -> Self {
Self {
ns_view: MainThreadMarker::run_on_main(|mtm| {
MainThreadBound::new(self.ns_view.get(mtm).clone(), mtm)
}),
}
}
}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
let mtm =
MainThreadMarker::new().expect("can only have handles on macOS on the main thread");
let ns_view = match handle.as_raw() {
rwh_06::RawWindowHandle::AppKit(handle) => {
// SAFETY: Taking `WindowHandle<'_>` ensures that the pointer is valid.
// Unwrap is fine, since the pointer comes from `NonNull`.
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap()
}
handle => panic!("invalid window handle {handle:?} on macOS"),
};
Self {
ns_view: MainThreadBound::new(ns_view, mtm),
}
}
#[cfg(feature = "rwh_06")]
pub(crate) fn raw_window_handle(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
let window_handle = rwh_06::AppKitWindowHandle::new({
let ptr = Id::as_ptr(self.ns_view.get(mtm)) as *mut _;
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
});
let handle = rwh_06::RawWindowHandle::AppKit(window_handle);
Ok(handle)
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub movable_by_window_background: bool,
@@ -147,52 +213,17 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
declare_class!(
#[derive(Debug)]
pub struct WinitWindow {
// TODO: Fix unnecessary boxing here
shared_state: IvarDrop<Box<Mutex<SharedState>>, "_shared_state">,
}
mod ivars;
pub struct WinitWindow;
unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)]
type Super = NSWindow;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
unsafe impl WinitWindow {
#[method(initWithContentRect:styleMask:state:)]
unsafe fn init(
this: *mut Self,
frame: NSRect,
mask: NSWindowStyleMask,
state: *mut c_void,
) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe {
msg_send![
super(this),
initWithContentRect: frame,
styleMask: mask,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
};
this.map(|this| {
// SAFETY: The pointer originally came from `Box::into_raw`.
Ivar::write(&mut this.shared_state, unsafe {
Box::from_raw(state as *mut Mutex<SharedState>)
});
// It is imperative to correct memory management that we
// disable the extra release that would otherwise happen when
// calling `clone` on the window.
this.setReleasedWhenClosed(false);
NonNull::from(this)
})
}
impl DeclaredClass for WinitWindow {
type Ivars = Mutex<SharedState>;
}
unsafe impl WinitWindow {
@@ -293,6 +324,7 @@ impl WinitWindow {
fn new(
attrs: WindowAttributes,
pl_attrs: PlatformSpecificWindowBuilderAttributes,
mtm: MainThreadMarker,
) -> Result<(Id<Self>, Id<WinitWindowDelegate>), RootOsError> {
trace_scope!("WinitWindow::new");
@@ -300,15 +332,15 @@ impl WinitWindow {
let screen = match attrs.fullscreen.0.clone().map(Into::into) {
Some(Fullscreen::Borderless(Some(monitor)))
| Some(Fullscreen::Exclusive(VideoMode { monitor, .. })) => {
monitor.ns_screen().or_else(NSScreen::main)
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
}
Some(Fullscreen::Borderless(None)) => NSScreen::main(),
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
None => None,
};
let frame = match &screen {
Some(screen) => screen.frame(),
None => {
let scale_factor = NSScreen::main()
let scale_factor = NSScreen::mainScreen(mtm)
.map(|screen| screen.backingScaleFactor() as f64)
.unwrap_or(1.0);
let (width, height) = match attrs.inner_size {
@@ -338,31 +370,31 @@ impl WinitWindow {
// if decorations is set to false, ignore pl_attrs
//
// if the titlebar is hidden, ignore other pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
NSWindowStyleMaskBorderless
| NSWindowStyleMaskResizable
| NSWindowStyleMaskMiniaturizable
} else {
// default case, resizable window with titlebar and titlebar buttons
NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask
NSWindowStyleMaskClosable
| NSWindowStyleMaskMiniaturizable
| NSWindowStyleMaskResizable
| NSWindowStyleMaskTitled
};
if !attrs.resizable {
masks &= !NSWindowStyleMask::NSResizableWindowMask;
masks &= !NSWindowStyleMaskResizable;
}
if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) {
masks &= !NSWindowStyleMask::NSMiniaturizableWindowMask;
masks &= !NSWindowStyleMaskMiniaturizable;
}
if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) {
masks &= !NSWindowStyleMask::NSClosableWindowMask;
masks &= !NSWindowStyleMaskClosable;
}
if pl_attrs.fullsize_content_view {
masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
masks |= NSWindowStyleMaskFullSizeContentView;
}
let state = SharedState {
@@ -372,18 +404,23 @@ impl WinitWindow {
..Default::default()
};
// Pass the state through FFI to the method declared on the class
let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast();
let this = mtm.alloc().set_ivars(Mutex::new(state));
let this: Option<Id<Self>> = unsafe {
msg_send_id![
WinitWindow::alloc(),
super(this),
initWithContentRect: frame,
styleMask: masks,
state: state_ptr,
backing: NSBackingStoreBuffered,
defer: false,
]
};
let this = this?;
// It is very important for correct memory management that we
// disable the extra release that would otherwise happen when
// calling `close` on the window.
unsafe { this.setReleasedWhenClosed(false) };
let resize_increments = match attrs
.resize_increments
.map(|i| i.to_logical::<f64>(this.scale_factor()))
@@ -401,26 +438,26 @@ impl WinitWindow {
if let Some(identifier) = pl_attrs.tabbing_identifier {
this.setTabbingIdentifier(&NSString::from_str(&identifier));
this.setTabbingMode(NSWindowTabbingMode::NSWindowTabbingModePreferred);
this.setTabbingMode(NSWindowTabbingModePreferred);
}
if attrs.content_protected {
this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
this.setSharingType(NSWindowSharingNone);
}
if pl_attrs.titlebar_transparent {
this.setTitlebarAppearsTransparent(true);
}
if pl_attrs.title_hidden {
this.setTitleVisibility(NSWindowTitleVisibility::Hidden);
this.setTitleVisibility(NSWindowTitleHidden);
}
if pl_attrs.titlebar_buttons_hidden {
for titlebar_button in &[
#[allow(deprecated)]
NSWindowButton::FullScreen,
NSWindowButton::Miniaturize,
NSWindowButton::Close,
NSWindowButton::Zoom,
NSWindowFullScreenButton,
NSWindowMiniaturizeButton,
NSWindowCloseButton,
NSWindowZoomButton,
] {
if let Some(button) = this.standardWindowButton(*titlebar_button) {
button.setHidden(true);
@@ -432,7 +469,7 @@ impl WinitWindow {
}
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) {
if let Some(button) = this.standardWindowButton(NSWindowZoomButton) {
button.setEnabled(false);
}
}
@@ -450,25 +487,16 @@ impl WinitWindow {
})
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?;
#[cfg(feature = "rwh_06")]
match attrs.parent_window.0 {
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
// SAFETY: Caller ensures the pointer is valid or NULL
// Unwrap is fine, since the pointer comes from `NonNull`.
let parent_view: Id<NSView> =
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
let parent = parent_view.window().ok_or_else(|| {
os_error!(OsError::CreationError(
"parent view should be installed in a window"
))
})?;
if let Some(parent_window) = attrs.parent_window {
let parent = parent_window.ns_view.get(mtm).window().ok_or_else(|| {
os_error!(OsError::CreationError(
"parent view should be installed in a window"
))
})?;
// SAFETY: We know that there are no parent -> child -> parent cycles since the only place in `winit`
// where we allow making a window a child window is right here, just after it's been created.
unsafe { parent.addChildWindow(&this, NSWindowOrderingMode::NSWindowAbove) };
}
Some(raw) => panic!("Invalid raw window handle {raw:?} on macOS"),
None => (),
// SAFETY: We know that there are no parent -> child -> parent cycles since the only place in `winit`
// where we allow making a window a child window is right here, just after it's been created.
unsafe { parent.addChildWindow_ordered(&this, NSWindowAbove) };
}
let view = WinitView::new(&this, pl_attrs.accepts_first_mouse);
@@ -476,6 +504,7 @@ impl WinitWindow {
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
// macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid
// always the default system value in favour of the user's code
#[allow(deprecated)]
view.setWantsBestResolutionOpenGLSurface(!pl_attrs.disallow_hidpi);
// On Mojave, views automatically become layer-backed shortly after being added to
@@ -483,17 +512,17 @@ impl WinitWindow {
// the view and its associated OpenGL context. To work around this, on Mojave we
// explicitly make the view layer-backed up front so that AppKit doesn't do it
// itself and break the association with its context.
if NSAppKitVersion::current().floor() > NSAppKitVersion::NSAppKitVersionNumber10_12 {
if unsafe { NSAppKitVersionNumber }.floor() > NSAppKitVersionNumber10_12 {
view.setWantsLayer(true);
}
// Configure the new view as the "key view" for the window
this.setContentView(&view);
this.setInitialFirstResponder(&view);
this.setContentView(Some(&view));
this.setInitialFirstResponder(Some(&view));
if attrs.transparent {
this.setOpaque(false);
this.setBackgroundColor(&NSColor::clear());
this.setBackgroundColor(Some(unsafe { &NSColor::clearColor() }));
}
if attrs.blur {
@@ -516,13 +545,13 @@ impl WinitWindow {
match attrs.preferred_theme {
Some(theme) => {
set_ns_theme(Some(theme));
set_ns_theme(Some(theme), mtm);
let mut state = this.lock_shared_state("WinitWindow::new");
state.current_theme = Some(theme);
}
None => {
let mut state = this.lock_shared_state("WinitWindow::new");
state.current_theme = Some(get_ns_theme());
state.current_theme = Some(get_ns_theme(mtm));
}
}
@@ -554,9 +583,10 @@ impl WinitWindow {
Ok((this, delegate))
}
#[track_caller]
pub(super) fn view(&self) -> Id<WinitView> {
// SAFETY: The view inside WinitWindow is always `WinitView`
unsafe { Id::cast(self.contentView()) }
unsafe { Id::cast(self.contentView().unwrap()) }
}
#[track_caller]
@@ -564,14 +594,14 @@ impl WinitWindow {
&self,
called_from_fn: &'static str,
) -> SharedStateMutexGuard<'_> {
SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn)
SharedStateMutexGuard::new(self.ivars().lock().unwrap(), called_from_fn)
}
fn set_style_mask(&self, mask: NSWindowStyleMask) {
self.setStyleMask(mask);
// If we don't do this, key handling will break
// (at least until the window is clicked again/etc.)
let _ = self.makeFirstResponder(Some(&self.contentView()));
let _ = self.makeFirstResponder(Some(&self.contentView().unwrap()));
}
}
@@ -592,7 +622,7 @@ impl WinitWindow {
// 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();
let window_number = unsafe { self.windowNumber() };
unsafe {
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius);
}
@@ -645,7 +675,7 @@ impl WinitWindow {
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
let frame = self.contentView().frame();
let frame = self.contentView().unwrap().frame();
let logical: LogicalSize<f64> = (frame.size.width as f64, frame.size.height as f64).into();
let scale_factor = self.scale_factor();
logical.to_physical(scale_factor)
@@ -767,9 +797,9 @@ impl WinitWindow {
if !fullscreen {
let mut mask = self.styleMask();
if resizable {
mask |= NSWindowStyleMask::NSResizableWindowMask;
mask |= NSWindowStyleMaskResizable;
} else {
mask &= !NSWindowStyleMask::NSResizableWindowMask;
mask &= !NSWindowStyleMaskResizable;
}
self.set_style_mask(mask);
}
@@ -786,15 +816,15 @@ impl WinitWindow {
let mut mask = self.styleMask();
if buttons.contains(WindowButtons::CLOSE) {
mask |= NSWindowStyleMask::NSClosableWindowMask;
mask |= NSWindowStyleMaskClosable;
} else {
mask &= !NSWindowStyleMask::NSClosableWindowMask;
mask &= !NSWindowStyleMaskClosable;
}
if buttons.contains(WindowButtons::MINIMIZE) {
mask |= NSWindowStyleMask::NSMiniaturizableWindowMask;
mask |= NSWindowStyleMaskMiniaturizable;
} else {
mask &= !NSWindowStyleMask::NSMiniaturizableWindowMask;
mask &= !NSWindowStyleMaskMiniaturizable;
}
// This must happen before the button's "enabled" status has been set,
@@ -804,7 +834,7 @@ impl WinitWindow {
// We edit the button directly instead of using `NSResizableWindowMask`,
// since that mask also affect the resizability of the window (which is
// controllable by other means in `winit`).
if let Some(button) = self.standardWindowButton(NSWindowButton::Zoom) {
if let Some(button) = self.standardWindowButton(NSWindowZoomButton) {
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
}
}
@@ -816,7 +846,7 @@ impl WinitWindow {
buttons |= WindowButtons::MINIMIZE;
}
if self
.standardWindowButton(NSWindowButton::Zoom)
.standardWindowButton(NSWindowZoomButton)
.map(|b| b.isEnabled())
.unwrap_or(true)
{
@@ -830,7 +860,14 @@ impl WinitWindow {
pub fn set_cursor_icon(&self, icon: CursorIcon) {
let view = self.view();
view.set_cursor_icon(NSCursor::from_icon(icon));
view.set_cursor_icon(cursor_from_icon(icon));
self.invalidateCursorRectsForView(&view);
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
let view = self.view();
view.set_cursor_icon(cursor.0.clone());
self.invalidateCursorRectsForView(&view);
}
@@ -883,8 +920,11 @@ impl WinitWindow {
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
let event = NSApp().currentEvent();
self.performWindowDragWithEvent(event.as_deref());
let mtm = MainThreadMarker::from(self);
let event = NSApplication::sharedApplication(mtm)
.currentEvent()
.unwrap();
self.performWindowDragWithEvent(&event);
Ok(())
}
@@ -907,9 +947,8 @@ impl WinitWindow {
// we make it resizable temporalily.
let curr_mask = self.styleMask();
let required =
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
let needs_temp_mask = !curr_mask.contains(required);
let required = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
let needs_temp_mask = !mask_contains(curr_mask, required);
if needs_temp_mask {
self.set_style_mask(required);
}
@@ -930,9 +969,9 @@ impl WinitWindow {
.take()
.unwrap_or_else(|| self.styleMask());
if shared_state.resizable {
base_mask | NSWindowStyleMask::NSResizableWindowMask
base_mask | NSWindowStyleMaskResizable
} else {
base_mask & !NSWindowStyleMask::NSResizableWindowMask
base_mask & !NSWindowStyleMaskResizable
}
}
@@ -963,7 +1002,7 @@ impl WinitWindow {
if minimized {
self.miniaturize(Some(self));
} else {
self.deminiaturize(Some(self));
unsafe { self.deminiaturize(Some(self)) };
}
}
@@ -974,6 +1013,7 @@ impl WinitWindow {
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let mtm = MainThreadMarker::from(self);
let is_zoomed = self.is_zoomed();
if is_zoomed == maximized {
return;
@@ -992,17 +1032,14 @@ impl WinitWindow {
return;
}
if self
.styleMask()
.contains(NSWindowStyleMask::NSResizableWindowMask)
{
if mask_contains(self.styleMask(), NSWindowStyleMaskResizable) {
drop(shared_state);
// Just use the native zoom if resizable
self.zoom(None);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if maximized {
let screen = NSScreen::main().expect("no screen found");
let screen = NSScreen::mainScreen(mtm).expect("no screen found");
screen.visibleFrame()
} else {
shared_state.saved_standard_frame()
@@ -1025,6 +1062,9 @@ impl WinitWindow {
#[inline]
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
let mut shared_state_lock = self.lock_shared_state("set_fullscreen");
if shared_state_lock.is_simple_fullscreen {
return;
@@ -1056,7 +1096,7 @@ impl WinitWindow {
}
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
}
.ns_screen()
.ns_screen(mtm)
.unwrap();
let old_screen = self.screen().unwrap();
@@ -1087,7 +1127,6 @@ impl WinitWindow {
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) {
let app = NSApp();
let mut shared_state_lock = self.lock_shared_state("set_fullscreen");
shared_state_lock.save_presentation_opts = Some(app.presentationOptions());
}
@@ -1145,7 +1184,7 @@ impl WinitWindow {
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
window.setLevel(NSWindowLevel::Normal);
window.setLevel(kCGNormalWindowLevel as NSWindowLevel);
window.toggleFullScreen(None);
}
@@ -1155,9 +1194,8 @@ impl WinitWindow {
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
let curr_mask = self.styleMask();
let required = NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
let required = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
if !mask_contains(curr_mask, required) {
self.set_style_mask(required);
self.lock_shared_state("set_fullscreen").saved_style = Some(curr_mask);
}
@@ -1186,30 +1224,27 @@ impl WinitWindow {
// of the menu bar, and this looks broken, so we must make sure
// that the menu bar is disabled. This is done in the window
// delegate in `window:willUseFullScreenPresentationOptions:`.
let app = NSApp();
self.lock_shared_state("set_fullscreen")
.save_presentation_opts = Some(app.presentationOptions());
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
let presentation_options = NSApplicationPresentationFullScreen
| NSApplicationPresentationHideDock
| NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options);
let window_level =
NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1);
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
self.setLevel(window_level);
}
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
let presentation_options = self
.lock_shared_state("set_fullscreen")
.save_presentation_opts
.unwrap_or_else(|| {
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
});
NSApp().setPresentationOptions(presentation_options);
.unwrap_or(
NSApplicationPresentationFullScreen
| NSApplicationPresentationAutoHideDock
| NSApplicationPresentationAutoHideMenuBar,
);
app.setPresentationOptions(presentation_options);
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
@@ -1221,7 +1256,7 @@ impl WinitWindow {
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
self.setLevel(NSWindowLevel::Normal);
self.setLevel(kCGNormalWindowLevel as NSWindowLevel);
}
_ => {}
};
@@ -1249,15 +1284,15 @@ impl WinitWindow {
let new_mask = {
let mut new_mask = if decorations {
NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask
NSWindowStyleMaskClosable
| NSWindowStyleMaskMiniaturizable
| NSWindowStyleMaskResizable
| NSWindowStyleMaskTitled
} else {
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable
};
if !resizable {
new_mask &= !NSWindowStyleMask::NSResizableWindowMask;
new_mask &= !NSWindowStyleMaskResizable;
}
new_mask
};
@@ -1272,9 +1307,9 @@ impl WinitWindow {
#[inline]
pub fn set_window_level(&self, level: WindowLevel) {
let level = match level {
WindowLevel::AlwaysOnTop => NSWindowLevel::Floating,
WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL,
WindowLevel::Normal => NSWindowLevel::Normal,
WindowLevel::AlwaysOnTop => kCGFloatingWindowLevel as NSWindowLevel,
WindowLevel::AlwaysOnBottom => (kCGNormalWindowLevel - 1) as NSWindowLevel,
WindowLevel::Normal => kCGNormalWindowLevel as NSWindowLevel,
};
self.setLevel(level);
}
@@ -1309,30 +1344,33 @@ impl WinitWindow {
#[inline]
pub fn focus_window(&self) {
let mtm = MainThreadMarker::from(self);
let is_minimized = self.isMiniaturized();
let is_visible = self.isVisible();
if !is_minimized && is_visible {
NSApp().activateIgnoringOtherApps(true);
#[allow(deprecated)]
NSApplication::sharedApplication(mtm).activateIgnoringOtherApps(true);
self.makeKeyAndOrderFront(None);
}
}
#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let mtm = MainThreadMarker::from(self);
let ns_request_type = request_type.map(|ty| match ty {
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
UserAttentionType::Critical => NSCriticalRequest,
UserAttentionType::Informational => NSInformationalRequest,
});
if let Some(ty) = ns_request_type {
NSApp().requestUserAttention(ty);
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
}
}
#[inline]
// Allow directly accessing the current monitor internally without unwrapping.
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
let display_id = self.screen()?.display_id();
let display_id = get_display_id(&*self.screen()?);
Some(MonitorHandle::new(display_id))
}
@@ -1357,7 +1395,7 @@ impl WinitWindow {
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::AppKitHandle::empty();
window_handle.ns_window = self as *const Self as *mut _;
window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _;
window_handle.ns_view = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
rwh_04::RawWindowHandle::AppKit(window_handle)
}
@@ -1366,7 +1404,7 @@ impl WinitWindow {
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::AppKitWindowHandle::empty();
window_handle.ns_window = self as *const Self as *mut _;
window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _;
window_handle.ns_view = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
rwh_05::RawWindowHandle::AppKit(window_handle)
}
@@ -1378,12 +1416,12 @@ impl WinitWindow {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
let window_handle = rwh_06::AppKitWindowHandle::new({
let ptr = Id::as_ptr(&self.contentView()) as *mut _;
let ptr = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
});
Ok(rwh_06::RawWindowHandle::AppKit(window_handle))
rwh_06::RawWindowHandle::AppKit(window_handle)
}
#[cfg(feature = "rwh_06")]
@@ -1416,21 +1454,24 @@ impl WinitWindow {
}
pub fn set_theme(&self, theme: Option<Theme>) {
set_ns_theme(theme);
self.lock_shared_state("set_theme").current_theme = theme.or_else(|| Some(get_ns_theme()));
let mtm = MainThreadMarker::from(self);
set_ns_theme(theme, mtm);
self.lock_shared_state("set_theme").current_theme =
theme.or_else(|| Some(get_ns_theme(mtm)));
}
#[inline]
pub fn set_content_protected(&self, protected: bool) {
self.setSharingType(if protected {
NSWindowSharingType::NSWindowSharingNone
NSWindowSharingNone
} else {
NSWindowSharingType::NSWindowSharingReadOnly
NSWindowSharingReadOnly
})
}
pub fn title(&self) -> String {
self.title_().to_string()
let window: &NSWindow = self;
window.title().to_string()
}
pub fn reset_dead_keys(&self) {
@@ -1447,9 +1488,10 @@ impl WindowExtMacOS for WinitWindow {
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
let mtm = MainThreadMarker::from(self);
let mut shared_state_lock = self.lock_shared_state("set_simple_fullscreen");
let app = NSApp();
let app = NSApplication::sharedApplication(mtm);
let is_native_fullscreen = shared_state_lock.fullscreen.is_some();
let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen;
@@ -1477,20 +1519,19 @@ impl WindowExtMacOS for WinitWindow {
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar;
app.setPresentationOptions(presentation_options);
// Hide the titlebar
self.toggle_style_mask(NSWindowStyleMask::NSTitledWindowMask, false);
self.toggle_style_mask(NSWindowStyleMaskTitled, false);
// Set the window frame to the screen frame size
let screen = self.screen().expect("expected screen to be available");
self.setFrame_display(screen.frame(), true);
// Fullscreen windows can't be resized, minimized, or moved
self.toggle_style_mask(NSWindowStyleMask::NSMiniaturizableWindowMask, false);
self.toggle_style_mask(NSWindowStyleMask::NSResizableWindowMask, false);
self.toggle_style_mask(NSWindowStyleMaskMiniaturizable, false);
self.toggle_style_mask(NSWindowStyleMaskResizable, false);
self.setMovable(false);
true
@@ -1539,24 +1580,20 @@ impl WindowExtMacOS for WinitWindow {
#[inline]
fn select_next_tab(&self) {
if let Some(group) = self.tabGroup() {
group.selectNextTab();
}
self.selectNextTab(None)
}
#[inline]
fn select_previous_tab(&self) {
if let Some(group) = self.tabGroup() {
group.selectPreviousTab()
}
unsafe { self.selectPreviousTab(None) }
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
if let Some(group) = self.tabGroup() {
if let Some(windows) = group.tabbedWindows() {
if let Some(windows) = unsafe { self.tabbedWindows() } {
if index < windows.len() {
group.setSelectedWindow(&windows[index]);
group.setSelectedWindow(Some(&windows[index]));
}
}
}
@@ -1564,8 +1601,7 @@ impl WindowExtMacOS for WinitWindow {
#[inline]
fn num_tabs(&self) -> usize {
self.tabGroup()
.and_then(|group| group.tabbedWindows())
unsafe { self.tabbedWindows() }
.map(|windows| windows.len())
.unwrap_or(1)
}
@@ -1589,25 +1625,31 @@ impl WindowExtMacOS for WinitWindow {
}
}
pub(super) fn get_ns_theme() -> Theme {
let app = NSApp();
fn mask_contains(mask: NSWindowStyleMask, value: NSWindowStyleMask) -> bool {
mask & value == value
}
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
let app = NSApplication::sharedApplication(mtm);
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
if !has_theme {
return Theme::Light;
}
let appearance = app.effectiveAppearance();
let name = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
NSString::from_str("NSAppearanceNameAqua"),
NSString::from_str("NSAppearanceNameDarkAqua"),
]));
let name = appearance
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
NSString::from_str("NSAppearanceNameAqua"),
NSString::from_str("NSAppearanceNameDarkAqua"),
]))
.unwrap();
match &*name.to_string() {
"NSAppearanceNameDarkAqua" => Theme::Dark,
_ => Theme::Light,
}
}
fn set_ns_theme(theme: Option<Theme>) {
let app = NSApp();
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
let app = NSApplication::sharedApplication(mtm);
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
if has_theme {
let appearance = theme.map(|t| {
@@ -1615,7 +1657,7 @@ fn set_ns_theme(theme: Option<Theme>) {
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
};
NSAppearance::appearanceNamed(&name)
NSAppearance::appearanceNamed(&name).unwrap()
});
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
}

View File

@@ -1,16 +1,19 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::ptr::{self, NonNull};
use std::ptr;
use icrate::Foundation::{NSArray, NSObject, NSSize, NSString};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::AnyObject;
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
use super::appkit::{
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
use icrate::AppKit::{
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSDraggingDestination,
NSFilenamesPboardType, NSPasteboard, NSWindowDelegate, NSWindowOcclusionStateVisible,
};
use icrate::Foundation::{MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSSize, NSString};
use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
};
use super::{
app_state::AppState,
util,
@@ -24,7 +27,9 @@ use crate::{
};
#[derive(Debug)]
pub struct State {
pub(crate) struct State {
window: Id<WinitWindow>,
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: Cell<bool>,
@@ -37,72 +42,21 @@ pub struct State {
}
declare_class!(
#[derive(Debug)]
pub(crate) struct WinitWindowDelegate {
window: IvarDrop<Id<WinitWindow>, "_window">,
// TODO: It may be possible for delegate methods to be called
// asynchronously, causing data races panics?
// TODO: Remove unnecessary boxing here
state: IvarDrop<Box<State>, "_state">,
}
mod ivars;
pub(crate) struct WinitWindowDelegate;
unsafe impl ClassType for WinitWindowDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindowDelegate";
}
unsafe impl WinitWindowDelegate {
#[method(initWithWindow:initialFullscreen:)]
unsafe fn init_with_winit(
this: *mut Self,
window: &WinitWindow,
initial_fullscreen: bool,
) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
this.map(|this| {
let scale_factor = window.scale_factor();
Ivar::write(&mut this.window, window.retain());
Ivar::write(
&mut this.state,
Box::new(State {
initial_fullscreen: Cell::new(initial_fullscreen),
previous_position: Cell::new(None),
previous_scale_factor: Cell::new(scale_factor),
}),
);
if scale_factor != 1.0 {
this.queue_static_scale_factor_changed_event();
}
this.window.setDelegate(Some(this));
// Enable theme change event
let notification_center: Id<AnyObject> =
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
let notification_name =
NSString::from_str("AppleInterfaceThemeChangedNotification");
let _: () = unsafe {
msg_send![
&notification_center,
addObserver: &*this
selector: sel!(effectiveAppearanceDidChange:)
name: &*notification_name
object: ptr::null::<AnyObject>()
]
};
NonNull::from(this)
})
}
impl DeclaredClass for WinitWindowDelegate {
type Ivars = State;
}
// NSWindowDelegate + NSDraggingDestination protocols
unsafe impl WinitWindowDelegate {
unsafe impl NSObjectProtocol for WinitWindowDelegate {}
unsafe impl NSWindowDelegate for WinitWindowDelegate {
#[method(windowShouldClose:)]
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
trace_scope!("windowShouldClose:");
@@ -117,7 +71,7 @@ declare_class!(
autoreleasepool(|_| {
// Since El Capitan, we need to be careful that delegate methods can't
// be called after the window closes.
self.window.setDelegate(None);
self.ivars().window.setDelegate(None);
});
self.queue_event(WindowEvent::Destroyed);
}
@@ -134,16 +88,19 @@ declare_class!(
trace_scope!("windowWillStartLiveResize:");
let increments = self
.ivars()
.window
.lock_shared_state("window_will_enter_fullscreen")
.resize_increments;
self.window.set_resize_increments_inner(increments);
self.ivars().window.set_resize_increments_inner(increments);
}
#[method(windowDidEndLiveResize:)]
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEndLiveResize:");
self.window.set_resize_increments_inner(NSSize::new(1., 1.));
self.ivars()
.window
.set_resize_increments_inner(NSSize::new(1., 1.));
}
// This won't be triggered if the move was part of a resize.
@@ -177,11 +134,183 @@ declare_class!(
// NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus.
self.window.view().reset_modifiers();
self.ivars().window.view().reset_modifiers();
self.queue_event(WindowEvent::Focused(false));
}
/// Invoked when before enter fullscreen
#[method(windowWillEnterFullScreen:)]
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillEnterFullScreen:");
let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_will_enter_fullscreen");
shared_state.maximized = self.ivars().window.is_zoomed();
let fullscreen = shared_state.fullscreen.as_ref();
match fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (),
// `window_will_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state
Some(Fullscreen::Borderless(_)) => (),
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => {
let current_monitor = self.ivars().window.current_monitor_inner();
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
}
}
shared_state.in_fullscreen_transition = true;
}
/// Invoked when before exit fullscreen
#[method(windowWillExitFullScreen:)]
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:");
let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_will_exit_fullscreen");
shared_state.in_fullscreen_transition = true;
}
#[method(window:willUseFullScreenPresentationOptions:)]
fn window_will_use_fullscreen_presentation_options(
&self,
_: Option<&AnyObject>,
proposed_options: NSApplicationPresentationOptions,
) -> NSApplicationPresentationOptions {
trace_scope!("window:willUseFullScreenPresentationOptions:");
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
// placed on top of the menu bar in exclusive fullscreen mode. This looks
// broken so we always disable the menu bar in exclusive fullscreen. We may
// still want to make this configurable for borderless fullscreen. Right now
// we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen.
let mut options = proposed_options;
let shared_state = self
.ivars()
.window
.lock_shared_state("window_will_use_fullscreen_presentation_options");
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
options = NSApplicationPresentationFullScreen
| NSApplicationPresentationHideDock
| NSApplicationPresentationHideMenuBar;
}
options
}
/// Invoked when entered fullscreen
#[method(windowDidEnterFullScreen:)]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:");
self.ivars().initial_fullscreen.set(false);
let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_did_enter_fullscreen");
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
self.ivars().window.set_fullscreen(target_fullscreen);
}
}
/// Invoked when exited fullscreen
#[method(windowDidExitFullScreen:)]
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:");
self.ivars().window.restore_state_from_fullscreen();
let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_did_exit_fullscreen");
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
self.ivars().window.set_fullscreen(target_fullscreen);
}
}
/// Invoked when fail to enter fullscreen
///
/// When this window launch from a fullscreen app (e.g. launch from VS Code
/// terminal), it creates a new virtual destkop and a transition animation.
/// This animation takes one second and cannot be disable without
/// elevated privileges. In this animation time, all toggleFullscreen events
/// will be failed. In this implementation, we will try again by using
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
/// was set).
///
/// From Apple doc:
/// In some cases, the transition to enter full-screen mode can fail,
/// due to being in the midst of handling some other animation or user gesture.
/// This method indicates that there was an error, and you should clean up any
/// work you may have done to prepare to enter full-screen mode.
#[method(windowDidFailToEnterFullScreen:)]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:");
let mut shared_state = self
.ivars()
.window
.lock_shared_state("window_did_fail_to_enter_fullscreen");
shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None;
if self.ivars().initial_fullscreen.get() {
#[allow(clippy::let_unit_value)]
unsafe {
let _: () = msg_send![
&*self.ivars().window,
performSelector: sel!(toggleFullScreen:),
withObject: ptr::null::<AnyObject>(),
afterDelay: 0.5,
];
};
} else {
self.ivars().window.restore_state_from_fullscreen();
}
}
// Invoked when the occlusion state of the window changes
#[method(windowDidChangeOcclusionState:)]
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeOcclusionState:");
let visible = self.ivars().window.occlusionState() & NSWindowOcclusionStateVisible
== NSWindowOcclusionStateVisible;
self.queue_event(WindowEvent::Occluded(!visible));
}
#[method(windowDidChangeScreen:)]
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self
.ivars()
.window
.lock_shared_state("window_did_change_screen")
.is_simple_fullscreen;
if is_simple_fullscreen {
if let Some(screen) = self.ivars().window.screen() {
self.ivars().window.setFrame_display(screen.frame(), true);
}
}
}
}
unsafe impl NSDraggingDestination for WinitWindowDelegate {
/// Invoked when the dragged image enters destination bounds or frame
#[method(draggingEntered:)]
fn dragging_entered(&self, sender: &NSObject) -> bool {
@@ -190,7 +319,9 @@ declare_class!(
use std::path::PathBuf;
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
let filenames = pb
.propertyListForType(unsafe { NSFilenamesPboardType })
.unwrap();
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
filenames.into_iter().for_each(|file| {
@@ -216,7 +347,9 @@ declare_class!(
use std::path::PathBuf;
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
let filenames = pb
.propertyListForType(unsafe { NSFilenamesPboardType })
.unwrap();
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
filenames.into_iter().for_each(|file| {
@@ -239,153 +372,9 @@ declare_class!(
trace_scope!("draggingExited:");
self.queue_event(WindowEvent::HoveredFileCancelled);
}
}
/// Invoked when before enter fullscreen
#[method(windowWillEnterFullScreen:)]
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillEnterFullScreen:");
let mut shared_state = self
.window
.lock_shared_state("window_will_enter_fullscreen");
shared_state.maximized = self.window.is_zoomed();
let fullscreen = shared_state.fullscreen.as_ref();
match fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (),
// `window_will_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state
Some(Fullscreen::Borderless(_)) => (),
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => {
let current_monitor = self.window.current_monitor_inner();
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
}
}
shared_state.in_fullscreen_transition = true;
}
/// Invoked when before exit fullscreen
#[method(windowWillExitFullScreen:)]
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:");
let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen");
shared_state.in_fullscreen_transition = true;
}
#[method(window:willUseFullScreenPresentationOptions:)]
fn window_will_use_fullscreen_presentation_options(
&self,
_: Option<&AnyObject>,
proposed_options: NSApplicationPresentationOptions,
) -> NSApplicationPresentationOptions {
trace_scope!("window:willUseFullScreenPresentationOptions:");
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
// placed on top of the menu bar in exclusive fullscreen mode. This looks
// broken so we always disable the menu bar in exclusive fullscreen. We may
// still want to make this configurable for borderless fullscreen. Right now
// we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen.
let mut options = proposed_options;
let shared_state = self
.window
.lock_shared_state("window_will_use_fullscreen_presentation_options");
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
}
options
}
/// Invoked when entered fullscreen
#[method(windowDidEnterFullScreen:)]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:");
self.state.initial_fullscreen.set(false);
let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen");
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
self.window.set_fullscreen(target_fullscreen);
}
}
/// Invoked when exited fullscreen
#[method(windowDidExitFullScreen:)]
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:");
self.window.restore_state_from_fullscreen();
let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen");
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
self.window.set_fullscreen(target_fullscreen);
}
}
/// Invoked when fail to enter fullscreen
///
/// When this window launch from a fullscreen app (e.g. launch from VS Code
/// terminal), it creates a new virtual destkop and a transition animation.
/// This animation takes one second and cannot be disable without
/// elevated privileges. In this animation time, all toggleFullscreen events
/// will be failed. In this implementation, we will try again by using
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
/// was set).
///
/// From Apple doc:
/// In some cases, the transition to enter full-screen mode can fail,
/// due to being in the midst of handling some other animation or user gesture.
/// This method indicates that there was an error, and you should clean up any
/// work you may have done to prepare to enter full-screen mode.
#[method(windowDidFailToEnterFullScreen:)]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:");
let mut shared_state = self
.window
.lock_shared_state("window_did_fail_to_enter_fullscreen");
shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None;
if self.state.initial_fullscreen.get() {
#[allow(clippy::let_unit_value)]
unsafe {
let _: () = msg_send![
&*self.window,
performSelector: sel!(toggleFullScreen:),
withObject: ptr::null::<AnyObject>(),
afterDelay: 0.5,
];
};
} else {
self.window.restore_state_from_fullscreen();
}
}
// Invoked when the occlusion state of the window changes
#[method(windowDidChangeOcclusionState:)]
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeOcclusionState:");
self.queue_event(WindowEvent::Occluded(
!self
.window
.occlusionState()
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
))
}
unsafe impl WinitWindowDelegate {
// Observe theme change
#[method(effectiveAppearanceDidChange:)]
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
@@ -402,8 +391,10 @@ declare_class!(
#[method(effectiveAppearanceDidChangedOnMainThread:)]
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
let theme = get_ns_theme();
let mtm = MainThreadMarker::from(self);
let theme = get_ns_theme(mtm);
let mut shared_state = self
.ivars()
.window
.lock_shared_state("effective_appearance_did_change");
let current_theme = shared_state.current_theme;
@@ -413,71 +404,80 @@ declare_class!(
self.queue_event(WindowEvent::ThemeChanged(theme));
}
}
#[method(windowDidChangeScreen:)]
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self
.window
.lock_shared_state("window_did_change_screen")
.is_simple_fullscreen;
if is_simple_fullscreen {
if let Some(screen) = self.window.screen() {
self.window.setFrame_display(screen.frame(), true);
}
}
}
}
);
impl WinitWindowDelegate {
pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self> {
unsafe {
msg_send_id![
Self::alloc(),
initWithWindow: window,
initialFullscreen: initial_fullscreen,
]
let mtm = MainThreadMarker::from(window);
let scale_factor = window.scale_factor();
let this = mtm.alloc().set_ivars(State {
window: window.retain(),
initial_fullscreen: Cell::new(initial_fullscreen),
previous_position: Cell::new(None),
previous_scale_factor: Cell::new(scale_factor),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
if scale_factor != 1.0 {
this.queue_static_scale_factor_changed_event();
}
window.setDelegate(Some(ProtocolObject::from_ref(&*this)));
// Enable theme change event
let notification_center: Id<AnyObject> =
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification");
let _: () = unsafe {
msg_send![
&notification_center,
addObserver: &*this
selector: sel!(effectiveAppearanceDidChange:)
name: &*notification_name
object: ptr::null::<AnyObject>()
]
};
this
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let event = Event::WindowEvent {
window_id: WindowId(self.window.id()),
window_id: WindowId(self.ivars().window.id()),
event,
};
AppState::queue_event(event);
}
fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.window.scale_factor();
if scale_factor == self.state.previous_scale_factor.get() {
let scale_factor = self.ivars().window.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
self.state.previous_scale_factor.set(scale_factor);
self.ivars().previous_scale_factor.set(scale_factor);
let suggested_size = self.view_size();
AppState::queue_static_scale_factor_changed_event(
self.window.clone(),
self.ivars().window.clone(),
suggested_size.to_physical(scale_factor),
scale_factor,
);
}
fn emit_move_event(&self) {
let rect = self.window.frame();
let rect = self.ivars().window.frame();
let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect);
if self.state.previous_position.get() != Some((x, y)) {
self.state.previous_position.set(Some((x, y)));
let scale_factor = self.window.scale_factor();
if self.ivars().previous_position.get() != Some((x, y)) {
self.ivars().previous_position.set(Some((x, y)));
let scale_factor = self.ivars().window.scale_factor();
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
self.queue_event(WindowEvent::Moved(physical_pos));
}
}
fn view_size(&self) -> LogicalSize<f64> {
let size = self.window.contentView().frame().size;
let size = self.ivars().window.contentView().unwrap().frame().size;
LogicalSize::new(size.width as f64, size.height as f64)
}
}

View File

@@ -9,7 +9,7 @@ use crate::dpi::{PhysicalPosition, PhysicalSize};
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
mod event_loop;
pub use self::window::Window;
pub(crate) use self::window::{OwnedWindowHandle, Window};
mod window;
struct RedoxSocket {
@@ -193,6 +193,8 @@ impl Display for OsError {
}
}
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

View File

@@ -12,8 +12,8 @@ use crate::{
};
use super::{
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
TimeSocket, WindowId, WindowProperties,
EventLoopWindowTarget, MonitorHandle, PlatformCustomCursor,
PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties,
};
// These values match the values uses in the `window_new` function in orbital:
@@ -25,6 +25,18 @@ const ORBITAL_FLAG_BORDERLESS: char = 'l';
const ORBITAL_FLAG_RESIZABLE: char = 'r';
const ORBITAL_FLAG_TRANSPARENT: char = 't';
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
// Parent windows are currently unsupported, though owned window
// handles would be implementable.
Self {}
}
}
pub struct Window {
window_socket: Arc<RedoxSocket>,
redraws: Arc<Mutex<VecDeque<WindowId>>>,
@@ -352,6 +364,8 @@ impl Window {
#[inline]
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(

View File

@@ -6,65 +6,67 @@ use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError
use std::sync::{Arc, Mutex};
use std::task::Poll;
// NOTE: This channel doesn't wake up when all senders or receivers are
// dropped. This is acceptable as long as it's only used in `Dispatcher`, which
// has it's own `Drop` behavior.
pub fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
let (sender, receiver) = mpsc::channel();
let sender = Arc::new(Mutex::new(sender));
let inner = Arc::new(Inner {
let shared = Arc::new(Shared {
closed: AtomicBool::new(false),
waker: AtomicWaker::new(),
});
let sender = AsyncSender {
sender,
inner: Arc::clone(&inner),
};
let sender = AsyncSender(Arc::new(SenderInner {
sender: Mutex::new(sender),
shared: Arc::clone(&shared),
}));
let receiver = AsyncReceiver {
receiver: Rc::new(receiver),
inner,
shared,
};
(sender, receiver)
}
pub struct AsyncSender<T> {
pub struct AsyncSender<T>(Arc<SenderInner<T>>);
struct SenderInner<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need
// to wrap it in an `Arc` to make it clonable on the main thread without
// to wrap `Sender` in an `Arc` to make it clonable on the main thread without
// having to block.
sender: Arc<Mutex<Sender<T>>>,
inner: Arc<Inner>,
sender: Mutex<Sender<T>>,
shared: Arc<Shared>,
}
impl<T> AsyncSender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.sender.lock().unwrap().send(event)?;
self.inner.waker.wake();
self.0.sender.lock().unwrap().send(event)?;
self.0.shared.waker.wake();
Ok(())
}
}
pub fn close(&self) {
self.inner.closed.store(true, Ordering::Relaxed);
self.inner.waker.wake()
impl<T> SenderInner<T> {
fn close(&self) {
self.shared.closed.store(true, Ordering::Relaxed);
self.shared.waker.wake();
}
}
impl<T> Clone for AsyncSender<T> {
fn clone(&self) -> Self {
Self {
sender: Arc::clone(&self.sender),
inner: Arc::clone(&self.inner),
}
Self(Arc::clone(&self.0))
}
}
impl<T> Drop for SenderInner<T> {
fn drop(&mut self) {
self.close();
}
}
pub struct AsyncReceiver<T> {
receiver: Rc<Receiver<T>>,
inner: Arc<Inner>,
shared: Arc<Shared>,
}
impl<T> AsyncReceiver<T> {
@@ -72,12 +74,12 @@ impl<T> AsyncReceiver<T> {
future::poll_fn(|cx| match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => {
self.inner.waker.register(cx.waker());
self.shared.waker.register(cx.waker());
match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => {
if self.inner.closed.load(Ordering::Relaxed) {
if self.shared.closed.load(Ordering::Relaxed) {
Poll::Ready(Err(RecvError))
} else {
Poll::Pending
@@ -104,12 +106,18 @@ impl<T> Clone for AsyncReceiver<T> {
fn clone(&self) -> Self {
Self {
receiver: Rc::clone(&self.receiver),
inner: Arc::clone(&self.inner),
shared: Arc::clone(&self.shared),
}
}
}
struct Inner {
impl<T> Drop for AsyncReceiver<T> {
fn drop(&mut self) {
self.shared.closed.store(true, Ordering::Relaxed);
}
}
struct Shared {
closed: AtomicBool,
waker: AtomicWaker,
}

View File

@@ -82,12 +82,6 @@ impl<T> Dispatcher<T> {
}
}
impl<T> Drop for Dispatcher<T> {
fn drop(&mut self) {
self.0.with_sender_data(|sender| sender.close())
}
}
pub struct DispatchRunner<T: 'static> {
wrapper: Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>,
receiver: AsyncReceiver<Closure<T>>,

View File

@@ -3,7 +3,7 @@ mod dispatcher;
mod waker;
mod wrapper;
use self::channel::{channel, AsyncReceiver, AsyncSender};
pub use self::channel::{channel, AsyncReceiver, AsyncSender};
pub use self::dispatcher::{DispatchRunner, Dispatcher};
pub use self::waker::{Waker, WakerSpawner};
use self::wrapper::Wrapper;

View File

@@ -0,0 +1,579 @@
use std::{
cell::RefCell,
future,
hash::{Hash, Hasher},
mem,
ops::DerefMut,
rc::{self, Rc},
sync::{self, Arc},
task::{Poll, Waker},
};
use crate::{
cursor::{BadImage, CursorImage},
platform_impl::platform::r#async,
};
use cursor_icon::CursorIcon;
use once_cell::sync::Lazy;
use wasm_bindgen::{closure::Closure, JsCast};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
Blob, Document, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions,
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
};
use self::thread_safe::ThreadSafe;
use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget};
#[derive(Debug)]
pub(crate) enum CustomCursorBuilder {
Image(CursorImage),
Url {
url: String,
hotspot_x: u16,
hotspot_y: u16,
},
}
impl CustomCursorBuilder {
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorBuilder, BadImage> {
Ok(CustomCursorBuilder::Image(CursorImage::from_rgba(
rgba, width, height, hotspot_x, hotspot_y,
)?))
}
}
#[derive(Clone, Debug)]
pub struct CustomCursor(Arc<Inner>);
impl Hash for CustomCursor {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for CustomCursor {}
impl CustomCursor {
pub(crate) fn build<T>(
builder: CustomCursorBuilder,
window_target: &EventLoopWindowTarget<T>,
) -> Self {
Lazy::force(&DROP_HANDLER);
Self(match builder {
CustomCursorBuilder::Image(image) => ImageState::from_rgba(
window_target.runner.window(),
window_target.runner.document().clone(),
&image,
),
CustomCursorBuilder::Url {
url,
hotspot_x,
hotspot_y,
} => ImageState::from_url(url, hotspot_x, hotspot_y),
})
}
}
#[derive(Debug)]
struct Inner(Option<ThreadSafe<RefCell<ImageState>>>);
static DROP_HANDLER: Lazy<AsyncSender<ThreadSafe<RefCell<ImageState>>>> = Lazy::new(|| {
let (sender, receiver) = r#async::channel();
wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} });
sender
});
impl Inner {
fn new() -> Arc<Self> {
Arc::new(Inner(Some(ThreadSafe::new(RefCell::new(
ImageState::Loading(None),
)))))
}
fn get(&self) -> &RefCell<ImageState> {
self.0
.as_ref()
.expect("value has accidently already been dropped")
.get()
}
}
impl Drop for Inner {
fn drop(&mut self) {
let value = self
.0
.take()
.expect("value has accidently already been dropped");
if !value.in_origin_thread() {
DROP_HANDLER
.send(value)
.expect("sender dropped in main thread")
}
}
}
#[derive(Debug)]
pub struct CursorState(Rc<RefCell<State>>);
impl CursorState {
pub fn new(style: Style) -> Self {
Self(Rc::new(RefCell::new(State {
style,
visible: true,
cursor: SelectedCursor::default(),
})))
}
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let mut this = self.0.borrow_mut();
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
state.take();
}
}
this.cursor = SelectedCursor::Named(cursor);
this.set_style();
}
pub(crate) fn set_custom_cursor(&self, cursor: CustomCursor) {
let mut this = self.0.borrow_mut();
match cursor.0.get().borrow_mut().deref_mut() {
ImageState::Loading(state) => {
this.cursor = SelectedCursor::ImageLoading {
state: cursor.0.clone(),
previous: mem::take(&mut this.cursor).into(),
};
*state = Some(Rc::downgrade(&self.0));
}
ImageState::Failed => log::error!("tried to load invalid cursor"),
ImageState::Ready(image) => {
this.cursor = SelectedCursor::ImageReady(image.clone());
this.set_style();
}
}
}
pub fn set_cursor_visible(&self, visible: bool) {
let mut state = self.0.borrow_mut();
if !visible && state.visible {
state.visible = false;
state.style.set("cursor", "none");
} else if visible && !state.visible {
state.visible = true;
state.set_style();
}
}
}
#[derive(Debug)]
struct State {
style: Style,
visible: bool,
cursor: SelectedCursor,
}
impl State {
pub fn set_style(&self) {
if self.visible {
let value = match &self.cursor {
SelectedCursor::Named(icon) => icon.name(),
SelectedCursor::ImageLoading { previous, .. } => previous.style(),
SelectedCursor::ImageReady(image) => &image.style,
};
self.style.set("cursor", value);
}
}
}
#[derive(Debug)]
enum SelectedCursor {
Named(CursorIcon),
ImageLoading {
state: Arc<Inner>,
previous: Previous,
},
ImageReady(Rc<Image>),
}
impl Default for SelectedCursor {
fn default() -> Self {
Self::Named(Default::default())
}
}
impl From<Previous> for SelectedCursor {
fn from(previous: Previous) -> Self {
match previous {
Previous::Named(icon) => Self::Named(icon),
Previous::Image(image) => Self::ImageReady(image),
}
}
}
#[derive(Debug)]
pub enum Previous {
Named(CursorIcon),
Image(Rc<Image>),
}
impl Previous {
fn style(&self) -> &str {
match self {
Previous::Named(icon) => icon.name(),
Previous::Image(image) => &image.style,
}
}
}
impl From<SelectedCursor> for Previous {
fn from(value: SelectedCursor) -> Self {
match value {
SelectedCursor::Named(icon) => Self::Named(icon),
SelectedCursor::ImageLoading { previous, .. } => previous,
SelectedCursor::ImageReady(image) => Self::Image(image),
}
}
}
#[derive(Debug)]
enum ImageState {
Loading(Option<rc::Weak<RefCell<State>>>),
Failed,
Ready(Rc<Image>),
}
impl ImageState {
fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc<Inner> {
// 1. Create an `ImageData` from the RGBA data.
// 2. Create an `ImageBitmap` from the `ImageData`.
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
// 4. Create a `Blob` from the `HTMLCanvasElement`.
// 5. Create an object URL from the `Blob`.
// 6. Decode the image on an `HTMLImageElement` from the URL.
// 7. Change the `CursorState` if queued.
// 1. Create an `ImageData` from the RGBA data.
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
#[cfg(target_feature = "atomics")]
// Can't share `SharedArrayBuffer` with `ImageData`.
let result = {
use js_sys::{Uint8Array, Uint8ClampedArray};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = ImageData)]
type ImageDataExt;
#[wasm_bindgen(catch, constructor, js_class = ImageData)]
fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;
}
let array = Uint8Array::new_with_length(image.rgba.len() as u32);
array.copy_from(&image.rgba);
let array = Uint8ClampedArray::new(&array);
ImageDataExt::new(array, image.width as u32)
.map(JsValue::from)
.map(ImageData::unchecked_from_js)
};
#[cfg(not(target_feature = "atomics"))]
let result = ImageData::new_with_u8_clamped_array(
wasm_bindgen::Clamped(&image.rgba),
image.width as u32,
);
let image_data = result.expect("found wrong image size");
// 2. Create an `ImageBitmap` from the `ImageData`.
//
// We call `createImageBitmap()` before spawning the future,
// to not have to clone the image buffer.
let mut options = ImageBitmapOptions::new();
options.premultiply_alpha(PremultiplyAlpha::None);
let bitmap = JsFuture::from(
window
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)
.expect("unexpected exception in `createImageBitmap()`"),
);
let this = Inner::new();
wasm_bindgen_futures::spawn_local({
let weak = Arc::downgrade(&this);
let CursorImage {
width,
height,
hotspot_x,
hotspot_y,
..
} = *image;
async move {
// Keep checking if all references are dropped between every `await` call.
if weak.strong_count() == 0 {
return;
}
let bitmap: ImageBitmap = bitmap
.await
.expect("found invalid state in `ImageData`")
.unchecked_into();
if weak.strong_count() == 0 {
return;
}
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.expect("invalid tag name")
.unchecked_into();
#[allow(clippy::disallowed_methods)]
canvas.set_width(width as u32);
#[allow(clippy::disallowed_methods)]
canvas.set_height(height as u32);
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
let context: ImageBitmapRenderingContext = canvas
.get_context("bitmaprenderer")
.expect("unexpected exception in `HTMLCanvasElement.getContext()`")
.expect("`bitmaprenderer` context unsupported")
.unchecked_into();
context.transfer_from_image_bitmap(&bitmap);
// 4. Create a `Blob` from the `HTMLCanvasElement`.
//
// To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done,
// we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted,
// but it would increase complexity and isn't possible in this case.
// Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately.
let value = Rc::new(RefCell::new(None));
let waker = Rc::new(RefCell::<Option<Waker>>::new(None));
let callback = Closure::once({
let value = value.clone();
let waker = waker.clone();
move |blob: Option<Blob>| {
*value.borrow_mut() = Some(blob);
if let Some(waker) = waker.borrow_mut().take() {
waker.wake();
}
}
});
canvas
.to_blob(callback.as_ref().unchecked_ref())
.expect("failed with `SecurityError` despite only source coming from memory");
let blob = future::poll_fn(|cx| {
if let Some(blob) = value.borrow_mut().take() {
Poll::Ready(blob)
} else {
*waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
})
.await;
let url = {
let Some(this) = weak.upgrade() else {
return;
};
let mut this = this.get().borrow_mut();
let Some(blob) = blob else {
log::error!("creating custom cursor failed");
let ImageState::Loading(state) = this.deref_mut() else {
unreachable!("found invalid state");
};
let state = state.take();
*this = ImageState::Failed;
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
let mut state = state.borrow_mut();
let SelectedCursor::ImageLoading { previous, .. } =
mem::take(&mut state.cursor)
else {
unreachable!("found invalid state");
};
state.cursor = previous.into();
}
return;
};
// 5. Create an object URL from the `Blob`.
Url::create_object_url_with_blob(&blob)
.expect("unexpected exception in `URL.createObjectURL()`")
};
Self::decode(weak, url, true, hotspot_x, hotspot_y).await;
}
});
this
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc<Inner> {
let this = Inner::new();
wasm_bindgen_futures::spawn_local(Self::decode(
Arc::downgrade(&this),
url,
false,
hotspot_x,
hotspot_y,
));
this
}
async fn decode(
weak: sync::Weak<Inner>,
url: String,
object: bool,
hotspot_x: u16,
hotspot_y: u16,
) {
if weak.strong_count() == 0 {
return;
}
// 6. Decode the image on an `HTMLImageElement` from the URL.
let image =
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
image.set_src(&url);
let result = JsFuture::from(image.decode()).await;
let Some(this) = weak.upgrade() else {
return;
};
let mut this = this.get().borrow_mut();
let ImageState::Loading(state) = this.deref_mut() else {
unreachable!("found invalid state");
};
let state = state.take();
if let Err(error) = result {
log::error!("creating custom cursor failed: {error:?}");
*this = ImageState::Failed;
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
let mut state = state.borrow_mut();
let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor)
else {
unreachable!("found invalid state");
};
state.cursor = previous.into();
}
return;
}
let image = Image::new(url, object, image, hotspot_x, hotspot_y);
// 7. Change the `CursorState` if queued.
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
let mut state = state.borrow_mut();
state.cursor = SelectedCursor::ImageReady(image.clone());
state.set_style();
}
*this = ImageState::Ready(image);
}
}
#[derive(Debug)]
pub struct Image {
style: String,
url: String,
object: bool,
_image: HtmlImageElement,
}
impl Drop for Image {
fn drop(&mut self) {
if self.object {
Url::revoke_object_url(&self.url)
.expect("unexpected exception in `URL.revokeObjectURL()`");
}
}
}
impl Image {
fn new(
url: String,
object: bool,
image: HtmlImageElement,
hotspot_x: u16,
hotspot_y: u16,
) -> Rc<Self> {
let style = format!("url({url}) {hotspot_x} {hotspot_y}, auto");
Rc::new(Self {
style,
url,
object,
_image: image,
})
}
}
mod thread_safe {
use std::mem;
use std::thread::{self, ThreadId};
#[derive(Debug)]
pub struct ThreadSafe<T> {
origin_thread: ThreadId,
value: T,
}
impl<T> ThreadSafe<T> {
pub fn new(value: T) -> Self {
Self {
origin_thread: thread::current().id(),
value,
}
}
pub fn get(&self) -> &T {
if self.origin_thread == thread::current().id() {
&self.value
} else {
panic!("value not accessible outside its origin thread")
}
}
pub fn in_origin_thread(&self) -> bool {
self.origin_thread == thread::current().id()
}
}
impl<T> Drop for ThreadSafe<T> {
fn drop(&mut self) {
if mem::needs_drop::<T>() && self.origin_thread != thread::current().id() {
panic!("value can't be dropped outside its origin thread")
}
}
}
unsafe impl<T> Send for ThreadSafe<T> {}
unsafe impl<T> Sync for ThreadSafe<T> {}
}

View File

@@ -67,7 +67,6 @@ pub struct Execution {
on_key_press: OnEventHandle<KeyboardEvent>,
on_key_release: OnEventHandle<KeyboardEvent>,
on_visibility_change: OnEventHandle<web_sys::Event>,
on_touch_end: OnEventHandle<web_sys::Event>,
}
enum RunnerEnum {
@@ -181,7 +180,6 @@ impl Shared {
on_key_press: RefCell::new(None),
on_key_release: RefCell::new(None),
on_visibility_change: RefCell::new(None),
on_touch_end: RefCell::new(None),
}
}))
}
@@ -342,8 +340,6 @@ impl Shared {
self.window().clone(),
"pointerdown",
Closure::new(move |event: PointerEvent| {
runner.transient_activation();
if !runner.device_events() {
return;
}
@@ -367,8 +363,6 @@ impl Shared {
self.window().clone(),
"pointerup",
Closure::new(move |event: PointerEvent| {
runner.transient_activation();
if !runner.device_events() {
return;
}
@@ -392,8 +386,6 @@ impl Shared {
self.window().clone(),
"keydown",
Closure::new(move |event: KeyboardEvent| {
runner.transient_activation();
if !runner.device_events() {
return;
}
@@ -452,14 +444,6 @@ impl Shared {
}
}),
));
let runner = self.clone();
*self.0.on_touch_end.borrow_mut() = Some(EventListenerHandle::new(
self.window().clone(),
"touchend",
Closure::new(move |_| {
runner.transient_activation();
}),
));
}
// Generate a strictly increasing ID
@@ -788,18 +772,6 @@ impl Shared {
}
}
fn transient_activation(&self) {
self.0
.all_canvases
.borrow()
.iter()
.for_each(|(_, canvas, _)| {
if let Some(canvas) = canvas.upgrade() {
canvas.borrow().transient_activation();
}
});
}
pub fn event_loop_recreation(&self, allow: bool) {
self.0.event_loop_recreation.set(allow)
}

View File

@@ -5,8 +5,6 @@ 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},
@@ -85,6 +83,7 @@ impl<T> EventLoopWindowTarget<T> {
) {
let canvas_clone = canvas.clone();
let mut canvas = canvas.borrow_mut();
#[cfg(any(feature = "rwh_04", feature = "rwh_05"))]
canvas.set_attribute("data-raw-handle", &id.0.to_string());
canvas.on_touch_start(prevent_default);
@@ -124,25 +123,6 @@ 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(
@@ -669,8 +649,6 @@ impl<T> EventLoopWindowTarget<T> {
let runner = self.runner.clone();
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
canvas.on_touch_end();
canvas.on_context_menu(prevent_default);
}

View File

@@ -18,6 +18,7 @@
// compliant way.
mod r#async;
mod cursor;
mod device;
mod error;
mod event_loop;
@@ -34,8 +35,12 @@ pub(crate) use self::event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
};
pub use self::monitor::{MonitorHandle, VideoMode};
pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId};
pub(crate) use self::window::{
OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId,
};
pub(crate) use self::keyboard::KeyEventExtra;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
pub(crate) use cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use cursor::CustomCursorBuilder as PlatformCustomCursorBuilder;

View File

@@ -1,5 +1,6 @@
use std::cell::Cell;
use std::rc::{Rc, Weak};
use std::ops::Deref;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use smol_str::SmolStr;
@@ -19,11 +20,10 @@ use crate::window::{WindowAttributes, WindowId as RootWindowId};
use super::super::WindowId;
use super::animation_frame::AnimationFrameHandler;
use super::event_handle::EventListenerHandle;
use super::fullscreen::FullscreenHandler;
use super::intersection_handle::IntersectionObserverHandle;
use super::media_query_handle::MediaQueryListHandle;
use super::pointer::PointerHandler;
use super::{event, ButtonsState, ResizeScaleHandle};
use super::{event, fullscreen, ButtonsState, ResizeScaleHandle};
#[allow(dead_code)]
pub struct Canvas {
@@ -50,11 +50,17 @@ pub struct Common {
pub window: web_sys::Window,
pub document: Document,
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
pub raw: HtmlCanvasElement,
style: CssStyleDeclaration,
/// Note: this is read-only because we use a pointer to this for [`WindowHandle`](rwh_06::WindowHandle).
raw: Rc<HtmlCanvasElement>,
style: Style,
old_size: Rc<Cell<PhysicalSize<u32>>>,
current_size: Rc<Cell<PhysicalSize<u32>>>,
fullscreen_handler: Rc<FullscreenHandler>,
}
#[derive(Clone, Debug)]
pub struct Style {
read: CssStyleDeclaration,
write: CssStyleDeclaration,
}
impl Canvas {
@@ -92,21 +98,15 @@ impl Canvas {
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
}
#[allow(clippy::disallowed_methods)]
let style = window
.get_computed_style(&canvas)
.expect("Failed to obtain computed style")
// this can't fail: we aren't using a pseudo-element
.expect("Invalid pseudo-element");
let style = Style::new(&window, &canvas);
let common = Common {
window: window.clone(),
document: document.clone(),
raw: canvas.clone(),
raw: Rc::new(canvas.clone()),
style,
old_size: Rc::default(),
current_size: Rc::default(),
fullscreen_handler: Rc::new(FullscreenHandler::new(document.clone(), canvas.clone())),
};
if let Some(size) = attr.inner_size {
@@ -130,7 +130,7 @@ impl Canvas {
}
if attr.fullscreen.0.is_some() {
common.fullscreen_handler.request_fullscreen();
fullscreen::request_fullscreen(&document, &canvas);
}
if attr.active {
@@ -181,9 +181,7 @@ impl Canvas {
y: bounds.y(),
};
if self.document().contains(Some(self.raw()))
&& self.style().get_property_value("display").unwrap() != "none"
{
if self.document().contains(Some(self.raw())) && self.style().get("display") != "none" {
position.x += super::style_size_property(self.style(), "border-left-width")
+ super::style_size_property(self.style(), "padding-left");
position.y += super::style_size_property(self.style(), "border-top-width")
@@ -229,7 +227,7 @@ impl Canvas {
}
#[inline]
pub fn style(&self) -> &CssStyleDeclaration {
pub fn style(&self) -> &Style {
&self.common.style
}
@@ -285,7 +283,7 @@ impl Canvas {
where
F: 'static + FnMut(PhysicalKey, Key, Option<SmolStr>, KeyLocation, bool, ModifiersState),
{
self.on_keyboard_press = Some(self.common.add_transient_event(
self.on_keyboard_press = Some(self.common.add_event(
"keydown",
move |event: KeyboardEvent| {
if prevent_default {
@@ -445,10 +443,6 @@ impl Canvas {
self.animation_frame_handler.on_animation_frame(f)
}
pub(crate) fn on_touch_end(&mut self) {
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",
@@ -461,15 +455,15 @@ impl Canvas {
}
pub fn request_fullscreen(&self) {
self.common.fullscreen_handler.request_fullscreen()
fullscreen::request_fullscreen(self.document(), self.raw());
}
pub fn exit_fullscreen(&self) {
self.common.fullscreen_handler.exit_fullscreen()
fullscreen::exit_fullscreen(self.document(), self.raw());
}
pub fn is_fullscreen(&self) -> bool {
self.common.fullscreen_handler.is_fullscreen()
fullscreen::is_fullscreen(self.document(), self.raw())
}
pub fn request_animation_frame(&self) {
@@ -520,10 +514,6 @@ impl Canvas {
}
}
pub(crate) fn transient_activation(&self) {
self.common.fullscreen_handler.transient_activation()
}
pub fn remove_listeners(&mut self) {
self.on_touch_start = None;
self.on_focus = None;
@@ -537,7 +527,6 @@ impl Canvas {
self.on_intersect = None;
self.animation_frame_handler.cancel();
self.on_touch_end = None;
self.common.fullscreen_handler.cancel();
self.on_context_menu = None;
}
}
@@ -552,29 +541,44 @@ impl Common {
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
{
EventListenerHandle::new(self.raw.clone(), event_name, Closure::new(handler))
EventListenerHandle::new(self.raw.deref().clone(), event_name, Closure::new(handler))
}
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
pub fn add_transient_event<E, F>(
&self,
event_name: &'static str,
mut handler: F,
) -> EventListenerHandle<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
{
let fullscreen_handler = Rc::downgrade(&self.fullscreen_handler);
self.add_event(event_name, move |event: E| {
handler(event);
if let Some(fullscreen_handler) = Weak::upgrade(&fullscreen_handler) {
fullscreen_handler.transient_activation()
}
})
pub fn raw(&self) -> &HtmlCanvasElement {
&self.raw
}
}
impl Style {
fn new(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> Self {
#[allow(clippy::disallowed_methods)]
let read = window
.get_computed_style(canvas)
.expect("Failed to obtain computed style")
// this can't fail: we aren't using a pseudo-element
.expect("Invalid pseudo-element");
#[allow(clippy::disallowed_methods)]
let write = canvas.style();
Self { read, write }
}
pub(crate) fn get(&self, property: &str) -> String {
self.read
.get_property_value(property)
.expect("Invalid property")
}
pub(crate) fn remove(&self, property: &str) {
self.write
.remove_property(property)
.expect("Property is read only");
}
pub(crate) fn set(&self, property: &str, value: &str) {
self.write
.set_property(property, value)
.expect("Property is read only");
}
}

View File

@@ -2,8 +2,8 @@ use crate::dpi::LogicalPosition;
use crate::event::{MouseButton, MouseScrollDelta};
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
use once_cell::unsync::OnceCell;
use smol_str::SmolStr;
use std::cell::OnceCell;
use std::convert::TryInto;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
@@ -81,22 +81,9 @@ 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(),
y: event.offset_y(),
x: event.offset_x() as f64,
y: event.offset_y() as f64,
}
}

View File

@@ -1,145 +1,91 @@
use std::cell::Cell;
use std::rc::Rc;
use std::cell::OnceCell;
use js_sys::Promise;
use once_cell::unsync::OnceCell;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{Document, Element, HtmlCanvasElement};
use super::EventListenerHandle;
pub fn request_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
if is_fullscreen(document, canvas) {
return;
}
thread_local! {
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = HtmlCanvasElement)]
type RequestFullscreen;
#[wasm_bindgen(method, js_name = requestFullscreen)]
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
fn webkit_request_fullscreen(this: &RequestFullscreen);
}
let canvas: &RequestFullscreen = canvas.unchecked_ref();
if has_fullscreen_api_support(canvas) {
thread_local! {
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
}
REJECT_HANDLER.with(|handler| {
let _ = canvas.request_fullscreen().catch(handler);
});
} else {
canvas.webkit_request_fullscreen();
}
}
pub struct FullscreenHandler {
document: Document,
canvas: HtmlCanvasElement,
fullscreen_requested: Rc<Cell<bool>>,
_fullscreen_change: EventListenerHandle<dyn FnMut()>,
pub fn is_fullscreen(document: &Document, canvas: &HtmlCanvasElement) -> bool {
#[wasm_bindgen]
extern "C" {
type FullscreenElement;
#[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)]
fn webkit_fullscreen_element(this: &FullscreenElement) -> Option<Element>;
}
let element = if has_fullscreen_api_support(canvas) {
#[allow(clippy::disallowed_methods)]
document.fullscreen_element()
} else {
let document: &FullscreenElement = document.unchecked_ref();
document.webkit_fullscreen_element()
};
match element {
Some(element) => {
let canvas: &Element = canvas;
canvas == &element
}
None => false,
}
}
impl FullscreenHandler {
pub fn new(document: Document, canvas: HtmlCanvasElement) -> Self {
let fullscreen_requested = Rc::new(Cell::new(false));
let fullscreen_change = EventListenerHandle::new(
canvas.clone(),
if has_fullscreen_api_support(&canvas) {
"fullscreenchange"
} else {
"webkitfullscreenchange"
},
Closure::new({
let fullscreen_requested = fullscreen_requested.clone();
move || {
// It doesn't matter if the canvas entered or exitted fullscreen mode,
// we don't want to request it again later.
fullscreen_requested.set(false);
}
}),
);
pub fn exit_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
#[wasm_bindgen]
extern "C" {
type ExitFullscreen;
Self {
document,
canvas,
fullscreen_requested,
_fullscreen_change: fullscreen_change,
}
#[wasm_bindgen(method, js_name = webkitExitFullscreen)]
fn webkit_exit_fullscreen(this: &ExitFullscreen);
}
fn internal_request_fullscreen(&self) {
#[wasm_bindgen]
extern "C" {
type RequestFullscreen;
#[wasm_bindgen(method, js_name = requestFullscreen)]
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
fn webkit_request_fullscreen(this: &RequestFullscreen);
}
let canvas: &RequestFullscreen = self.canvas.unchecked_ref();
if has_fullscreen_api_support(&self.canvas) {
thread_local! {
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
}
REJECT_HANDLER.with(|handler| {
let _ = canvas.request_fullscreen().catch(handler);
});
} else {
canvas.webkit_request_fullscreen();
}
}
pub fn request_fullscreen(&self) {
if !self.is_fullscreen() {
self.internal_request_fullscreen();
self.fullscreen_requested.set(true);
}
}
pub fn transient_activation(&self) {
if self.fullscreen_requested.get() {
self.internal_request_fullscreen()
}
}
pub fn is_fullscreen(&self) -> bool {
#[wasm_bindgen]
extern "C" {
type FullscreenElement;
#[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)]
fn webkit_fullscreen_element(this: &FullscreenElement) -> Option<Element>;
}
let element = if has_fullscreen_api_support(&self.canvas) {
#[allow(clippy::disallowed_methods)]
self.document.fullscreen_element()
} else {
let document: &FullscreenElement = self.document.unchecked_ref();
document.webkit_fullscreen_element()
};
match element {
Some(element) => {
let canvas: &Element = &self.canvas;
canvas == &element
}
None => false,
}
}
pub fn exit_fullscreen(&self) {
#[wasm_bindgen]
extern "C" {
type ExitFullscreen;
#[wasm_bindgen(method, js_name = webkitExitFullscreen)]
fn webkit_exit_fullscreen(this: &ExitFullscreen);
}
if has_fullscreen_api_support(&self.canvas) {
#[allow(clippy::disallowed_methods)]
self.document.exit_fullscreen()
} else {
let document: &ExitFullscreen = self.document.unchecked_ref();
document.webkit_exit_fullscreen()
}
self.fullscreen_requested.set(false);
}
pub fn cancel(&self) {
self.fullscreen_requested.set(false);
if has_fullscreen_api_support(canvas) {
#[allow(clippy::disallowed_methods)]
document.exit_fullscreen()
} else {
let document: &ExitFullscreen = document.unchecked_ref();
document.webkit_exit_fullscreen()
}
}
fn has_fullscreen_api_support(canvas: &HtmlCanvasElement) -> bool {
thread_local! {
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
}
FULLSCREEN_API_SUPPORT.with(|support| {
*support.get_or_init(|| {
#[wasm_bindgen]

View File

@@ -10,6 +10,7 @@ mod resize_scaling;
mod schedule;
pub use self::canvas::Canvas;
pub use self::canvas::Style;
pub use self::event::ButtonsState;
pub use self::event_handle::EventListenerHandle;
pub use self::resize_scaling::ResizeScaleHandle;
@@ -17,9 +18,7 @@ pub use self::schedule::Schedule;
use crate::dpi::{LogicalPosition, LogicalSize};
use wasm_bindgen::closure::Closure;
use web_sys::{
CssStyleDeclaration, Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState,
};
use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
@@ -51,8 +50,8 @@ pub fn scale_factor(window: &web_sys::Window) -> f64 {
window.device_pixel_ratio()
}
fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
if style.get_property_value("box-sizing").unwrap() == "border-box" {
fn fix_canvas_size(style: &Style, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
if style.get("box-sizing") == "border-box" {
size.width += style_size_property(style, "border-left-width")
+ style_size_property(style, "border-right-width")
+ style_size_property(style, "padding-left")
@@ -69,76 +68,68 @@ fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> L
pub fn set_canvas_size(
document: &Document,
raw: &HtmlCanvasElement,
style: &CssStyleDeclaration,
style: &Style,
new_size: LogicalSize<f64>,
) {
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
if !document.contains(Some(raw)) || style.get("display") == "none" {
return;
}
let new_size = fix_canvas_size(style, new_size);
set_canvas_style_property(raw, "width", &format!("{}px", new_size.width));
set_canvas_style_property(raw, "height", &format!("{}px", new_size.height));
style.set("width", &format!("{}px", new_size.width));
style.set("height", &format!("{}px", new_size.height));
}
pub fn set_canvas_min_size(
document: &Document,
raw: &HtmlCanvasElement,
style: &CssStyleDeclaration,
style: &Style,
dimensions: Option<LogicalSize<f64>>,
) {
if let Some(dimensions) = dimensions {
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
if !document.contains(Some(raw)) || style.get("display") == "none" {
return;
}
let new_size = fix_canvas_size(style, dimensions);
set_canvas_style_property(raw, "min-width", &format!("{}px", new_size.width));
set_canvas_style_property(raw, "min-height", &format!("{}px", new_size.height));
style.set("min-width", &format!("{}px", new_size.width));
style.set("min-height", &format!("{}px", new_size.height));
} else {
style
.remove_property("min-width")
.expect("Property is read only");
style
.remove_property("min-height")
.expect("Property is read only");
style.remove("min-width");
style.remove("min-height");
}
}
pub fn set_canvas_max_size(
document: &Document,
raw: &HtmlCanvasElement,
style: &CssStyleDeclaration,
style: &Style,
dimensions: Option<LogicalSize<f64>>,
) {
if let Some(dimensions) = dimensions {
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
if !document.contains(Some(raw)) || style.get("display") == "none" {
return;
}
let new_size = fix_canvas_size(style, dimensions);
set_canvas_style_property(raw, "max-width", &format!("{}px", new_size.width));
set_canvas_style_property(raw, "max-height", &format!("{}px", new_size.height));
style.set("max-width", &format!("{}px", new_size.width));
style.set("max-height", &format!("{}px", new_size.height));
} else {
style
.remove_property("max-width")
.expect("Property is read only");
style
.remove_property("max-height")
.expect("Property is read only");
style.remove("max-width");
style.remove("max-height");
}
}
pub fn set_canvas_position(
document: &Document,
raw: &HtmlCanvasElement,
style: &CssStyleDeclaration,
style: &Style,
mut position: LogicalPosition<f64>,
) {
if document.contains(Some(raw)) && style.get_property_value("display").unwrap() != "none" {
if document.contains(Some(raw)) && style.get("display") != "none" {
position.x -= style_size_property(style, "margin-left")
+ style_size_property(style, "border-left-width")
+ style_size_property(style, "padding-left");
@@ -147,30 +138,21 @@ pub fn set_canvas_position(
+ style_size_property(style, "padding-top");
}
set_canvas_style_property(raw, "position", "fixed");
set_canvas_style_property(raw, "left", &format!("{}px", position.x));
set_canvas_style_property(raw, "top", &format!("{}px", position.y));
style.set("position", "fixed");
style.set("left", &format!("{}px", position.x));
style.set("top", &format!("{}px", position.y));
}
/// This function will panic if the element is not inserted in the DOM
/// or is not a CSS property that represents a size in pixel.
pub fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 {
let prop = style
.get_property_value(property)
.expect("Found invalid property");
pub fn style_size_property(style: &Style, property: &str) -> f64 {
let prop = style.get(property);
prop.strip_suffix("px")
.expect("Element was not inserted into the DOM or is not a size in pixel")
.parse()
.expect("CSS property is not a size in pixel")
}
pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) {
let style = raw.style();
style
.set_property(property, value)
.unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}"))
}
pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
window
.match_media("(prefers-color-scheme: dark)")

View File

@@ -80,7 +80,7 @@ impl PointerHandler {
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
let window = canvas_common.window.clone();
self.on_pointer_release = Some(canvas_common.add_transient_event(
self.on_pointer_release = Some(canvas_common.add_event(
"pointerup",
move |event: PointerEvent| {
let modifiers = event::mouse_modifiers(&event);
@@ -117,8 +117,8 @@ impl PointerHandler {
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
let window = canvas_common.window.clone();
let canvas = canvas_common.raw.clone();
self.on_pointer_press = Some(canvas_common.add_transient_event(
let canvas = canvas_common.raw().clone();
self.on_pointer_press = Some(canvas_common.add_event(
"pointerdown",
move |event: PointerEvent| {
if prevent_default {
@@ -174,7 +174,7 @@ impl PointerHandler {
B: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, ButtonsState, MouseButton),
{
let window = canvas_common.window.clone();
let canvas = canvas_common.raw.clone();
let canvas = canvas_common.raw().clone();
self.on_cursor_move = Some(canvas_common.add_event(
"pointermove",
move |event: PointerEvent| {

View File

@@ -2,14 +2,14 @@ use js_sys::{Array, Object};
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{
CssStyleDeclaration, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver,
ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize,
Window,
Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions,
ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window,
};
use crate::dpi::{LogicalSize, PhysicalSize};
use super::super::backend;
use super::canvas::Style;
use super::media_query_handle::MediaQueryListHandle;
use std::cell::{Cell, RefCell};
@@ -22,7 +22,7 @@ impl ResizeScaleHandle {
window: Window,
document: Document,
canvas: HtmlCanvasElement,
style: CssStyleDeclaration,
style: Style,
scale_handler: S,
resize_handler: R,
) -> Self
@@ -51,7 +51,7 @@ struct ResizeScaleInternal {
window: Window,
document: Document,
canvas: HtmlCanvasElement,
style: CssStyleDeclaration,
style: Style,
mql: MediaQueryListHandle,
observer: ResizeObserver,
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
@@ -65,7 +65,7 @@ impl ResizeScaleInternal {
window: Window,
document: Document,
canvas: HtmlCanvasElement,
style: CssStyleDeclaration,
style: Style,
scale_handler: S,
resize_handler: R,
) -> Rc<RefCell<Self>>
@@ -152,9 +152,7 @@ impl ResizeScaleInternal {
}
fn notify(&mut self) {
if !self.document.contains(Some(&self.canvas))
|| self.style.get_property_value("display").unwrap() == "none"
{
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
let size = PhysicalSize::new(0, 0);
if self.notify_scale.replace(false) {
@@ -180,7 +178,7 @@ impl ResizeScaleInternal {
backend::style_size_property(&self.style, "height"),
);
if self.style.get_property_value("box-sizing").unwrap() == "border-box" {
if self.style.get("box-sizing") == "border-box" {
size.width -= backend::style_size_property(&self.style, "border-left-width")
+ backend::style_size_property(&self.style, "border-right-width")
+ backend::style_size_property(&self.style, "padding-left")
@@ -246,10 +244,7 @@ impl ResizeScaleInternal {
.get(0)
.unchecked_into();
let writing_mode = self
.style
.get_property_value("writing-mode")
.expect("`writing-mode` is a valid CSS property");
let writing_mode = self.style.get("writing-mode");
// means the canvas is not inserted into the DOM
if writing_mode.is_empty() {

View File

@@ -1,5 +1,5 @@
use js_sys::{Function, Object, Promise, Reflect};
use once_cell::unsync::OnceCell;
use std::cell::OnceCell;
use std::time::Duration;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;

View File

@@ -7,15 +7,28 @@ use crate::window::{
};
use crate::SendSyncWrapper;
use web_sys::HtmlCanvasElement;
use super::cursor::CursorState;
use super::r#async::Dispatcher;
use super::PlatformCustomCursor;
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
use web_sys::HtmlCanvasElement;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
// Parent windows are currently unsupported, though owned window
// handles would be implementable.
Self {}
}
}
pub struct Window {
inner: Dispatcher<Inner>,
}
@@ -24,7 +37,7 @@ pub struct Inner {
id: WindowId,
pub window: web_sys::Window,
canvas: Rc<RefCell<backend::Canvas>>,
previous_pointer: RefCell<&'static str>,
cursor: CursorState,
destroy_fn: Option<Box<dyn FnOnce()>>,
}
@@ -43,6 +56,7 @@ impl Window {
let canvas =
backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?;
let canvas = Rc::new(RefCell::new(canvas));
let cursor = CursorState::new(canvas.borrow().style().clone());
target.register(&canvas, id, prevent_default);
@@ -53,7 +67,7 @@ impl Window {
id,
window: window.clone(),
canvas,
previous_pointer: RefCell::new("auto"),
cursor,
destroy_fn: Some(destroy_fn),
};
@@ -82,6 +96,22 @@ impl Window {
.value()
.map(|inner| inner.canvas.borrow().raw().clone())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
self.inner
.value()
.map(|inner| {
let canvas = inner.canvas.borrow();
// SAFETY: This will only work if the reference to `HtmlCanvasElement` stays valid.
let canvas: &wasm_bindgen::JsValue = canvas.raw();
let window_handle =
rwh_06::WebCanvasWindowHandle::new(std::ptr::NonNull::from(canvas).cast());
rwh_06::RawWindowHandle::WebCanvas(window_handle)
})
.ok_or(rwh_06::HandleError::Unavailable)
}
}
impl Inner {
@@ -195,8 +225,12 @@ impl Inner {
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
*self.previous_pointer.borrow_mut() = cursor.name();
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name());
self.cursor.set_cursor_icon(cursor)
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
self.cursor.set_custom_cursor(cursor)
}
#[inline]
@@ -222,15 +256,7 @@ impl Inner {
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
if !visible {
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none");
} else {
backend::set_canvas_style_property(
self.canvas.borrow().raw(),
"cursor",
&self.previous_pointer.borrow(),
);
}
self.cursor.set_cursor_visible(visible)
}
#[inline]
@@ -379,13 +405,6 @@ impl Inner {
rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let window_handle = rwh_06::WebWindowHandle::new(self.id.0);
Ok(rwh_06::RawWindowHandle::Web(window_handle))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

@@ -96,7 +96,7 @@ use runner::{EventLoopRunner, EventLoopRunnerShared};
use self::runner::RunnerState;
use super::window::set_skip_taskbar;
use super::{window::set_skip_taskbar, SelectedCursor};
pub(crate) struct WindowData<T: 'static> {
pub window_state: Arc<Mutex<WindowState>>,
@@ -530,10 +530,6 @@ 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()
}
@@ -1979,16 +1975,21 @@ unsafe fn public_window_callback_inner<T: 'static>(
// `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement.
let in_client_area = super::loword(lparam as u32) as u32 == HTCLIENT;
if in_client_area {
Some(window_state.mouse.cursor)
Some(window_state.mouse.selected_cursor.clone())
} else {
None
}
};
match set_cursor_to {
Some(cursor) => {
let cursor = unsafe { LoadCursorW(0, util::to_windows_cursor(cursor)) };
unsafe { SetCursor(cursor) };
Some(selected_cursor) => {
let hcursor = match selected_cursor {
SelectedCursor::Named(cursor_icon) => unsafe {
LoadCursorW(0, util::to_windows_cursor(cursor_icon))
},
SelectedCursor::Custom(cursor) => cursor.as_raw_handle(),
};
unsafe { SetCursor(hcursor) };
result = ProcResult::Value(0);
}
None => result = ProcResult::DefWindowProc(wparam),

View File

@@ -163,10 +163,6 @@ 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();

View File

@@ -1,18 +1,23 @@
use std::{fmt, io, mem, path::Path, sync::Arc};
use std::{ffi::c_void, fmt, io, mem, path::Path, sync::Arc};
use cursor_icon::CursorIcon;
use windows_sys::{
core::PCWSTR,
Win32::{
Foundation::HWND,
Graphics::Gdi::{
CreateBitmap, CreateCompatibleBitmap, DeleteObject, GetDC, ReleaseDC, SetBitmapBits,
},
UI::WindowsAndMessaging::{
CreateIcon, DestroyIcon, LoadImageW, SendMessageW, HICON, ICON_BIG, ICON_SMALL,
IMAGE_ICON, LR_DEFAULTSIZE, LR_LOADFROMFILE, WM_SETICON,
CreateIcon, CreateIconIndirect, DestroyCursor, DestroyIcon, LoadImageW, SendMessageW,
HCURSOR, HICON, ICONINFO, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE,
LR_LOADFROMFILE, WM_SETICON,
},
},
};
use crate::dpi::PhysicalSize;
use crate::icon::*;
use crate::{cursor::CursorImage, dpi::PhysicalSize};
use super::util;
@@ -160,3 +165,92 @@ pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
SendMessageW(hwnd, WM_SETICON, icon_type as usize, 0);
}
}
#[derive(Debug, Clone)]
pub enum SelectedCursor {
Named(CursorIcon),
Custom(WinCursor),
}
impl Default for SelectedCursor {
fn default() -> Self {
Self::Named(Default::default())
}
}
#[derive(Clone, Debug)]
pub struct WinCursor {
inner: Arc<RaiiCursor>,
}
impl WinCursor {
pub fn as_raw_handle(&self) -> HICON {
self.inner.handle
}
fn from_handle(handle: HCURSOR) -> Self {
Self {
inner: Arc::new(RaiiCursor { handle }),
}
}
pub(crate) fn new(image: &CursorImage) -> Result<Self, io::Error> {
let mut bgra = image.rgba.clone();
bgra.chunks_exact_mut(4).for_each(|chunk| chunk.swap(0, 2));
let w = image.width as i32;
let h = image.height as i32;
unsafe {
let hdc_screen = GetDC(0);
if hdc_screen == 0 {
return Err(io::Error::last_os_error());
}
let hbm_color = CreateCompatibleBitmap(hdc_screen, w, h);
ReleaseDC(0, hdc_screen);
if hbm_color == 0 {
return Err(io::Error::last_os_error());
}
if SetBitmapBits(hbm_color, bgra.len() as u32, bgra.as_ptr() as *const c_void) == 0 {
DeleteObject(hbm_color);
return Err(io::Error::last_os_error());
};
// Mask created according to https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap#parameters
let mask_bits: Vec<u8> = vec![0xff; ((((w + 15) >> 4) << 1) * h) as usize];
let hbm_mask = CreateBitmap(w, h, 1, 1, mask_bits.as_ptr() as *const _);
if hbm_mask == 0 {
DeleteObject(hbm_color);
return Err(io::Error::last_os_error());
}
let icon_info = ICONINFO {
fIcon: 0,
xHotspot: image.hotspot_x as u32,
yHotspot: image.hotspot_y as u32,
hbmMask: hbm_mask,
hbmColor: hbm_color,
};
let handle = CreateIconIndirect(&icon_info as *const _);
DeleteObject(hbm_color);
DeleteObject(hbm_mask);
if handle == 0 {
return Err(io::Error::last_os_error());
}
Ok(Self::from_handle(handle))
}
}
}
#[derive(Debug)]
struct RaiiCursor {
handle: HCURSOR,
}
impl Drop for RaiiCursor {
fn drop(&mut self) {
unsafe { DestroyCursor(self.handle) };
}
}

View File

@@ -10,12 +10,14 @@ pub(crate) use self::{
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
icon::WinIcon,
icon::{SelectedCursor, WinIcon},
monitor::{MonitorHandle, VideoMode},
window::Window,
window::{OwnedWindowHandle, Window},
};
pub use self::icon::WinIcon as PlatformIcon;
pub(crate) use crate::cursor::OnlyCursorImage as PlatformCustomCursor;
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
use crate::platform_impl::Fullscreen;
use crate::event::DeviceId as RootDeviceId;

View File

@@ -66,13 +66,14 @@ use crate::{
dpi::{dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi},
drop_handler::FileDropHandler,
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
icon::{self, IconType},
icon::{self, IconType, WinCursor},
ime::ImeContext,
keyboard::KeyEventBuilder,
monitor::{self, MonitorHandle},
util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
Fullscreen, PlatformSpecificWindowBuilderAttributes, WindowId,
Fullscreen, PlatformCustomCursor, PlatformSpecificWindowBuilderAttributes, SelectedCursor,
WindowId,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -80,6 +81,23 @@ use crate::{
},
};
#[derive(Debug, Clone)]
pub(crate) struct OwnedWindowHandle {
hwnd: HWND,
}
impl OwnedWindowHandle {
#[cfg(feature = "rwh_06")]
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
// TODO: Do we need to do something to extend the lifetime of the window handle?
let hwnd = match handle.as_raw() {
rwh_06::RawWindowHandle::Win32(handle) => handle.hwnd.get() as HWND,
handle => panic!("invalid window handle {handle:?} on Windows"),
};
Self { hwnd }
}
}
/// The Win32 implementation of the main `Window` object.
pub(crate) struct Window {
/// Main handle for the window.
@@ -395,13 +413,28 @@ impl Window {
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state_lock().mouse.cursor = cursor;
self.window_state_lock().mouse.selected_cursor = SelectedCursor::Named(cursor);
self.thread_executor.execute_in_thread(move || unsafe {
let cursor = LoadCursorW(0, util::to_windows_cursor(cursor));
SetCursor(cursor);
});
}
#[inline]
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
let new_cursor = match WinCursor::new(&cursor.0) {
Ok(cursor) => cursor,
Err(err) => {
warn!("Failed to create custom cursor: {err}");
return;
}
};
self.window_state_lock().mouse.selected_cursor = SelectedCursor::Custom(new_cursor.clone());
self.thread_executor.execute_in_thread(move || unsafe {
SetCursor(new_cursor.as_raw_handle());
});
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let confine = match mode {
@@ -1285,33 +1318,25 @@ where
// so the diffing later can work.
window_flags.set(WindowFlags::CLOSABLE, true);
let mut fallback_parent = || match pl_attribs.owner {
Some(parent) => {
window_flags.set(WindowFlags::POPUP, true);
Some(parent)
let parent = if let Some(parent_window) = &attributes.parent_window {
window_flags.set(WindowFlags::CHILD, true);
if pl_attribs.menu.is_some() {
warn!("Setting a menu on a child window is unsupported");
}
None => {
window_flags.set(WindowFlags::ON_TASKBAR, true);
None
}
};
#[cfg(feature = "rwh_06")]
let parent = match attributes.parent_window.0 {
Some(rwh_06::RawWindowHandle::Win32(handle)) => {
window_flags.set(WindowFlags::CHILD, true);
if pl_attribs.menu.is_some() {
warn!("Setting a menu on a child window is unsupported");
Some(parent_window.hwnd)
} else {
match pl_attribs.owner {
Some(parent) => {
window_flags.set(WindowFlags::POPUP, true);
Some(parent)
}
None => {
window_flags.set(WindowFlags::ON_TASKBAR, true);
None
}
Some(handle.hwnd.get() as HWND)
}
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"),
None => fallback_parent(),
};
#[cfg(not(feature = "rwh_06"))]
let parent = fallback_parent();
let mut initdata = InitData {
event_loop,
attributes,
@@ -1375,7 +1400,7 @@ unsafe fn register_window_class<T: 'static>(class_name: &[u16]) {
unsafe { RegisterClassExW(&class) };
}
struct ComInitialized(#[allow(dead_code)] *mut ());
struct ComInitialized(*mut ());
impl Drop for ComInitialized {
fn drop(&mut self) {
unsafe { CoUninitialize() };

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