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
128 changed files with 5261 additions and 7611 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:
@@ -81,7 +81,7 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
run: cargo generate-lockfile
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -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,78 +11,18 @@ Unreleased` header.
# Unreleased
# 0.29.12
- On X11, fix use after free during xinput2 handling.
- On X11, filter close to zero values in mouse device events
# 0.29.11
- On Wayland, fix DeviceEvent::Motion not being sent
- On X11, don't require XIM to run.
- On X11, fix xkb state not being updated correctly sometimes leading to wrong input.
- Fix compatibility with 32-bit platforms without 64-bit atomics.
- On macOS, fix incorrect IME cursor rect origin.
- On X11, fix swapped instance and general class names.
- On Windows, fixed a race condition when sending an event through the loop proxy.
- On Wayland, disable `Occluded` event handling.
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
- On Orbital, implement `KeyEventExtModifierSupplement`.
- On Orbital, map keys to `NamedKey` when possible.
- On Orbital, implement `set_cursor_grab`.
- On Orbital, implement `set_cursor_visible`.
- On Orbital, implement `drag_window`.
- On Orbital, implement `drag_resize_window`.
- On Orbital, implement `set_transparent`.
- On Orbital, implement `set_visible`.
- On Orbital, implement `is_visible`.
- On Orbital, implement `set_resizable`.
- On Orbital, implement `is_resizable`.
- On Orbital, implement `set_maximized`.
- On Orbital, implement `is_maximized`.
- On Orbital, implement `set_decorations`.
- On Orbital, implement `is_decorated`.
- On Orbital, implement `set_window_level`.
- On Orbital, emit `DeviceEvent::MouseMotion`.
- On Wayland, fix title in CSD not updated from `AboutToWait`.
# 0.29.10
- On Web, account for canvas being focused already before event loop starts.
- On Web, increase cursor position accuracy.
# 0.29.9
- On X11, fix `NotSupported` error not propagated when creating event loop.
- On Wayland, fix resize not issued when scale changes
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
# 0.29.8
- On X11, fix IME input lagging behind.
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
- On X11, fix keymap not updated from xmodmap.
- On X11, reduce the amount of time spent fetching screen resources.
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
- On Wayland, fix `Window::inner_size` not using the correct rounding.
# 0.29.7
- On X11, fix `Xft.dpi` reload during runtime.
- On X11, fix window minimize.
# 0.29.6
- On 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.12"
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",
@@ -167,7 +188,7 @@ wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = tr
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.2"
xkbcommon-dl = "0.4.0"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
@@ -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.12"
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
@@ -150,13 +150,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.12", 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>>> = const { 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

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

View File

@@ -40,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

@@ -574,7 +574,7 @@ pub enum WindowEvent {
/// ### Others
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
/// - **Android / Windows / Orbital:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding

View File

@@ -11,7 +11,7 @@ use std::marker::PhantomData;
use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt};
#[cfg(not(wasm_platform))]
@@ -459,16 +459,16 @@ pub enum DeviceEvents {
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial {
serial: usize,
serial: u64,
}
impl AsyncRequestSerial {
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: We rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them.
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
// NOTE: we rely on wrap around here, while the user may just request
// in the loop u64::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}

View File

@@ -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

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

View File

@@ -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

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

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

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

View File

@@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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))
@@ -521,7 +565,7 @@ impl Window {
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb::reset_dead_keys()
common::xkb_state::reset_dead_keys()
}
#[inline]
@@ -659,11 +703,11 @@ impl KeyEventExtModifierSupplement for KeyEvent {
impl PhysicalKeyExtScancode for PhysicalKey {
fn from_scancode(scancode: u32) -> PhysicalKey {
common::xkb::scancode_to_keycode(scancode)
common::keymap::scancode_to_keycode(scancode)
}
fn to_scancode(self) -> Option<u32> {
common::xkb::physicalkey_to_scancode(self)
common::keymap::physicalkey_to_scancode(self)
}
}
@@ -757,11 +801,8 @@ impl<T: 'static> EventLoop<T> {
let backend = match (
attributes.forced_backend,
env::var("WAYLAND_DISPLAY")
.ok()
.filter(|var| !var.is_empty())
.or_else(|| env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some(),
.map(|var| !var.is_empty())
.unwrap_or(false),
env::var("DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
@@ -775,15 +816,10 @@ impl<T: 'static> EventLoop<T> {
#[cfg(x11_platform)]
(None, _, true) => Backend::X,
// No backend is present.
(_, wayland_display, x11_display) => {
let msg = if wayland_display && !cfg!(wayland_platform) {
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland"
} else if x11_display && !cfg!(x11_platform) {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11"
} else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
};
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
_ => {
return Err(EventLoopError::Os(os_error!(OsError::Misc(
"neither WAYLAND_DISPLAY nor DISPLAY is set."
))));
}
};
@@ -792,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()),
}
}
@@ -802,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)))
@@ -926,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())
}
@@ -938,12 +970,10 @@ impl<T> EventLoopWindowTarget<T> {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
}
#[allow(dead_code)]
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
#[allow(dead_code)]
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}

View File

@@ -10,12 +10,13 @@ use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use sctk::reexports::calloop;
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, QueueHandle};
use crate::dpi::LogicalSize;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{
@@ -33,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>;
@@ -355,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.
@@ -383,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
@@ -419,7 +420,7 @@ impl<T: 'static> EventLoop<T> {
.redraw_requested
.store(true, Ordering::Relaxed);
size
physical_size
});
callback(
@@ -465,45 +466,45 @@ impl<T: 'static> EventLoop<T> {
window_ids.extend(state.window_requests.get_mut().keys());
});
for window_id in window_ids.iter() {
let event = self.with_state(|state| {
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(window_id));
mem::drop(state.windows.get_mut().remove(window_id));
return Some(WindowEvent::Destroyed);
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
false
} else {
let mut window = state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap();
if window.frame_callback_state() == FrameCallbackState::Requested {
false
} else {
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested
}
}
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,
window_id: crate::window::WindowId(window_id),
event: WindowEvent::RedrawRequested,
},
&self.window_target,
);
@@ -518,42 +519,6 @@ impl<T: 'static> EventLoop<T> {
// This is always the last event we dispatch before poll again
callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;
for window_id in window_ids.drain(..) {
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
Some(window) => {
let refresh = window.lock().unwrap().refresh_frame();
if refresh {
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
}
refresh
}
None => false,
});
}
// Wakeup event loop if needed.
//
// If the user draws from the `AboutToWait` this is likely not required, however
// we can't do much about it.
if wake_up {
match &self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
}
#[cfg(x11_platform)]
PlatformEventLoopWindowTarget::X(_) => unreachable!(),
}
}
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids);
@@ -664,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) {}
@@ -719,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

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

View File

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

View File

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

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,26 +291,27 @@ 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)
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure(configure, &self.shm, &self.subcompositor_state);
.configure(
configure,
&self.shm,
&self.subcompositor_state,
&mut self.events_sink,
);
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the
// users, since it can break a lot of things, thus it'll ask users to redraw instead.
self.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
// Manually mark that we've got an event, since configure may not generate a resize.
self.dispatched_events = true;
// 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;
}
}
}
@@ -394,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,
@@ -407,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

@@ -3,6 +3,7 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use sctk::reexports::calloop;
use sctk::reexports::client::protocol::wl_display::WlDisplay;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
@@ -19,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,
@@ -279,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]
@@ -307,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]
@@ -326,9 +327,7 @@ impl Window {
self.window_state
.lock()
.unwrap()
.set_min_inner_size(min_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
.set_min_inner_size(min_size)
}
/// Set the maximum inner size for the window.
@@ -339,9 +338,7 @@ impl Window {
self.window_state
.lock()
.unwrap()
.set_max_inner_size(max_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
.set_max_inner_size(max_size)
}
#[inline]
@@ -390,10 +387,7 @@ impl Window {
#[inline]
pub fn set_resizable(&self, resizable: bool) {
if self.window_state.lock().unwrap().set_resizable(resizable) {
// NOTE: Requires commit to be applied.
self.request_redraw();
}
self.window_state.lock().unwrap().set_resizable(resizable);
}
#[inline]
@@ -512,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,13 +1,11 @@
//! 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 ahash::HashSet;
use log::{info, warn};
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
@@ -20,18 +18,23 @@ 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::platform_impl::wayland::logical_to_physical_rounded;
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::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,
@@ -87,10 +92,8 @@ pub struct WindowState {
/// Whether the frame is resizable.
resizable: bool,
// NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new
// is created, since add/removed stuff could be delivered a bit out of order.
/// Seats that has keyboard focus on that window.
seat_focus: HashSet<ObjectId>,
/// Whether the window has focus.
has_focus: bool,
/// The scale factor of the window.
scale_factor: f64,
@@ -180,13 +183,13 @@ 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,
frame: None,
frame_callback_state: FrameCallbackState::None,
seat_focus: Default::default(),
has_focus: false,
has_pending_move: None,
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
@@ -199,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),
@@ -257,7 +261,8 @@ impl WindowState {
configure: WindowConfigure,
shm: &Shm,
subcompositor: &Option<Arc<SubcompositorState>>,
) -> bool {
event_sink: &mut EventSink,
) -> 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.
@@ -300,6 +305,19 @@ impl WindowState {
let stateless = Self::is_stateless(&configure);
// Emit `Occluded` event on suspension change.
let occluded = configure.state.contains(XdgWindowState::SUSPENDED);
if self
.last_configure
.as_ref()
.map(|c| c.state.contains(XdgWindowState::SUSPENDED))
.unwrap_or(false)
!= occluded
{
let window_id = make_wid(self.window.wl_surface());
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
}
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
// Configure the window states.
frame.update_state(configure.state);
@@ -356,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
}
}
@@ -498,12 +516,10 @@ impl WindowState {
}
/// Set the resizable state on the window.
///
/// Returns `true` when the state was applied.
#[inline]
pub fn set_resizable(&mut self, resizable: bool) -> bool {
pub fn set_resizable(&mut self, resizable: bool) {
if self.resizable == resizable {
return false;
return;
}
self.resizable = resizable;
@@ -519,14 +535,12 @@ impl WindowState {
if let Some(frame) = self.frame.as_mut() {
frame.set_resizable(resizable);
}
true
}
/// Whether the window is focused by any seat.
/// Whether the window is focused.
#[inline]
pub fn has_focus(&self) -> bool {
!self.seat_focus.is_empty()
self.has_focus
}
/// Whether the IME is allowed.
@@ -608,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);
}
@@ -639,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.
@@ -694,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;
@@ -710,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.
@@ -844,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();
@@ -888,16 +954,12 @@ impl WindowState {
}
}
/// Add seat focus for the window.
/// Mark that the window has focus.
///
/// Should be used from routine that sends focused event.
#[inline]
pub fn add_seat_focus(&mut self, seat: ObjectId) {
self.seat_focus.insert(seat);
}
/// Remove seat focus from the window.
#[inline]
pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
self.seat_focus.remove(seat);
pub fn set_has_focus(&mut self, has_focus: bool) {
self.has_focus = has_focus;
}
/// Returns `true` if the requested state was applied.

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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

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

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]
@@ -324,7 +329,7 @@ impl XConnection {
}
}
pub struct ScreenResources {
pub(crate) struct ScreenResources {
/// List of attached modes.
modes: Vec<randr::ModeInfo>,
@@ -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()?;

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
};
@@ -118,7 +122,7 @@ impl SharedState {
unsafe impl Send for UnownedWindow {}
unsafe impl Sync for UnownedWindow {}
pub struct UnownedWindow {
pub(crate) struct UnownedWindow {
pub(crate) xconn: Arc<XConnection>, // never changes
xwindow: xproto::Window, // never changes
#[allow(dead_code)]
@@ -126,7 +130,7 @@ pub 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()),
@@ -396,7 +397,7 @@ impl UnownedWindow {
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
{
let (instance, class) = if let Some(name) = pl_attribs.name {
let (class, instance) = if let Some(name) = pl_attribs.name {
(name.instance, name.general)
} else {
let class = env::args_os()
@@ -541,9 +542,9 @@ impl UnownedWindow {
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
// Try to create input context for the window.
if let Some(ime) = event_loop.ime.as_ref() {
let result = ime
{
let result = event_loop
.ime
.borrow_mut()
.create_context(window.xwindow as ffi::Window, false);
leap!(result);
@@ -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,25 +4,17 @@ 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::{self, ConnectionExt},
},
resource_manager,
xcb_ffi::XCBConnection,
};
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
/// A connection to an X server.
pub struct XConnection {
pub(crate) struct XConnection {
pub xlib: ffi::Xlib,
pub xcursor: ffi::Xcursor,
@@ -53,13 +45,7 @@ pub struct XConnection {
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
/// The resource database.
database: RwLock<resource_manager::Database>,
/// RandR version.
randr_version: (u32, u32),
/// Atom for the XSettings screen.
xsettings_screen: Option<xproto::Atom>,
database: resource_manager::Database,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
@@ -108,28 +94,16 @@ impl XConnection {
// Get the default screen.
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
// Load the database.
let database = resource_manager::new_from_default(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Load the RandR version.
let randr_version = xcb
.randr_query_version(1, 3)
.expect("failed to request XRandR version")
.reply()
.expect("failed to query XRandR version");
let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen);
if xsettings_screen.is_none() {
log::warn!("error setting XSETTINGS; Xft options won't reload automatically")
}
// Fetch atoms.
// Fetch the atoms.
let atoms = Atoms::new(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Load the database.
let database = resource_manager::new_from_default(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
Ok(XConnection {
xlib,
xcursor,
@@ -141,44 +115,11 @@ 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),
xsettings_screen,
})
}
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.ok()?
.reply()
.ok()?
.atom;
// Get PropertyNotify events from the XSETTINGS window.
// TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
// in order to accomodate for a changed window here.
let selector_window = xcb
.get_selection_owner(xsettings_screen)
.ok()?
.reply()
.ok()?
.owner;
xcb.change_window_attributes(
selector_window,
&xproto::ChangeWindowAttributesAux::new()
.event_mask(xproto::EventMask::PROPERTY_CHANGE),
)
.ok()?
.check()
.ok()?;
Some(xsettings_screen)
}
/// Checks whether an error has been triggered by the previous function calls.
#[inline]
pub fn check_errors(&self) -> Result<(), XError> {
@@ -190,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 {
@@ -223,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.
@@ -264,12 +192,6 @@ impl XConnection {
}
}
}
/// Get the atom for Xsettings.
#[inline]
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
self.xsettings_screen
}
}
impl fmt::Debug for XConnection {

View File

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

View File

@@ -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

@@ -1,4 +1,5 @@
use std::{
self,
ffi::c_void,
panic::{AssertUnwindSafe, UnwindSafe},
ptr,
@@ -20,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
@@ -35,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 LogicalSize { width, height } = self.state.ime_size.get();
let x = base_x + self.state.ime_position.get().x;
let y = base_y - self.state.ime_position.get().y - height;
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()));
}

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