Compare commits

..

50 Commits

Author SHA1 Message Date
Mads Marquart
48974ed7ec macOS: Fix pump_app_events
Use the system's mechanisms for pumping events, namely
`nextEventMatchingMask:untilDate:inMode:dequeue:`. This still has a few
problems as detailed in the documentation for `pump_app_events` (which
is why we switched away from it all the way back in Winit v0.20), but it
doesn't have nearly as many problems as the current implementation does.
2025-03-03 08:46:54 +01:00
Mads Marquart
82021cc5c7 macOS: Remove panic wrapper
This is unnecessary, unwinding in CF observer callbacks is safe (and is
safe in Rust after the introduction of `extern "C-unwind"`).

Panicking elsewhere (such as in NSNotificationCenter callbacks or
delegate methods) _may_ still lead to an abort, if AppKit tries to catch
it with libc++, since Rust panics are not compatible with those.

That's "just" a quality-of-implementation detail of current Rust though,
not an inherent limitation, and should really be solved upstream.
2025-03-01 22:13:39 +01:00
Mads Marquart
39c0862198 apple: Implement wake-ups using system mechanisms (#4146)
Do the wake-up inside the perform callback on `CFRunLoopSource`.
2025-03-01 20:18:28 +01:00
Mads Marquart
aa8ebdc795 Remove top-level Event enum (#4153) 2025-03-01 13:16:39 +01:00
Mads Marquart
ea68916055 macOS: Parse more key codes into named keys (#4148) 2025-03-01 12:41:46 +01:00
Mads Marquart
e26b831f23 Windows: Use ApplicationHandler (#4141)
* Make ActiveEventLoop a thin wrapper over EventLoopRunner
* Use ApplicationHandler instead of Event
2025-03-01 12:09:59 +01:00
Mads Marquart
8c3e69c08b macOS: Fix crash when reconnecting monitors (#4151)
CFArrayGetValueAtIndex does not return a retained value, so we must
retain ourselves.
2025-02-28 17:32:24 +01:00
rctlmk
46879429ed Windows: add IconExtWindows::from_resource_name (#4137) 2025-02-26 22:02:32 +01:00
Mads Marquart
0c89ea7386 Merge KeyEventExtra into KeyEvent (#4029)
To make the fields easier to use, and to allow constructing KeyEvent in
user test code.
2025-02-26 17:51:49 +01:00
dependabot[bot]
6c0e3c3b15 Remove @types/eslint__js (#4133)
It is deprecated.
2025-02-24 20:17:44 +01:00
Mads Marquart
675582bd46 Swizzle sendEvent: instead of subclassing NSApplication (#4036)
This is done to avoid order-dependent behavior that you'd otherwise
encounter where `EventLoop::new` had to be called at the beginning of
`fn main` to ensure that Winit's application was the one being
registered as the main application by calling `sharedApplication`.

Fixes https://github.com/rust-windowing/winit/issues/3772.

This should also make it (more) possible to use multiple versions of
Winit in the same application (though that's still untested).

Finally, it should allow the user to override `NSApplication` themselves
if they need to do that for some reason.
2025-02-24 10:38:10 +01:00
Mads Marquart
4d6fe7e35c web: Avoid top-level Event 2025-02-24 11:21:55 +03:00
Mads Marquart
f9912baf09 x11: Call ApplicationHandler directly
Instead of going through the Event enum.
2025-02-24 11:13:25 +03:00
Mads Marquart
f290619dce wayland: use custom Event enum for buffered events 2025-02-24 10:56:11 +03:00
valadaptive
23011c6b0a Update to windows-sys 0.59 (#4098) 2025-02-24 04:40:11 +01:00
Kirill Chibisov
3a39a6ddb0 examples: make default example simple
The old example is in application for more sophisticated use of winit
and for maintainers to test things, since it does pretty much
everything.
2025-02-21 20:35:22 +03:00
Kirill Chibisov
90cf9a3398 docs: remove dead doc link 2025-02-20 22:03:03 +03:00
Kirill Chibisov
05d8fa0b91 chore: fix clippy lints 2025-02-20 22:03:03 +03:00
Kirill Chibisov
d7d20507ed ci: disable 32-bit windows gnu on nightly 2025-02-20 22:03:03 +03:00
Bruce Mitchener
f6e66a71f8 doc: remove spurious derive directive
This was unintentionally added in eccd9e415.
2025-02-18 14:53:16 +03:00
Kirill Chibisov
c09160d1a8 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.

This also includes 0.30.8 sync, since it was forgotten.
2025-02-06 13:26:30 +03:00
Mads Marquart
2230e71093 ios: fix timers
Fixes #4074.
Fixes #3816.
2025-02-06 12:56:10 +03:00
Kirill Chibisov
5e6350d142 x11: fix modifiers replay
The serial was not unique, thus leading to issues and replay being
triggered for normal input. Track modifiers based on they keycodes
instead, since it's more unique.

Links: https://github.com/alacritty/alacritty/issues/8461
2025-02-05 09:04:09 +03:00
Kirill Chibisov
a6998af997 x11: fix crash with uim
Let's just not forward events to the IME once the user requested that
it should be disabled, though, still try to change its state explicitly.

Fixes #4082.
2025-02-03 20:43:43 +03:00
Mads Marquart
5c48ec7977 Document that we require cargo +nightly fmt (#4105) 2025-01-31 18:24:33 +01:00
Mads Marquart
953d9b4268 Update to objc2 v0.6 (#4092)
* Use available! macro
* Use objc2-core-foundation and objc2-core-graphics
* Use MainThreadBound instead of StaticMainThreadBound hack
2025-01-28 21:31:14 +01:00
valadaptive
f5dcd2aabe Rework Drag-And-Drop API (#4079)
* Add cursor position drag and drop events.
* Reword drag events to match pointer ones.
* appkit: Use `convertPoint_fromView` for coordinate conversion.
* appkit: use ProtocolObject<dyn NSDraggingInfo>.
* x11: store dnd.position as pair of i16

  It's what translate_coords takes anyway, so the extra precision is
  misleading if we're going to cast it to i16 everywhere it's used.

  We can also simplify the "unpacking" from the XdndPosition message--we
  can and should use the value of 16 as the shift instead of
  size_of::<c_short> * 2 or something like that, because the specification
  gives us the constant 16.
* x11: store translated DnD coords.
* x11: don't emit DragLeave without DragEnter.
* windows: only emit DragEnter if valid.
* windows: in DnD, always set pdwEffect.

  It appears other apps (like Chromium) set pdwEffect on Drop too:
  61a391b86b/ui/base/dragdrop/drop_target_win.cc
* docs: make it clearer that drag events are for dragged *files*.
* examples/dnd: handle RedrawRequested event.

Co-authored-by: amrbashir <amr.bashir2015@gmail.com>
2025-01-28 21:10:40 +01:00
Tom Churchman
77f1c73f06 wayland: clear IME preedit only when necessary
When all we'll be doing is setting a new preedit, the preedit doesn't
have to be explicitly cleared first. This change is perhaps debatable.

The direct reason for this is to make it easier to work around
quirks/bugs: in Masonry we've found IBus appears to resend
the IME preedit in response to `Window::set_ime_cursor_area`
(`zwp_text_input_v3::set_cursor_rectangle`). Because currently the
preedit is first cleared, a new IME cursor area is sent, which again
causes IBus to resend the preedit. This can loop for a while.

The Wayland protocol is mechanically quite prescriptive,
it says for zwp_text_input_v3:event:done.

> 1. Replace existing preedit string with the cursor. 
> 2. Delete requested surrounding text.
> 3. Insert commit string with the cursor at its end.
> 4. Calculate surrounding text to send.
> 5. Insert new preedit text in cursor position.
> 6. Place cursor inside preedit text.

Winit currently doesn't do surrounding text, so 2. and 4. can be
ignored. In Winit's IME model, without a commit, sending just the
`Ime::Preedit` event without explicitly clearing is arguably still
equivalent to doing 1., 5., and 6.
2025-01-17 19:29:10 +03:00
Pascal Hertleif
24c226416e Use wrapper type for CFUUID (#4032)
This no longer exposes `CGDisplayCreateUUIDFromDisplayID` and instead
uses `CFUUID` to avoid a leak.

Monitor comparisons should also be more stable now.
2025-01-17 12:38:42 +01:00
Sl-L
69382fda9a examples/child_window: use distinct color/position for children
This should help with understanding how they work.
2025-01-10 18:07:17 +03:00
Kirill Chibisov
ee245c569d api: make VideoModeHandle into VideoMode
The video mode is generally a static data and not a reference to some
video mode. This changes the exclusive fullscreen API to match that an
accept a monitor now.
2025-01-02 03:29:42 +03:00
Kirill Chibisov
5462f27dda api: add ActivationToken::{from,into}_raw
This is needed when passing and getting token from the IPC to activate
the window.
2024-12-31 06:08:18 +03:00
Kirill Chibisov
927deb030f x11: fix cursor grab mode tracking on error
Fixes #4064.
2024-12-31 05:52:12 +03:00
Kirill Chibisov
5ea81efc74 x11: add workaround for disabling IME on gnome
GNOME doesn't list that there's a _NONE_ style at all, but it still
works if you use it.
2024-12-28 14:25:56 -08:00
Matt Campbell
6896de5b73 windows: fix the event loop not waking on accessibility requests
Fixes #4055.
2024-12-24 00:23:44 +03:00
Kirill Chibisov
d736763216 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-12-22 23:13:25 +03:00
Kirill Chibisov
e316a89847 x11: fix KeyboardInput delivered twice with IME
The filtered events were still processed even though they shouldn't once
we know that they're filtered.

Fixes #4048.
2024-12-22 17:51:44 +03:00
Kirill Chibisov
f0d8689039 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-12-21 09:39:09 +03:00
Exidex
4d5e68c6e2 Implement ability to make non-activating window on macOS (#4035)
Fixes #3894
2024-12-13 00:56:39 +01:00
Skip R.
5835c9102e macOS: Fix checking for undocumented cursors (#4033)
`AnyClass::responds_to` delegates to `class_respondsToSelector`, a
function provided by the Objective-C runtime. However, at some point,
this began to return `false` for selectors referring to undocumented
cursors, despite the cursors remaining accessible via said selectors.
That this check fails prevents the cursors from being used.

We can instead send `respondsToSelector:` to the `NSCursor` class
itself. As an instance method, this is nominally impossible; however,
Apple grants an exemption[1] that permits class objects to perform
instance methods defined in the root class.

Checking for the undocumented cursors in this way gets them working
again, at least on macOS Sequoia 15.1.1.

[1]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html#//apple_ref/doc/uid/TP30001163-CH11-TPXREF120
2024-12-08 22:01:57 +01:00
Mads Marquart
e47081e385 docs: show less of the unstable docs in the README 2024-12-06 13:16:30 +03:00
Benjamin Brienen
171d53c042 Update smol_str (#3991) 2024-12-03 23:07:19 +01:00
Mads Marquart
35379f305a Remove feature description in FEATURES.md (#3479)
It easily becomes out of date.
2024-12-03 21:24:57 +01:00
Mads Marquart
4d2a0dd2b3 iOS: Never queue application-level events (#3905)
Events like `resumed`, `new_events`, `about_to_wait`, and so on will
never happen as a result of programmer action, so we'll never need to
queue those. This allows us to remove the need for the old `Event`
struct in the iOS backend.

Furthermore, we can now remove `InUserCallback`, since that state is
already stored inside `EventHandler`.

I've tried to otherwise keep the semantics as close to the original by
calling `handle_nonuser_events(mtm, [])`, which flushes pending events.
2024-12-03 19:53:29 +01:00
Mads Marquart
4a8b659228 Fix MonitorHandle PartialEq and Hash on iOS (#4013) 2024-12-03 19:02:53 +01:00
Mads Marquart
f314cd2b9a macOS: Fix crash when pressing Caps Lock (#4024)
Events emitted by `flagsChanged:` cannot access
`charactersIgnoringModifiers`. We were previously doing this because we
were trying to re-use the `create_key_event` function, but that is unsuited
for this purpose, so I have separated the `flagsChanged:` logic out from it.
2024-12-03 18:48:23 +01:00
Mads Marquart
3657506f6e macOS: Avoid redundant initial resize event (#3913)
The `NSViewFrameDidChangeNotification` that we listen to is emitted when
`-[NSWindow setContentView]` is called, since that sets the frame of the
view as well.

So now we register the notification later, so that it's not triggered at
window creation.

This behaviour is well described in the documentation:
https://developer.apple.com/documentation/appkit/nsview/postsframechangednotifications?language=objc
2024-12-03 18:35:04 +01:00
Mads Marquart
edca3ebc41 macOS: Align scancode conversions with Chromium and Firefox (#4019)
Also fix missing codes in physicalkey_to_scancode - This had become out of
sync with scancode_to_physicalkey.
2024-12-03 18:17:57 +01:00
Mads Marquart
ca46e29203 macOS: Fix surface position (#4027) 2024-12-03 17:31:32 +01:00
Mads Marquart
132fbe14d5 macOS: Fix safe area on macOS 10.14 and below (#4028) 2024-12-03 17:19:45 +01:00
108 changed files with 5026 additions and 5882 deletions

View File

@@ -2,4 +2,3 @@
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

View File

@@ -74,15 +74,19 @@ jobs:
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Web on nightly needs extra arguments
# Web on nightly needs extra arguments
- toolchain: nightly
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
platform: { name: 'Web' }
# Rustup is broken.
- toolchain: nightly
platform: { name: 'Windows 32bit GNU' }
# Android is tested on stable-3
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
platform: { name: 'Android' }
# Redox OS doesn't follow MSRV
- toolchain: '1.73'
platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
platform: { name: 'Redox OS' }
include:
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }

View File

@@ -19,6 +19,11 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
life during review it's recommended to check the "give contributors write access
to the branch" checkbox.
We use unstable Rustfmt options across the project, so please run
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
the maintainers can do it for you before merging, just state so in your pull
request description.
#### Handling review
During the review process certain events could require an action from your side,

View File

@@ -21,7 +21,7 @@ name = "winit"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version = "0.30.5"
version = "0.30.9"
[package.metadata.docs.rs]
features = [
@@ -81,7 +81,7 @@ cursor-icon = "1.1.0"
dpi = { version = "0.1.1", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false }
[dev-dependencies]
@@ -104,14 +104,15 @@ ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.5.1"
core-foundation = "0.9.3"
objc2 = "0.5.2"
block2 = "0.6.0"
dispatch2 = { version = "0.2.0", default-features = false, features = ["std", "objc2"] }
objc2 = "0.6.0"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
objc2-app-kit = { version = "0.2.2", features = [
objc2-app-kit = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-foundation",
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
@@ -128,6 +129,7 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPanel",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
@@ -140,9 +142,37 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-foundation = { version = "0.2.2", features = [
objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"block2",
"dispatch",
"CFBase",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { version = "0.3.0", default-features = false, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSAttributedString",
"NSData",
@@ -164,20 +194,29 @@ objc2-foundation = { version = "0.2.2", features = [
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [
objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"CFCGTypes",
"CFBase",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"block2",
"dispatch",
"objc2-core-foundation",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSProcessInfo",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.2.2", features = [
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-foundation",
"UIApplication",
"UIDevice",
"UIEvent",
@@ -203,7 +242,7 @@ objc2-ui-kit = { version = "0.2.2", features = [
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
windows-sys = { version = "0.52.0", features = [
windows-sys = { version = "0.59.0", features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",

View File

@@ -47,201 +47,3 @@ through the implementation work necessary to function on all platforms. When one
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor 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.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|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** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
[#242]: https://github.com/rust-windowing/winit/issues/242
[#306]: https://github.com/rust-windowing/winit/issues/306
[#315]: https://github.com/rust-windowing/winit/issues/315
[#319]: https://github.com/rust-windowing/winit/issues/319
[#33]: https://github.com/rust-windowing/winit/issues/33
[#459]: https://github.com/rust-windowing/winit/issues/459
[#5]: https://github.com/rust-windowing/winit/issues/5
[#63]: https://github.com/rust-windowing/winit/issues/63
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804

View File

@@ -2,13 +2,13 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
[![UNSTABLE docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=UNSTABLE%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml
[dependencies]
winit = "0.30.5"
winit = "0.30.9"
```
## [Documentation](https://docs.rs/winit)
@@ -66,4 +66,4 @@ same MSRV policy.
### Platform-specific usage
Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.

1241
examples/application.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -13,12 +13,36 @@ fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"]
mod fill;
struct WindowData {
window: Box<dyn Window>,
color: u32,
}
impl WindowData {
fn new(window: Box<dyn Window>, color: u32) -> Self {
Self { window, color }
}
}
#[derive(Default)]
struct Application {
parent_window_id: WindowId,
windows: HashMap<WindowId, Box<dyn Window>>,
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, WindowData>,
}
impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
@@ -42,15 +66,24 @@ fn main() -> Result<(), impl std::error::Error> {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = self.windows.get(&self.parent_window_id).unwrap();
let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
let child_index = self.windows.len() - 1;
let child_color =
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window =
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window);
self.windows.insert(child_id, WindowData::new(child_window, child_color));
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window.as_ref());
if window_id == self.parent_window_id.unwrap() {
fill::fill_window(window.window.as_ref());
} else {
fill::fill_window_with_color(window.window.as_ref(), window.color);
}
}
},
_ => (),
@@ -58,29 +91,23 @@ fn main() -> Result<(), impl std::error::Error> {
}
}
fn init(event_loop: &dyn ActiveEventLoop) -> Application {
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
let parent_window_id = window.id();
let windows = HashMap::from([(window.id(), window)]);
Application { parent_window_id, windows }
}
fn spawn_child_window(
parent: &dyn Window,
event_loop: &dyn ActiveEventLoop,
child_count: usize,
) -> Box<dyn Window> {
let parent = parent.raw_window_handle().unwrap();
// As child count increases, x goes from 0*128 to 5*128 and then repeats
let x: f64 = child_count.rem_euclid(5) as f64 * 128.0;
// After 5 windows have been put side by side horizontally, a new row starts
let y: f64 = (child_count / 5) as f64 * 96.0;
let mut window_attributes = WindowAttributes::default()
.with_title("child window")
.with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(128.0f32, 96.0))
.with_position(Position::Logical(LogicalPosition::new(x, y)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
@@ -89,7 +116,7 @@ fn main() -> Result<(), impl std::error::Error> {
}
let event_loop = EventLoop::new().unwrap();
event_loop.run(init)
event_loop.run_app(Application::default())
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]

View File

@@ -43,31 +43,16 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
event_loop.run(ControlFlowDemo::new)
event_loop.run_app(ControlFlowDemo::default())
}
#[derive(Default)]
struct ControlFlowDemo {
mode: Mode,
request_redraw: bool,
wait_cancelled: bool,
close_requested: bool,
window: Box<dyn Window>,
}
impl ControlFlowDemo {
fn new(event_loop: &dyn ActiveEventLoop) -> Self {
let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
let window = event_loop.create_window(window_attributes).unwrap();
Self {
mode: Mode::default(),
request_redraw: false,
wait_cancelled: false,
close_requested: false,
window,
}
}
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for ControlFlowDemo {
@@ -80,6 +65,13 @@ impl ApplicationHandler for ControlFlowDemo {
}
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
&mut self,
_event_loop: &dyn ActiveEventLoop,
@@ -120,8 +112,9 @@ impl ApplicationHandler for ControlFlowDemo {
_ => (),
},
WindowEvent::RedrawRequested => {
self.window.pre_present_notify();
fill::fill_window(&*self.window);
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
_ => (),
}
@@ -129,7 +122,7 @@ impl ApplicationHandler for ControlFlowDemo {
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
if self.request_redraw && !self.wait_cancelled && !self.close_requested {
self.window.request_redraw();
self.window.as_ref().unwrap().request_redraw();
}
match self.mode {

64
examples/dnd.rs Normal file
View File

@@ -0,0 +1,64 @@
use std::error::Error;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::new()?;
let app = Application::new();
Ok(event_loop.run_app(app)?)
}
/// Application state and event handling.
struct Application {
window: Option<Box<dyn Window>>,
}
impl Application {
fn new() -> Self {
Self { window: None }
}
}
impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes =
WindowAttributes::default().with_title("Drag and drop files on me!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::DragLeft { .. }
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => {
println!("{:?}", event);
},
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
WindowEvent::CloseRequested => {
event_loop.exit();
},
_ => {},
}
}
}

View File

@@ -8,7 +8,7 @@ fn main() -> std::process::ExitCode {
use std::time::Duration;
use winit::application::ApplicationHandler;
use winit::event::{StartCause, WindowEvent};
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::window::{Window, WindowAttributes, WindowId};
@@ -22,12 +22,9 @@ fn main() -> std::process::ExitCode {
}
impl ApplicationHandler for PumpDemo {
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
if matches!(cause, StartCause::Init) && self.window.is_none() {
let window_attributes =
WindowAttributes::default().with_title("A fantastic window!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(

View File

@@ -21,8 +21,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
window: Option<Box<dyn Window>>,
}
impl App {
fn init_window(&mut self, event_loop: &dyn ActiveEventLoop) {
impl ApplicationHandler for App {
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(window) = self.window.as_ref() {
window.request_redraw();
}
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default()
.with_title("Fantastic window number one!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
@@ -30,14 +36,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
self.window_id = Some(window.id());
self.window = Some(window);
}
}
impl ApplicationHandler for App {
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(window) = self.window.as_ref() {
window.request_redraw();
}
}
fn window_event(
&mut self,
@@ -83,20 +81,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut event_loop = EventLoop::new().unwrap();
let mut app = App { idx: 1, ..Default::default() };
event_loop.run_on_demand(|event_loop| {
app.init_window(event_loop);
&mut app
})?;
event_loop.run_app_on_demand(&mut app)?;
println!("--------------------------------------------------------- Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5));
app.idx += 1;
event_loop.run_on_demand(|event_loop| {
app.init_window(event_loop);
&mut app
})?;
event_loop.run_app_on_demand(&mut app)?;
println!("--------------------------------------------------------- Finished second loop");
Ok(())
}

View File

@@ -9,7 +9,10 @@
#[allow(unused_imports)]
pub use platform::cleanup_window;
#[allow(unused_imports)]
pub use platform::fill_window;
#[allow(unused_imports)]
pub use platform::fill_window_with_color;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform {
@@ -70,7 +73,7 @@ mod platform {
}
}
pub fn fill_window(window: &dyn Window) {
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
@@ -84,17 +87,21 @@ mod platform {
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;
// Fill a buffer with a solid color
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.fill(color);
buffer.present().expect("Failed to present the softbuffer buffer");
})
}
#[allow(dead_code)]
pub fn fill_window(window: &dyn Window) {
fill_window_with_color(window, 0xff181818);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
@@ -112,6 +119,11 @@ mod platform {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.

File diff suppressed because it is too large Load Diff

View File

@@ -13,28 +13,39 @@ fn main() -> Result<(), Box<dyn Error>> {
mod fill;
pub struct XEmbedDemo {
window: Box<dyn Window>,
parent_window_id: u32,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for XEmbedDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default()
.with_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(self.parent_window_id);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let window = self.window.as_ref().unwrap();
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
self.window.pre_present_notify();
fill::fill_window(&*self.window);
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
_ => (),
}
}
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
self.window.request_redraw();
self.window.as_ref().unwrap().request_redraw();
}
}
@@ -47,15 +58,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?;
Ok(event_loop.run(|event_loop| {
let window_attributes = WindowAttributes::default()
.with_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id);
let window = event_loop.create_window(window_attributes).unwrap();
XEmbedDemo { window }
})?)
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
}
#[cfg(not(x11_platform))]

View File

@@ -7,8 +7,6 @@ use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId;
/// The handler of the application events.
///
/// This is [dropped][std::ops::Drop] when the event loop is being shut down.
pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed.
///
@@ -103,9 +101,7 @@ pub trait ApplicationHandler {
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
///
@@ -146,6 +142,8 @@ pub trait ApplicationHandler {
/// # ) {
/// # }
/// #
/// # fn can_create_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &dyn ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged.
/// //
@@ -164,6 +162,8 @@ pub trait ApplicationHandler {
///
/// let (sender, receiver) = mpsc::channel();
///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
@@ -182,8 +182,9 @@ pub trait ApplicationHandler {
/// }
/// });
///
/// event_loop.run(|_event_loop| MyApp { receiver })?;
/// event_loop.run_app(&mut app)?;
///
/// drop(app);
/// background_thread.join().unwrap();
///
/// Ok(())
@@ -285,13 +286,13 @@ pub trait ApplicationHandler {
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
/// after the app becomes invisible.
///
/// Applications that need to run on Android must be able to handle their underlying
/// [`SurfaceView`] being destroyed, which in turn indirectly invalidates any existing
/// render surfaces that may have been created outside of Winit (such as an `EGLSurface`,
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]).
/// Applications that need to run on Android should assume their [`NativeWindow`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// This means that in this method, you must drop all render surfaces before the event callback
/// completes, and only re-create them in or after [`can_create_surfaces()`] is next recieved.
/// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces
/// before the event callback completes, which may be re-created when the application next
/// receives [`can_create_surfaces()`].
///
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
@@ -309,6 +310,14 @@ pub trait ApplicationHandler {
let _ = event_loop;
}
/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this method is called, it is guaranteed that the event loop
/// will exit right after.
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application has received a memory warning.
///
/// ## Platform-specific
@@ -404,6 +413,11 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
@@ -473,6 +487,11 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);

View File

@@ -59,24 +59,19 @@ changelog entry.
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
and `Serialize` on many types.
- Add `MonitorHandle::current_video_mode()`.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`
to use a larger style of titlebar.
- Add `WindowId::into_raw()` and `from_raw()`.
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
pointer events as part of the pointer event overhaul.
- Add `DeviceId::into_raw()` and `from_raw()`.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- Added `Window::surface_position`, which is the position of the surface inside the window.
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
- On X11 and Wayland, improved scancode conversions for more obscure key codes.
- On Windows, improved scancode conversions for more obscure key codes.
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
- On Windows, add `IconExtWindows::from_resource_name`.
### Changed
@@ -164,6 +159,36 @@ changelog entry.
- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
- On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation.
- On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3`
- Rename `VideoModeHandle` to `VideoMode`, now it only stores plain data.
- Make `Fullscreen::Exclusive` contain `(MonitorHandle, VideoMode)`.
- Reworked the file drag-and-drop API.
The `WindowEvent::DroppedFile`, `WindowEvent::HoveredFile` and `WindowEvent::HoveredFileCancelled`
events have been removed, and replaced with `WindowEvent::DragEntered`, `WindowEvent::DragMoved`,
`WindowEvent::DragDropped` and `WindowEvent::DragLeft`.
The old drag-and-drop events were emitted once per file. This occurred when files were *first*
hovered over the window, dropped, or left the window. The new drag-and-drop events are emitted
once per set of files dragged, and include a list of all dragged files. They also include the
pointer position.
The rough correspondence is:
- `WindowEvent::HoveredFile` -> `WindowEvent::DragEntered`
- `WindowEvent::DroppedFile` -> `WindowEvent::DragDropped`
- `WindowEvent::HoveredFileCancelled` -> `WindowEvent::DragLeft`
The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves
whilst files are being dragged over the window. It doesn't contain any file paths, just the
pointer position.
- Updated `objc2` to `v0.6`.
- Updated `windows-sys` to `v0.59`.
- To match the corresponding changes in `windows-sys`, the `HWND`, `HMONITOR`, and `HMENU` types
now alias to `*mut c_void` instead of `isize`.
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
- Removed `KeyEventExtModifierSupplement`, and made the fields `text_with_all_modifiers` and
`key_without_modifiers` public on `KeyEvent` instead.
### Removed
@@ -200,13 +225,8 @@ changelog entry.
### Fixed
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name.
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
- On macOS, fixed the scancode conversion for audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`.
- On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, fixed the behaviour of `pump_app_events` to match what the system expects.

View File

@@ -1,3 +1,59 @@
## 0.30.9
### Changed
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
### Fixed
- On X11, fix crash with uim.
- On X11, fix modifiers for keys that were sent by the same X11 request.
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
## 0.30.8
### Added
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
- On X11, add a workaround for disabling IME on GNOME.
### Fixed
- On Windows, fixed the event loop not waking on accessibility requests.
- On X11, fixed cursor grab mode state tracking on error.
## 0.30.7
### Fixed
- On X11, fixed KeyboardInput delivered twice when IME enabled.
## 0.30.6
### Added
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
### Fixed
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
## 0.30.5
### Added

View File

@@ -1,4 +1,39 @@
//! The event enums and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level
//! documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
//!
//! ```rust,ignore
//! let mut start_cause = StartCause::Init;
//!
//! while !elwt.exiting() {
//! app.new_events(event_loop, start_cause);
//!
//! for event in (window events, user events, device events) {
//! // This will pick the right method on the application based on the event.
//! app.handle_event(event_loop, event);
//! }
//!
//! for window_id in (redraw windows) {
//! app.window_event(event_loop, window_id, RedrawRequested);
//! }
//!
//! app.about_to_wait(event_loop);
//! start_cause = wait_if_necessary();
//! }
//!
//! app.exiting(event_loop);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
@@ -14,60 +49,9 @@ use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
#[cfg(doc)]
use crate::window::Window;
use crate::window::{ActivationToken, Theme, WindowId};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
/// See [`ApplicationHandler::new_events()`] for details.
///
/// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events()
NewEvents(StartCause),
/// See [`ApplicationHandler::window_event()`] for details.
///
/// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event()
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event()`] for details.
///
/// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event()
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
/// See [`ApplicationHandler::suspended()`] for details.
///
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
Suspended,
/// See [`ApplicationHandler::resumed()`] for details.
///
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
Resumed,
/// See [`ApplicationHandler::about_to_wait()`] for details.
///
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
AboutToWait,
/// See [`ApplicationHandler::memory_warning()`] for details.
///
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
MemoryWarning,
/// User requested a wake up.
UserWakeUp,
}
use crate::window::{ActivationToken, Theme};
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -130,23 +114,42 @@ pub enum WindowEvent {
/// The window has been destroyed.
Destroyed,
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
DroppedFile(PathBuf),
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted for each file
/// separately.
HoveredFile(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
/// hovered.
HoveredFileCancelled,
/// A file drag operation has entered the window.
DragEntered {
/// List of paths that are being dragged onto the window.
paths: Vec<PathBuf>,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// A file drag operation has moved over the window.
DragMoved {
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// The file drag operation has dropped file(s) on the window.
DragDropped {
/// List of paths that are being dragged onto the window.
paths: Vec<PathBuf>,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// The file drag operation has been cancelled or left the window.
DragLeft {
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
///
/// ## Platform-specific
///
/// - **Windows:** Always emits [`None`].
position: Option<PhysicalPosition<f64>>,
},
/// The window gained or lost focus.
///
@@ -162,6 +165,7 @@ pub enum WindowEvent {
/// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
/// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
/// events which are not marked as `is_synthetic`.
/// - **iOS:** Unsupported.
KeyboardInput {
device_id: Option<DeviceId>,
event: KeyEvent,
@@ -365,10 +369,18 @@ pub enum WindowEvent {
/// Touchpad pressure event.
///
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the
/// touchpad is being pressed) and stage (integer representing the click level).
TouchpadPressure { device_id: Option<DeviceId>, pressure: f32, stage: i64 },
/// ## Platform-specific
///
/// - **macOS**: Only supported on Apple forcetouch-capable macbooks.
/// - **Android / iOS / Wayland / X11 / Windows / Orbital / Web:** Unsupported.
TouchpadPressure {
device_id: Option<DeviceId>,
/// Value between 0 and 1 representing how hard the touchpad is being
/// pressed.
pressure: f32,
/// Represents the click level.
stage: i64,
},
/// The window's scale factor has changed.
///
@@ -510,7 +522,7 @@ pub enum PointerSource {
/// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`].
/// - **Android:** Will never be [`None`]. If the device doesn't support pressure
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).#[derive(Debug, Clone, Copy, PartialEq)]
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
/// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity,
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
force: Option<Force>,
@@ -719,12 +731,6 @@ pub struct KeyEvent {
/// you somehow see this in the wild, we'd like to know :)
pub physical_key: keyboard::PhysicalKey,
// Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms
#[cfg_attr(
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
///
/// This has two use cases:
@@ -740,7 +746,7 @@ pub struct KeyEvent {
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS.
///
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
/// [`key_without_modifiers`]: Self::key_without_modifiers
pub logical_key: keyboard::Key,
/// Contains the text produced by this keypress.
@@ -761,7 +767,7 @@ pub struct KeyEvent {
/// This is `None` if the current keypress cannot
/// be interpreted as text.
///
/// See also: `text_with_all_modifiers()`
/// See also [`text_with_all_modifiers`][Self::text_with_all_modifiers].
pub text: Option<SmolStr>,
/// Contains the location of this key on the keyboard.
@@ -817,13 +823,33 @@ pub struct KeyEvent {
/// ```
pub repeat: bool,
/// Platform-specific key event information.
/// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd>.
///
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
/// all modifiers applied.
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
///
/// On Android, iOS, Redox and Web, this type is a no-op.
pub(crate) platform_specific: platform_impl::KeyEventExtra,
/// ## Platform-specific
///
/// - **Android:** Unimplemented, this field is always the same value as `text`.
/// - **iOS:** Unimplemented, this field is always the same value as `text`.
/// - **Web:** Unsupported, this field is always the same value as `text`.
pub text_with_all_modifiers: Option<SmolStr>,
/// This value ignores all modifiers including, but not limited to <kbd>Shift</kbd>,
/// <kbd>Caps Lock</kbd>, and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case [`logical_key`][Self::logical_key] reports [`Dead`][keyboard::Key::Dead],
/// this will still report the key as `Character` according to the current keyboard
/// layout. This value cannot be `Dead`.
///
/// ## Platform-specific
///
/// - **Android:** Unimplemented, this field is always the same value as `logical_key`.
/// - **iOS:** Unimplemented, this field is always the same value as `logical_key`.
/// - **Web:** Unsupported, this field is always the same value as `logical_key`.
pub key_without_modifiers: keyboard::Key,
}
/// Describes keyboard modifiers event.
@@ -1134,122 +1160,108 @@ mod tests {
macro_rules! foreach_event {
($closure:expr) => {{
foreach_event!(window: $closure);
foreach_event!(device: $closure);
}};
(window: $closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let mut with_window_event: &mut dyn FnMut(event::WindowEvent) = &mut $closure;
let fid = event::FingerId::from_raw(0);
#[allow(deprecated)]
{
use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
use crate::window::WindowId;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
// Mainline events.
let wid = WindowId::from_raw(0);
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(Suspended);
x(Resumed);
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(SurfaceResized((0, 0).into()));
with_window_event(DragEntered { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragMoved { position: (0, 0).into() });
with_window_event(DragDropped { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragLeft { position: Some((0, 0).into()) });
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}};
(device: $closure:expr) => {{
use event::DeviceEvent::*;
// Window events.
let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
#[allow(unused_mut)]
let mut with_device_event: &mut dyn FnMut(event::DeviceEvent) = &mut $closure;
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(SurfaceResized((0, 0).into()));
with_window_event(DroppedFile("x.txt".into()));
with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
#[allow(deprecated)]
{
use event::DeviceEvent::*;
let with_device_event =
|dev_ev| x(event::Event::DeviceEvent { device_id: None, event: dev_ev });
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
}
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0) });
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
}};
}
#[allow(clippy::redundant_clone)]
#[allow(clippy::clone_on_copy)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event| {
foreach_event!(|event| {
let event2 = event.clone();
assert_eq!(event, event2);
})
});
}
#[test]
@@ -1267,7 +1279,7 @@ mod tests {
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event| {
foreach_event!(|event| {
let _ = format!("{event:?}");
});
let _ = event::StartCause::Init.clone();

View File

@@ -195,48 +195,10 @@ impl EventLoop {
}
impl EventLoop {
/// Run the event loop on the current thread.
///
/// You pass in a closure that returns your application state. This closure has access to the
/// currently running event loop, allowing you to initialize your windows and surfaces in here.
/// Run the application with the event loop on the calling thread.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// ## Event loop flow
///
/// This function internally handles the different parts of a traditional event-handling loop.
/// You could imagine this method being implemented like this:
///
/// ```rust,ignore
/// // Initialize.
/// let mut app = init_closure(event_loop);
/// let mut start_cause = StartCause::Init;
///
/// // Run loop.
/// while !elwt.exiting() {
/// // Wake up.
/// app.new_events(event_loop, start_cause);
///
/// // Handle events by the user.
/// for (device_id, event) in incoming_device_events {
/// app.device_event(event_loop, device_id, event);
/// }
/// for (window_id, event) in incoming_window_events {
/// app.window_event(event_loop, window_id, event);
/// }
///
/// // Done handling events, wait until we're woken up again.
/// app.about_to_wait(event_loop);
/// start_cause = wait_if_necessary();
/// }
///
/// // Finished running, drop application state.
/// drop(app);
/// ```
///
/// This is of course a very coarse-grained overview, and leaves out timing details like
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`].
///
/// ## Platform-specific
///
/// - **iOS:** Will never return to the caller and so values not passed to this function will
@@ -251,7 +213,7 @@ impl EventLoop {
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] instead of [`run()`] to avoid the need for the Javascript exception trick, and to
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
/// make it clearer that the event loop runs asynchronously (via the browser's own,
/// internal, event loop) and doesn't block the current thread of execution like it does
/// on other platforms.
@@ -261,20 +223,11 @@ impl EventLoop {
/// [^1]: `spawn_app()` is only available on the Web platform.
///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run()`]: Self::run()
/// [`run_app()`]: Self::run_app()
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<A: ApplicationHandler>(
self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.event_loop.run(init_closure)
}
/// Run the event loop with the given application state.
#[deprecated = "less flexible version of `run`"]
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
self.run(|_event_loop| app)
self.event_loop.run_app(app)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@@ -439,7 +392,9 @@ pub trait ActiveEventLoop: AsAny {
/// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow;
/// This stops the event loop.
/// This exits the event loop.
///
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
fn exit(&self);
/// Returns if the [`EventLoop`] is about to stop.

View File

@@ -1,14 +1,24 @@
//! Winit is a cross-platform window creation and event loop management library.
//!
//! # Building windows
//!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
//! the [`EventLoop::new()`] function.
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//! // ...
//! }
//! ```
//!
//! Then you create a [`Window`] with [`create_window`].
//!
//! # Event handling
//!
//! Basically all of the functionality that Winit exposes requires an [`ActiveEventLoop`], which you
//! can get access to by running an [`EventLoop`] using [`EventLoop::new()`] and
//! [`EventLoop::run()`].
//!
//! Once it's running, you can create your [`Window`]s with [`ActiveEventLoop::create_window()`] by
//! passing in the desired [`WindowAttributes`].
//!
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the
//! window or a key getting pressed while the window is focused. Devices can generate
@@ -16,10 +26,9 @@
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`].
//!
//! You retrieve by implementing [`ApplicationHandler`] for a new type, which will be the state of
//! your application. The methods in this trait will continuously receive events until
//! [`ActiveEventLoop::exit()`] is used, at which point your application state will be dropped, and
//! the application shuts down.
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
@@ -40,20 +49,19 @@
//! use winit::application::ApplicationHandler;
//! use winit::event::WindowEvent;
//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
//! use winit::window::{Window, WindowId};
//! use winit::window::{Window, WindowId, WindowAttributes};
//!
//! #[derive(Default)]
//! struct App {
//! window: Box<dyn Window>,
//! window: Option<Box<dyn Window>>,
//! }
//!
//! impl ApplicationHandler for App {
//! fn window_event(
//! &mut self,
//! event_loop: &dyn ActiveEventLoop,
//! id: WindowId,
//! event: WindowEvent,
//! ) {
//! // Called by `EventLoop::run` when a new event happens on the window.
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
//! }
//!
//! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
//! match event {
//! WindowEvent::CloseRequested => {
//! println!("The close button was pressed; stopping");
@@ -73,19 +81,16 @@
//! // You only need to call this if you've determined that you need to redraw in
//! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead.
//! self.window.request_redraw();
//! },
//! self.window.as_ref().unwrap().request_redraw();
//! }
//! _ => (),
//! }
//! }
//! }
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Create a new event loop.
//! let event_loop = EventLoop::new()?;
//!
//! // Configure settings before launching.
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
@@ -96,21 +101,8 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//!
//! // Launch and begin running our event loop.
//! event_loop.run(|event_loop| {
//! // The event loop has launched, and we can initialize our UI state in this closure.
//!
//! // Create a simple window with default attributes.
//! let window = event_loop
//! .create_window(Window::default_attributes())
//! .expect("failed creating window");
//!
//! // Give our newly created application state to Winit, which will, when necessary, call
//! // the `ApplicationHandler` methods configured above.
//! App { window }
//! })?;
//!
//! Ok(())
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! }
//! ```
//!
@@ -258,21 +250,18 @@
//! |32-bit ARM Android |`arm-linux-androideabi` |Android |
//! |64-bit SPARC Linux with glibc |`sparc64-unknown-linux-gnu` |X11, Wayland |
//!
//! [`ActiveEventLoop`]: event_loop::ActiveEventLoop
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run()`]: event_loop::EventLoop::run
//! [`ActiveEventLoop::exit()`]: event_loop::ActiveEventLoop::exit
//! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app
//! [`exit()`]: event_loop::ActiveEventLoop::exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new
//! [`ActiveEventLoop::create_window()`]: event_loop::ActiveEventLoop::create_window
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`ApplicationHandler`]: application::ApplicationHandler
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.

View File

@@ -1,10 +1,5 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32};
use crate::dpi::{PhysicalPosition, PhysicalSize};
@@ -12,87 +7,63 @@ use crate::platform_impl;
/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
/// Can be retrieved with [`MonitorHandle::video_modes()`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
}
impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f)
}
}
impl PartialOrd for VideoModeHandle {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoModeHandle {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
self.monitor().cmp(&other.monitor()).then(
self.size()
.cmp(&other.size())
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
}
impl VideoModeHandle {
impl VideoMode {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size()
self.size
}
/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.video_mode.bit_depth()
self.bit_depth
}
/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.video_mode.refresh_rate_millihertz()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.video_mode.monitor() }
self.refresh_rate_millihertz
}
}
impl std::fmt::Display for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size().width,
self.size().height,
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
impl fmt::Display for VideoMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}x{}", self.size.width, self.size.height)?;
if let Some(refresh_rate) = self.refresh_rate_millihertz {
write!(f, "@{refresh_rate}mHz")?;
}
if let Some(bit_depth) = self.bit_depth {
write!(f, " ({bit_depth} bpp)")?;
}
Ok(())
}
}
/// Handle to a monitor.
///
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
/// Allows you to retrieve basic information and metadata about a monitor.
///
/// Can be used in [`Window`] creation to place the window on a specific
/// monitor.
///
/// This can be retrieved from one of the following methods, which return an
/// iterator of [`MonitorHandle`]s:
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
///
/// ## Platform-specific
///
@@ -186,13 +157,13 @@ impl MonitorHandle {
/// Returns the currently active video mode of this monitor.
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
pub fn current_video_mode(&self) -> Option<VideoMode> {
self.inner.current_video_mode()
}
/// Returns all fullscreen video modes supported by this monitor.
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner.video_modes()
}
}

View File

@@ -62,7 +62,7 @@
//! 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.30.5",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.9",
//! 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

View File

@@ -107,7 +107,7 @@ use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::monitor::{MonitorHandle, VideoMode};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`Window`] that are specific to iOS.
@@ -384,23 +384,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoModeHandle`] for this monitor.
/// Returns the preferred [`VideoMode`] for this monitor.
///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoModeHandle;
fn preferred_video_mode(&self) -> VideoMode;
}
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]
fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
fn preferred_video_mode(&self) -> VideoMode {
self.inner.preferred_video_mode()
}
}

View File

@@ -21,26 +21,21 @@
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop;
//!
//! declare_class!(
//! define_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! struct AppDelegate;
//!
//! unsafe impl ClassType for AppDelegate {
//! type Super = NSObject;
//! type Mutability = mutability::MainThreadOnly;
//! const NAME: &'static str = "MyAppDelegate";
//! }
//!
//! impl DeclaredClass for AppDelegate {}
//!
//! unsafe impl NSObjectProtocol for AppDelegate {}
//!
//! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[method(application:openURLs:)]
//! #[unsafe(method(application:openURLs:))]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example.
@@ -51,7 +46,7 @@
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
//! }
//! }
//!
@@ -65,7 +60,7 @@
//! let app = NSApplication::sharedApplication(mtm);
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
//!
//! // event_loop.run(|event_loop| { ... })?;
//! // event_loop.run_app(&mut my_app);
//! Ok(())
//! }
//! ```
@@ -332,6 +327,13 @@ pub trait WindowAttributesExtMacOS {
fn with_borderless_game(self, borderless_game: bool) -> Self;
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
/// [`NSWindow`].
///
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
fn with_panel(self, panel: bool) -> Self;
}
impl WindowAttributesExtMacOS for WindowAttributes {
@@ -412,6 +414,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}
#[inline]
fn with_panel(mut self, panel: bool) -> Self {
self.platform_specific.panel = panel;
self
}
}
pub trait EventLoopBuilderExtMacOS {
@@ -505,7 +513,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
}
}

View File

@@ -42,15 +42,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;
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
pub mod scancode;

View File

@@ -1,35 +0,0 @@
use crate::event::KeyEvent;
use crate::keyboard::Key;
/// Additional methods for the `KeyEvent` which cannot be implemented on all
/// platforms.
pub trait KeyEventExtModifierSupplement {
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
fn text_with_all_modifiers(&self) -> Option<&str>;
/// This value ignores all modifiers including,
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
/// and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case `logical_key` reports `Dead`, this will still report the
/// key as `Character` according to the current keyboard layout. This value
/// cannot be `Dead`.
fn key_without_modifiers(&self) -> Key;
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific.text_with_all_modifiers.as_ref().map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

View File

@@ -83,22 +83,18 @@ 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 application whenever
/// the application `RunLoop` indicates that it is preparing to block and wait for new events.
/// - **MacOS**: Certain actions like resizing the window will enter a "modal" state, where
/// `pump_app_events` will process events internally, and block until the resize is over.
///
/// 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 `NSApplication` which aren't accessible to
/// application developers)
/// Thus, if you render or run your game code outside of `ApplicationHandler`, your
/// application will freeze while the window resizes. The recommended approach is to render
/// inside [`WindowEvent::RedrawRequested`] instead.
///
/// It's likely this will be less efficient than polling on other OSs and
/// 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 `NSApplication` is global state.
/// Furthermore, when pumping events the `NSApplication` is still considered stopped to
/// crates like `rfd` that inspect [`-[NSApplication isRunning]`][isrunning].
///
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
/// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested
/// [isrunning]: https://developer.apple.com/documentation/appkit/nsapplication/isrunning?language=objc
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,

View File

@@ -1,14 +1,16 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event_loop::EventLoop;
#[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
use crate::{
event_loop::ActiveEventLoop, platform::pump_events::EventLoopExtPumpEvents, window::Window,
};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`)
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// closures and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit.
@@ -21,7 +23,7 @@ pub trait EventLoopExtRunOnDemand {
/// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API)
///
/// Each time `run_on_demand` is called the startup sequence of `init`, followed by
/// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by
/// `resume` is being preserved.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -38,7 +40,7 @@ pub trait EventLoopExtRunOnDemand {
/// [^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
/// specifically need the ability to re-run a single event loop more than once
///
/// # Supported Platforms
@@ -58,18 +60,12 @@ pub trait EventLoopExtRunOnDemand {
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError>;
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.event_loop.run_on_demand(init_closure)
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app_on_demand(app)
}
}

View File

@@ -65,9 +65,9 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
let _is_wayland = self.is_wayland();
if _is_wayland {
env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
env::var(WAYLAND_VAR).ok().map(ActivationToken::from_raw)
} else {
env::var(X11_VAR).ok().map(ActivationToken::_new)
env::var(X11_VAR).ok().map(ActivationToken::from_raw)
}
}
}
@@ -111,6 +111,6 @@ pub fn reset_activation_token_env() {
///
/// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
env::set_var(X11_VAR, &token.token);
env::set_var(WAYLAND_VAR, token.token);
}

View File

@@ -190,10 +190,10 @@ pub trait EventLoopExtWeb {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run()`")]
#[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")]
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run()`]"
doc = "[`run_app()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
@@ -206,9 +206,9 @@ pub trait EventLoopExtWeb {
///
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run()`]: EventLoop::run()"
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^1]: `run()` is _not_ available on Wasm when the target supports `exception-handling`.
/// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`].

View File

@@ -19,11 +19,11 @@ use crate::monitor::MonitorHandle;
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
/// Window Handle type used by Win32 API
pub type HWND = isize;
pub type HWND = *mut c_void;
/// Menu Handle type used by Win32 API
pub type HMENU = isize;
pub type HMENU = *mut c_void;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = isize;
pub type HMONITOR = *mut c_void;
/// Describes a system-drawn backdrop material of a window.
///
@@ -660,6 +660,17 @@ impl DeviceIdExtWindows for DeviceId {
}
/// Additional methods on `Icon` that are specific to Windows.
///
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
///
/// The `ICON` resource definition statement use the following syntax:
/// ```rc
/// nameID ICON filename
/// ```
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
/// `filename` is the name of the file that contains the resource.
///
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
@@ -671,7 +682,12 @@ pub trait IconExtWindows: Sized {
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
-> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library.
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
///
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
///
/// [`from_resource_name`]: IconExtWindows::from_resource_name
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
@@ -679,6 +695,55 @@ pub trait IconExtWindows: Sized {
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library by its name.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
///
/// # Notes
///
/// Consider the following resource definition statements:
/// ```rc
/// app ICON "app.ico"
/// 1 ICON "a.ico"
/// 0027 ICON "custom.ico"
/// 0 ICON "alt.ico"
/// ```
///
/// Due to some internal implementation details of the resource embedding/loading process on
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
/// [`from_resource`]:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// use winit::platform::windows::IconExtWindows;
/// use winit::window::Icon;
///
/// assert!(Icon::from_resource_name("app", None).is_ok());
/// assert!(Icon::from_resource(1, None).is_ok());
/// assert!(Icon::from_resource(27, None).is_ok());
/// assert!(Icon::from_resource_name("27", None).is_err());
/// assert!(Icon::from_resource_name("0027", None).is_err());
/// ```
///
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
/// name:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// # use winit::platform::windows::IconExtWindows;
/// # use winit::window::Icon;
/// assert!(Icon::from_resource_name("0", None).is_ok());
/// assert!(Icon::from_resource(0, None).is_err());
/// ```
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
@@ -694,4 +759,9 @@ impl IconExtWindows for Icon {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?;
Ok(Icon { inner: win_icon })
}
}

View File

@@ -1,6 +1,5 @@
use std::cell::Cell;
use std::hash::Hash;
use std::num::{NonZeroU16, NonZeroU32};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -21,7 +20,7 @@ use crate::event_loop::{
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
use crate::platform::pump_events::PumpStatus;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
@@ -97,9 +96,6 @@ impl RedrawRequester {
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop {
pub(crate) android_app: AndroidApp,
window_target: ActiveEventLoop,
@@ -479,7 +475,8 @@ impl EventLoop {
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
text_with_all_modifiers: None,
key_without_modifiers: keycodes::to_logical(key_char, keycode),
},
is_synthetic: false,
};
@@ -496,21 +493,15 @@ impl EventLoop {
input_status
}
pub fn run<A: ApplicationHandler>(
mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
mut app: A,
) -> Result<(), EventLoopError> {
self.window_target.clear_exit();
let mut app = init_closure(&self.window_target);
loop {
match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => {
@@ -552,6 +543,8 @@ impl EventLoop {
if self.exiting() {
self.loop_running = false;
app.exiting(&self.window_target);
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
@@ -1025,32 +1018,11 @@ impl MonitorHandle {
unreachable!()
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
pub fn current_video_mode(&self) -> Option<VideoMode> {
unreachable!()
}
pub fn video_modes(&self) -> std::iter::Empty<VideoModeHandle> {
unreachable!()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle;
impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> {
unreachable!()
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
unreachable!()
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
unreachable!()
}
pub fn monitor(&self) -> MonitorHandle {
pub fn video_modes(&self) -> std::iter::Empty<VideoMode> {
unreachable!()
}
}

View File

@@ -1,51 +1,101 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
pub(super) struct WinitApplication;
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
// SAFETY: Creating in a `const` context, where there is no concept of the main thread.
MainThreadBound::new(Cell::new(None), unsafe { MainThreadMarker::new_unchecked() })
};
impl DeclaredClass for WinitApplication {}
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
let mtm = MainThreadMarker::from(app);
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)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
#[method(sendEvent:)]
fn send_event(&self, event: &NSEvent) {
// 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 = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{
if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event);
}
} else {
let app_state = AppState::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&app_state, event);
unsafe { msg_send![super(self), sendEvent: event] }
}
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
//
// 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 = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) {
if let Some(key_window) = app.keyWindow() {
key_window.sendEvent(event);
}
return;
}
);
// Events are generally scoped to the window level, so the best way
// to get device events is to listen for them on NSApplication.
let app_state = AppState::get(mtm);
maybe_dispatch_device_event(&app_state, event);
let original = ORIGINAL.get(mtm).get().expect("no existing sendEvent: handler set");
original(app, sel, event)
}
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
///
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
/// give the user full control over their `NSApplication`, so we override the method here using
/// method swizzling instead.
///
/// This _should_ also allow two versions of Winit to exist in the same application.
///
/// See the following links for more info on method swizzling:
/// - <https://nshipster.com/method-swizzling/>
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
///
/// NOTE: This function assumes that the passed in application object is the one returned from
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
/// For testing though, we allow it to be a different object.
pub(crate) fn override_send_event(global_app: &NSApplication) {
let mtm = MainThreadMarker::from(global_app);
let class = global_app.class();
let method =
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything.
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
return;
}
// SAFETY: Our implementation has:
// 1. The same signature as `sendEvent:`.
// 2. Does not impose extra safety requirements on callers.
let original = unsafe { method.set_implementation(overridden) };
// SAFETY: This is the actual signature of `sendEvent:`.
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
// stored here.
//
// It is only usable from the main thread, however, so we're good!
ORIGINAL.get(mtm).set(Some(original));
}
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let event_type = unsafe { event.r#type() };
@@ -87,3 +137,52 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
_ => (),
}
}
#[cfg(test)]
mod tests {
use objc2::rc::Retained;
use objc2::{define_class, msg_send, ClassType};
use objc2_app_kit::NSResponder;
use objc2_foundation::NSObject;
use super::*;
#[test]
fn test_override() {
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
let Some(mtm) = MainThreadMarker::new() else { return };
// Create a new application, without making it the shared application.
let app = unsafe { NSApplication::new(mtm) };
override_send_event(&app);
// Test calling twice works.
override_send_event(&app);
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
// unsafe {
// let event = super::super::event::dummy_event().unwrap();
// app.sendEvent(&event)
// }
}
#[test]
fn test_custom_class() {
let Some(_mtm) = MainThreadMarker::new() else { return };
define_class!(
#[unsafe(super(NSApplication, NSResponder, NSObject))]
#[name = "TestApplication"]
pub(super) struct TestApplication;
impl TestApplication {
#[unsafe(method(sendEvent:))]
fn send_event(&self, _event: &NSEvent) {
todo!()
}
}
);
let app: Retained<TestApplication> = unsafe { msg_send![TestApplication::class(), new] };
override_send_event(&app);
}
}

View File

@@ -1,20 +1,22 @@
use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::{Rc, Weak};
use std::sync::atomic::Ordering as AtomicOrdering;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::{MainThreadMarker, NSNotification};
use objc2_foundation::NSNotification;
use super::super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, EventLoopProxy, PanicInfo};
use super::super::event_loop_proxy::EventLoopProxy;
use super::event_loop::{stop_app_immediately, ActiveEventLoop};
use super::menu;
use super::observer::{EventLoopWaker, RunLoop};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop, ControlFlow};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
#[derive(Debug)]
@@ -26,41 +28,22 @@ pub(super) struct AppState {
run_loop: RunLoop,
event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
stop_after_wait: Cell<bool>,
stop_on_redraw: Cell<bool>,
/// Whether `applicationDidFinishLaunching:` has been run or not.
/// Whether `NSApplicationDidFinishLaunchingNotification` has been sent.
is_launched: Cell<bool>,
/// Whether an `EventLoop` is currently running.
is_running: Cell<bool>,
/// Whether the user has requested the event loop to exit.
exit: Cell<bool>,
control_flow: Cell<ControlFlow>,
waker: RefCell<EventLoopWaker>,
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_redraw: RefCell<Vec<WindowId>>,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
// as such should be careful to not add fields that, in turn, strongly reference those.
}
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
// main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
StaticMainThreadBound(OnceCell::new());
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
impl AppState {
pub(super) fn setup_global(
@@ -69,25 +52,23 @@ impl AppState {
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Rc<Self> {
let this = Rc::new(AppState {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
let this = Rc::new(Self {
mtm,
activation_policy,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
event_loop_proxy,
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
stop_after_wait: Cell::new(false),
stop_on_redraw: Cell::new(false),
is_launched: Cell::new(false),
is_running: Cell::new(false),
exit: Cell::new(false),
control_flow: Cell::new(ControlFlow::default()),
waker: RefCell::new(EventLoopWaker::new()),
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
});
@@ -103,15 +84,17 @@ impl AppState {
.clone()
}
// NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationDidFinishLaunchingNotification");
// NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates. There is no other
// way to know this information, other than to keep track of it
// ourselves.
self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// We need to delay setting the activation policy and activating the app until
// `NSApplicationDidFinishLaunchingNotification` has been sent. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
if let Some(activation_policy) = self.activation_policy {
app.setActivationPolicy(activation_policy);
@@ -141,100 +124,39 @@ impl AppState {
self.waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_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 self.stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// 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.
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.internal_exit();
}
/// Place the event handler in the application state for the duration
/// of the given closure.
pub fn set_init_closure<A: ApplicationHandler, R>(
pub fn set_event_handler<R>(
&self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
let init_closure = Box::new(
|active_event_loop: &'_ dyn ActiveEventLoop| -> Box<dyn ApplicationHandler + '_> {
Box::new(init_closure(active_event_loop))
},
);
self.event_handler.set(init_closure, closure)
self.event_handler.set(handler, closure)
}
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.event_loop_proxy
}
/// If `pump_events` is called to progress the event loop then we
/// bootstrap the event loop via `-[NSApplication run]` but will use
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
pub fn set_stop_on_launch(&self) {
self.stop_on_launch.set(true);
}
pub fn set_stop_before_wait(&self, value: bool) {
self.stop_before_wait.set(value)
}
pub fn set_stop_after_wait(&self, value: bool) {
self.stop_after_wait.set(value)
}
pub fn set_stop_on_redraw(&self, value: bool) {
self.stop_on_redraw.set(value)
}
pub fn set_wait_timeout(&self, value: Option<Instant>) {
self.wait_timeout.set(value)
}
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
///
/// 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.
pub fn internal_exit(self: &Rc<Self>) {
self.set_is_running(false);
self.set_stop_on_redraw(false);
self.set_stop_before_wait(false);
self.set_stop_after_wait(false);
self.set_wait_timeout(None);
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
}
pub fn is_launched(&self) -> bool {
self.is_launched.get()
}
pub fn set_is_running(&self, value: bool) {
self.is_running.set(value)
}
pub fn is_running(&self) -> bool {
self.is_running.get()
}
pub fn exit(&self) {
self.exit.set(true)
}
@@ -262,14 +184,6 @@ impl AppState {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
// events as a way to ensure that `pump_events` can't block an external loop
// indefinitely
if self.stop_on_redraw.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
}
@@ -321,21 +235,12 @@ impl AppState {
}
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
pub fn wakeup(self: &Rc<Self>) {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
if !self.event_handler.ready() {
return;
}
if self.stop_after_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
let start = self.start_time.get().unwrap();
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
@@ -353,22 +258,14 @@ impl AppState {
}
// Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
pub fn cleared(self: &Rc<Self>) {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
if !self.event_handler.ready() {
return;
}
if self.event_loop_proxy.wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
@@ -384,24 +281,12 @@ impl AppState {
stop_app_immediately(&app);
}
if self.stop_before_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
self.start_time.set(Some(Instant::now()));
let wait_timeout = self.wait_timeout.get(); // configured by pump_events
let app_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Instant::now()),
ControlFlow::WaitUntil(instant) => Some(instant),
};
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
self.waker.borrow_mut().start_at(app_timeout);
}
}
/// Returns the minimum `Option<Instant>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}

View File

@@ -4,11 +4,10 @@ use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{msg_send_id, sel, ClassType};
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString,
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
};
use crate::cursor::{CursorImage, OnlyCursorImageSource};
@@ -67,8 +66,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class();
if cls.responds_to(sel) {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
if unsafe { msg_send![cls, respondsToSelector: sel] } {
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
Some(cursor)
} else {
tracing::warn!("cursor `{sel}` appears to be invalid");
@@ -130,25 +129,21 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send_id![
msg_send![
<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()
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() {
x = n.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()
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
if let Ok(n) = n.downcast::<NSNumber>() {
y = n.as_cgfloat();
}
}
@@ -188,6 +183,7 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
}
#[allow(deprecated)]
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
match icon {
CursorIcon::Default => default_cursor(),
@@ -208,7 +204,9 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(),

View File

@@ -1,10 +1,10 @@
use std::ffi::c_void;
use std::ptr::NonNull;
use core_foundation::base::CFRelease;
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
use dispatch2::run_on_main;
use objc2::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_foundation::{run_on_main, NSPoint};
use objc2_core_foundation::{CFData, CFDataGetBytePtr, CFRetained};
use objc2_foundation::NSPoint;
use smol_str::SmolStr;
use super::ffi;
@@ -14,37 +14,29 @@ use crate::keyboard::{
PhysicalKey,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
/// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key {
let mut string = [0; 16];
let input_source;
let layout;
unsafe {
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
if input_source.is_null() {
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
}
let layout_data =
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
if layout_data.is_null() {
CFRelease(input_source as *mut c_void);
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
}
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
}
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
};
let input_source = unsafe { CFRetained::from_raw(ptr) };
let layout_data = unsafe {
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData)
};
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
};
let layout = unsafe { CFDataGetBytePtr(layout_data).cast() };
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0;
let mut dead_keys = 0;
let modifiers = 0;
let mut string = [0; 16];
let translate_result = unsafe {
ffi::UCKeyTranslate(
layout,
@@ -59,9 +51,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
string.as_mut_ptr(),
)
};
unsafe {
CFRelease(input_source as *mut c_void);
}
if translate_result != 0 {
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
return Key::Unidentified(NativeKey::MacOS(scancode));
@@ -92,17 +81,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
/// Create `KeyEvent` for the given `NSEvent`.
///
/// This function shouldn't be called when the IME input is in process.
pub(crate) fn create_key_event(
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
) -> KeyEvent {
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
let mut physical_key = scancode_to_physicalkey(scancode as u32);
// NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance:
@@ -111,20 +95,15 @@ pub(crate) fn create_key_event(
// * 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() {
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() {
None
} else {
let characters =
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
if characters.is_empty() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
};
let key_from_code = code_to_key(physical_key, scancode);
@@ -133,8 +112,8 @@ pub(crate) fn create_key_event(
let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
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
@@ -172,7 +151,8 @@ pub(crate) fn create_key_event(
repeat: is_repeat,
state,
text,
platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
text_with_all_modifiers,
key_without_modifiers,
}
}
@@ -182,6 +162,9 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
};
// Roughly same handling as Firefox and Chromium:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMKeyName.h
// https://chromium.googlesource.com/chromium/src.git/+/010a75a426c4a2292955a52f480e9251cacf750e/ui/events/keycodes/keyboard_code_conversion_mac.mm#100
Key::Named(match code {
KeyCode::Enter => NamedKey::Enter,
KeyCode::Tab => NamedKey::Tab,
@@ -196,14 +179,17 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
KeyCode::ShiftRight => NamedKey::Shift,
KeyCode::AltRight => NamedKey::Alt,
KeyCode::ControlRight => NamedKey::Control,
KeyCode::CapsLock => NamedKey::CapsLock,
KeyCode::NumLock => NamedKey::NumLock,
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
KeyCode::AudioVolumeMute => NamedKey::AudioVolumeMute,
// Other numpad keys all generate text on macOS (if I understand correctly)
KeyCode::NumpadEnter => NamedKey::Enter,
KeyCode::Fn => NamedKey::Fn,
KeyCode::F1 => NamedKey::F1,
KeyCode::F2 => NamedKey::F2,
KeyCode::F3 => NamedKey::F3,
@@ -224,17 +210,27 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
KeyCode::F18 => NamedKey::F18,
KeyCode::F19 => NamedKey::F19,
KeyCode::F20 => NamedKey::F20,
KeyCode::F21 => NamedKey::F21,
KeyCode::F22 => NamedKey::F22,
KeyCode::F23 => NamedKey::F23,
KeyCode::F24 => NamedKey::F24,
KeyCode::Insert => NamedKey::Insert,
KeyCode::Home => NamedKey::Home,
KeyCode::PageUp => NamedKey::PageUp,
KeyCode::Delete => NamedKey::Delete,
KeyCode::End => NamedKey::End,
KeyCode::Help => NamedKey::Help,
KeyCode::PageDown => NamedKey::PageDown,
KeyCode::ArrowLeft => NamedKey::ArrowLeft,
KeyCode::ArrowRight => NamedKey::ArrowRight,
KeyCode::ArrowDown => NamedKey::ArrowDown,
KeyCode::ArrowUp => NamedKey::ArrowUp,
KeyCode::ContextMenu => NamedKey::ContextMenu,
KeyCode::Lang2 => NamedKey::Eisu,
KeyCode::Lang1 => NamedKey::KanjiMode,
_ => return Key::Unidentified(NativeKey::MacOS(scancode)),
})
}
@@ -318,26 +314,19 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty();
state
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift));
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
state.set(
ModifiersState::CONTROL,
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
);
state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control));
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option));
pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
state.set(
ModifiersState::SUPER,
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
);
state.set(ModifiersState::SUPER, flags.contains(NSEventModifierFlags::Command));
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
@@ -377,6 +366,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::KeyX => Some(0x07),
KeyCode::KeyC => Some(0x08),
KeyCode::KeyV => Some(0x09),
KeyCode::IntlBackslash => Some(0x0a),
KeyCode::KeyB => Some(0x0b),
KeyCode::KeyQ => Some(0x0c),
KeyCode::KeyW => Some(0x0d),
@@ -422,18 +412,21 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::SuperRight => Some(0x36),
KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a),
KeyCode::ControlLeft => Some(0x3b),
KeyCode::ShiftRight => Some(0x3c),
KeyCode::AltRight => Some(0x3d),
KeyCode::ControlRight => Some(0x3e),
KeyCode::Fn => Some(0x3f),
KeyCode::F17 => Some(0x40),
KeyCode::NumpadDecimal => Some(0x41),
KeyCode::NumpadMultiply => Some(0x43),
KeyCode::NumpadAdd => Some(0x45),
KeyCode::NumLock => Some(0x47),
KeyCode::AudioVolumeUp => Some(0x49),
KeyCode::AudioVolumeDown => Some(0x4a),
KeyCode::AudioVolumeUp => Some(0x48),
KeyCode::AudioVolumeDown => Some(0x49),
KeyCode::AudioVolumeMute => Some(0x4a),
KeyCode::NumpadDivide => Some(0x4b),
KeyCode::NumpadEnter => Some(0x4c),
KeyCode::NumpadSubtract => Some(0x4e),
@@ -452,17 +445,22 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::Numpad8 => Some(0x5b),
KeyCode::Numpad9 => Some(0x5c),
KeyCode::IntlYen => Some(0x5d),
KeyCode::IntlRo => Some(0x5e),
KeyCode::NumpadComma => Some(0x5f),
KeyCode::F5 => Some(0x60),
KeyCode::F6 => Some(0x61),
KeyCode::F7 => Some(0x62),
KeyCode::F3 => Some(0x63),
KeyCode::F8 => Some(0x64),
KeyCode::F9 => Some(0x65),
KeyCode::Lang2 => Some(0x66),
KeyCode::F11 => Some(0x67),
KeyCode::Lang1 => Some(0x68),
KeyCode::F13 => Some(0x69),
KeyCode::F16 => Some(0x6a),
KeyCode::F14 => Some(0x6b),
KeyCode::F10 => Some(0x6d),
KeyCode::ContextMenu => Some(0x6e),
KeyCode::F12 => Some(0x6f),
KeyCode::F15 => Some(0x71),
KeyCode::Insert => Some(0x72),
@@ -478,11 +476,26 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::ArrowRight => Some(0x7c),
KeyCode::ArrowDown => Some(0x7d),
KeyCode::ArrowUp => Some(0x7e),
KeyCode::Power => Some(0x7f),
_ => None,
}
}
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// See also:
// Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
//
// Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values:
//
// > the system handles some function keys at a lower level and your app never sees them.
// > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and
// > Function key found on many Macs.
//
// So the handling of some of these is mostly for show.
PhysicalKey::Code(match scancode {
0x00 => KeyCode::KeyA,
0x01 => KeyCode::KeyS,
@@ -494,7 +507,11 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x07 => KeyCode::KeyX,
0x08 => KeyCode::KeyC,
0x09 => KeyCode::KeyV,
// 0x0a => World 1,
// This key is typically located near LeftShift key, roughly the same location as backquote
// (`) on Windows' US layout.
//
// The keycap varies on international keyboards.
0x0a => KeyCode::IntlBackslash,
0x0b => KeyCode::KeyB,
0x0c => KeyCode::KeyQ,
0x0d => KeyCode::KeyW,
@@ -536,7 +553,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x31 => KeyCode::Space,
0x32 => KeyCode::Backquote,
0x33 => KeyCode::Backspace,
// 0x34 => unknown,
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter
0x35 => KeyCode::Escape,
0x36 => KeyCode::SuperRight,
0x37 => KeyCode::SuperLeft,
@@ -555,15 +572,10 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 0x44 => unknown,
0x45 => KeyCode::NumpadAdd,
// 0x46 => unknown,
0x47 => KeyCode::NumLock,
// 0x48 => KeyCode::NumpadClear,
// TODO: (Artur) for me, kVK_VolumeUp is 0x48
// macOS 10.11
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
// Versions/A/Headers/Events.h
0x49 => KeyCode::AudioVolumeUp,
0x4a => KeyCode::AudioVolumeDown,
0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear
0x48 => KeyCode::AudioVolumeUp,
0x49 => KeyCode::AudioVolumeDown,
0x4a => KeyCode::AudioVolumeMute,
0x4b => KeyCode::NumpadDivide,
0x4c => KeyCode::NumpadEnter,
// 0x4d => unknown,
@@ -583,23 +595,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x5b => KeyCode::Numpad8,
0x5c => KeyCode::Numpad9,
0x5d => KeyCode::IntlYen,
// 0x5e => JIS Ro,
// 0x5f => unknown,
0x5e => KeyCode::IntlRo,
0x5f => KeyCode::NumpadComma,
0x60 => KeyCode::F5,
0x61 => KeyCode::F6,
0x62 => KeyCode::F7,
0x63 => KeyCode::F3,
0x64 => KeyCode::F8,
0x65 => KeyCode::F9,
// 0x66 => JIS Eisuu (macOS),
0x66 => KeyCode::Lang2,
0x67 => KeyCode::F11,
// 0x68 => JIS Kanna (macOS),
0x68 => KeyCode::Lang1,
0x69 => KeyCode::F13,
0x6a => KeyCode::F16,
0x6b => KeyCode::F14,
// 0x6c => unknown,
0x6d => KeyCode::F10,
// 0x6e => unknown,
0x6e => KeyCode::ContextMenu,
0x6f => KeyCode::F12,
// 0x70 => unknown,
0x71 => KeyCode::F15,
@@ -616,11 +628,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x7c => KeyCode::ArrowRight,
0x7d => KeyCode::ArrowDown,
0x7e => KeyCode::ArrowUp,
// 0x7f => unknown,
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
// backquote (`) on Windows' US layout.
0xa => KeyCode::Backquote,
0x7f => KeyCode::Power, // On 10.7 and 10.8 only
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
})
}

View File

@@ -1,29 +1,21 @@
use std::any::Any;
use std::cell::Cell;
use std::os::raw::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::time::Duration;
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{msg_send_id, sel, ClassType};
use objc2::runtime::ProtocolObject;
use objc2::{available, MainThreadMarker};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow,
NSApplicationWillTerminateNotification, NSEventMask, NSWindow,
};
use objc2_foundation::{
NSDate, NSDefaultRunLoopMode, NSNotificationCenter, NSObjectProtocol, NSTimeInterval,
};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app::WinitApplication;
use super::app::override_send_event;
use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
@@ -33,8 +25,7 @@ use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::macos::ActivationPolicy;
@@ -42,36 +33,6 @@ use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
}
// WARNING:
// As long as this struct is used through its `impl`, it is UnwindSafe.
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
impl UnwindSafe for PanicInfo {}
impl RefUnwindSafe for PanicInfo {}
impl PanicInfo {
pub fn is_panicking(&self) -> bool {
let inner = self.inner.take();
let result = inner.is_some();
self.inner.set(inner);
result
}
/// Overwrites the current state if the current state is not panicking
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
if !self.is_panicking() {
self.inner.set(Some(p));
}
}
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
self.inner.take()
}
}
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(super) app_state: Rc<AppState>,
@@ -129,7 +90,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
// Dark appearance was introduced in macOS 10.14
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
@@ -176,15 +138,17 @@ pub struct EventLoop {
app: Retained<NSApplication>,
app_state: Rc<AppState>,
/// Whether an outer event loop is running.
pump_has_sent_init: bool,
window_target: ActiveEventLoop,
panic_info: Rc<PanicInfo>,
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
// the system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -207,16 +171,6 @@ impl EventLoop {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() {
panic!(
"`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication"
);
}
let activation_policy = match attributes.activation_policy {
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
@@ -231,6 +185,21 @@ impl EventLoop {
attributes.activate_ignoring_other_apps,
);
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
// Queue `NSApplicationDidFinishLaunchingNotification` and generally
// make sure the application is fully initialized (once the run loop
// starts).
//
// This is technically only necessary when using `pump_app_events`
// (`app.run()` will do it for us in `run_app_on_demand`), but we
// might as well do it everywhere.
unsafe { app.finishLaunching() };
let center = unsafe { NSNotificationCenter::defaultCenter() };
let weak_app_state = Rc::downgrade(&app_state);
@@ -257,14 +226,13 @@ impl EventLoop {
},
);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
setup_control_flow_observers(mtm);
Ok(EventLoop {
app,
app_state: app_state.clone(),
pump_has_sent_init: false,
window_target: ActiveEventLoop { app_state, mtm },
panic_info,
_did_finish_launching_observer,
_will_terminate_observer,
})
@@ -274,47 +242,32 @@ impl EventLoop {
&self.window_target
}
pub fn run<A: ApplicationHandler>(
mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups.
pub fn run_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
mut app: A,
) -> Result<(), EventLoopError> {
self.app_state.clear_exit();
self.app_state.set_init_closure(init_closure, || {
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(false);
self.app_state.set_stop_on_redraw(false);
if self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_is_running(true);
// The `NSApplicationDidFinishLaunchingNotification` notification is globally
// only delivered once, but for the purpose of our events, we want to act
// as-if an entirely new event loop has been started on each invocation of
// `run_app_on_demand`.
self.app_state.dispatch_init_events();
}
// SAFETY: We do not run the application re-entrantly
unsafe { 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 `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() {
resume_unwind(panic);
}
// NOTE: We don't base this on `pump_events` because
// `nextEventMatchingMask:untilDate:inMode:dequeue:` is worse supported,
// especially as the top-level handler. In part because this sets the `isRunning`
// flag (which is used by crates like `rfd`), while `nextEventMatchingMask` won't.
//
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
self.app.run();
self.app_state.internal_exit()
})
@@ -330,60 +283,43 @@ impl EventLoop {
) -> PumpStatus {
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
// 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 !self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch();
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched
} else if !self.app_state.is_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.
self.app_state.set_is_running(true);
if self.app_state.is_launched() && !self.pump_has_sent_init {
// If the application is already launched, we won't get the re-initialization
// events. Dispatch them here instead.
self.app_state.dispatch_init_events();
} else {
// Only run for as long as the given `Duration` allows so we don't block the
// external loop.
match timeout {
Some(Duration::ZERO) => {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(true);
},
Some(duration) => {
self.app_state.set_stop_before_wait(false);
let timeout = Instant::now() + duration;
self.app_state.set_wait_timeout(Some(timeout));
self.app_state.set_stop_after_wait(true);
},
None => {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(true);
},
}
self.app_state.set_stop_on_redraw(true);
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
}
self.pump_has_sent_init = true;
// 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 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() {
resume_unwind(panic);
// Only run for as long as the given `Duration` allows so we don't block the
// external loop.
let expiration_date = match timeout {
Some(Duration::ZERO) => unsafe { NSDate::distantPast() },
Some(duration) => unsafe {
NSDate::dateWithTimeIntervalSinceNow(
duration.as_secs_f64() as NSTimeInterval
)
},
None => unsafe { NSDate::distantFuture() },
};
// Wait for an event to arrive within the specified duration,
// and let the application handle it if one did.
let event = unsafe {
self.app.nextEventMatchingMask_untilDate_inMode_dequeue(
NSEventMask::Any,
Some(&expiration_date),
NSDefaultRunLoopMode,
true,
)
};
if let Some(event) = event {
unsafe { self.app.sendEvent(&event) };
}
if self.app_state.exiting() {
self.app_state.internal_exit();
// If we start again, we'll emit a new set of initialization events.
self.pump_has_sent_init = false;
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
@@ -410,88 +346,3 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
app.postEvent_atStart(&dummy_event().unwrap(), true);
});
}
/// Catches panics that happen inside `f` and when a panic
/// 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> {
match catch_unwind(f) {
Ok(r) => Some(r),
Err(e) => {
// It's important that we set the panic before requesting a `stop`
// because some callback are still called during the `stop` message
// and we need to know in those callbacks if the application is currently
// panicking
{
let panic_info = panic_info.upgrade().unwrap();
panic_info.set_panic(e);
}
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
None
},
}
}
#[derive(Debug)]
pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy {
pub(crate) fn new() -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// Let the main thread know there's a new event.
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}

View File

@@ -4,18 +4,10 @@
use std::ffi::c_void;
use core_foundation::array::CFArrayRef;
use core_foundation::dictionary::CFDictionaryRef;
use core_foundation::string::CFStringRef;
use core_foundation::uuid::CFUUIDRef;
use core_graphics::base::CGError;
use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject;
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
use objc2_core_foundation::{cf_type, CFString, CFUUID};
use objc2_core_graphics::CGDirectDisplayID;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
@@ -23,22 +15,6 @@ pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP";
@@ -55,9 +31,6 @@ pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut c_void;
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13.
//
@@ -67,54 +40,11 @@ pub type CGDisplayModeRef = *mut c_void;
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
}
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
@@ -124,50 +54,13 @@ extern "C" {
) -> i32;
}
mod core_video {
use super::*;
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {}
// CVBase.h
pub type CVTimeFlags = i32; // int32_t
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CVTime {
pub time_value: i64, // int64_t
pub time_scale: i32, // int32_t
pub flags: i32, // int32_t
}
// CVReturn.h
pub type CVReturn = i32; // int32_t
pub const kCVReturnSuccess: CVReturn = 0;
// CVDisplayLink.h
pub type CVDisplayLinkRef = *mut c_void;
extern "C" {
pub fn CVDisplayLinkCreateWithCGDisplay(
displayID: CGDirectDisplayID,
displayLinkOut: *mut CVDisplayLinkRef,
) -> CVReturn;
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
displayLink: CVDisplayLinkRef,
) -> CVTime;
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
}
}
pub use core_video::*;
#[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void);
pub type TISInputSourceRef = *mut TISInputSource;
cf_type!(
#[encoding_name = "__TISInputSource"]
unsafe impl TISInputSource {}
);
#[repr(transparent)]
pub struct UCKeyboardLayout(std::ffi::c_void);
@@ -184,15 +77,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
#[link(name = "Carbon", kind = "framework")]
extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
#[allow(non_snake_case)]
pub fn TISGetInputSourceProperty(
inputSource: TISInputSourceRef,
propertyKey: CFStringRef,
inputSource: &TISInputSource,
propertyKey: &CFString,
) -> *mut c_void;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
pub fn LMGetKbdType() -> u8;

View File

@@ -1,8 +1,8 @@
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::sel;
use objc2::{sel, MainThreadMarker};
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
use objc2_foundation::{ns_string, NSProcessInfo, NSString};
struct KeyEquivalent<'a> {
key: &'a NSString,
@@ -48,10 +48,7 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: Some(
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
}),
);

View File

@@ -15,11 +15,11 @@ mod window;
mod window_delegate;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;

View File

@@ -1,38 +1,46 @@
#![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque;
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32};
use std::ptr::NonNull;
use std::{fmt, ptr};
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
use core_foundation::base::{CFRelease, TCFType};
use core_foundation::string::CFString;
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
use dispatch2::run_on_main;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::MainThreadMarker;
use objc2_app_kit::NSScreen;
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
use objc2_core_foundation::{
CFArrayGetCount, CFArrayGetValueAtIndex, CFRetained, CFUUIDGetUUIDBytes,
};
#[allow(deprecated)]
use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
CGDisplayMode, CGDisplayModeCopyPixelEncoding, CGDisplayModeGetPixelHeight,
CGDisplayModeGetPixelWidth, CGDisplayModeGetRefreshRate, CGDisplayModelNumber,
CGGetActiveDisplayList, CGMainDisplayID,
};
#[allow(deprecated)]
use objc2_core_video::{
kCVReturnSuccess, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVTimeFlags,
};
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
use super::ffi;
use super::util::cgerr;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::VideoMode;
#[derive(Clone)]
pub struct VideoModeHandle {
size: PhysicalSize<u32>,
bit_depth: Option<NonZeroU16>,
refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) mode: VideoMode,
pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode,
}
impl PartialEq for VideoModeHandle {
fn eq(&self, other: &Self) -> bool {
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
self.monitor == other.monitor && self.mode == other.mode
}
}
@@ -40,56 +48,35 @@ impl Eq for VideoModeHandle {}
impl std::hash::Hash for VideoModeHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate_millihertz.hash(state);
self.monitor.hash(state);
}
}
impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoModeHandle")
.field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
f.debug_struct("VideoMode")
.field("mode", &self.mode)
.field("monitor", &self.monitor)
.finish()
}
}
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
unsafe impl Send for NativeDisplayMode {}
unsafe impl Sync for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoModeHandle {
fn new(
monitor: MonitorHandle,
mode: NativeDisplayMode,
native_mode: NativeDisplayMode,
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
unsafe {
#[allow(deprecated)]
let pixel_encoding =
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0))
.to_string();
CGDisplayModeCopyPixelEncoding(Some(&native_mode.0)).unwrap().to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
@@ -100,48 +87,38 @@ impl VideoModeHandle {
unimplemented!()
};
VideoModeHandle {
let mode = VideoMode {
size: PhysicalSize::new(
ffi::CGDisplayModeGetPixelWidth(mode.0) as u32,
ffi::CGDisplayModeGetPixelHeight(mode.0) as u32,
CGDisplayModeGetPixelWidth(Some(&native_mode.0)) as u32,
CGDisplayModeGetPixelHeight(Some(&native_mode.0)) as u32,
),
refresh_rate_millihertz,
bit_depth: NonZeroU16::new(bit_depth),
monitor: monitor.clone(),
native_mode: mode,
}
};
VideoModeHandle { mode, monitor: monitor.clone(), native_mode }
}
}
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID);
impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
fn uuid(&self) -> [u8; 16] {
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
let cf_uuid = unsafe { CFRetained::from_raw(NonNull::new(ptr).unwrap()) };
unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into()
}
}
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
self.uuid() == other.uuid()
}
}
@@ -155,35 +132,43 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
self.uuid().cmp(&other.uuid())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
self.uuid().hash(state);
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
monitors.push_back(MonitorHandle(display));
}
monitors
} else {
VecDeque::with_capacity(0)
let mut expected_count = 0;
let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
if res.is_err() {
return VecDeque::with_capacity(0);
}
let mut displays: Vec<CGDirectDisplayID> = vec![0; expected_count as usize];
let mut actual_count = 0;
let res = cgerr(unsafe {
CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count)
});
displays.truncate(actual_count as usize);
if res.is_err() {
return VecDeque::with_capacity(0);
}
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
monitors.push_back(MonitorHandle(display));
}
monitors
}
pub fn primary_monitor() -> MonitorHandle {
MonitorHandle(CGDisplay::main().id)
MonitorHandle(unsafe { CGMainDisplayID() })
}
impl fmt::Debug for MonitorHandle {
@@ -205,8 +190,7 @@ impl MonitorHandle {
// TODO: Be smarter about this:
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
pub fn name(&self) -> Option<String> {
let MonitorHandle(display_id) = *self;
let screen_num = CGDisplay::new(display_id).model_number();
let screen_num = unsafe { CGDisplayModelNumber(self.0) };
Some(format!("Monitor #{screen_num}"))
}
@@ -220,7 +204,7 @@ impl MonitorHandle {
// This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
let bounds = unsafe { CGDisplayBounds(self.0) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor()))
}
@@ -236,38 +220,40 @@ impl MonitorHandle {
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
refresh_rate_millihertz(self.0, &current_display_mode)
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
pub fn current_video_mode(&self) -> Option<VideoMode> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz))
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes_handles().map(|handle| handle.mode)
}
pub(crate) fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
unsafe {
let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let array = CGDisplayCopyAllDisplayModes(self.0, None)
.expect("failed to get list of display modes");
let array_count = CFArrayGetCount(&array);
let modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
let mode = CFArrayGetValueAtIndex(&array, i) as *mut CGDisplayMode;
CFRetained::retain(NonNull::new(mode).unwrap())
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
@@ -287,13 +273,11 @@ impl MonitorHandle {
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
let uuid = self.uuid();
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)
};
uuid == other_uuid
let other = MonitorHandle::new(other_native_id);
uuid == other.uuid()
})
}
}
@@ -306,15 +290,14 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
// 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:
// The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation 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 };
.objectForKey(key)
.expect("failed getting screen display id from device description")
.downcast::<NSNumber>()
.expect("NSScreenNumber must be NSNumber");
obj.as_u32()
})
@@ -335,7 +318,7 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
// It is intentional that we use `CGMainDisplayID` (as opposed to
// `NSScreen::mainScreen`), because that's what the screen coordinates
// are relative to, no matter which display the window is currently on.
let main_screen_height = CGDisplay::main().bounds().size.height;
let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height;
let y = main_screen_height - frame.size.height - frame.origin.y;
NSPoint::new(frame.origin.x, y)
@@ -343,25 +326,29 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
unsafe {
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0);
let refresh_rate = CGDisplayModeGetRefreshRate(Some(&mode.0));
if refresh_rate > 0.0 {
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess {
#[allow(deprecated)]
if CVDisplayLinkCreateWithCGDisplay(id, NonNull::from(&mut display_link))
!= kCVReturnSuccess
{
return None;
}
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
ffi::CVDisplayLinkRelease(display_link);
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
#[allow(deprecated)]
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(&display_link);
// This value is indefinite if an invalid display link was specified
if time.flags & ffi::kCVTimeIsIndefinite != 0 {
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
return None;
}
(time.time_scale as i64)
.checked_div(time.time_value)
(time.timeScale as i64)
.checked_div(time.timeValue)
.map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new)
}

View File

@@ -4,132 +4,78 @@
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
use std::cell::Cell;
use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::Weak;
use std::time::Instant;
use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
CFRunLoop, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserver, CFRunLoopObserverCallBack, CFRunLoopObserverContext,
CFRunLoopObserverCreate, CFRunLoopPerformBlock, CFRunLoopTimer, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
};
use objc2_foundation::MainThreadMarker;
use tracing::error;
use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
{
let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) };
// Asserting unwind safety on this type should be fine because `PanicInfo` is
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
// `from_raw` takes ownership of the data behind the pointer.
// But if this scope takes ownership of the weak pointer, then
// the weak pointer will get free'd at the end of the scope.
// However we want to keep that weak reference around after the function.
std::mem::forget(info_from_raw);
let mtm = MainThreadMarker::new().unwrap();
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
let _ = &panic_info;
f(panic_info.0)
});
}
// begin is queued with the highest priority to ensure it is processed before other observers
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
panic_info: *mut c_void,
_info: *mut c_void,
) {
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`");
},
_ => unreachable!(),
}
});
match activity {
CFRunLoopActivity::AfterWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
},
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopExiting would get sent after AboutToWait
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
panic_info: *mut c_void,
_info: *mut c_void,
) {
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`");
},
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
});
match activity {
CFRunLoopActivity::BeforeWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).cleared();
},
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
#[derive(Debug)]
pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
pub struct RunLoop(CFRetained<CFRunLoop>);
impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm;
RunLoop(unsafe { CFRunLoopGetMain() })
RunLoop(unsafe { CFRunLoopGetMain() }.unwrap())
}
pub fn wakeup(&self) {
unsafe { CFRunLoopWakeUp(self.0) }
unsafe { CFRunLoopWakeUp(&self.0) }
}
unsafe fn add_observer(
&self,
flags: CFOptionFlags,
flags: CFRunLoopActivity,
// The lower the value, the sooner this will run
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) {
let observer = unsafe {
CFRunLoopObserverCreate(
ptr::null_mut(),
flags,
ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
context,
)
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
let observer =
unsafe { CFRunLoopObserverCreate(None, flags.0, true, priority, handler, context) }
.unwrap();
unsafe { CFRunLoopAddObserver(&self.0, Some(&observer), kCFRunLoopCommonModes) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
@@ -166,10 +112,6 @@ impl RunLoop {
/// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
extern "C" {
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
}
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
@@ -195,33 +137,33 @@ impl RunLoop {
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
unsafe { CFRunLoopPerformBlock(&self.0, Some(mode), Some(&block)) }
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
info: ptr::null_mut(),
version: 0,
retain: None,
release: None,
copyDescription: None,
};
run_loop.add_observer(
kCFRunLoopAfterWaiting,
CFRunLoopActivity::AfterWaiting,
CFIndex::MIN,
control_flow_begin_handler,
Some(control_flow_begin_handler),
&mut context as *mut _,
);
run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
CFIndex::MAX,
control_flow_end_handler,
Some(control_flow_end_handler),
&mut context as *mut _,
);
}
@@ -229,7 +171,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
#[derive(Debug)]
pub struct EventLoopWaker {
timer: CFRunLoopTimerRef,
timer: CFRetained<CFRunLoopTimer>,
/// An arbitrary instant in the past, that will trigger an immediate wake
/// We save this as the `next_fire_date` for consistency so we can
@@ -244,30 +186,28 @@ pub struct EventLoopWaker {
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
unsafe { CFRunLoopTimerInvalidate(&self.timer) };
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
None,
f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
Some(wakeup_main_loop),
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
)
.unwrap();
CFRunLoopAddTimer(&CFRunLoopGetMain().unwrap(), Some(&timer), kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
@@ -275,14 +215,14 @@ impl EventLoopWaker {
pub fn stop(&mut self) {
if self.next_fire_date.is_some() {
self.next_fire_date = None;
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) };
}
}
pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant);
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) };
}
}
@@ -300,7 +240,7 @@ impl EventLoopWaker {
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
}
}
},

View File

@@ -1,5 +1,8 @@
use objc2_core_graphics::CGError;
use tracing::trace;
use crate::error::OsError;
macro_rules! trace_scope {
($s:literal) => {
let _crate =
@@ -26,3 +29,12 @@ impl Drop for TraceGuard {
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
}
}
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success {
Ok(())
} else {
Err(os_error!(format!("CGError {err:?}")))
}
}

View File

@@ -4,17 +4,16 @@ use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc;
use objc2::rc::{Retained, WeakId};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
NSTrackingRectTag, NSView, NSWindow,
};
use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_state::AppState;
@@ -23,11 +22,11 @@ use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey,
};
use super::window::WinitWindow;
use super::window::window_id;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, PointerKind,
PointerSource, TouchPhase, WindowEvent,
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
@@ -134,35 +133,25 @@ pub struct ViewState {
marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view
_ns_window: WeakId<WinitWindow>,
/// The state of the `Option` as `Alt`.
option_as_alt: Cell<OptionAsAlt>,
}
declare_class!(
define_class!(
#[unsafe(super(NSView, NSResponder, NSObject))]
#[ivars = ViewState]
#[name = "WinitView"]
pub(super) struct WinitView;
unsafe impl ClassType for WinitView {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
impl DeclaredClass for WinitView {
type Ivars = ViewState;
}
unsafe impl WinitView {
#[method(isFlipped)]
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(isFlipped))]
fn is_flipped(&self) -> bool {
// `winit` uses the upper-left corner as the origin.
true
}
#[method(viewDidMoveToWindow)]
#[unsafe(method(viewDidMoveToWindow))]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -177,9 +166,10 @@ declare_class!(
self.ivars().tracking_rect.set(Some(tracking_rect));
}
#[method(frameDidChange:)]
fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:");
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))]
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
@@ -192,41 +182,45 @@ declare_class!(
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.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
// 1. When a new window is created as a tab, the frame size may change without a window
// resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height).
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::SurfaceResized(size));
}
#[method(drawRect:)]
#[unsafe(method(drawRect:))]
fn draw_rect(&self, _rect: NSRect) {
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.ivars()._ns_window.load() {
self.ivars().app_state.handle_redraw(window.id());
}
self.ivars().app_state.handle_redraw(window_id(&self.window()));
// This is a direct subclass of NSView, no need to call superclass' drawRect:
}
#[method(acceptsFirstResponder)]
#[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder");
true
}
// This is necessary to prevent a beefy terminal error on MacBook Pros:
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
// TODO: Add an API extension for using `NSTouchBar`
#[method_id(touchBar)]
// IMKInputSession [0x7fc573576ff0
// presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self
// textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error
// Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this
// process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from
// this process.}, com.apple.inputmethod.EmojiFunctionRowItem TODO: Add an API
// extension for using `NSTouchBar`
#[unsafe(method_id(touchBar))]
fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar");
None
}
#[method(resetCursorRects)]
#[unsafe(method(resetCursorRects))]
fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects");
let bounds = self.bounds();
@@ -241,13 +235,13 @@ declare_class!(
}
unsafe impl NSTextInputClient for WinitView {
#[method(hasMarkedText)]
#[unsafe(method(hasMarkedText))]
fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0
}
#[method(markedRange)]
#[unsafe(method(markedRange))]
fn marked_range(&self) -> NSRange {
trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length();
@@ -259,14 +253,14 @@ declare_class!(
}
}
#[method(selectedRange)]
#[unsafe(method(selectedRange))]
fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange");
// Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0)
}
#[method(setMarkedText:selectedRange:replacementRange:)]
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
fn set_marked_text(
&self,
string: &NSObject,
@@ -276,23 +270,15 @@ declare_class!(
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_attributed_nsstring(string),
string.string(),
)
let (marked_text, string) = if let Some(string) =
string.downcast_ref::<NSAttributedString>()
{
(NSMutableAttributedString::from_attributed_nsstring(string), string.string())
} else if let Some(string) = string.downcast_ref::<NSString>() {
(NSMutableAttributedString::from_nsstring(string), string.copy())
} else {
let string: *const NSObject = string;
let string: *const NSString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
)
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
panic!("unexpected text {string:?}")
};
// Update marked text.
@@ -328,7 +314,7 @@ declare_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[method(unmarkText)]
#[unsafe(method(unmarkText))]
fn unmark_text(&self) {
trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
@@ -345,13 +331,13 @@ declare_class!(
}
}
#[method_id(validAttributesForMarkedText)]
#[unsafe(method_id(validAttributesForMarkedText))]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText");
NSArray::new()
}
#[method_id(attributedSubstringForProposedRange:actualRange:)]
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
fn attributed_substring_for_proposed_range(
&self,
_range: NSRange,
@@ -361,42 +347,36 @@ declare_class!(
None
}
#[method(characterIndexForPoint:)]
#[unsafe(method(characterIndexForPoint:))]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:");
0
}
#[method(firstRectForCharacterRange:actualRange:)]
#[unsafe(method(firstRectForCharacterRange:actualRange:))]
fn first_rect_for_character_range(
&self,
_range: NSRange,
_actual_range: *mut NSRange,
) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:");
let rect = NSRect::new(
self.ivars().ime_position.get(),
self.ivars().ime_size.get()
);
let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get());
// Return value is expected to be in screen coordinates, so we need a conversion here
self.window()
.convertRectToScreen(self.convertRect_toView(rect, None))
self.window().convertRectToScreen(self.convertRect_toView(rect, None))
}
#[method(insertText:replacementRange:)]
#[unsafe(method(insertText:replacementRange:))]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let string = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
unsafe { &*string }.string().to_string()
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
string.string().to_string()
} else if let Some(string) = string.downcast_ref::<NSString>() {
string.to_string()
} else {
let string: *const NSObject = string;
let string: *const NSString = string.cast();
unsafe { &*string }.to_string()
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
panic!("unexpected text {string:?}")
};
let is_control = string.chars().next().is_some_and(|c| c.is_control());
@@ -409,15 +389,16 @@ declare_class!(
}
}
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[method(doCommandBySelector:)]
// Basically, we're sent this message whenever a keyboard event that doesn't generate a
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[unsafe(method(doCommandBySelector:))]
fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just committed 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.
// We shouldn't forward any character from just committed 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.ivars().ime_state.get() == ImeState::Committed {
return;
}
@@ -431,10 +412,14 @@ declare_class!(
}
// Send command action to user if they requested it.
let window_id = self.window().id();
let window_id = window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
if let Some(handler) = app.macos_handler() {
handler.standard_key_binding(event_loop, window_id, command.name());
handler.standard_key_binding(
event_loop,
window_id,
command.name().to_str().unwrap(),
);
}
});
@@ -444,8 +429,9 @@ declare_class!(
}
}
unsafe impl WinitView {
#[method(keyDown:)]
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:");
{
@@ -488,14 +474,14 @@ declare_class!(
// Allow normal input after the commit.
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.ivars().ime_state.get(),
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
event: key_event,
@@ -504,7 +490,7 @@ declare_class!(
}
}
#[method(keyUp:)]
#[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:");
@@ -512,26 +498,23 @@ declare_class!(
self.update_modifiers(&event, false);
// We want to send keyboard input when we are currently in the ground state.
if matches!(
self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
event: create_key_event(&event, false, false, None),
event: create_key_event(&event, false, false),
is_synthetic: false,
});
}
}
#[method(flagsChanged:)]
#[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:");
self.update_modifiers(event, true);
}
#[method(insertTab:)]
#[unsafe(method(insertTab:))]
fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:");
let window = self.window();
@@ -542,7 +525,7 @@ declare_class!(
}
}
#[method(insertBackTab:)]
#[unsafe(method(insertBackTab:))]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:");
let window = self.window();
@@ -555,7 +538,7 @@ declare_class!(
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[method(cancelOperation:)]
#[unsafe(method(cancelOperation:))]
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:");
@@ -565,7 +548,7 @@ declare_class!(
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
@@ -585,42 +568,42 @@ declare_class!(
//
// See https://github.com/rust-windowing/winit/pull/1490 for history.
#[method(mouseDown:)]
#[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[method(mouseUp:)]
#[unsafe(method(mouseUp:))]
fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[method(rightMouseDown:)]
#[unsafe(method(rightMouseDown:))]
fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[method(rightMouseUp:)]
#[unsafe(method(rightMouseUp:))]
fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[method(otherMouseDown:)]
#[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[method(otherMouseUp:)]
#[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:");
self.mouse_motion(event);
@@ -629,27 +612,27 @@ declare_class!(
// No tracing on these because that would be overly verbose
#[method(mouseMoved:)]
#[unsafe(method(mouseMoved:))]
fn mouse_moved(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(mouseDragged:)]
#[unsafe(method(mouseDragged:))]
fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(rightMouseDragged:)]
#[unsafe(method(rightMouseDragged:))]
fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(otherMouseDragged:)]
#[unsafe(method(otherMouseDragged:))]
fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(mouseEntered:)]
#[unsafe(method(mouseEntered:))]
fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:");
@@ -663,7 +646,7 @@ declare_class!(
});
}
#[method(mouseExited:)]
#[unsafe(method(mouseExited:))]
fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:");
@@ -677,7 +660,7 @@ declare_class!(
});
}
#[method(scrollWheel:)]
#[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:");
@@ -694,9 +677,9 @@ declare_class!(
};
// The "momentum phase," if any, has higher priority than touch phase (the two should
// 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.
// 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.
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
@@ -710,17 +693,13 @@ declare_class!(
self.update_modifiers(event, false);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel {
device_id: None,
delta,
phase,
});
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
}
#[method(magnifyWithEvent:)]
#[unsafe(method(magnifyWithEvent:))]
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
@@ -742,18 +721,16 @@ declare_class!(
});
}
#[method(smartMagnifyWithEvent:)]
#[unsafe(method(smartMagnifyWithEvent:))]
fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture {
device_id: None,
});
self.queue_event(WindowEvent::DoubleTapGesture { device_id: None });
}
#[method(rotateWithEvent:)]
#[unsafe(method(rotateWithEvent:))]
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
@@ -775,7 +752,7 @@ declare_class!(
});
}
#[method(pressureChangeWithEvent:)]
#[unsafe(method(pressureChangeWithEvent:))]
fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:");
@@ -789,13 +766,13 @@ declare_class!(
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs.
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[method(_wantsKeyDownForEvent:)]
#[unsafe(method(_wantsKeyDownForEvent:))]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:");
true
}
#[method(acceptsFirstMouse:)]
#[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse
@@ -806,11 +783,10 @@ declare_class!(
impl WinitView {
pub(super) fn new(
app_state: &Rc<AppState>,
window: &WinitWindow,
accepts_first_mouse: bool,
option_as_alt: OptionAsAlt,
mtm: MainThreadMarker,
) -> Retained<Self> {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState {
app_state: Rc::clone(app_state),
cursor_state: Default::default(),
@@ -825,38 +801,21 @@ impl WinitView {
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true);
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&this,
sel!(frameDidChange:),
Some(NSViewFrameDidChangeNotification),
Some(&this),
)
}
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Retained<WinitWindow> {
// TODO: Simply use `window` property on `NSView`.
// That only returns a window _after_ the view has been attached though!
// (which is incompatible with `frameDidChange:`)
//
// unsafe { msg_send_id![self, window] }
self.ivars()._ns_window.load().expect("view to have a window")
fn window(&self) -> Retained<NSWindow> {
(**self).window().expect("view must be installed in a window")
}
fn queue_event(&self, event: WindowEvent) {
let window_id = self.window().id();
let window_id = window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
@@ -962,22 +921,34 @@ impl WinitView {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
let logical_key = code_to_key(physical_key, scancode);
// Ignore processing of unknown modifiers because we can't determine whether
// it was pressed or release reliably.
let Some(event_modifier) = key_to_modifier(&key) else {
//
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
// when holding Caps Lock while pressing another key, see:
// https://github.com/alacritty/alacritty/issues/8268
let Some(event_modifier) = key_to_modifier(&logical_key) else {
break 'send_event;
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let mut event = KeyEvent {
location: code_to_location(physical_key),
logical_key: logical_key.clone(),
physical_key,
repeat: false,
// We'll correct this later.
state: Pressed,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
};
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
let phys_mod =
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);

View File

@@ -1,10 +1,11 @@
#![allow(clippy::unnecessary_cast)]
use dispatch2::MainThreadBound;
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use objc2::{define_class, MainThreadMarker, Message};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject;
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
@@ -16,7 +17,7 @@ use crate::window::{
};
pub(crate) struct Window {
window: MainThreadBound<Retained<WinitWindow>>,
window: MainThreadBound<Retained<NSWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
@@ -64,7 +65,7 @@ impl Window {
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_, _))) {
self.set_fullscreen(None);
}
@@ -332,27 +333,21 @@ impl CoreWindow for Window {
}
}
declare_class!(
define_class!(
#[unsafe(super(NSWindow, NSResponder, NSObject))]
#[name = "WinitWindow"]
#[derive(Debug)]
pub struct WinitWindow;
unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)]
type Super = NSWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
/// This documentation attribute makes rustfmt work for some reason?
impl WinitWindow {
#[unsafe(method(canBecomeMainWindow))]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[method(canBecomeKeyWindow)]
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
@@ -360,8 +355,24 @@ declare_class!(
}
);
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
define_class!(
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
#[name = "WinitPanel"]
#[derive(Debug)]
pub struct WinitPanel;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitPanel {
// although NSPanel can become key window
// it doesn't if window doesn't have NSWindowStyleMask::Titled
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
}
);
pub(super) fn window_id(window: &NSWindow) -> WindowId {
WindowId::from_raw(window as *const _ as usize)
}

View File

@@ -6,25 +6,32 @@ use std::ptr;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use core_graphics::display::CGDisplay;
use monitor::VideoModeHandle;
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2::{
available, define_class, msg_send, sel, ClassType, DefinedClass, MainThreadMarker,
MainThreadOnly, Message,
};
use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
NSWindowToolbarStyle,
NSColor, NSDraggingDestination, NSDraggingInfo, NSFilenamesPboardType,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
};
use objc2_core_foundation::{CGFloat, CGPoint};
use objc2_core_graphics::{
CGAcquireDisplayFadeReservation, CGAssociateMouseAndMouseCursorPosition, CGDisplayCapture,
CGDisplayFade, CGDisplayRelease, CGDisplaySetDisplayMode, CGReleaseDisplayFadeReservation,
CGRestorePermanentDisplayConfiguration, CGShieldingWindowLevel, CGWarpMouseCursorPosition,
};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
ns_string, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSNotificationCenter, NSObject,
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
@@ -32,8 +39,9 @@ use super::app_state::AppState;
use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::util::cgerr;
use super::view::WinitView;
use super::window::WinitWindow;
use super::window::{window_id, WinitPanel, WinitWindow};
use super::{ffi, Fullscreen, MonitorHandle};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
@@ -62,6 +70,7 @@ pub struct PlatformSpecificWindowAttributes {
pub option_as_alt: OptionAsAlt,
pub borderless_game: bool,
pub unified_titlebar: bool,
pub panel: bool,
}
impl Default for PlatformSpecificWindowAttributes {
@@ -81,6 +90,7 @@ impl Default for PlatformSpecificWindowAttributes {
option_as_alt: Default::default(),
borderless_game: false,
unified_titlebar: false,
panel: false,
}
}
}
@@ -90,7 +100,7 @@ pub(crate) struct State {
/// Strong reference to the global application state.
app_state: Rc<AppState>,
window: Retained<WinitWindow>,
window: Retained<NSWindow>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
//
@@ -132,30 +142,24 @@ pub(crate) struct State {
is_borderless_game: Cell<bool>,
}
declare_class!(
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "WinitWindowDelegate"]
#[ivars = State]
pub(crate) struct WindowDelegate;
unsafe impl ClassType for WindowDelegate {
type Super = NSObject;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindowDelegate";
}
impl DeclaredClass for WindowDelegate {
type Ivars = State;
}
unsafe impl NSObjectProtocol for WindowDelegate {}
unsafe impl NSWindowDelegate for WindowDelegate {
#[method(windowShouldClose:)]
#[unsafe(method(windowShouldClose:))]
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
trace_scope!("windowShouldClose:");
self.queue_event(WindowEvent::CloseRequested);
false
}
#[method(windowWillClose:)]
#[unsafe(method(windowWillClose:))]
fn window_will_close(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillClose:");
// `setDelegate:` retains the previous value and then autoreleases it
@@ -167,14 +171,14 @@ declare_class!(
self.queue_event(WindowEvent::Destroyed);
}
#[method(windowDidResize:)]
#[unsafe(method(windowDidResize:))]
fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:");
// NOTE: WindowEvent::SurfaceResized is reported in frameDidChange.
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
self.emit_move_event();
}
#[method(windowWillStartLiveResize:)]
#[unsafe(method(windowWillStartLiveResize:))]
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillStartLiveResize:");
@@ -182,20 +186,20 @@ declare_class!(
self.set_resize_increments_inner(increments);
}
#[method(windowDidEndLiveResize:)]
#[unsafe(method(windowDidEndLiveResize:))]
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEndLiveResize:");
self.set_resize_increments_inner(NSSize::new(1., 1.));
}
// This won't be triggered if the move was part of a resize.
#[method(windowDidMove:)]
#[unsafe(method(windowDidMove:))]
fn window_did_move(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidMove:");
self.emit_move_event();
}
#[method(windowDidChangeBackingProperties:)]
#[unsafe(method(windowDidChangeBackingProperties:))]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
let scale_factor = self.scale_factor();
@@ -211,7 +215,7 @@ declare_class!(
});
}
#[method(windowDidBecomeKey:)]
#[unsafe(method(windowDidBecomeKey:))]
fn window_did_become_key(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidBecomeKey:");
// TODO: center the cursor if the window had mouse grab when it
@@ -219,7 +223,7 @@ declare_class!(
self.queue_event(WindowEvent::Focused(true));
}
#[method(windowDidResignKey:)]
#[unsafe(method(windowDidResignKey:))]
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResignKey:");
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
@@ -235,7 +239,7 @@ declare_class!(
}
/// Invoked when before enter fullscreen
#[method(windowWillEnterFullScreen:)]
#[unsafe(method(windowWillEnterFullScreen:))]
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillEnterFullScreen:");
@@ -245,7 +249,7 @@ declare_class!(
// Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (),
Some(Fullscreen::Exclusive(..)) => (),
// `window_will_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state
@@ -261,14 +265,14 @@ declare_class!(
}
/// Invoked when before exit fullscreen
#[method(windowWillExitFullScreen:)]
#[unsafe(method(windowWillExitFullScreen:))]
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:");
self.ivars().in_fullscreen_transition.set(true);
}
#[method(window:willUseFullScreenPresentationOptions:)]
#[unsafe(method(window:willUseFullScreenPresentationOptions:))]
fn window_will_use_fullscreen_presentation_options(
&self,
_: Option<&AnyObject>,
@@ -285,17 +289,17 @@ declare_class!(
// user-provided options are ignored in exclusive fullscreen.
let mut options = proposed_options;
let fullscreen = self.ivars().fullscreen.borrow();
if let Some(Fullscreen::Exclusive(_)) = &*fullscreen {
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
if let Some(Fullscreen::Exclusive(..)) = &*fullscreen {
options = NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
}
options
}
/// Invoked when entered fullscreen
#[method(windowDidEnterFullScreen:)]
#[unsafe(method(windowDidEnterFullScreen:))]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:");
self.ivars().initial_fullscreen.set(false);
@@ -306,7 +310,7 @@ declare_class!(
}
/// Invoked when exited fullscreen
#[method(windowDidExitFullScreen:)]
#[unsafe(method(windowDidExitFullScreen:))]
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:");
@@ -333,7 +337,7 @@ declare_class!(
/// due to being in the midst of handling some other animation or user gesture.
/// This method indicates that there was an error, and you should clean up any
/// work you may have done to prepare to enter full-screen mode.
#[method(windowDidFailToEnterFullScreen:)]
#[unsafe(method(windowDidFailToEnterFullScreen:))]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:");
self.ivars().in_fullscreen_transition.set(false);
@@ -352,14 +356,14 @@ declare_class!(
}
// Invoked when the occlusion state of the window changes
#[method(windowDidChangeOcclusionState:)]
#[unsafe(method(windowDidChangeOcclusionState:))]
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeOcclusionState:");
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
self.queue_event(WindowEvent::Occluded(!visible));
}
#[method(windowDidChangeScreen:)]
#[unsafe(method(windowDidChangeScreen:))]
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
@@ -373,67 +377,114 @@ declare_class!(
unsafe impl NSDraggingDestination for WindowDelegate {
/// Invoked when the dragged image enters destination bounds or frame
#[method(draggingEntered:)]
fn dragging_entered(&self, sender: &NSObject) -> bool {
#[unsafe(method(draggingEntered:))]
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingEntered:");
use std::path::PathBuf;
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
let pb = unsafe { sender.draggingPasteboard() };
let filenames = pb
.propertyListForType(unsafe { NSFilenamesPboardType })
.unwrap()
.downcast::<NSArray>()
.unwrap();
let paths = filenames
.into_iter()
.map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
.collect();
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::HoveredFile(path));
});
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
let position =
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragEntered { paths, position });
true
}
#[unsafe(method(wantsPeriodicDraggingUpdates))]
fn wants_periodic_dragging_updates(&self) -> bool {
trace_scope!("wantsPeriodicDraggingUpdates:");
true
}
/// Invoked periodically as the image is held within the destination area, allowing
/// modification of the dragging operation or mouse-pointer position.
#[unsafe(method(draggingUpdated:))]
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingUpdated:");
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
let position =
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragMoved { position });
true
}
/// Invoked when the image is released
#[method(prepareForDragOperation:)]
#[unsafe(method(prepareForDragOperation:))]
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
trace_scope!("prepareForDragOperation:");
true
}
/// Invoked after the released image has been removed from the screen
#[method(performDragOperation:)]
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
#[unsafe(method(performDragOperation:))]
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("performDragOperation:");
use std::path::PathBuf;
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
let pb = unsafe { sender.draggingPasteboard() };
let filenames = pb
.propertyListForType(unsafe { NSFilenamesPboardType })
.unwrap()
.downcast::<NSArray>()
.unwrap();
let paths = filenames
.into_iter()
.map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
.collect();
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::DroppedFile(path));
});
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
let position =
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragDropped { paths, position });
true
}
/// Invoked when the dragging operation is complete
#[method(concludeDragOperation:)]
#[unsafe(method(concludeDragOperation:))]
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
trace_scope!("concludeDragOperation:");
}
/// Invoked when the dragging operation is cancelled
#[method(draggingExited:)]
fn dragging_exited(&self, _sender: Option<&NSObject>) {
#[unsafe(method(draggingExited:))]
fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
trace_scope!("draggingExited:");
self.queue_event(WindowEvent::HoveredFileCancelled);
let position = sender.map(|sender| {
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor())
});
self.queue_event(WindowEvent::DragLeft { position });
}
}
// Key-Value Observing
unsafe impl WindowDelegate {
#[method(observeValueForKeyPath:ofObject:change:context:)]
/// Key-Value Observing
impl WindowDelegate {
#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
fn observe_value(
&self,
key_path: Option<&NSString>,
@@ -449,19 +500,15 @@ declare_class!(
"requested a change dictionary in `addObserver`, but none was provided",
);
let old = change
.get(unsafe { NSKeyValueChangeOldKey })
.objectForKey(unsafe { NSKeyValueChangeOldKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change
.get(unsafe { NSKeyValueChangeNewKey })
.objectForKey(unsafe { NSKeyValueChangeNewKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
let old: *const AnyObject = old;
let old: *const NSAppearance = old.cast();
let old: &NSAppearance = unsafe { &*old };
let new: *const AnyObject = new;
let new: *const NSAppearance = new.cast();
let new: &NSAppearance = unsafe { &*new };
// The value of `effectiveAppearance` is `NSAppearance`
let old = old.downcast::<NSAppearance>().unwrap();
let new = new.downcast::<NSAppearance>().unwrap();
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
@@ -472,8 +519,8 @@ declare_class!(
return;
}
let old = appearance_to_theme(old);
let new = appearance_to_theme(new);
let old = appearance_to_theme(&old);
let new = appearance_to_theme(&new);
// Check that the theme changed in Winit's terms (the theme might have changed on
// other parameters, such as level of contrast, but the event should not be emitted
// in those cases).
@@ -501,11 +548,11 @@ fn new_window(
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
mtm: MainThreadMarker,
) -> Option<Retained<WinitWindow>> {
) -> Option<Retained<NSWindow>> {
autoreleasepool(|_| {
let screen = match attrs.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Borderless(Some(monitor)))
| Some(Fullscreen::Exclusive(VideoModeHandle { monitor, .. })) => {
| Some(Fullscreen::Exclusive(monitor, _)) => {
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
},
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
@@ -584,16 +631,33 @@ fn new_window(
// confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let window: Option<Retained<WinitWindow>> = unsafe {
msg_send_id![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
let window: Retained<NSWindow> = if attrs.platform_specific.panel {
masks |= NSWindowStyleMask::NonactivatingPanel;
let window: Option<Retained<WinitPanel>> = unsafe {
msg_send![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::Buffered,
defer: false,
]
};
window?.as_super().as_super().retain()
} else {
let window: Option<Retained<WinitWindow>> = unsafe {
msg_send![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::Buffered,
defer: false,
]
};
window?.as_super().retain()
};
let window = window?;
// It is very important for correct memory management that we
// disable the extra release that would otherwise happen when
@@ -609,22 +673,22 @@ fn new_window(
}
if attrs.content_protected {
window.setSharingType(NSWindowSharingType::NSWindowSharingNone);
window.setSharingType(NSWindowSharingType::None);
}
if attrs.platform_specific.titlebar_transparent {
window.setTitlebarAppearsTransparent(true);
}
if attrs.platform_specific.title_hidden {
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden);
window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
}
if attrs.platform_specific.titlebar_buttons_hidden {
for titlebar_button in &[
#[allow(deprecated)]
NSWindowFullScreenButton,
NSWindowButton::NSWindowMiniaturizeButton,
NSWindowButton::NSWindowCloseButton,
NSWindowButton::NSWindowZoomButton,
NSWindowButton::MiniaturizeButton,
NSWindowButton::CloseButton,
NSWindowButton::ZoomButton,
] {
if let Some(button) = window.standardWindowButton(*titlebar_button) {
button.setHidden(true);
@@ -644,7 +708,7 @@ fn new_window(
}
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) {
if let Some(button) = window.standardWindowButton(NSWindowButton::ZoomButton) {
button.setEnabled(false);
}
}
@@ -658,9 +722,9 @@ fn new_window(
let view = WinitView::new(
app_state,
&window,
attrs.platform_specific.accepts_first_mouse,
attrs.platform_specific.option_as_alt,
mtm,
);
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
@@ -682,6 +746,23 @@ fn new_window(
window.setContentView(Some(&view));
window.setInitialFirstResponder(Some(&view));
// Configure the view to send notifications whenever its frame rectangle changes.
//
// We explicitly do this _after_ setting the view as the content view of the window, to
// avoid a resize event when creating the window.
view.setPostsFrameChangedNotifications(true);
// `setPostsFrameChangedNotifications` posts the notification immediately, so register the
// observer _after_, again so that the event isn't triggered initially.
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&view,
sel!(viewFrameDidChangeNotification:),
Some(NSViewFrameDidChangeNotification),
Some(&view),
)
}
if attrs.transparent {
window.setOpaque(false);
// See `set_transparent` for details on why we do this.
@@ -689,10 +770,7 @@ fn new_window(
}
// register for drag and drop operations.
window
.registerForDraggedTypes(&NSArray::from_id_slice(&[
unsafe { NSFilenamesPboardType }.copy()
]));
window.registerForDraggedTypes(&NSArray::from_slice(&[unsafe { NSFilenamesPboardType }]));
Some(window)
})
@@ -720,9 +798,7 @@ impl WindowDelegate {
// 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(&window, NSWindowOrderingMode::NSWindowAbove)
};
unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) };
},
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
None => (),
@@ -763,7 +839,7 @@ impl WindowDelegate {
saved_style: Cell::new(None),
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
let delegate: Retained<WindowDelegate> = unsafe { msg_send![super(delegate), init] };
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
@@ -774,8 +850,7 @@ impl WindowDelegate {
window.addObserver_forKeyPath_options_context(
&delegate,
ns_string!("effectiveAppearance"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old,
ptr::null_mut(),
)
};
@@ -819,22 +894,22 @@ impl WindowDelegate {
#[track_caller]
pub(super) fn view(&self) -> Retained<WinitView> {
// SAFETY: The view inside WinitWindow is always `WinitView`
unsafe { Retained::cast(self.window().contentView().unwrap()) }
// The view inside WinitWindow should always be set and be `WinitView`.
self.window().contentView().unwrap().downcast().unwrap()
}
#[track_caller]
pub(super) fn window(&self) -> &WinitWindow {
pub(super) fn window(&self) -> &NSWindow {
&self.ivars().window
}
#[track_caller]
pub(crate) fn id(&self) -> WindowId {
self.window().id()
window_id(self.window())
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = self.window().id();
let window_id = window_id(self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
@@ -933,7 +1008,7 @@ impl WindowDelegate {
}
pub fn request_redraw(&self) {
self.ivars().app_state.queue_redraw(self.window().id());
self.ivars().app_state.queue_redraw(window_id(self.window()));
}
#[inline]
@@ -945,8 +1020,21 @@ impl WindowDelegate {
}
pub fn surface_position(&self) -> PhysicalPosition<i32> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let logical = LogicalPosition::new(content_rect.origin.x, content_rect.origin.y);
// The calculation here is a bit awkward because we've gotta reconcile the
// different origins (Winit prefers top-left vs. NSWindow's bottom-left),
// and I couldn't find a built-in way to do so.
// The position of the window and the view, both in Winit screen coordinates.
let window_position = flip_window_screen_coordinates(self.window().frame());
let view_position = flip_window_screen_coordinates(
self.window().contentRectForFrameRect(self.window().frame()),
);
// And use that to convert the view position to window coordinates.
let surface_position =
NSPoint::new(view_position.x - window_position.x, view_position.y - window_position.y);
let logical = LogicalPosition::new(surface_position.x, surface_position.y);
logical.to_physical(self.scale_factor())
}
@@ -980,19 +1068,24 @@ impl WindowDelegate {
// we've set it up with `additionalSafeAreaInsets`.
unsafe { self.view().safeAreaInsets() }
} else {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
// Includes NSWindowStyleMask::FullSizeContentView
// Convert from window coordinates to view coordinates
let safe_rect = unsafe {
self.view().convertRect_fromView(self.window().contentLayoutRect(), None)
// If `safeAreaInsets` is not available, we'll have to do the calculation ourselves.
let window_rect = unsafe {
self.window().convertRectFromScreen(
self.window().contentRectForFrameRect(self.window().frame()),
)
};
// This includes NSWindowStyleMask::FullSizeContentView.
let layout_rect = unsafe { self.window().contentLayoutRect() };
// Calculate the insets from window coordinates in AppKit's coordinate system.
NSEdgeInsets {
top: safe_rect.origin.y - content_rect.origin.y,
left: safe_rect.origin.x - content_rect.origin.x,
bottom: (content_rect.size.height + content_rect.origin.x)
- (safe_rect.size.height + safe_rect.origin.x),
right: (content_rect.size.width + content_rect.origin.y)
- (safe_rect.size.width + safe_rect.origin.y),
top: (window_rect.size.height + window_rect.origin.y)
- (layout_rect.size.height + layout_rect.origin.y),
left: layout_rect.origin.x - window_rect.origin.x,
bottom: layout_rect.origin.y - window_rect.origin.y,
right: (window_rect.size.width + window_rect.origin.x)
- (layout_rect.size.width + layout_rect.origin.x),
}
};
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
@@ -1122,8 +1215,7 @@ impl WindowDelegate {
// 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.window().standardWindowButton(NSWindowButton::NSWindowZoomButton)
{
if let Some(button) = self.window().standardWindowButton(NSWindowButton::ZoomButton) {
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
}
}
@@ -1136,7 +1228,7 @@ impl WindowDelegate {
}
if self
.window()
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
.standardWindowButton(NSWindowButton::ZoomButton)
.map(|b| b.isEnabled())
.unwrap_or(true)
{
@@ -1175,8 +1267,9 @@ impl WindowDelegate {
};
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
.map_err(|status| os_error!(format!("CGError {status}")).into())
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(associate_mouse_cursor) })?;
Ok(())
}
#[inline]
@@ -1198,14 +1291,12 @@ impl WindowDelegate {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let window_position = flip_window_screen_coordinates(content_rect);
let cursor_position = cursor_position.to_logical::<CGFloat>(self.scale_factor());
let point = core_graphics::display::CGPoint {
let point = CGPoint {
x: window_position.x + cursor_position.x,
y: window_position.y + cursor_position.y,
};
CGDisplay::warp_mouse_cursor_position(point)
.map_err(|status| os_error!(format!("CGError {status}")))?;
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
.map_err(|status| os_error!(format!("CGError {status}")))?;
cgerr(unsafe { CGWarpMouseCursorPosition(point) })?;
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(true) })?;
Ok(())
}
@@ -1372,7 +1463,7 @@ impl WindowDelegate {
return;
}
},
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
Fullscreen::Exclusive(monitor, _) => monitor.clone(),
}
.ns_screen(mtm)
.unwrap();
@@ -1383,7 +1474,7 @@ impl WindowDelegate {
}
}
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
if let Some(Fullscreen::Exclusive(ref monitor, ref video_mode)) = fullscreen {
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
// same thing as we're doing here (captures the display, sets the
// video mode, and hides the menu bar and dock), with the exception
@@ -1396,7 +1487,7 @@ impl WindowDelegate {
// parameter, which is not consistent with the docs saying that it
// takes a `NSDictionary`..
let display_id = video_mode.monitor().native_identifier();
let display_id = monitor.native_identifier();
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
@@ -1407,10 +1498,8 @@ impl WindowDelegate {
unsafe {
// Fade to black (and wait for the fade to complete) to hide the
// flicker from capturing the display and switching display mode
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
== ffi::kCGErrorSuccess
{
ffi::CGDisplayFade(
if cgerr(CGAcquireDisplayFadeReservation(5.0, &mut fade_token)).is_ok() {
CGDisplayFade(
fade_token,
0.3,
ffi::kCGDisplayBlendNormal,
@@ -1418,25 +1507,27 @@ impl WindowDelegate {
0.0,
0.0,
0.0,
ffi::TRUE,
true,
);
}
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
cgerr(CGDisplayCapture(display_id)).unwrap();
}
let video_mode =
match monitor.video_modes_handles().find(|mode| &mode.mode == video_mode) {
Some(video_mode) => video_mode,
None => return,
};
unsafe {
let result = ffi::CGDisplaySetDisplayMode(
display_id,
video_mode.native_mode.0,
std::ptr::null(),
);
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
cgerr(CGDisplaySetDisplayMode(display_id, Some(&video_mode.native_mode.0), None))
.expect("failed to set video mode");
// After the display has been configured, fade back in
// asynchronously
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
ffi::CGDisplayFade(
CGDisplayFade(
fade_token,
0.6,
ffi::kCGDisplayBlendSolidColor,
@@ -1444,16 +1535,16 @@ impl WindowDelegate {
0.0,
0.0,
0.0,
ffi::FALSE,
false,
);
ffi::CGReleaseDisplayFadeReservation(fade_token);
CGReleaseDisplayFadeReservation(fade_token);
}
}
}
self.ivars().fullscreen.replace(fullscreen.clone());
fn toggle_fullscreen(window: &WinitWindow) {
fn toggle_fullscreen(window: &NSWindow) {
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
@@ -1478,8 +1569,8 @@ impl WindowDelegate {
// `window:willUseFullScreenPresentationOptions` because for some reason
// the menu bar remains interactable despite being hidden.
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
let presentation_options = NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
app.setPresentationOptions(presentation_options);
}
@@ -1489,11 +1580,11 @@ impl WindowDelegate {
// State is restored by `window_did_exit_fullscreen`
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
restore_and_release_display(&video_mode.monitor());
(Some(Fullscreen::Exclusive(ref monitor, _)), None) => {
restore_and_release_display(monitor);
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(..))) => {
// If we're already in fullscreen mode, calling
// `CGDisplayCapture` will place the shielding window on top of
// our window, which results in a black display and is not what
@@ -1504,24 +1595,23 @@ impl WindowDelegate {
// delegate in `window:willUseFullScreenPresentationOptions:`.
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
let presentation_options = NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
app.setPresentationOptions(presentation_options);
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
let window_level = unsafe { CGShieldingWindowLevel() } as NSWindowLevel + 1;
self.window().setLevel(window_level);
},
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
(Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => {
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::AutoHideDock
| NSApplicationPresentationOptions::AutoHideMenuBar,
);
app.setPresentationOptions(presentation_options);
restore_and_release_display(&video_mode.monitor());
restore_and_release_display(monitor);
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
@@ -1629,8 +1719,8 @@ impl WindowDelegate {
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 => NSRequestUserAttentionType::CriticalRequest,
UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest,
});
if let Some(ty) = ns_request_type {
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
@@ -1690,7 +1780,7 @@ impl WindowDelegate {
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
@@ -1705,9 +1795,9 @@ impl WindowDelegate {
#[inline]
pub fn set_content_protected(&self, protected: bool) {
self.window().setSharingType(if protected {
NSWindowSharingType::NSWindowSharingNone
NSWindowSharingType::None
} else {
NSWindowSharingType::NSWindowSharingReadOnly
NSWindowSharingType::ReadOnly
})
}
@@ -1724,8 +1814,8 @@ fn restore_and_release_display(monitor: &MonitorHandle) {
let available_monitors = monitor::available_monitors();
if available_monitors.contains(monitor) {
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
CGRestorePermanentDisplayConfiguration();
cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap();
};
} else {
warn!(
@@ -1770,9 +1860,8 @@ impl WindowExtMacOS for WindowDelegate {
self.ivars().is_simple_fullscreen.set(true);
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
let presentation_options = NSApplicationPresentationOptions::AutoHideDock
| NSApplicationPresentationOptions::AutoHideMenuBar;
app.setPresentationOptions(presentation_options);
// Hide the titlebar
@@ -1853,10 +1942,14 @@ impl WindowExtMacOS for WindowDelegate {
#[inline]
fn select_tab_at_index(&self, index: usize) {
if !available!(macos = 10.13) {
tracing::warn!("window tab groups are only available on macOS 10.13+");
return;
}
if let Some(group) = self.window().tabGroup() {
if let Some(windows) = unsafe { self.window().tabbedWindows() } {
if index < windows.len() {
group.setSelectedWindow(Some(&windows[index]));
group.setSelectedWindow(Some(&windows.objectAtIndex(index)));
}
}
}
@@ -1929,9 +2022,9 @@ fn dark_appearance_name() -> &'static NSString {
}
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
unsafe { NSAppearanceNameAqua.copy() },
dark_appearance_name().copy(),
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[
unsafe { NSAppearanceNameAqua },
dark_appearance_name(),
]));
if let Some(best_match) = best_match {
if *best_match == *dark_appearance_name() {

View File

@@ -1,55 +1,33 @@
use std::cell::Cell;
use std::cell::RefCell;
use std::{fmt, mem};
use crate::application::ApplicationHandler;
use crate::event_loop::ActiveEventLoop;
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
/// to it within the execution of a closure.
#[derive(Default)]
pub(crate) struct EventHandler {
state: Cell<State>,
}
type InitClosure<'handler> =
Box<dyn FnOnce(&dyn ActiveEventLoop) -> Box<dyn ApplicationHandler + 'handler> + 'handler>;
#[derive(Default)]
enum State {
/// Not registered by the event loop.
#[default]
NotRegistered,
/// The event is registered by the event loop.
Registered(InitClosure<'static>),
/// The application has been initialized, and we're ready to handle events.
Ready(Box<dyn ApplicationHandler + 'static>),
/// Currently executing the handler.
CurrentlyExecuting,
/// The application has been terminated.
Terminated,
// TODO: Invalid state?
/// This can be in the following states:
/// - Not registered by the event loop (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
}
impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state.replace(State::CurrentlyExecuting);
// NOTE: We're very careful not to panic inside the "critial" section here.
let string = match &state {
State::NotRegistered => "<not registered>",
State::Registered(_) => "<registered>",
State::Ready(_) => "<ready>",
State::CurrentlyExecuting => "<currently executing>",
State::Terminated => "<terminated>",
let state = match self.inner.try_borrow().as_deref() {
Ok(Some(_)) => "<available>",
Ok(None) => "<not set>",
Err(_) => "<in use>",
};
self.state.set(state);
f.debug_struct("EventHandler").field("state", &string).finish_non_exhaustive()
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
}
}
impl EventHandler {
pub(crate) const fn new() -> Self {
Self { state: Cell::new(State::NotRegistered) }
pub(crate) fn new() -> Self {
Self { inner: RefCell::new(None) }
}
/// Set the event loop handler for the duration of the given closure.
@@ -59,7 +37,7 @@ impl EventHandler {
/// from within the closure.
pub(crate) fn set<'handler, R>(
&self,
init_closure: InitClosure<'handler>,
app: &'handler mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
@@ -68,9 +46,14 @@ impl EventHandler {
// This is sound, since we make sure to unset the handler again at the
// end of this function, and as such the lifetime isn't actually
// extended beyond `'handler`.
let handler = unsafe { mem::transmute::<InitClosure<'handler>, InitClosure<'static>>(app) };
let handler = unsafe {
mem::transmute::<
&'handler mut dyn ApplicationHandler,
&'static mut dyn ApplicationHandler,
>(app)
};
match self.state.try_borrow_mut().as_deref_mut() {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(_)) => {
unreachable!("tried to set handler while another was already set");
},
@@ -86,7 +69,7 @@ impl EventHandler {
impl Drop for ClearOnDrop<'_> {
fn drop(&mut self) {
match self.0.state.try_borrow_mut().as_deref_mut() {
match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
*data = None;
},
@@ -118,16 +101,11 @@ impl EventHandler {
// soundness.
}
fn init(&self) {}
fn terminate(&self) {}
#[cfg(target_os = "macos")]
pub(crate) fn in_use(&self) -> bool {
self.inner.try_borrow().is_err()
}
#[cfg(target_os = "macos")]
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
@@ -141,7 +119,7 @@ impl EventHandler {
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
callback(user_app);
callback(*user_app);
},
Ok(None) => {
// `NSApplication`, our app state and this handler are all

View File

@@ -0,0 +1,126 @@
use std::os::raw::c_void;
use std::sync::Arc;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate,
CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use crate::event_loop::EventLoopProxyProvider;
/// A waker that signals a `CFRunLoopSource` on the main thread.
///
/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an
/// atomic around that we check on each iteration of the event loop).
///
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct EventLoopProxy {
source: CFRetained<CFRunLoopSource>,
/// Cached value of `CFRunLoopGetMain`.
main_loop: CFRetained<CFRunLoop>,
}
// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`.
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl EventLoopProxy {
/// Create a new proxy, registering it to be performed on the main thread.
///
/// The provided closure should call `proxy_wake_up` on the application.
pub(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
// We use an `Arc` here to make sure that the reference-counting of the signal container is
// atomic (`Retained`/`CFRetained` would be valid alternatives too).
let signaller = Arc::new(signaller);
unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
unsafe { Arc::increment_strong_count(info.cast::<F>()) };
info
}
unsafe extern "C-unwind" fn release<F>(info: *const c_void) {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
unsafe { Arc::decrement_strong_count(info.cast::<F>()) };
}
// Pointer equality / hashing.
extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 {
(info1 == info2) as u8
}
extern "C-unwind" fn hash(info: *const c_void) -> usize {
info as usize
}
// Call the provided closure.
unsafe extern "C-unwind" fn perform<F: Fn()>(info: *mut c_void) {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
let signaller = unsafe { &*info.cast::<F>() };
(signaller)();
}
// Fire last.
let order = CFIndex::MAX - 1;
// This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the
// information is copied, and not actually mutated.
let mut context = CFRunLoopSourceContext {
version: 0,
// This is retained on creation.
info: Arc::as_ptr(&signaller) as *mut c_void,
retain: Some(retain::<F>),
release: Some(release::<F>),
copyDescription: None,
equal: Some(equal),
hash: Some(hash),
schedule: None,
cancel: None,
perform: Some(perform::<F>),
};
// SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and
// `equal`/`hash` only access a pointer).
//
// Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but
// that's okay, since we are on the main thread, and the source is only added to the main
// run loop (below), and hence only performed there.
//
// Keeping the closure alive beyond this scope is fine, because `F: 'static`.
let source = unsafe {
let _ = mtm;
CFRunLoopSourceCreate(None, order, &mut context).unwrap()
};
// Register the source to be performed on the main thread.
let main_loop = unsafe { CFRunLoopGetMain() }.unwrap();
unsafe { CFRunLoopAddSource(&main_loop, Some(&source), kCFRunLoopCommonModes) };
Self { source, main_loop }
}
// FIXME(madsmtm): Use this on macOS too.
// More difficult there, since the user can re-start the event loop.
#[cfg_attr(target_os = "macos", allow(dead_code))]
pub(crate) fn invalidate(&self) {
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that
// we only need to register a single source even if there's multiple proxies in use.
unsafe { CFRunLoopSourceInvalidate(&self.source) };
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
// Signal the source, which ends up later invoking `perform` on the main thread.
//
// Multiple signals in quick succession are automatically coalesced into a single signal.
unsafe { CFRunLoopSourceSignal(&self.source) };
// Let the main thread know there's a new event.
//
// This is required since we may be (probably are) running on a different thread, and the
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
unsafe { CFRunLoopWakeUp(&self.main_loop) };
}
}

View File

@@ -3,6 +3,7 @@
#[cfg(target_os = "macos")]
mod appkit;
mod event_handler;
mod event_loop_proxy;
mod notification_center;
#[cfg(not(target_os = "macos"))]
mod uikit;

View File

@@ -2,7 +2,10 @@ use std::ptr::NonNull;
use block2::RcBlock;
use objc2::rc::Retained;
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
use objc2::runtime::ProtocolObject;
use objc2_foundation::{
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
};
/// Observe the given notification.
///
@@ -12,7 +15,7 @@ pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,
) -> Retained<NSObject> {
) -> Retained<ProtocolObject<dyn NSObjectProtocol>> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() });
});

View File

@@ -3,32 +3,29 @@
use std::cell::{OnceCell, RefCell, RefMut};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use dispatch2::MainThreadBound;
use objc2::rc::Retained;
use objc2::sel;
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopAddTimer,
CFRunLoopGetMain, CFRunLoopTimer, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerSetNextFireDate, CGRect, CGSize,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView};
use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy;
use super::window::WinitUIWindow;
use super::{ActiveEventLoop, EventLoopProxy};
use super::ActiveEventLoop;
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
macro_rules! bug {
($($msg:tt)*) => {
@@ -47,45 +44,17 @@ macro_rules! bug_assert {
/// This is stored separately from AppState, since AppState needs to be accessible while the handler
/// is executing.
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
// of the main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
StaticMainThreadBound(OnceCell::new());
static GLOBAL: MainThreadBound<OnceCell<EventHandler>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
GLOBAL.get(mtm).get_or_init(EventHandler::new)
}
fn handle_event(mtm: MainThreadMarker, event: Event) {
let event_loop = &ActiveEventLoop { mtm };
get_handler(mtm).handle(|app| match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserWakeUp => app.proxy_wake_up(event_loop),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::CreateSurfaces => app.can_create_surfaces(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
})
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event),
Window { window_id: WindowId, event: WindowEvent },
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -96,14 +65,9 @@ pub struct ScaleFactorChanged {
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> {
Success { active_control_flow: ControlFlow, processing_redraws: bool },
ReentrancyPrevented { queued_events: &'a mut Vec<EventWrapper> },
}
impl Event {
impl EventWrapper {
fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
matches!(self, Self::Window { event: WindowEvent::RedrawRequested, .. })
}
}
@@ -112,18 +76,12 @@ impl Event {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
Initial {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingRedraws {
active_control_flow: ControlFlow,
},
@@ -140,10 +98,11 @@ pub(crate) struct AppState {
control_flow: ControlFlow,
waker: EventLoopWaker,
event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>,
}
impl AppState {
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
pub(crate) fn get_mut(mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
@@ -155,19 +114,21 @@ impl AppState {
if guard.is_none() {
#[inline(never)]
#[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>, mtm: MainThreadMarker) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain().unwrap() });
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}));
**guard = Some(AppState {
app_state: Some(AppStateImpl::Initial {
queued_events: Vec::new(),
queued_gpu_redraws: HashSet::new(),
}),
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(),
waker,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
event_loop_proxy,
queued_events: Vec::new(),
});
}
init_guard(&mut guard);
init_guard(&mut guard, mtm);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
@@ -217,48 +178,34 @@ impl AppState {
matches!(self.state(), AppStateImpl::Terminated)
}
fn did_finish_launching_transition(&mut self) -> Vec<EventWrapper> {
let (events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Initial { queued_events, queued_gpu_redraws } => {
(queued_events, queued_gpu_redraws)
},
fn did_finish_launching_transition(&mut self) {
let queued_gpu_redraws = match self.take_state() {
AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws,
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
events
}
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
fn wakeup_transition(&mut self) -> Option<StartCause> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
return None;
}
let event = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll))
},
let start_cause = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
}))
StartCause::WaitCancelled { start, requested_resume: None }
},
(ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => {
if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
}))
StartCause::ResumeTimeReached { start, requested_resume }
} else {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}))
StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
}
},
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
@@ -268,55 +215,7 @@ impl AppState {
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
Some(event)
}
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
// If we're not able to process an event due to recursion or `Init` not having been sent out
// yet, then queue the events up.
match self.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_events, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => {
// A lifetime cast: early returns are not currently handled well with NLL, but
// polonius handles them well. This transmute is a safe workaround.
return unsafe {
mem::transmute::<
UserCallbackTransitionResult<'_>,
UserCallbackTransitionResult<'_>,
>(UserCallbackTransitionResult::ReentrancyPrevented {
queued_events,
})
};
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::PollFinished { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::Terminated => {
bug!("unexpected attempted to process an event {:?}", s)
},
}
let (queued_gpu_redraws, active_control_flow, processing_redraws) = match self.take_state()
{
AppStateImpl::Initial { .. } | AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => {
(queued_gpu_redraws, active_control_flow, false)
},
AppStateImpl::ProcessingRedraws { active_control_flow } => {
(Default::default(), active_control_flow, true)
},
AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(),
};
self.set_state(AppStateImpl::InUserCallback {
queued_events: Vec::new(),
queued_gpu_redraws,
});
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws }
Some(start_cause)
}
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
@@ -344,6 +243,7 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.waker.stop()
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
@@ -372,7 +272,7 @@ impl AppState {
fn terminated_transition(&mut self) {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
s => bug!("terminated while not processing events {:?}", s),
}
}
@@ -393,8 +293,7 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<W
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => {
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => {
let _ = queued_gpu_redraws.insert(window);
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
@@ -418,27 +317,24 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
let events = AppState::get_mut(mtm).did_finish_launching_transition();
AppState::get_mut(mtm).did_finish_launching_transition();
let events = [
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)),
EventWrapper::StaticEvent(Event::CreateSurfaces),
]
.into_iter()
.chain(events);
handle_nonuser_events(mtm, events);
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() {
let cause = match this.wakeup_transition() {
None => return,
Some(wakeup_event) => wakeup_event,
Some(cause) => cause,
};
drop(this);
handle_nonuser_event(mtm, wakeup_event)
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
@@ -454,132 +350,66 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
return;
}
let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events);
return;
},
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => {
(active_control_flow, processing_redraws)
},
};
if !get_handler(mtm).ready() {
// Prevent re-entrancy; queue the events up for once we're done handling the event instead.
this.queued_events.extend(events);
return;
}
let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. });
drop(this);
for wrapper in events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_event(mtm, event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
for event in events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_wrapped_event(mtm, event)
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(if processing_redraws {
bug_assert!(
queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws { active_control_flow }
} else {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow }
});
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: \
{:#?}",
event
);
}
handle_event(mtm, event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
for event in queued_events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_wrapped_event(mtm, event);
}
}
}
fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
},
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => {
(active_control_flow, processing_redraws)
},
};
if processing_redraws {
let this = AppState::get_mut(mtm);
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
let event_loop_proxy = this.event_loop_proxy().clone();
drop(this);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
handle_event(mtm, Event::UserWakeUp);
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state =
Some(AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow });
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => handle_event(mtm, event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
}
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
handle_event(mtm, Event::UserWakeUp);
for event in queued_events {
handle_wrapped_event(mtm, event);
}
}
}
@@ -590,17 +420,11 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
if let Ok(window) = window.downcast::<WinitUIWindow>() {
events.push(EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::Occluded(occluded),
}));
});
}
}
handle_nonuser_events(mtm, events);
@@ -623,40 +447,48 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
.map(|window| EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
.collect();
drop(this);
handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.resumed(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_suspended(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.suspended(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_memory_warning(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.memory_warning(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
if let Ok(window) = window.downcast::<WinitUIWindow>() {
events.push(EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::Destroyed,
}));
});
}
}
handle_nonuser_events(mtm, events);
@@ -665,20 +497,30 @@ pub(crate) fn terminated(application: &UIApplication) {
this.terminated_transition();
drop(this);
handle_event(mtm, Event::LoopExiting)
get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
let this = AppState::get_mut(mtm);
// Prevent EventLoopProxy from firing again.
this.event_loop_proxy.invalidate();
}
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
match event {
EventWrapper::Window { window_id, event } => get_handler(mtm)
.handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
}
fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) {
let ScaleFactorChanged { suggested_size, scale_factor, window } = event;
let new_surface_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::ScaleFactorChanged {
get_handler(mtm).handle(|app| {
app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged {
scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
},
};
handle_event(mtm, event);
});
});
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size);
@@ -699,46 +541,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
timer: CFRetained<CFRunLoopTimer>,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
CFRunLoopTimerInvalidate(&self.timer);
}
}
}
impl EventLoopWaker {
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
fn new(rl: CFRetained<CFRunLoop>) -> EventLoopWaker {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
None,
f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
Some(wakeup_main_loop),
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
)
.unwrap();
CFRunLoopAddTimer(&rl, Some(&timer), kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {
@@ -751,94 +593,8 @@ impl EventLoopWaker {
let duration = instant - now;
let fsecs =
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
}
}
}
}
macro_rules! os_capabilities {
(
$(
$(#[$attr:meta])*
$error_name:ident: $objc_call:literal,
$name:ident: $major:literal-$minor:literal
),*
$(,)*
) => {
#[derive(Clone, Debug)]
pub struct OSCapabilities {
$(
pub $name: bool,
)*
os_version: NSOperatingSystemVersion,
}
impl OSCapabilities {
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
$(let $name = meets_requirements(os_version, $major, $minor);)*
Self { $($name,)* os_version, }
}
}
impl OSCapabilities {$(
$(#[$attr])*
pub fn $error_name(&self, extra_msg: &str) {
tracing::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
)*}
};
}
os_capabilities! {
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
#[allow(unused)] // error message unused
safe_area_err_msg: "-[UIView safeAreaInsets]",
safe_area: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
home_indicator_hidden: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
defer_system_gestures: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
maximum_frames_per_second: 10-3,
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
#[allow(unused)] // error message unused
force_touch_err_msg: "-[UITouch force]",
force_touch: 9-0,
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
fn get_version() -> NSOperatingSystemVersion {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
// not support debugging on devices with an iOS version of less than 8. Another example, in
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
}

View File

@@ -1,18 +1,15 @@
use std::ffi::{c_char, c_int, c_void};
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2::runtime::ProtocolObject;
use objc2::{msg_send, ClassType, MainThreadMarker};
use objc2_core_foundation::{
kCFRunLoopDefaultMode, CFIndex, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopGetMain,
CFRunLoopObserver, CFRunLoopObserverCreate,
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
@@ -23,15 +20,13 @@ use objc2_ui_kit::{
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper};
use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::{app_state, monitor, MonitorHandle};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform_impl::Window;
@@ -129,13 +124,13 @@ pub struct EventLoop {
// system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_did_become_active_observer: Retained<NSObject>,
_will_resign_active_observer: Retained<NSObject>,
_will_enter_foreground_observer: Retained<NSObject>,
_did_enter_background_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
_did_receive_memory_warning_observer: Retained<NSObject>,
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_become_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_resign_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_enter_foreground_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -174,17 +169,13 @@ impl EventLoop {
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
},
move |_| app_state::handle_resumed(mtm),
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
},
move |_| app_state::handle_suspended(mtm),
);
let _will_enter_foreground_observer = create_observer(
&center,
@@ -194,9 +185,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
// The `object` in `UIApplicationWillEnterForegroundNotification` is documented to
// be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap();
send_occluded_event_for_all_windows(&app, false);
},
);
@@ -208,9 +199,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
// The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be
// `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap();
send_occluded_event_for_all_windows(&app, true);
},
);
@@ -221,9 +212,9 @@ impl EventLoop {
move |notification| {
let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object");
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// (somewhat) documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
// The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented
// to be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap();
app_state::terminated(&app);
},
);
@@ -231,12 +222,7 @@ impl EventLoop {
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| {
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::MemoryWarning),
);
},
move |_| app_state::handle_memory_warning(mtm),
);
Ok(EventLoop {
@@ -254,7 +240,7 @@ impl EventLoop {
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
unsafe { msg_send![UIApplication::class(), sharedApplication] };
assert!(
application.is_none(),
"\
@@ -287,78 +273,19 @@ impl EventLoop {
}
}
pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy {
pub(crate) fn new() -> EventLoopProxy {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other
// observers
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
@@ -374,65 +301,68 @@ fn setup_control_flow_observers() {
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef,
extern "C-unwind" fn control_flow_main_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
let main_loop = CFRunLoopGetMain();
let main_loop = CFRunLoopGetMain().unwrap();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
None,
CFRunLoopActivity::AfterWaiting.0,
true,
CFIndex::MIN,
control_flow_begin_handler,
Some(control_flow_begin_handler),
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
)
.unwrap();
CFRunLoopAddObserver(&main_loop, Some(&begin_observer), kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler,
Some(control_flow_main_end_handler),
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
)
.unwrap();
CFRunLoopAddObserver(&main_loop, Some(&main_end_observer), kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
CFIndex::MAX,
control_flow_end_handler,
Some(control_flow_end_handler),
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
)
.unwrap();
CFRunLoopAddObserver(&main_loop, Some(&end_observer), kCFRunLoopDefaultMode);
}
}

View File

@@ -10,9 +10,9 @@ mod window;
use std::fmt;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
@@ -20,9 +20,6 @@ pub(crate) use crate::cursor::{
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}
#[derive(Debug)]
pub enum OsError {}

View File

@@ -1,30 +1,29 @@
#![allow(clippy::unnecessary_cast)]
use std::collections::{BTreeSet, VecDeque};
use std::num::{NonZeroU16, NonZeroU32};
use std::collections::VecDeque;
use std::num::NonZeroU32;
use std::{fmt, hash, ptr};
use objc2::mutability::IsRetainable;
use dispatch2::{run_on_main, MainThreadBound};
use objc2::rc::Retained;
use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2::{available, MainThreadMarker, Message};
use objc2_foundation::NSInteger;
use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::app_state;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
use crate::dpi::PhysicalPosition;
use crate::monitor::VideoMode;
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
impl<T: Message> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self {
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
}
}
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
impl<T: 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() };
@@ -32,7 +31,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
}
}
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
impl<T: 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() };
@@ -40,14 +39,12 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
}
}
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
impl<T: Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle {
pub(crate) size: (u32, u32),
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) mode: VideoMode,
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle,
}
impl VideoModeHandle {
@@ -58,30 +55,18 @@ impl VideoModeHandle {
) -> VideoModeHandle {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
VideoModeHandle {
size: (size.width as u32, size.height as u32),
let mode = VideoMode {
size: (size.width as u32, size.height as u32).into(),
bit_depth: None,
refresh_rate_millihertz,
};
VideoModeHandle {
mode,
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
monitor: MonitorHandle::new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
None
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> {
self.screen_mode.0.get(mtm)
}
@@ -101,13 +86,20 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state);
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
}
}
@@ -121,8 +113,10 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
}
}
@@ -155,7 +149,7 @@ impl MonitorHandle {
#[allow(deprecated)]
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &**self.ui_screen(mtm))
.position(|rhs| rhs == *self.ui_screen(mtm))
.map(|idx| idx.to_string())
}
})
@@ -170,52 +164,53 @@ impl MonitorHandle {
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
pub fn current_video_mode(&self) -> Option<VideoMode> {
Some(run_on_main(|mtm| {
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).currentMode().unwrap(),
mtm,
)
.mode
}))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
pub fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm);
// Use Ord impl of RootVideoModeHandle
let modes: BTreeSet<_> = ui_screen
ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoModeHandle {
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm),
})
.collect();
modes.into_iter().map(|mode| mode.video_mode)
.map(|mode| VideoModeHandle::new(ui_screen.clone(), mode, mtm))
.collect::<Vec<_>>()
.into_iter()
})
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes_handles().map(|handle| handle.mode)
}
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoModeHandle {
pub fn preferred_video_mode(&self) -> VideoMode {
run_on_main(|mtm| {
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(),
mtm,
)
.mode
})
}
}
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
let refresh_rate_millihertz: NSInteger = {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
if available!(ios = 10.3, tvos = 10.2) {
uiscreen.maximumFramesPerSecond()
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
@@ -228,7 +223,9 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
tracing::warn!(
"`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps"
);
60
}
};
@@ -240,3 +237,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
}
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(&*screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

@@ -3,8 +3,9 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2::{available, define_class, msg_send, sel, DefinedClass, MainThreadMarker};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect};
use objc2_foundation::{NSObject, NSSet, NSString};
use objc2_ui_kit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
@@ -17,11 +18,10 @@ use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalPosition;
use crate::event::{
ButtonSource, ElementState, Event, FingerId, Force, KeyEvent, PointerKind, PointerSource,
TouchPhase, WindowEvent,
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::KeyEventExtra;
use crate::window::WindowAttributes;
pub struct WinitViewState {
@@ -39,36 +39,26 @@ pub struct WinitViewState {
fingers: Cell<u8>,
}
declare_class!(
define_class!(
#[unsafe(super(UIView, UIResponder, NSObject))]
#[name = "WinitUIView"]
#[ivars = WinitViewState]
pub(crate) struct WinitView;
unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIView";
}
impl DeclaredClass for WinitView {
type Ivars = WinitViewState;
}
unsafe impl WinitView {
#[method(drawRect:)]
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(drawRect:))]
fn draw_rect(&self, rect: CGRect) {
let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
}),
);
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
});
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
#[method(layoutSubviews)]
#[unsafe(method(layoutSubviews))]
fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
@@ -82,16 +72,13 @@ declare_class!(
.to_physical(scale_factor);
let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::SurfaceResized(size),
}),
);
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::SurfaceResized(size),
});
}
#[method(setContentScaleFactor:)]
#[unsafe(method(setContentScaleFactor:))]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap();
let _: () =
@@ -124,50 +111,46 @@ declare_class!(
let window_id = window.id();
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window,
scale_factor,
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
},
))),
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
window,
scale_factor,
suggested_size: size.to_physical(scale_factor),
}))
.chain(std::iter::once(EventWrapper::Window {
window_id,
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
})),
);
}
#[method(safeAreaInsetsDidChange)]
#[unsafe(method(safeAreaInsetsDidChange))]
fn safe_area_changed(&self) {
debug!("safeAreaInsetsDidChange was called, requesting redraw");
// When the safe area changes we want to make sure to emit a redraw event
self.setNeedsDisplay();
}
#[method(touchesBegan:withEvent:)]
#[unsafe(method(touchesBegan:withEvent:))]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[method(touchesMoved:withEvent:)]
#[unsafe(method(touchesMoved:withEvent:))]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[method(touchesEnded:withEvent:)]
#[unsafe(method(touchesEnded:withEvent:))]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[method(touchesCancelled:withEvent:)]
#[unsafe(method(touchesCancelled:withEvent:))]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[method(pinchGesture:)]
#[unsafe(method(pinchGesture:))]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap();
@@ -175,54 +158,48 @@ declare_class!(
UIGestureRecognizerState::Began => {
self.ivars().pinch_last_delta.set(recognizer.scale());
(TouchPhase::Started, 0.0)
}
},
UIGestureRecognizerState::Changed => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale());
(TouchPhase::Moved, recognizer.scale() - last_scale)
}
},
UIGestureRecognizerState::Ended => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0);
(TouchPhase::Moved, recognizer.scale() - last_scale)
}
},
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale())
}
},
state => panic!("unexpected recognizer state: {state:?}"),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::PinchGesture {
device_id: None,
delta: delta as f64,
phase,
},
});
event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase },
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(doubleTapGesture:)]
#[unsafe(method(doubleTapGesture:))]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::DoubleTapGesture {
device_id: None,
},
});
event: WindowEvent::DoubleTapGesture { device_id: None },
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
}
#[method(rotationGesture:)]
#[unsafe(method(rotationGesture:))]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap();
@@ -231,41 +208,42 @@ declare_class!(
self.ivars().rotation_last_delta.set(0.0);
(TouchPhase::Started, 0.0)
}
},
UIGestureRecognizerState::Changed => {
let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation());
let last_rotation =
self.ivars().rotation_last_delta.replace(recognizer.rotation());
(TouchPhase::Moved, recognizer.rotation() - last_rotation)
}
},
UIGestureRecognizerState::Ended => {
let last_rotation = self.ivars().rotation_last_delta.replace(0.0);
(TouchPhase::Ended, recognizer.rotation() - last_rotation)
}
},
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation())
}
},
state => panic!("unexpected recognizer state: {state:?}"),
};
// Make delta negative to match macos, convert to degrees
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RotationGesture {
device_id: None,
delta: -delta.to_degrees() as _,
phase,
},
});
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(panGesture:)]
#[unsafe(method(panGesture:))]
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
let window = self.window().unwrap();
@@ -276,7 +254,7 @@ declare_class!(
self.ivars().pan_last_delta.set(translation);
(TouchPhase::Started, 0.0, 0.0)
}
},
UIGestureRecognizerState::Changed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation);
@@ -284,39 +262,40 @@ declare_class!(
let dy = translation.y - last_pan.y;
(TouchPhase::Moved, dx, dy)
}
},
UIGestureRecognizerState::Ended => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
let last_pan: CGPoint =
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
let dx = translation.x - last_pan.x;
let dy = translation.y - last_pan.y;
(TouchPhase::Ended, dx, dy)
}
},
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
let last_pan: CGPoint =
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
}
},
state => panic!("unexpected recognizer state: {state:?}"),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::PanGesture {
device_id: None,
delta: PhysicalPosition::new(dx as _, dy as _),
phase,
},
});
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(canBecomeFirstResponder)]
#[unsafe(method(canBecomeFirstResponder))]
fn can_become_first_responder(&self) -> bool {
true
}
@@ -325,27 +304,30 @@ declare_class!(
unsafe impl NSObjectProtocol for WinitView {}
unsafe impl UIGestureRecognizerDelegate for WinitView {
#[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool {
#[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))]
fn should_recognize_simultaneously(
&self,
_gesture_recognizer: &UIGestureRecognizer,
_other_gesture_recognizer: &UIGestureRecognizer,
) -> bool {
true
}
}
unsafe impl UITextInputTraits for WinitView {
}
unsafe impl UITextInputTraits for WinitView {}
unsafe impl UIKeyInput for WinitView {
#[method(hasText)]
#[unsafe(method(hasText))]
fn has_text(&self) -> bool {
true
}
#[method(insertText:)]
#[unsafe(method(insertText:))]
fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text)
}
#[method(deleteBackward)]
#[unsafe(method(deleteBackward))]
fn delete_backward(&self) {
self.handle_delete_backward()
}
@@ -371,7 +353,7 @@ impl WinitView {
primary_finger: Cell::new(None),
fingers: Cell::new(0),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
@@ -383,8 +365,8 @@ impl WinitView {
}
fn window(&self) -> Option<Retained<WinitUIWindow>> {
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
(**self).window().map(|window| unsafe { Retained::cast(window) })
// `WinitView`s should always be installed in a `WinitUIWindow`
(**self).window().map(|window| window.downcast().unwrap())
}
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
@@ -479,13 +461,12 @@ impl WinitView {
fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap();
let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches {
let logical_location = touch.locationInView(None);
let touch_type = touch.r#type();
let force = if let UITouchType::Pencil = touch_type {
None
} else if os_supports_force {
} else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
let trait_collection = self.traitCollection();
let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support.
@@ -502,7 +483,7 @@ impl WinitView {
} else {
None
};
let touch_id = touch as *const UITouch as usize;
let touch_id = Retained::as_ptr(&touch) as usize;
let phase = touch.phase();
let position = {
let scale_factor = self.contentScaleFactor();
@@ -538,7 +519,7 @@ impl WinitView {
}
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerEntered {
device_id: None,
@@ -550,8 +531,8 @@ impl WinitView {
PointerKind::Touch(finger_id)
},
},
}));
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
});
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerButton {
device_id: None,
@@ -564,7 +545,7 @@ impl WinitView {
ButtonSource::Touch { finger_id, force }
},
},
}));
});
},
UITouchPhase::Moved => {
let (primary, source) = if let UITouchType::Pencil = touch_type {
@@ -576,7 +557,7 @@ impl WinitView {
})
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerMoved {
device_id: None,
@@ -584,7 +565,7 @@ impl WinitView {
position,
source,
},
}));
});
},
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended | UITouchPhase::Cancelled => {
@@ -600,7 +581,7 @@ impl WinitView {
};
if let UITouchPhase::Ended = phase {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerButton {
device_id: None,
@@ -613,10 +594,10 @@ impl WinitView {
ButtonSource::Touch { finger_id, force }
},
},
}));
});
}
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerLeft {
device_id: None,
@@ -628,7 +609,7 @@ impl WinitView {
PointerKind::Touch(finger_id)
},
},
}));
});
},
_ => panic!("unexpected touch phase: {phase:?}"),
}
@@ -647,29 +628,30 @@ impl WinitView {
text.to_string().chars().flat_map(|c| {
let text = smol_str::SmolStr::from_iter([c]);
// Emit both press and release events
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(
NativeKeyCode::Unidentified,
),
platform_specific: KeyEventExtra {},
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
is_synthetic: false,
device_id: None,
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
text_with_all_modifiers: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
key_without_modifiers: Key::Character(text.clone()),
},
})
is_synthetic: false,
},
})
}),
);
@@ -681,23 +663,22 @@ impl WinitView {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(
mtm,
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false,
location: KeyLocation::Standard,
text: None,
},
is_synthetic: false,
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
repeat: false,
location: KeyLocation::Standard,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: Key::Named(NamedKey::Backspace),
},
})
is_synthetic: false,
},
}),
);
}

View File

@@ -1,14 +1,13 @@
use std::cell::Cell;
use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2_foundation::NSObject;
use objc2_ui_kit::{
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
UIUserInterfaceIdiom, UIView, UIViewController,
};
use super::app_state::{self};
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::window::WindowAttributes;
@@ -20,51 +19,42 @@ pub struct ViewControllerState {
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
}
declare_class!(
define_class!(
#[unsafe(super(UIViewController, UIResponder, NSObject))]
#[name = "WinitUIViewController"]
#[ivars = ViewControllerState]
pub(crate) struct WinitViewController;
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIViewController";
}
impl DeclaredClass for WinitViewController {
type Ivars = ViewControllerState;
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
/// This documentation attribute makes rustfmt work for some reason?
impl WinitViewController {
#[unsafe(method(shouldAutorotate))]
fn should_autorotate(&self) -> bool {
true
}
#[method(prefersStatusBarHidden)]
#[unsafe(method(prefersStatusBarHidden))]
fn prefers_status_bar_hidden(&self) -> bool {
self.ivars().prefers_status_bar_hidden.get()
}
#[method(preferredStatusBarStyle)]
#[unsafe(method(preferredStatusBarStyle))]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.ivars().preferred_status_bar_style.get()
}
#[method(prefersHomeIndicatorAutoHidden)]
#[unsafe(method(prefersHomeIndicatorAutoHidden))]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.ivars().prefers_home_indicator_auto_hidden.get()
}
#[method(supportedInterfaceOrientations)]
#[unsafe(method(supportedInterfaceOrientations))]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.ivars().supported_orientations.get()
}
#[method(preferredScreenEdgesDeferringSystemGestures)]
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.ivars()
.preferred_screen_edges_deferring_system_gestures
.get()
self.ivars().preferred_screen_edges_deferring_system_gestures.get()
}
}
);
@@ -87,11 +77,13 @@ impl WinitViewController {
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.ivars().prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
if available!(ios = 11.0, visionos = 1.0) {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
tracing::warn!(
"`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \
Ignoring"
);
}
}
@@ -101,11 +93,13 @@ impl WinitViewController {
UIRectEdge(val.bits().into())
};
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
if available!(ios = 11.0, visionos = 1.0) {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
tracing::warn!(
"`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \
visionOS. Ignoring"
);
}
}
@@ -146,7 +140,7 @@ impl WinitViewController {
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
this.set_prefers_status_bar_hidden(
window_attributes.platform_specific.prefers_status_bar_hidden,

View File

@@ -2,11 +2,11 @@
use std::collections::VecDeque;
use dispatch2::MainThreadBound;
use objc2::rc::Retained;
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_foundation::{
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol,
};
use objc2::{available, class, define_class, msg_send, MainThreadMarker};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
use objc2_foundation::{NSObject, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow,
@@ -23,7 +23,7 @@ use crate::dpi::{
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{Event, WindowEvent};
use crate::event::WindowEvent;
use crate::icon::Icon;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
@@ -32,43 +32,31 @@ use crate::window::{
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
declare_class!(
define_class!(
#[unsafe(super(UIWindow, UIResponder, NSObject))]
#[name = "WinitUIWindow"]
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow;
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIWindow";
}
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
/// This documentation attribute makes rustfmt work for some reason?
impl WinitUIWindow {
#[unsafe(method(becomeKeyWindow))]
fn become_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: self.id(),
event: WindowEvent::Focused(true),
}),
);
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
window_id: self.id(),
event: WindowEvent::Focused(true),
});
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[method(resignKeyWindow)]
#[unsafe(method(resignKeyWindow))]
fn resign_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: self.id(),
event: WindowEvent::Focused(false),
}),
);
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
window_id: self.id(),
event: WindowEvent::Focused(false),
});
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
}
@@ -86,15 +74,18 @@ impl WinitUIWindow {
// into very confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
let this: Retained<Self> = unsafe { msg_send![mtm.alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
Some(Fullscreen::Exclusive(ref monitor, ref video_mode)) => {
let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
if let Some(video_mode) =
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
{
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
}
this.setScreen(screen);
},
Some(Fullscreen::Borderless(Some(ref monitor))) => {
@@ -205,8 +196,7 @@ impl Inner {
}
pub fn safe_area(&self) -> PhysicalInsets<u32> {
// Only available on iOS 11.0
let insets = if app_state::os_capabilities().safe_area {
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
self.view.safeAreaInsets()
} else {
// Assume the status bar frame is the only thing that obscures the view
@@ -312,9 +302,13 @@ impl Inner {
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen(mtm);
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
Some(Fullscreen::Exclusive(monitor, video_mode)) => {
let uiscreen = monitor.ui_screen(mtm);
if let Some(video_mode) =
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
{
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
}
uiscreen.clone()
},
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
@@ -489,7 +483,7 @@ impl Window {
let main_screen = UIScreen::mainScreen(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Exclusive(ref monitor, _)) => monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(None)) | None => &main_screen,
};

View File

@@ -16,7 +16,6 @@ use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle
use crate::event::{ElementState, KeyEvent};
use crate::keyboard::{Key, KeyLocation};
use crate::platform_impl::KeyEventExtra;
use crate::utils::Lazy;
mod compose;
@@ -198,9 +197,16 @@ impl KeyContext<'_> {
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 }
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
text_with_all_modifiers,
key_without_modifiers,
}
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {

View File

@@ -4,26 +4,23 @@
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::env;
use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::time::Duration;
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex};
use smol_str::SmolStr;
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
#[cfg(x11_platform)]
use self::x11::{XConnection, XError, XNotSupported};
use crate::application::ApplicationHandler;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
use crate::dpi::PhysicalPosition;
#[cfg(x11_platform)]
use crate::dpi::Size;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::{EventLoopError, NotSupportedError};
use crate::event_loop::ActiveEventLoop;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
use crate::keyboard::Key;
use crate::monitor::VideoMode;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
@@ -165,52 +162,16 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
pub fn current_video_mode(&self) -> Option<VideoMode> {
x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoModeHandle {
#[cfg(x11_platform)]
X(x11::VideoModeHandle),
#[cfg(wayland_platform)]
Wayland(wayland::VideoModeHandle),
}
impl VideoModeHandle {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.size())
}
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
}
#[inline]
pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum PlatformCustomCursor {
#[cfg(wayland_platform)]
@@ -329,9 +290,9 @@ impl EventLoop {
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
Backend::Wayland => EventLoop::new_wayland_any_thread(),
#[cfg(x11_platform)]
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
Backend::X => EventLoop::new_x11_any_thread(),
}
}
@@ -360,18 +321,15 @@ impl EventLoop {
}
}
pub fn run<A: ApplicationHandler>(
self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(init_closure))
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
}
pub fn run_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
app: A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(init_closure))
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
}
pub fn pump_app_events<A: ApplicationHandler>(

View File

@@ -15,7 +15,7 @@ use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError, RequestError};
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event::{DeviceEvent, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
@@ -38,6 +38,12 @@ pub use crate::event_loop::EventLoopProxy as CoreEventLoopProxy;
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
#[derive(Debug)]
pub(crate) enum Event {
WindowEvent { window_id: WindowId, event: WindowEvent },
DeviceEvent { event: DeviceEvent },
}
/// The Wayland event loop.
pub struct EventLoop {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
@@ -146,21 +152,15 @@ impl EventLoop {
Ok(event_loop)
}
pub fn run<A: ApplicationHandler>(
mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
mut app: A,
) -> Result<(), EventLoopError> {
self.active_event_loop.clear_exit();
let mut app = init_closure(&self.active_event_loop);
let exit = loop {
match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => {
@@ -204,6 +204,8 @@ impl EventLoop {
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(&self.active_event_loop);
PumpStatus::Exit(code)
} else {
PumpStatus::Continue
@@ -387,10 +389,9 @@ impl EventLoop {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.active_event_loop, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, device_id, event)
Event::DeviceEvent { event } => {
app.device_event(&self.active_event_loop, None, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
@@ -403,10 +404,9 @@ impl EventLoop {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.active_event_loop, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, device_id, event)
Event::DeviceEvent { event } => {
app.device_event(&self.active_event_loop, None, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}

View File

@@ -2,7 +2,8 @@
use std::vec::Drain;
use crate::event::{DeviceEvent, Event, WindowEvent};
use super::Event;
use crate::event::{DeviceEvent, WindowEvent};
use crate::window::WindowId;
/// An event loop's sink to deliver events from the Wayland event callbacks
@@ -26,7 +27,7 @@ impl EventSink {
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent) {
self.window_events.push(Event::DeviceEvent { event, device_id: None });
self.window_events.push(Event::DeviceEvent { event });
}
/// Add new window event to a queue.

View File

@@ -1,7 +1,7 @@
//! Winit's Wayland backend.
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use output::{MonitorHandle, VideoModeHandle};
pub use output::MonitorHandle;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
pub use window::Window;

View File

@@ -1,11 +1,11 @@
use std::num::{NonZeroU16, NonZeroU32};
use std::num::NonZeroU32;
use sctk::output::{Mode, OutputData};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Proxy;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::monitor::VideoMode;
#[derive(Clone, Debug)]
pub struct MonitorHandle {
@@ -54,27 +54,19 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
pub fn current_video_mode(&self) -> Option<VideoMode> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| {
let mode = info.modes.iter().find(|mode| mode.current).cloned();
mode.map(|mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode))
})
mode.map(wayland_mode_to_core_mode)
})
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
let output_data = self.proxy.data::<OutputData>().unwrap();
let modes = output_data.with_output_info(|info| info.modes.clone());
let monitor = self.clone();
modes.into_iter().map(move |mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode))
})
modes.into_iter().map(wayland_mode_to_core_mode)
}
}
@@ -104,38 +96,11 @@ impl std::hash::Hash for MonitorHandle {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) size: PhysicalSize<u32>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) monitor: MonitorHandle,
}
impl VideoModeHandle {
fn new(monitor: MonitorHandle, mode: Mode) -> Self {
VideoModeHandle {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
monitor: monitor.clone(),
}
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
None
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
/// Convert Wayland's [`Mode`] to winit's [`VideoMode`].
fn wayland_mode_to_core_mode(mode: Mode) -> VideoMode {
VideoMode {
size: (mode.dimensions.0, mode.dimensions.1).into(),
bit_depth: None,
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
}
}

View File

@@ -27,7 +27,10 @@ use sctk::seat::pointer::{
use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent};
use crate::event::{
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, WindowId};

View File

@@ -119,11 +119,15 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
None => return,
};
// Clear preedit at the start of `Done`.
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
// Clear preedit, unless all we'll be doing next is sending a new preedit.
if text_input_data.pending_commit.is_some()
|| text_input_data.pending_preedit.is_none()
{
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
}
// Send `Commit`.
if let Some(text) = text_input_data.pending_commit.take() {

View File

@@ -78,7 +78,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
state.events_sink.push_window_event(
crate::event::WindowEvent::ActivationTokenDone {
serial: *serial,
token: ActivationToken::_new(token),
token: ActivationToken::from_raw(token),
},
*window_id,
);

View File

@@ -139,7 +139,7 @@ impl Window {
// Set startup mode.
match attributes.fullscreen.map(Into::into) {
Some(Fullscreen::Exclusive(_)) => {
Some(Fullscreen::Exclusive(..)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
},
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
@@ -165,7 +165,7 @@ impl Window {
if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
{
xdg_activation.activate(token._token, &surface);
xdg_activation.activate(token.token, &surface);
}
// XXX Do initial commit.
@@ -438,7 +438,7 @@ impl CoreWindow for Window {
fn set_fullscreen(&self, fullscreen: Option<CoreFullscreen>) {
match fullscreen {
Some(CoreFullscreen::Exclusive(_)) => {
Some(CoreFullscreen::Exclusive(..)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
},
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]

View File

@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use std::str::Utf8Error;
use std::sync::Arc;
use dpi::PhysicalPosition;
use percent_encoding::percent_decode;
use x11rb::protocol::xproto::{self, ConnectionExt};
@@ -45,13 +46,25 @@ pub struct Dnd {
pub type_list: Option<Vec<xproto::Atom>>,
// Populated by XdndPosition event handler
pub source_window: Option<xproto::Window>,
// Populated by XdndPosition event handler
pub position: PhysicalPosition<f64>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub dragging: bool,
}
impl Dnd {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None })
Ok(Dnd {
xconn,
version: None,
type_list: None,
source_window: None,
position: PhysicalPosition::default(),
result: None,
dragging: false,
})
}
pub fn reset(&mut self) {
@@ -59,6 +72,7 @@ impl Dnd {
self.type_list = None;
self.source_window = None;
self.result = None;
self.dragging = false;
}
pub unsafe fn send_status(

File diff suppressed because it is too large Load Diff

View File

@@ -122,19 +122,15 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let is_allowed =
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
let new_context = {
let result = unsafe {
ImeContext::new(
xconn,
new_im.im,
style,
&new_im,
*window,
spot,
(*inner).event_sender.clone(),
is_allowed,
)
};
if result.is_err() {

View File

@@ -7,7 +7,7 @@ use std::{fmt, mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use super::{ffi, util, XConnection, XError};
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
/// IME creation error.
@@ -197,7 +197,7 @@ struct ImeContextClientData {
pub struct ImeContext {
pub(crate) ic: ffi::XIC,
pub(crate) ic_spot: ffi::XPoint,
pub(crate) style: Style,
pub(crate) allowed: bool,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
// from there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>,
@@ -206,11 +206,11 @@ pub struct ImeContext {
impl ImeContext {
pub(crate) unsafe fn new(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: Style,
im: &InputMethod,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
event_sender: ImeEventSender,
allowed: bool,
) -> Result<Self, ImeContextCreationError> {
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
@@ -219,20 +219,24 @@ impl ImeContext {
cursor_pos: 0,
}));
let style = if allowed { im.preedit_style } else { im.none_style };
let ic = match style as _ {
Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic(
xconn,
im,
im.im,
style,
window,
client_data as ffi::XPointer,
)
},
Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im, style, window)
ImeContext::create_nothing_ic(xconn, im.im, style, window)
},
Style::None(style) => unsafe {
ImeContext::create_none_ic(xconn, im.im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
@@ -241,7 +245,7 @@ impl ImeContext {
let mut context = ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
style,
allowed,
_client_data: unsafe { Box::from_raw(client_data) },
};
@@ -348,7 +352,7 @@ impl ImeContext {
}
pub fn is_allowed(&self) -> bool {
!matches!(self.style, Style::None(_))
self.allowed
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks

View File

@@ -177,7 +177,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
)
.map_err(GetXimServersError::GetPropertyError)?
.into_iter()
.map(ffi::Atom::from)
.map(|atom| atom as _)
.collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());

View File

@@ -10,13 +10,12 @@ use std::sync::Arc;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use tracing::debug;
use self::callbacks::*;
use self::context::ImeContext;
pub use self::context::ImeContextCreationError;
use self::inner::{close_im, ImeInner};
use self::input_method::{PotentialInputMethods, Style};
use self::input_method::PotentialInputMethods;
use super::{ffi, util, XConnection, XError};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -113,39 +112,26 @@ impl Ime {
pub fn create_context(
&mut self,
window: ffi::Window,
with_preedit: bool,
with_ime: bool,
) -> Result<bool, ImeContextCreationError> {
let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
None
} else {
let im = self.inner.im.as_ref().unwrap();
let style = if with_preedit { im.preedit_style } else { im.none_style };
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
im.im,
style,
im,
window,
None,
self.inner.event_sender.clone(),
with_ime,
)?
};
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
Some(context)

View File

@@ -24,7 +24,7 @@ use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::event::{DeviceId, Event, StartCause, WindowEvent};
use crate::event::{DeviceId, StartCause, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
@@ -373,21 +373,15 @@ impl EventLoop {
&self.event_processor.target
}
pub fn run<A: ApplicationHandler>(
mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
mut app: A,
) -> Result<(), EventLoopError> {
self.event_processor.target.clear_exit();
let mut app = init_closure(&self.event_processor.target);
let exit = loop {
match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => {
@@ -435,6 +429,8 @@ impl EventLoop {
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(self.window_target());
PumpStatus::Exit(code)
} else {
PumpStatus::Continue
@@ -536,7 +532,7 @@ impl EventLoop {
Some(Ok(token)) => {
let event = WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
token: crate::window::ActivationToken::from_raw(token),
};
app.window_event(&self.event_processor.target, window_id, event);
},
@@ -578,22 +574,7 @@ impl EventLoop {
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: Event| {
if let Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } = event
{
window_target.redraw_sender.send(window_id);
} else {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
});
self.event_processor.process_event(&mut xev, app);
}
}

View File

@@ -1,12 +1,12 @@
use std::num::{NonZeroU16, NonZeroU32};
use std::num::NonZeroU32;
use x11rb::connection::RequestConnection;
use x11rb::protocol::randr::{self, ConnectionExt as _};
use x11rb::protocol::xproto;
use super::{util, X11Error, XConnection};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl::VideoModeHandle as PlatformVideoModeHandle;
use crate::dpi::PhysicalPosition;
use crate::monitor::VideoMode;
// Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
@@ -21,32 +21,14 @@ impl XConnection {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) current: bool,
pub(crate) size: (u32, u32),
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) mode: VideoMode,
pub(crate) native_mode: randr::Mode,
pub(crate) monitor: Option<MonitorHandle>,
}
impl VideoModeHandle {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
#[inline]
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone().unwrap()
impl From<VideoModeHandle> for VideoMode {
fn from(handle: VideoModeHandle) -> Self {
handle.mode
}
}
@@ -65,7 +47,7 @@ pub struct MonitorHandle {
/// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect,
/// Supported video modes on this monitor
video_modes: Vec<VideoModeHandle>,
pub(crate) video_modes: Vec<VideoModeHandle>,
}
impl PartialEq for MonitorHandle {
@@ -159,17 +141,13 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
self.video_modes.iter().find(|mode| mode.current).cloned().map(PlatformVideoModeHandle::X)
pub fn current_video_mode(&self) -> Option<VideoMode> {
self.video_modes.iter().find(|mode| mode.current).cloned().map(Into::into)
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
let monitor = self.clone();
self.video_modes.clone().into_iter().map(move |mut x| {
x.monitor = Some(monitor.clone());
PlatformVideoModeHandle::X(x)
})
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes.clone().into_iter().map(Into::into)
}
}

View File

@@ -95,7 +95,7 @@ impl FrameExtentsHeuristic {
impl XConnection {
// This is adequate for inner_position
pub fn translate_coords(
pub fn translate_coords_root(
&self,
window: xproto::Window,
root: xproto::Window,
@@ -103,6 +103,19 @@ impl XConnection {
self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into)
}
pub fn translate_coords(
&self,
src_w: xproto::Window,
dst_w: xproto::Window,
src_x: i16,
src_y: i16,
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
self.xcb_connection()
.translate_coordinates(src_w, dst_w, src_x, src_y)?
.reply()
.map_err(Into::into)
}
// This is adequate for surface_size
pub fn get_geometry(
&self,
@@ -189,7 +202,7 @@ impl XConnection {
// that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = {
let coords = self
.translate_coords(window, root)
.translate_coords_root(window, root)
.expect("Failed to translate window coordinates");
(coords.dst_y, coords.child)
};

View File

@@ -7,6 +7,7 @@ use x11rb::protocol::randr::{self, ConnectionExt as _};
use super::*;
use crate::dpi::validate_scale_factor;
use crate::monitor::VideoMode;
use crate::platform_impl::platform::x11::{monitor, VideoModeHandle};
/// Represents values of `WINIT_HIDPI_FACTOR`.
@@ -85,9 +86,11 @@ impl XConnection {
.map(|mode| {
VideoModeHandle {
current: mode.id == current_mode,
size: (mode.width.into(), mode.height.into()),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode),
bit_depth: NonZeroU16::new(bit_depth as u16),
mode: VideoMode {
size: (mode.width as u32, mode.height as u32).into(),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode),
bit_depth: NonZeroU16::new(bit_depth as u16),
},
native_mode: mode.id,
// This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user

View File

@@ -20,10 +20,11 @@ use super::util::{self, SelectedCursor};
use super::{
ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, XConnection,
};
use crate::application::ApplicationHandler;
use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{Event, SurfaceSizeWriter, WindowEvent};
use crate::event::{SurfaceSizeWriter, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform::x11::WindowType;
use crate::platform_impl::x11::atoms::*;
@@ -32,7 +33,6 @@ use crate::platform_impl::x11::{
};
use crate::platform_impl::{
common, Fullscreen, MonitorHandle as PlatformMonitorHandle, PlatformCustomCursor, PlatformIcon,
VideoModeHandle as PlatformVideoModeHandle,
};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
@@ -327,7 +327,7 @@ impl Drop for Window {
let xconn = &window.xconn;
// Restore the video mode on drop.
if let Some(Fullscreen::Exclusive(_)) = window.fullscreen() {
if let Some(Fullscreen::Exclusive(..)) = window.fullscreen() {
window.set_fullscreen(None);
}
@@ -880,7 +880,7 @@ impl UnownedWindow {
// Remove the startup notification if we have one.
if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() {
leap!(xconn.remove_activation_token(xwindow, &startup._token));
leap!(xconn.remove_activation_token(xwindow, &startup.token));
}
// We never want to give the user a broken window, since by then, it's too late to handle.
@@ -1035,20 +1035,17 @@ impl UnownedWindow {
// fullscreen, so we can restore it upon exit, as XRandR does not
// provide a mechanism to set this per app-session or restore this
// to the desktop video mode as macOS and Windows do
(&None, &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))))
| (
&Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))),
) => {
let monitor = video_mode.monitor.as_ref().unwrap();
(&None, &Some(Fullscreen::Exclusive(ref monitor, _)))
| (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref monitor, _))) => {
let id = monitor.native_identifier();
shared_state_lock.desktop_video_mode = Some((
monitor.id,
self.xconn.get_crtc_mode(monitor.id).expect("Failed to get desktop video mode"),
id,
self.xconn.get_crtc_mode(id).expect("Failed to get desktop video mode"),
));
},
// Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
(&Some(Fullscreen::Exclusive(..)), &None)
| (&Some(Fullscreen::Exclusive(..)), &Some(Fullscreen::Borderless(_))) => {
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
self.xconn
.set_crtc_config(monitor_id, mode_id)
@@ -1072,8 +1069,8 @@ impl UnownedWindow {
},
Some(fullscreen) => {
let (video_mode, monitor) = match fullscreen {
Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => {
(Some(video_mode), video_mode.monitor.clone().unwrap())
Fullscreen::Exclusive(PlatformMonitorHandle::X(monitor), video_mode) => {
(Some(video_mode), monitor.clone())
},
Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => {
(None, monitor)
@@ -1090,7 +1087,15 @@ impl UnownedWindow {
return Ok(None);
}
if let Some(video_mode) = video_mode {
if let Some(native_mode) = video_mode.and_then(|requested| {
monitor.video_modes.iter().find_map(|mode| {
if mode.mode == requested {
Some(mode.native_mode)
} else {
None
}
})
}) {
// FIXME: this is actually not correct if we're setting the
// video mode to a resolution higher than the current
// desktop resolution, because XRandR does not automatically
@@ -1117,7 +1122,7 @@ impl UnownedWindow {
// this will make someone unhappy, but it's very unusual for
// games to want to do this anyway).
self.xconn
.set_crtc_config(monitor.id, video_mode.native_mode)
.set_crtc_config(monitor.id, native_mode)
.expect("failed to set video mode");
}
@@ -1207,7 +1212,8 @@ impl UnownedWindow {
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
mut callback: impl FnMut(Event),
app: &mut dyn ApplicationHandler,
event_loop: &ActiveEventLoop,
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
@@ -1227,12 +1233,9 @@ impl UnownedWindow {
let old_surface_size = PhysicalSize::new(width, height);
let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
callback(Event::WindowEvent {
window_id: self.id(),
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
},
app.window_event(event_loop, self.id(), WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
});
let new_surface_size = *surface_size.lock().unwrap();
@@ -1516,7 +1519,7 @@ impl UnownedWindow {
// This should be okay to unwrap since the only error XTranslateCoordinates can return
// is BadWindow, and if the window handle is bad we have bigger problems.
self.xconn
.translate_coords(self.xwindow, self.root)
.translate_coords_root(self.xwindow, self.root)
.map(|coords| (coords.dst_x.into(), coords.dst_y.into()))
.unwrap()
}
@@ -1827,6 +1830,11 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
// We don't support the locked cursor yet, so ignore it early on.
if mode == CursorGrabMode::Locked {
return Err(NotSupportedError::new("locked cursor is not implemented on X11").into());
}
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
if mode == *grabbed_lock {
return Ok(());
@@ -1838,6 +1846,7 @@ impl UnownedWindow {
.xcb_connection()
.ungrab_pointer(x11rb::CURRENT_TIME)
.expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
*grabbed_lock = CursorGrabMode::None;
let result = match mode {
CursorGrabMode::None => self
@@ -1845,34 +1854,33 @@ impl UnownedWindow {
.flush_requests()
.map_err(|err| RequestError::Os(os_error!(X11Error::Xlib(err)))),
CursorGrabMode::Confined => {
let result = {
self.xconn
.xcb_connection()
.grab_pointer(
true as _,
self.xwindow,
xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC,
self.xwindow,
0u32,
x11rb::CURRENT_TIME,
)
.expect("Failed to call `grab_pointer`")
.reply()
.expect("Failed to receive reply from `grab_pointer`")
};
let result = self
.xconn
.xcb_connection()
.grab_pointer(
true as _,
self.xwindow,
xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC,
self.xwindow,
0u32,
x11rb::CURRENT_TIME,
)
.expect("Failed to call `grab_pointer`")
.reply()
.expect("Failed to receive reply from `grab_pointer`");
match result.status {
xproto::GrabStatus::SUCCESS => Ok(()),
@@ -1892,11 +1900,7 @@ impl UnownedWindow {
}
.map_err(|err| RequestError::Os(os_error!(err)))
},
CursorGrabMode::Locked => {
return Err(
NotSupportedError::new("locked cursor is not implemented on X11").into()
);
},
CursorGrabMode::Locked => return Ok(()),
};
if result.is_ok() {

View File

@@ -1,4 +1,4 @@
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)]
@@ -30,17 +30,19 @@ use self::web as platform;
use self::windows as platform;
/// Helper for converting between platform-specific and generic
/// [`VideoModeHandle`]/[`MonitorHandle`]
/// [`VideoMode`]/[`MonitorHandle`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Fullscreen {
Exclusive(VideoModeHandle),
Exclusive(MonitorHandle, VideoMode),
Borderless(Option<MonitorHandle>),
}
impl From<RootFullscreen> for Fullscreen {
fn from(f: RootFullscreen) -> Self {
match f {
RootFullscreen::Exclusive(mode) => Self::Exclusive(mode.video_mode),
RootFullscreen::Exclusive(handle, video_mode) => {
Self::Exclusive(handle.inner, video_mode)
},
RootFullscreen::Borderless(Some(handle)) => Self::Borderless(Some(handle.inner)),
RootFullscreen::Borderless(None) => Self::Borderless(None),
}
@@ -50,8 +52,8 @@ impl From<RootFullscreen> for Fullscreen {
impl From<Fullscreen> for RootFullscreen {
fn from(f: Fullscreen) -> Self {
match f {
Fullscreen::Exclusive(video_mode) => {
Self::Exclusive(RootVideoModeHandle { video_mode })
Fullscreen::Exclusive(inner, video_mode) => {
Self::Exclusive(RootMonitorHandle { inner }, video_mode)
},
Fullscreen::Borderless(Some(inner)) => {
Self::Borderless(Some(RootMonitorHandle { inner }))

View File

@@ -12,8 +12,7 @@ use orbclient::{
use smol_str::SmolStr;
use super::{
KeyEventExtra, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket,
WindowProperties,
MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowProperties,
};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
@@ -372,10 +371,8 @@ impl EventLoop {
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
key_without_modifiers,
text_with_all_modifiers,
},
is_synthetic: false,
};
@@ -483,11 +480,7 @@ impl EventLoop {
}
}
pub fn run<A: ApplicationHandler>(
mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
let mut app = init_closure(&self.window_target);
pub fn run_app<A: ApplicationHandler>(mut self, mut app: A) -> Result<(), EventLoopError> {
let mut start_cause = StartCause::Init;
loop {
app.new_events(&self.window_target, start_cause);
@@ -658,6 +651,8 @@ impl EventLoop {
}
}
app.exiting(&self.window_target);
Ok(())
}

View File

@@ -1,16 +1,13 @@
#![cfg(target_os = "redox")]
use std::num::{NonZeroU16, NonZeroU32};
use std::{fmt, str};
use smol_str::SmolStr;
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::keyboard::Key;
mod event_loop;
pub use self::window::Window;
use crate::dpi::PhysicalPosition;
use crate::monitor::VideoMode;
mod event_loop;
mod window;
pub(crate) use crate::cursor::{
@@ -151,43 +148,12 @@ impl MonitorHandle {
1.0 // TODO
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
pub fn current_video_mode(&self) -> Option<VideoMode> {
// (it is guaranteed to support 32 bit color though)
Some(VideoModeHandle { monitor: self.clone() })
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.current_video_mode().into_iter()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle {
monitor: MonitorHandle,
}
impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> {
// TODO
PhysicalSize::default()
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
None
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
// TODO
None
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
std::iter::empty()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub key_without_modifiers: Key,
pub text_with_all_modifiers: Option<SmolStr>,
}

View File

@@ -1,7 +1,6 @@
use super::{backend, HasMonitorPermissionFuture, MonitorPermissionFuture};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError};
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
@@ -24,20 +23,20 @@ impl EventLoop {
Ok(EventLoop { elw: ActiveEventLoop::new() })
}
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let event_loop = self.elw.clone();
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
let handler: Box<dyn FnMut(Event)> =
Box::new(|event| handle_event(&mut app, &event_loop, event));
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
let app = Box::new(app);
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
// because this function will never return and all resources not cleaned up by the point we
// `throw` will leak, making this actually `'static`.
let handler = unsafe {
std::mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event) + 'static>>(handler)
let app = unsafe {
std::mem::transmute::<
Box<dyn ApplicationHandler + '_>,
Box<dyn ApplicationHandler + 'static>,
>(app)
};
self.elw.run(handler, false);
self.elw.run(app, false);
// Throw an exception to break out of Rust execution and use unreachable to tell the
// compiler this function won't return, giving it a return type of '!'
@@ -48,9 +47,8 @@ impl EventLoop {
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, mut app: A) {
let event_loop = self.elw.clone();
self.elw.run(Box::new(move |event| handle_event(&mut app, &event_loop, event)), true);
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app), true);
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
@@ -85,17 +83,3 @@ impl EventLoop {
self.elw.runner.monitor().has_detailed_monitor_permission_async()
}
}
fn handle_event<A: ApplicationHandler>(app: &mut A, target: &ActiveEventLoop, event: Event) {
match event {
Event::NewEvents(cause) => app.new_events(target, cause),
Event::WindowEvent { window_id, event } => app.window_event(target, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(target, device_id, event),
Event::UserWakeUp => app.proxy_wake_up(target),
Event::Suspended => app.suspended(target),
Event::Resumed => app.resumed(target),
Event::CreateSurfaces => app.can_create_surfaces(target),
Event::AboutToWait => app.about_to_wait(target),
Event::MemoryWarning => app.memory_warning(target),
}
}

View File

@@ -13,11 +13,12 @@ use web_time::{Duration, Instant};
use super::super::event;
use super::super::main_thread::MainThreadMarker;
use super::super::monitor::MonitorHandler;
use super::backend;
use super::proxy::EventLoopProxy;
use super::state::State;
use super::{backend, ActiveEventLoop};
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, ElementState, Event, RawKeyEvent, StartCause, WindowEvent};
use crate::event::{DeviceEvent, DeviceId, ElementState, RawKeyEvent, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform_impl::platform::backend::{EventListenerHandle, SafeAreaHandle};
@@ -27,8 +28,6 @@ use crate::window::WindowId;
pub struct Shared(Rc<Execution>);
pub(super) type EventHandler = dyn FnMut(Event);
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
@@ -47,7 +46,7 @@ struct Execution {
runner: RefCell<RunnerEnum>,
suspended: Cell<bool>,
event_loop_recreation: Cell<bool>,
events: RefCell<VecDeque<EventWrapper>>,
events: RefCell<VecDeque<Event>>,
id: Cell<usize>,
window: web_sys::Window,
navigator: Navigator,
@@ -93,12 +92,13 @@ impl RunnerEnum {
struct Runner {
state: State,
event_handler: Box<EventHandler>,
app: Box<dyn ApplicationHandler>,
event_loop: ActiveEventLoop,
}
impl Runner {
pub fn new(event_handler: Box<EventHandler>) -> Self {
Runner { state: State::Init, event_handler }
pub fn new(app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) -> Self {
Runner { state: State::Init, app, event_loop }
}
/// Returns the corresponding `StartCause` for the current `state`, or `None`
@@ -115,19 +115,33 @@ impl Runner {
})
}
fn handle_single_event(&mut self, runner: &Shared, event: impl Into<EventWrapper>) {
match event.into() {
EventWrapper::Event(event) => (self.event_handler)(event),
EventWrapper::ScaleChange { canvas, size, scale } => {
fn handle_single_event(&mut self, runner: &Shared, event: Event) {
match event {
Event::NewEvents(cause) => self.app.new_events(&self.event_loop, cause),
Event::WindowEvent { window_id, event } => {
self.app.window_event(&self.event_loop, window_id, event)
},
Event::ScaleChange { canvas, size, scale } => {
if let Some(canvas) = canvas.upgrade() {
canvas.handle_scale_change(
runner,
|event| (self.event_handler)(event),
|window_id, event| {
self.app.window_event(&self.event_loop, window_id, event);
},
size,
scale,
)
}
},
Event::DeviceEvent { device_id, event } => {
self.app.device_event(&self.event_loop, device_id, event)
},
Event::UserWakeUp => self.app.proxy_wake_up(&self.event_loop),
Event::Suspended => self.app.suspended(&self.event_loop),
Event::Resumed => self.app.resumed(&self.event_loop),
Event::CreateSurfaces => self.app.can_create_surfaces(&self.event_loop),
Event::AboutToWait => self.app.about_to_wait(&self.event_loop),
Event::LoopExiting => self.app.exiting(&self.event_loop),
}
}
}
@@ -216,13 +230,13 @@ impl Shared {
self.0.destroy_pending.borrow_mut().push_back(id);
}
pub(crate) fn start(&self, event_handler: Box<EventHandler>) {
pub(crate) fn start(&self, app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) {
let mut runner = self.0.runner.borrow_mut();
assert!(matches!(*runner, RunnerEnum::Pending));
if self.0.monitor.is_initializing() {
*runner = RunnerEnum::Initializing(Runner::new(event_handler));
*runner = RunnerEnum::Initializing(Runner::new(app, event_loop));
} else {
*runner = RunnerEnum::Running(Runner::new(event_handler));
*runner = RunnerEnum::Running(Runner::new(app, event_loop));
drop(runner);
@@ -445,7 +459,7 @@ impl Shared {
pub fn request_redraw(&self, id: WindowId) {
self.0.redraw_pending.borrow_mut().insert(id);
self.send_events::<EventWrapper>(iter::empty());
self.send_events([]);
}
fn init(&self) {
@@ -473,7 +487,7 @@ impl Shared {
// Add an event to the event loop runner, from the user or an event handler
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub(crate) fn send_event<E: Into<EventWrapper>>(&self, event: E) {
pub(crate) fn send_event(&self, event: Event) {
self.send_events(iter::once(event));
}
@@ -514,7 +528,7 @@ impl Shared {
// Add a series of events to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub(crate) fn send_events<E: Into<EventWrapper>>(&self, events: impl IntoIterator<Item = E>) {
pub(crate) fn send_events(&self, events: impl IntoIterator<Item = Event>) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
@@ -539,7 +553,7 @@ impl Shared {
}
if !process_immediately {
// Queue these events to look at later
self.0.events.borrow_mut().extend(events.into_iter().map(Into::into));
self.0.events.borrow_mut().extend(events);
return;
}
// At this point, we know this is a fresh set of events
@@ -557,8 +571,7 @@ impl Shared {
// Take the start event, then the events provided to this function, and run an iteration of
// the event loop
let start_event = Event::NewEvents(start_cause);
let events =
iter::once(EventWrapper::from(start_event)).chain(events.into_iter().map(Into::into));
let events = iter::once(start_event).chain(events);
self.run_until_cleared(events);
}
@@ -579,9 +592,9 @@ impl Shared {
// cleared
//
// This will also process any events that have been queued or that are queued during processing
fn run_until_cleared<E: Into<EventWrapper>>(&self, events: impl Iterator<Item = E>) {
fn run_until_cleared(&self, events: impl Iterator<Item = Event>) {
for event in events {
self.handle_event(event.into());
self.handle_event(event);
}
self.process_destroy_pending_windows();
@@ -609,13 +622,13 @@ impl Shared {
self.apply_control_flow();
// We don't call `handle_loop_destroyed` here because we don't need to
// perform cleanup when the Web browser is going to destroy the page.
todo!("drop the application handler");
self.handle_event(Event::LoopExiting);
}
// handle_event takes in events and either queues them or applies a callback
//
// It should only ever be called from `run_until_cleared`.
fn handle_event(&self, event: impl Into<EventWrapper>) {
fn handle_event(&self, event: Event) {
if self.is_closed() {
self.exit();
}
@@ -625,7 +638,7 @@ impl Shared {
},
// If an event is being handled without a runner somehow, add it to the event queue so
// it will eventually be processed
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event.into()),
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event),
// If the Runner has been destroyed, there is nothing to do.
RunnerEnum::Destroyed => return,
// This function should never be called if we are still waiting for something.
@@ -652,13 +665,7 @@ impl Shared {
let mut events = self.0.events.borrow_mut();
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
events.extend(
self.0
.event_loop_proxy
.take()
.then_some(Event::UserWakeUp)
.map(EventWrapper::from),
);
events.extend(self.0.event_loop_proxy.take().then_some(Event::UserWakeUp));
events.pop_front()
};
@@ -713,7 +720,7 @@ impl Shared {
}
fn handle_loop_destroyed(&self) {
todo!("drop the application handler");
self.handle_event(Event::LoopExiting);
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
*self.0.page_transition_event_handle.borrow_mut() = None;
*self.0.on_mouse_move.borrow_mut() = None;
@@ -845,13 +852,16 @@ impl WeakShared {
}
}
pub(crate) enum EventWrapper {
Event(Event),
#[allow(clippy::enum_variant_names)]
pub(crate) enum Event {
NewEvents(StartCause),
WindowEvent { window_id: WindowId, event: WindowEvent },
ScaleChange { canvas: Weak<backend::Canvas>, size: PhysicalSize<u32>, scale: f64 },
}
impl From<Event> for EventWrapper {
fn from(value: Event) -> Self {
Self::Event(value)
}
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
Suspended,
CreateSurfaces,
Resumed,
AboutToWait,
LoopExiting,
UserWakeUp,
}

View File

@@ -6,12 +6,13 @@ use std::sync::Arc;
use web_sys::Element;
use super::super::lock;
use super::super::monitor::MonitorPermissionFuture;
use super::super::{lock, KeyEventExtra};
use super::runner::EventWrapper;
use super::runner::Event;
use super::{backend, runner};
use crate::application::ApplicationHandler;
use crate::error::{NotSupportedError, RequestError};
use crate::event::{ElementState, Event, KeyEvent, TouchPhase, WindowEvent};
use crate::event::{ElementState, KeyEvent, TouchPhase, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
@@ -54,13 +55,9 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub(crate) fn run(
&self,
event_handler: Box<runner::EventHandler>,
event_loop_recreation: bool,
) {
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
self.runner.event_loop_recreation(event_loop_recreation);
self.runner.start(event_handler);
self.runner.start(app, self.clone());
}
pub fn generate_id(&self) -> WindowId {
@@ -143,12 +140,13 @@ impl ActiveEventLoop {
device_id: None,
event: KeyEvent {
physical_key,
logical_key,
text,
logical_key: logical_key.clone(),
text: text.clone(),
location,
state: ElementState::Pressed,
repeat,
platform_specific: KeyEventExtra,
text_with_all_modifiers: text,
key_without_modifiers: logical_key,
},
is_synthetic: false,
},
@@ -177,12 +175,13 @@ impl ActiveEventLoop {
device_id: None,
event: KeyEvent {
physical_key,
logical_key,
text,
logical_key: logical_key.clone(),
text: text.clone(),
location,
state: ElementState::Released,
repeat,
platform_specific: KeyEventExtra,
text_with_all_modifiers: text,
key_without_modifiers: logical_key,
},
is_synthetic: false,
},
@@ -396,7 +395,7 @@ impl ActiveEventLoop {
let canvas = canvas_clone.clone();
move |size, scale| {
runner.send_event(EventWrapper::ScaleChange {
runner.send_event(Event::ScaleChange {
canvas: Rc::downgrade(&canvas),
size,
scale,

View File

@@ -2,9 +2,6 @@ use smol_str::SmolStr;
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(crate) struct KeyEventExtra;
impl Key {
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
Key::Named(match kav {

View File

@@ -40,10 +40,8 @@ pub(crate) use cursor::{
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::keyboard::KeyEventExtra;
pub(crate) use self::monitor::{
HasMonitorPermissionFuture, MonitorHandle, MonitorPermissionFuture, OrientationLockFuture,
VideoModeHandle,
};
use self::web_sys as backend;
pub use self::window::{PlatformSpecificWindowAttributes, Window};

View File

@@ -3,9 +3,8 @@ use std::cmp::Ordering;
use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::hash::{Hash, Hasher};
use std::iter::{self, Once};
use std::mem;
use std::num::{NonZeroU16, NonZeroU32};
use std::num::NonZeroU16;
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::rc::{Rc, Weak};
@@ -29,7 +28,7 @@ use super::main_thread::MainThreadMarker;
use super::r#async::{Dispatcher, Notified, Notifier};
use super::web_sys::{Engine, EventListenerHandle};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
use crate::platform::web::{
MonitorPermissionError, Orientation, OrientationData, OrientationLock, OrientationLockError,
};
@@ -59,12 +58,16 @@ impl MonitorHandle {
self.inner.queue(|inner| inner.name())
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
Some(VideoModeHandle(self.clone()))
pub fn current_video_mode(&self) -> Option<VideoMode> {
Some(VideoMode {
size: self.inner.queue(|inner| inner.size()),
bit_depth: self.inner.queue(|inner| inner.bit_depth()),
refresh_rate_millihertz: None,
})
}
pub fn video_modes(&self) -> Once<VideoModeHandle> {
iter::once(VideoModeHandle(self.clone()))
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.current_video_mode().into_iter()
}
pub fn orientation(&self) -> OrientationData {
@@ -252,35 +255,6 @@ impl OrientationLockError {
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct VideoModeHandle(MonitorHandle);
impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> {
self.0.inner.queue(|inner| inner.size())
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.0.inner.queue(|inner| inner.bit_depth())
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
None
}
pub fn monitor(&self) -> MonitorHandle {
self.0.clone()
}
}
impl Debug for VideoModeHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let (size, bit_depth) = self.0.inner.queue(|this| (this.size(), this.bit_depth()));
f.debug_struct("MonitorHandle").field("size", &size).field("bit_depth", &bit_depth).finish()
}
}
struct Inner {
window: WindowExt,
engine: Option<Engine>,

View File

@@ -1,7 +1,6 @@
{
"devDependencies": {
"@eslint/js": "^9",
"@types/eslint__js": "^8",
"eslint": "^9",
"typescript": "^5",
"typescript-eslint": "^8"

View File

@@ -12,6 +12,7 @@ use web_sys::{
};
use super::super::cursor::CursorHandler;
use super::super::event_loop::runner;
use super::super::main_thread::MainThreadMarker;
use super::animation_frame::AnimationFrameHandler;
use super::event_handle::EventListenerHandle;
@@ -23,7 +24,7 @@ use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::RequestError;
use crate::event::{
ButtonSource, DeviceId, ElementState, MouseScrollDelta, PointerKind, PointerSource,
SurfaceSizeWriter,
SurfaceSizeWriter, WindowEvent,
};
use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey};
use crate::platform_impl::Fullscreen;
@@ -487,7 +488,7 @@ impl Canvas {
pub(crate) fn handle_scale_change(
&self,
runner: &super::super::event_loop::runner::Shared,
event_handler: impl FnOnce(crate::event::Event),
event_handler: impl FnOnce(WindowId, WindowEvent),
current_size: PhysicalSize<u32>,
scale: f64,
) {
@@ -495,12 +496,9 @@ impl Canvas {
self.set_current_size(current_size);
let new_size = {
let new_size = Arc::new(Mutex::new(current_size));
event_handler(crate::event::Event::WindowEvent {
window_id: self.id,
event: crate::event::WindowEvent::ScaleFactorChanged {
scale_factor: scale,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_size)),
},
event_handler(self.id, WindowEvent::ScaleFactorChanged {
scale_factor: scale,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_size)),
});
let new_size = *new_size.lock().unwrap();
@@ -523,7 +521,7 @@ impl Canvas {
} else if self.old_size() != new_size {
// Then we at least send a resized event.
self.set_old_size(new_size);
runner.send_event(crate::event::Event::WindowEvent {
runner.send_event(runner::Event::WindowEvent {
window_id: self.id,
event: crate::event::WindowEvent::SurfaceResized(new_size),
})

View File

@@ -55,7 +55,7 @@ pub(crate) fn request_fullscreen(
let canvas: &RequestFullscreen = canvas.unchecked_ref();
match fullscreen {
Fullscreen::Exclusive(_) => error!("Exclusive full screen mode is not supported"),
Fullscreen::Exclusive(..) => error!("Exclusive full screen mode is not supported"),
Fullscreen::Borderless(Some(monitor)) => {
if !monitor::has_screen_details_support(window) {
error!(

View File

@@ -133,7 +133,7 @@ fn should_apps_use_dark_mode() -> bool {
let module = LoadLibraryA("uxtheme.dll\0".as_ptr());
if module == 0 {
if module.is_null() {
return None;
}

View File

@@ -3,11 +3,15 @@
use std::ffi::c_void;
use windows_sys::core::{IUnknown, GUID, HRESULT};
use windows_sys::core::{GUID, HRESULT};
use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL};
use windows_sys::Win32::System::Com::{
IAdviseSink, IDataObject, IEnumFORMATETC, IEnumSTATDATA, FORMATETC, STGMEDIUM,
};
use windows_sys::Win32::System::Com::{FORMATETC, STGMEDIUM};
pub type IUnknown = *mut c_void;
pub type IAdviseSink = *mut c_void;
pub type IDataObject = *mut c_void;
pub type IEnumFORMATETC = *mut c_void;
pub type IEnumSTATDATA = *mut c_void;
#[repr(C)]
pub struct IUnknownVtbl {
@@ -72,13 +76,13 @@ pub struct IDropTargetVtbl {
This: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: u32,
pt: *const POINTL,
pt: POINTL,
pdwEffect: *mut u32,
) -> HRESULT,
pub DragOver: unsafe extern "system" fn(
This: *mut IDropTarget,
grfKeyState: u32,
pt: *const POINTL,
pt: POINTL,
pdwEffect: *mut u32,
) -> HRESULT,
pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT,
@@ -86,7 +90,7 @@ pub struct IDropTargetVtbl {
This: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: u32,
pt: *const POINTL,
pt: POINTL,
pdwEffect: *mut u32,
) -> HRESULT,
}

View File

@@ -73,7 +73,7 @@ pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
let hdc = unsafe { GetDC(hwnd) };
if hdc == 0 {
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
}
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
@@ -85,7 +85,7 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) };
if monitor == 0 {
if monitor.is_null() {
return BASE_DPI;
}

View File

@@ -5,27 +5,28 @@ use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use tracing::debug;
use windows_sys::core::{IUnknown, GUID, HRESULT};
use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINTL, S_OK};
use windows_sys::Win32::System::Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL};
use windows_sys::core::{GUID, HRESULT};
use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINT, POINTL, S_OK};
use windows_sys::Win32::Graphics::Gdi::ScreenToClient;
use windows_sys::Win32::System::Com::{DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL};
use windows_sys::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE};
use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP};
use crate::event::Event;
use crate::dpi::PhysicalPosition;
use crate::event::WindowEvent;
use crate::platform_impl::platform::definitions::{
IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl,
IDataObject, IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknown, IUnknownVtbl,
};
use crate::window::WindowId;
#[repr(C)]
pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
send_event: Box<dyn Fn(Event)>,
send_event: Box<dyn Fn(WindowEvent)>,
cursor_effect: u32,
hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any
* `HoveredFileCancelled` emitted */
valid: bool, /* If the currently hovered item is not valid there must not be any
* `DragLeft` emitted */
}
pub struct FileDropHandler {
@@ -34,14 +35,14 @@ pub struct FileDropHandler {
#[allow(non_snake_case)]
impl FileDropHandler {
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(Event)>) -> FileDropHandler {
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(WindowEvent)>) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl },
refcount: AtomicUsize::new(1),
window,
send_event,
cursor_effect: DROPEFFECT_NONE,
hovered_is_valid: false,
valid: false,
});
FileDropHandler { data: Box::into_raw(data) }
}
@@ -77,22 +78,23 @@ impl FileDropHandler {
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: u32,
_pt: *const POINTL,
pt: POINTL,
pdwEffect: *mut u32,
) -> HRESULT {
use crate::event::WindowEvent::HoveredFile;
let drop_handler = unsafe { Self::from_interface(this) };
let hdrop = unsafe {
Self::iterate_filenames(pDataObj, |filename| {
drop_handler.send_event(Event::WindowEvent {
window_id: WindowId::from_raw(drop_handler.window as usize),
event: HoveredFile(filename),
});
})
};
drop_handler.hovered_is_valid = hdrop.is_some();
let mut pt = POINT { x: pt.x, y: pt.y };
unsafe {
ScreenToClient(drop_handler.window, &mut pt);
}
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
let mut paths = Vec::new();
let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) };
drop_handler.valid = hdrop.is_some();
if drop_handler.valid {
(drop_handler.send_event)(WindowEvent::DragEntered { paths, position });
}
drop_handler.cursor_effect =
if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
if drop_handler.valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
unsafe {
*pdwEffect = drop_handler.cursor_effect;
}
@@ -103,10 +105,18 @@ impl FileDropHandler {
pub unsafe extern "system" fn DragOver(
this: *mut IDropTarget,
_grfKeyState: u32,
_pt: *const POINTL,
pt: POINTL,
pdwEffect: *mut u32,
) -> HRESULT {
let drop_handler = unsafe { Self::from_interface(this) };
if drop_handler.valid {
let mut pt = POINT { x: pt.x, y: pt.y };
unsafe {
ScreenToClient(drop_handler.window, &mut pt);
}
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
(drop_handler.send_event)(WindowEvent::DragMoved { position });
}
unsafe {
*pdwEffect = drop_handler.cursor_effect;
}
@@ -115,13 +125,9 @@ impl FileDropHandler {
}
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
use crate::event::WindowEvent::HoveredFileCancelled;
let drop_handler = unsafe { Self::from_interface(this) };
if drop_handler.hovered_is_valid {
drop_handler.send_event(Event::WindowEvent {
window_id: WindowId::from_raw(drop_handler.window as usize),
event: HoveredFileCancelled,
});
if drop_handler.valid {
(drop_handler.send_event)(WindowEvent::DragLeft { position: None });
}
S_OK
@@ -131,21 +137,27 @@ impl FileDropHandler {
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: u32,
_pt: *const POINTL,
_pdwEffect: *mut u32,
pt: POINTL,
pdwEffect: *mut u32,
) -> HRESULT {
use crate::event::WindowEvent::DroppedFile;
let drop_handler = unsafe { Self::from_interface(this) };
let hdrop = unsafe {
Self::iterate_filenames(pDataObj, |filename| {
drop_handler.send_event(Event::WindowEvent {
window_id: WindowId::from_raw(drop_handler.window as usize),
event: DroppedFile(filename),
});
})
};
if let Some(hdrop) = hdrop {
unsafe { DragFinish(hdrop) };
if drop_handler.valid {
let mut pt = POINT { x: pt.x, y: pt.y };
unsafe {
ScreenToClient(drop_handler.window, &mut pt);
}
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
let mut paths = Vec::new();
let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) };
(drop_handler.send_event)(WindowEvent::DragDropped { paths, position });
if let Some(hdrop) = hdrop {
unsafe {
DragFinish(hdrop);
}
}
}
unsafe {
*pdwEffect = drop_handler.cursor_effect;
}
S_OK
@@ -155,9 +167,9 @@ impl FileDropHandler {
unsafe { &mut *(this as *mut _) }
}
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<HDROP>
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, mut callback: F) -> Option<HDROP>
where
F: Fn(PathBuf),
F: FnMut(PathBuf),
{
let drop_format = FORMATETC {
cfFormat: CF_HDROP,
@@ -207,12 +219,6 @@ impl FileDropHandler {
}
}
impl FileDropHandlerData {
fn send_event(&self, event: Event) {
(self.send_event)(event);
}
}
impl Drop for FileDropHandler {
fn drop(&mut self) {
unsafe {

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,27 @@
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{mem, panic};
use windows_sys::Win32::Foundation::HWND;
use super::ControlFlow;
use super::{ActiveEventLoop, ControlFlow, EventLoopThreadExecutor};
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event::{DeviceEvent, DeviceId, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
use crate::platform_impl::platform::event_loop::{WindowData, GWL_USERDATA};
use crate::platform_impl::platform::get_window_long;
use crate::window::WindowId;
type EventHandler = Cell<Option<Box<dyn FnMut(Event)>>>;
type EventHandler = Cell<Option<&'static mut (dyn ApplicationHandler + 'static)>>;
pub(crate) struct EventLoopRunner {
pub(super) thread_id: u32,
// The event loop's win32 handles
pub(super) thread_msg_target: HWND,
@@ -29,8 +34,8 @@ pub(crate) struct EventLoopRunner {
exit: Cell<Option<i32>>,
runner_state: Cell<RunnerState>,
last_events_cleared: Cell<Instant>,
event_handler: EventHandler,
event_buffer: RefCell<VecDeque<BufferedEvent>>,
event_handler: Rc<EventHandler>,
event_buffer: RefCell<VecDeque<Event>>,
panic_error: Cell<Option<PanicError>>,
}
@@ -51,14 +56,20 @@ pub(crate) enum RunnerState {
Destroyed,
}
enum BufferedEvent {
Event(Event),
ScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
#[derive(Debug, Clone)]
pub(crate) enum Event {
Device { device_id: DeviceId, event: DeviceEvent },
Window { window_id: WindowId, event: WindowEvent },
BufferedScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
// FIXME(madsmtm): Coalesce these into a flag (or similar) instead of handling them as events.
// https://github.com/rust-windowing/winit/pull/3687
WakeUp,
}
impl EventLoopRunner {
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner {
EventLoopRunner {
pub(crate) fn new(thread_id: u32, thread_msg_target: HWND) -> Self {
Self {
thread_id,
thread_msg_target,
interrupt_msg_dispatch: Cell::new(false),
runner_state: Cell::new(RunnerState::Uninitialized),
@@ -66,40 +77,50 @@ impl EventLoopRunner {
exit: Cell::new(None),
panic_error: Cell::new(None),
last_events_cleared: Cell::new(Instant::now()),
event_handler: Cell::new(None),
event_handler: Rc::new(Cell::new(None)),
event_buffer: RefCell::new(VecDeque::new()),
}
}
/// Associate the application's event handler with the runner
/// Associate the application's event handler with the runner.
///
/// # Safety
/// This is ignoring the lifetime of the application handler (which may not
/// outlive the EventLoopRunner) and can lead to undefined behaviour if
/// the handler is not cleared before the end of real lifetime.
///
/// All public APIs that take an event handler (`run`, `run_on_demand`,
/// `pump_events`) _must_ pair a call to `set_event_handler` with
/// a call to `clear_event_handler` before returning to avoid
/// undefined behaviour.
pub(crate) unsafe fn set_event_handler<F>(&self, f: F)
where
F: FnMut(Event),
{
// Erase closure lifetime.
// SAFETY: Caller upholds that the lifetime of the closure is upheld.
let f =
unsafe { mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event)>>(Box::new(f)) };
let old_event_handler = self.event_handler.replace(Some(f));
assert!(old_event_handler.is_none());
}
/// The returned type must not be leaked (as that would allow the application to be associated
/// with the runner for too long).
pub(crate) unsafe fn set_app<'app>(
&self,
app: &'app mut (dyn ApplicationHandler + 'app),
) -> impl Drop + 'app {
// Erase app lifetime, to allow storing on the event loop runner.
//
// SAFETY: Caller upholds that the lifetime of the closure is upheld, by not dropping the
// return type which resets it.
let f = unsafe {
mem::transmute::<
&'app mut (dyn ApplicationHandler + 'app),
&'static mut (dyn ApplicationHandler + 'static),
>(app)
};
pub(crate) fn clear_event_handler(&self) {
self.event_handler.set(None);
let old_event_handler = self.event_handler.replace(Some(f));
assert!(old_event_handler.is_none());
struct Resetter(Rc<EventHandler>);
impl Drop for Resetter {
fn drop(&mut self) {
self.0.set(None);
}
}
Resetter(self.event_handler.clone())
}
pub(crate) fn reset_runner(&self) {
let EventLoopRunner {
let Self {
thread_id: _,
thread_msg_target: _,
interrupt_msg_dispatch,
runner_state,
@@ -188,21 +209,26 @@ impl EventLoopRunner {
None
}
}
#[inline(always)]
pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor {
EventLoopThreadExecutor { thread_id: self.thread_id, target_window: self.thread_msg_target }
}
}
/// Event dispatch functions.
impl EventLoopRunner {
pub(crate) fn prepare_wait(&self) {
pub(crate) fn prepare_wait(self: &Rc<Self>) {
self.move_state_to(RunnerState::Idle);
}
pub(crate) fn wakeup(&self) {
pub(crate) fn wakeup(self: &Rc<Self>) {
self.move_state_to(RunnerState::HandlingMainEvents);
}
pub(crate) fn send_event(&self, event: Event) {
if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event {
self.call_event_handler(event);
pub(crate) fn send_event(self: &Rc<Self>, event: Event) {
if let Event::Window { event: WindowEvent::RedrawRequested, .. } = event {
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
// As a rule, to ensure that `pump_events` can't block an external event loop
// for too long, we always guarantee that `pump_events` will return control to
// the external loop asap after a `RedrawRequested` event is dispatched.
@@ -210,31 +236,34 @@ impl EventLoopRunner {
} else if self.should_buffer() {
// If the runner is already borrowed, we're in the middle of an event loop invocation.
// Add the event to a buffer to be processed later.
self.event_buffer.borrow_mut().push_back(BufferedEvent::from_event(event))
self.event_buffer.borrow_mut().push_back(event.buffer_scale_factor())
} else {
self.call_event_handler(event);
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
self.dispatch_buffered_events();
}
}
pub(crate) fn loop_destroyed(&self) {
pub(crate) fn loop_destroyed(self: &Rc<Self>) {
self.move_state_to(RunnerState::Destroyed);
}
fn call_event_handler(&self, event: Event) {
fn call_event_handler(
self: &Rc<Self>,
closure: impl FnOnce(&mut dyn ApplicationHandler, &dyn RootActiveEventLoop),
) {
self.catch_unwind(|| {
let mut event_handler = self.event_handler.take().expect(
let event_handler = self.event_handler.take().expect(
"either event handler is re-entrant (likely), or no event handler is registered \
(very unlikely)",
);
event_handler(event);
closure(event_handler, ActiveEventLoop::from_ref(self));
assert!(self.event_handler.replace(Some(event_handler)).is_none());
});
}
fn dispatch_buffered_events(&self) {
fn dispatch_buffered_events(self: &Rc<Self>) {
loop {
// We do this instead of using a `while let` loop because if we use a `while let`
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
@@ -242,14 +271,17 @@ impl EventLoopRunner {
// `process_event` will fail.
let buffered_event_opt = self.event_buffer.borrow_mut().pop_front();
match buffered_event_opt {
Some(e) => e.dispatch_event(|e| self.call_event_handler(e)),
Some(e) => {
self.call_event_handler(|app, event_loop| e.dispatch_event(app, event_loop))
},
None => break,
}
}
}
/// Dispatch control flow events (`new_events`, `about_to_wait`) as necessary to bring the
/// internal `RunnerState` to the new runner state.
/// Dispatch control flow events (`NewEvents`, `AboutToWait`, and
/// `LoopExiting`) as necessary to bring the internal `RunnerState` to the
/// new runner state.
///
/// The state transitions are defined as follows:
///
@@ -271,7 +303,7 @@ impl EventLoopRunner {
/// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the
/// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and
/// `new_runner_state == Idle`), the intermediate state transitions will still be executed.
fn move_state_to(&self, new_runner_state: RunnerState) {
fn move_state_to(self: &Rc<Self>, new_runner_state: RunnerState) {
use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized};
match (self.runner_state.replace(new_runner_state), new_runner_state) {
@@ -286,13 +318,14 @@ impl EventLoopRunner {
},
(Uninitialized, Idle) => {
self.call_new_events(true);
self.call_event_handler(Event::AboutToWait);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.last_events_cleared.set(Instant::now());
},
(Uninitialized, Destroyed) => {
self.call_new_events(true);
self.call_event_handler(Event::AboutToWait);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.last_events_cleared.set(Instant::now());
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
},
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
@@ -300,23 +333,26 @@ impl EventLoopRunner {
(Idle, HandlingMainEvents) => {
self.call_new_events(false);
},
(Idle, Destroyed) => {},
(Idle, Destroyed) => {
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
},
(HandlingMainEvents, Idle) => {
// This is always the last event we dispatch before waiting for new events
self.call_event_handler(Event::AboutToWait);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.last_events_cleared.set(Instant::now());
},
(HandlingMainEvents, Destroyed) => {
self.call_event_handler(Event::AboutToWait);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.last_events_cleared.set(Instant::now());
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
},
(Destroyed, _) => panic!("cannot move state from Destroyed"),
}
}
fn call_new_events(&self, init: bool) {
fn call_new_events(self: &Rc<Self>, init: bool) {
let start_cause = match (init, self.control_flow(), self.exit.get()) {
(true, ..) => StartCause::Init,
(false, ControlFlow::Poll, None) => StartCause::Poll,
@@ -338,45 +374,55 @@ impl EventLoopRunner {
}
},
};
self.call_event_handler(Event::NewEvents(start_cause));
self.call_event_handler(|app, event_loop| app.new_events(event_loop, start_cause));
// NB: For consistency all platforms must call `can_create_surfaces` even though Windows
// applications don't themselves have a formal surface destroy/create lifecycle.
if init {
self.call_event_handler(Event::CreateSurfaces);
self.call_event_handler(|app, event_loop| app.can_create_surfaces(event_loop));
}
self.dispatch_buffered_events();
}
}
impl BufferedEvent {
pub fn from_event(event: Event) -> BufferedEvent {
match event {
Event::WindowEvent {
impl Event {
/// Mark ScaleFactorChanged as being buffered (which forces us to re-handle when the user set a
/// new size).
pub fn buffer_scale_factor(self) -> Self {
match self {
Self::Window {
event: WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer },
window_id,
} => BufferedEvent::ScaleFactorChanged(
} => Event::BufferedScaleFactorChanged(
window_id.into_raw() as HWND,
scale_factor,
*surface_size_writer.new_surface_size.upgrade().unwrap().lock().unwrap(),
),
event => BufferedEvent::Event(event),
event => event,
}
}
pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) {
pub fn dispatch_event(
self,
app: &mut dyn ApplicationHandler,
event_loop: &dyn RootActiveEventLoop,
) {
match self {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window, scale_factor, new_surface_size) => {
Self::Window { window_id, event } => app.window_event(event_loop, window_id, event),
Self::Device { device_id, event } => {
app.device_event(event_loop, Some(device_id), event)
},
Self::BufferedScaleFactorChanged(window, scale_factor, new_surface_size) => {
let user_new_surface_size = Arc::new(Mutex::new(new_surface_size));
dispatch(Event::WindowEvent {
window_id: WindowId::from_raw(window as usize),
event: WindowEvent::ScaleFactorChanged {
app.window_event(
event_loop,
WindowId::from_raw(window as usize),
WindowEvent::ScaleFactorChanged {
scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(
&user_new_surface_size,
)),
},
});
);
let surface_size = *user_new_surface_size.lock().unwrap();
drop(user_new_surface_size);
@@ -390,6 +436,7 @@ impl BufferedEvent {
window_flags.set_size(window, surface_size);
}
},
Self::WakeUp => app.proxy_wake_up(event_loop),
}
}
}

View File

@@ -1,7 +1,7 @@
use std::ffi::c_void;
use std::path::Path;
use std::sync::Arc;
use std::{fmt, io, mem};
use std::{fmt, io, mem, ptr};
use cursor_icon::CursorIcon;
use windows_sys::core::PCWSTR;
@@ -40,7 +40,7 @@ impl RgbaIcon {
assert_eq!(and_mask.len(), pixel_count);
let handle = unsafe {
CreateIcon(
0,
ptr::null_mut(),
self.width as i32,
self.height as i32,
1,
@@ -49,7 +49,7 @@ impl RgbaIcon {
rgba.as_ptr(),
)
};
if handle != 0 {
if !handle.is_null() {
Ok(WinIcon::from_handle(handle))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
@@ -68,6 +68,9 @@ struct RaiiIcon {
handle: HICON,
}
unsafe impl Send for RaiiIcon {}
unsafe impl Sync for RaiiIcon {}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct WinIcon {
inner: Arc<RaiiIcon>,
@@ -91,7 +94,7 @@ impl WinIcon {
let handle = unsafe {
LoadImageW(
0,
ptr::null_mut(),
wide_path.as_ptr(),
IMAGE_ICON,
width,
@@ -99,7 +102,7 @@ impl WinIcon {
LR_DEFAULTSIZE | LR_LOADFROMFILE,
)
};
if handle != 0 {
if !handle.is_null() {
Ok(WinIcon::from_handle(handle as HICON))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
@@ -109,20 +112,35 @@ impl WinIcon {
pub fn from_resource(
resource_id: u16,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
Self::from_resource_ptr(resource_id as PCWSTR, size)
}
pub fn from_resource_name(
resource_name: &str,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let wide_name = util::encode_wide(resource_name);
Self::from_resource_ptr(wide_name.as_ptr(), size)
}
fn from_resource_ptr(
resource: PCWSTR,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
let handle = unsafe {
LoadImageW(
util::get_instance_handle(),
resource_id as PCWSTR,
resource,
IMAGE_ICON,
width,
height,
LR_DEFAULTSIZE,
)
};
if handle != 0 {
if !handle.is_null() {
Ok(WinIcon::from_handle(handle as HICON))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
@@ -136,7 +154,7 @@ impl WinIcon {
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
unsafe {
SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle());
SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle() as isize);
}
}
@@ -187,13 +205,13 @@ impl WinCursor {
let h = image.height as i32;
unsafe {
let hdc_screen = GetDC(0);
if hdc_screen == 0 {
let hdc_screen = GetDC(ptr::null_mut());
if hdc_screen.is_null() {
return Err(os_error!(io::Error::last_os_error()).into());
}
let hbm_color = CreateCompatibleBitmap(hdc_screen, w, h);
ReleaseDC(0, hdc_screen);
if hbm_color == 0 {
ReleaseDC(ptr::null_mut(), hdc_screen);
if hbm_color.is_null() {
return Err(os_error!(io::Error::last_os_error()).into());
}
if SetBitmapBits(hbm_color, bgra.len() as u32, bgra.as_ptr() as *const c_void) == 0 {
@@ -204,7 +222,7 @@ impl WinCursor {
// Mask created according to https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap#parameters
let mask_bits: Vec<u8> = vec![0xff; ((((w + 15) >> 4) << 1) * h) as usize];
let hbm_mask = CreateBitmap(w, h, 1, 1, mask_bits.as_ptr() as *const _);
if hbm_mask == 0 {
if hbm_mask.is_null() {
DeleteObject(hbm_color);
return Err(os_error!(io::Error::last_os_error()).into());
}
@@ -220,7 +238,7 @@ impl WinCursor {
let handle = CreateIconIndirect(&icon_info as *const _);
DeleteObject(hbm_color);
DeleteObject(hbm_mask);
if handle == 0 {
if handle.is_null() {
return Err(os_error!(io::Error::last_os_error()).into());
}
@@ -234,6 +252,9 @@ pub struct RaiiCursor {
handle: HCURSOR,
}
unsafe impl Send for RaiiCursor {}
unsafe impl Sync for RaiiCursor {}
impl Drop for RaiiCursor {
fn drop(&mut self) {
unsafe { DestroyCursor(self.handle) };

View File

@@ -3,12 +3,11 @@ use std::os::windows::prelude::OsStringExt;
use std::ptr::null_mut;
use windows_sys::Win32::Foundation::{POINT, RECT};
use windows_sys::Win32::Globalization::HIMC;
use windows_sys::Win32::UI::Input::Ime::{
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM, GCS_COMPATTR,
GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, HIMC, IACE_CHILDREN, IACE_DEFAULT,
};
use windows_sys::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED};
@@ -138,9 +137,9 @@ impl ImeContext {
}
if allowed {
unsafe { ImmAssociateContextEx(hwnd, 0, IACE_DEFAULT) };
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_DEFAULT) };
} else {
unsafe { ImmAssociateContextEx(hwnd, 0, IACE_CHILDREN) };
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_CHILDREN) };
}
}

View File

@@ -12,7 +12,7 @@ use unicode_segmentation::UnicodeSegmentation;
use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM};
use windows_sys::Win32::System::SystemServices::LANG_KOREAN;
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW,
GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW, HKL,
MAPVK_VK_TO_VSC_EX, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR,
VK_CONTROL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT,
VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, VK_NUMLOCK,
@@ -20,7 +20,6 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT,
VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP,
};
use windows_sys::Win32::UI::TextServices::HKL;
use windows_sys::Win32::UI::WindowsAndMessaging::{
PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, WM_KEYLAST,
WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
@@ -32,7 +31,7 @@ use crate::platform_impl::platform::event_loop::ProcResult;
use crate::platform_impl::platform::keyboard_layout::{
Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE,
};
use crate::platform_impl::platform::{loword, primarylangid, KeyEventExtra};
use crate::platform_impl::platform::{loword, primarylangid};
pub type ExScancode = u16;
@@ -452,7 +451,7 @@ impl KeyEventBuilder {
let mut event = event_info.finalize();
event.logical_key = logical_key;
event.platform_specific.text_with_all_modifiers = text;
event.text_with_all_modifiers = text;
Some(MessageAsKeyEvent { event, is_synthetic: true })
}
}
@@ -630,10 +629,8 @@ impl PartialKeyEventInfo {
location: self.location,
state: self.key_state,
repeat: self.is_repeat,
platform_specific: KeyEventExtra {
text_with_all_modifiers: char_with_all_modifiers,
key_without_modifiers: self.key_without_modifiers,
},
text_with_all_modifiers: char_with_all_modifiers,
key_without_modifiers: self.key_without_modifiers,
}
}
}

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