Compare commits

...

167 Commits

Author SHA1 Message Date
dependabot[bot]
ba6da05252 chore: bump the github-actions group across 1 directory with 3 updates
Bumps the github-actions group with 3 updates in the / directory: [actions/cache](https://github.com/actions/cache), [actions/checkout](https://github.com/actions/checkout) and [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact).


Updates `actions/cache` from 5 to 6
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `actions/upload-pages-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/upload-pages-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 18:53:04 +00:00
Vitaly Kravchenko
27e17e3f29 Fix AppKit live resize redraw timing 2026-06-24 17:03:24 +02:00
Simon Hausmann
56fde0791b Fix CI
Pin wayland-protocols to the previous version that still compiles with
our MSRV.
2026-06-24 16:58:57 +02:00
William-Selna
4ef144b519 macOS: make Ime::Preedit cursor range surrogate-safe in setMarkedText
`setMarkedText:selectedRange:replacementRange:` converted the IME's UTF-16
`selectedRange` into UTF-8 byte offsets by taking `substringToIndex:` prefixes
and measuring them with `NSString::len()`
(`lengthOfBytesUsingEncoding:NSUTF8StringEncoding`). When an index falls inside
a surrogate pair, the prefix ends in a lone high surrogate, which UTF-8 cannot
represent, so `lengthOfBytesUsingEncoding:` returns 0 for the entire prefix and
the offset silently collapses to 0.
2026-06-20 01:34:43 +09:00
Mads Marquart
7fe8c206ae chore: avoid unsafe in examples
By using `OwnedDisplayHandle`, which uses reference-counting, instead of
`DisplayHandle<'static>` (where the user has to guarantee that the
Application doesn't outlive the event loop).
2026-06-15 03:36:22 +09:00
Sergey Kikevich
fb78ecbb03 windows: fix freeze on keyboard layout switch
Any winit app on Windows freezes when the keyboard layout is switched by
tools such as Punto Switcher. A minidump of the frozen process shows the
main thread blocked in MsgWaitForMultipleObjectsEx (winit's event-loop
wait), re-entered during message dispatch through the switcher's injected
global hook (pshook64.dll, via CallNextHookEx) — a re-entrant Win32
message-loop wait, not a mutex deadlock (no LAYOUT_CACHE frames present).

Handle WM_INPUTLANGCHANGE to refresh the cached keyboard layout, then
defer to DefWindowProc so the message still propagates to first-level
child windows as the Win32 docs require. The cache refresh is the minimal
change that stops the freeze (verified on Windows 11 by isolation
variants); update_modifiers and swallowing the message are not needed.
2026-06-15 03:29:35 +09:00
Martin Marmsoler
850d5f59a7 winit-wayland: add pointer gesture hold 2026-06-13 02:36:23 +09:00
Martin Marmsoler
81b2729765 winit-x11: fix clippy lint 2026-06-05 02:25:58 +09:00
Kirill Chibisov
c4afadbfab winit-wayland: use ext-background-effect if available
A more modern protocol compared to the KDE one.
2026-04-04 23:59:33 +09:00
dependabot[bot]
b5252f1366 chore: bump actions/configure-pages (#4550) 2026-03-28 07:44:01 +01:00
Mads Marquart
f93a223da9 Clean up cargo-deny
Remove the matrix in the CI action; EmbarkStudios/cargo-deny#324 hasn't been resolved yet, but since we've split Winit out into multiple crates, there's still value in minimizing dependency conflicts even if they won't be hit by users, since e.g. Rust-Analyzer will by default check the entire workspace (and thus download and compile duplicate dependencies).
2026-03-28 10:28:31 +09:00
dependabot[bot]
d75a0dada0 chore: bump actions/deploy-pages from 4 to 5 (#4545) 2026-03-26 06:32:41 +01:00
Mads Marquart
9bf46af6f7 AppKit: Use fn_addr_eq now that it's in MSRV (#4532) 2026-03-26 05:50:16 +01:00
Mads Marquart
464c37a94e Remove leftover apple/appkit/mod.rs (#4533) 2026-03-26 05:49:49 +01:00
Mads Marquart
557d285170 Remove symlinking between winit-appkit and winit-uikit (#4530) 2026-03-26 05:49:12 +01:00
Mads Marquart
ba856e127a Fix CI (#4546)
* Fix cargo-deny erroring on new jni-sys version
* Fix unicode-segmentation bumping MSRV
* Fix cargo-deny finding new script in android-activity
2026-03-26 05:26:54 +01:00
Charlie Tonneslan
0ffd303db6 fix(typo): dependant -> dependent (#4540) 2026-03-24 15:54:31 +01:00
Mads Marquart
5a74bf0aab Android: Add further scancode conversions (#4023)
Firefox' source at:
https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h

Seems to use some other form of scan code? So instead we try to map as
many of the codes available on `Keycode` that they do as possible.
2026-03-19 02:27:23 +01:00
Mads Marquart
117ec364f9 examples: Always use tracing helper module 2026-03-18 22:55:47 +01:00
Mads Marquart
9f789e56ee examples: Use tracing macros instead of println!
This allows the examples to work a bit better in WASM and on iOS.
2026-03-18 22:55:47 +01:00
Mads Marquart
91558169d2 example: Fix tracing registration in pump_events
Tracing subscribers must be set up before `EventLoop::new()`.
2026-03-18 22:55:47 +01:00
Mads Marquart
4998cb990f AppKit: Trace sendEvent: calls
Most events in AppKit go through `sendEvent:`, and they contain a lot of
information, so it's nice to surface this when debugging.

We could override `sendEvent:` in UIKit and track this in there too, but
that's much less important, since there the relevant events are fairly
narrowly scoped, see the link below, other events go through CFRunLoop.
https://developer.apple.com/documentation/uikit/uievent/eventtype
2026-03-18 22:55:47 +01:00
Mads Marquart
98692641c4 UIKit: Add tracing spans 2026-03-18 22:55:47 +01:00
Mads Marquart
a630b5333c Apple: Track spans across queued closures 2026-03-18 22:55:47 +01:00
Mads Marquart
ca7735f10b Apple: Use tracing spans instead of custom trace_scope! macro
Spans are more powerful, and can even optionally be emitted as events by
using `.with_span_events(tracing_subscriber::fmt::format::FmtSpan::*)`.
2026-03-18 22:55:47 +01:00
Mads Marquart
7adb805011 Apple: Trace CFRunLoop activities
Add two run loop observers that:
- Create a TRACE-level span when the run loop enters a new state.
- Drops the span when the run loop exits that state.

These spans attach information to events, such that e.g. resizing a view
produces messages like:
```
TRACE inside runloop{mode=NSEventTrackingRunLoopMode}:timers:
  winit_appkit::util: Triggered `drawRect:` target="winit_appkit::view"
TRACE inside runloop{mode=NSEventTrackingRunLoopMode}:timers:
  winit_appkit::util: Completed `drawRect:` target="winit_appkit::view"
```
2026-03-18 22:55:47 +01:00
Mads Marquart
a8c7d809b9 Use new macOS 15 cursors for resize icons (#4422)
These look slightly different from the old ones. Verified that we now
use the same cursor icons as those used in Safari 26.
2026-03-18 22:33:53 +01:00
RandomScientist
4d81f4aa62 internal(macOS) use NSTrackingArea instead of trackingRect (#4514) 2026-03-18 02:03:16 +01:00
Mads Marquart
4f29aed5ee Synchronize changelogs with v0.30.13 2026-03-17 23:22:47 +09:00
Mads Marquart
7864d02077 Remove indentation in fill.rs helper 2026-03-17 04:34:15 +01:00
Mads Marquart
0f2d59cbba Allow building examples using softbuffer on Android
These probably aren't runnable in the current state, but it should at
least allow us to get rid of a bunch of cfgs.
2026-03-17 04:34:15 +01:00
Mads Marquart
f0c4adc58c Update to softbuffer v0.4.8 2026-03-17 04:34:15 +01:00
junglie85
8e38112ad3 Add Send and Sync to OwnedDisplayHandle (#4509) 2026-03-17 03:54:42 +01:00
RandomScientist
6f3d763650 Document potential user expectation of ctrl+left click behavior on macOS. (#4518) 2026-03-17 03:39:12 +01:00
Ali
7a11ff8766 Add missing scancodes for orbital (#4515)
add missing keymaps
2026-03-17 03:29:27 +01:00
jgcodes2020
211ae8f6c7 winit-x11: replace XQueryKeymap with equivalent x11rb call (#4497)
And slightly optimize the first_bit helper function.
2026-03-17 03:15:48 +01:00
RandomScientist
0b6b794f01 fix(macOS) "fix" toggling IME on appkit backend (#4512)
Co-authored-by: Random-Scientist <Random-Scientist@users.noreply.github.com>
2026-03-14 02:50:13 +01:00
Marijn Suijten
5e2f421e34 fix(android): Populate KeyEvent.text via Key::to_text()
The `text` field on `KeyEvent` was hardcoded to `None` on Android,
making it impossible for custom `NativeActivity` subclasses that
show the IME to receive functional text input using *for example* the
existing `winit-egui` crate which relies on this field being set.

Use `Key::to_text()` on press events to derive `text` from
`logical_key`, matching the convention used by the Windows and macOS
backends.

Supposedly that doesn't include all kinds of special virtual unicode
keys, but at least the basics work this way.
2026-03-02 22:37:30 +09:00
Pedro Macedo
41f4265957 wayland: implement resize increments 2026-03-01 22:28:35 +09:00
Kotomine Shiki
c535968128 win32: fix ime setcontext lparam
Fixes #3893.
2026-03-01 22:08:45 +09:00
Takaranoao
85ff2fa093 fix(macOS): clamp IME selected_range to prevent substringToIndex crash
macOS native Pinyin IME can send a selected_range that exceeds the
marked text string length (e.g. index 8 for a 6-character string).
This caused an NSRangeException in substringToIndex:, crashing the
application with SIGABRT.

Clamp both location and end to the string's UTF-16 length before
calling substringToIndex.
2026-03-01 21:55:06 +09:00
voxelmagpie
44865be79d wayland: fix panic when fancy wacom tablet is connected
With some setups certain events were not handled leading to crashes,
thus add missing handles.

Fixes #4493.
2026-02-28 15:07:40 +09:00
SuchAFuriousDeath
f3fb2fe3a6 winit-x11: fix debug mode overflow panic in set_timestamp
Fixes #4484
2026-02-24 23:15:49 +09:00
Mads Marquart
fa10ca1764 Refactor usage of CFRunLoopObserver (#4349)
Added a common interface that:
- Uses closures instead of static functions. This should allow easier
  refactoring in the future.
- Returns a handle which is invalidated on `Drop`. This should avoid
  situations where the event loop has exited, but an observer is still
  called because the user spawned the application later on.
- Is properly main-thread safe.

This interface is placed in winit-common, to allow using it in both
winit-appkit and winit-uikit.
2026-02-17 16:21:18 +01:00
Mads Marquart
4fda048729 Reduce duplication in dpi module (#2148)
* Deduplicate Pixel impl for integers using a macro
* Deduplicate [Logical|Physical][Size|Position] From impls using a macro

Co-authored-by: Osspial <osspial@gmail.com>
2026-02-17 15:45:53 +01:00
Tarek Abdel Sater
983e50971d macOS: fix borderless game resetting after switching spaces (#4482) 2026-02-11 22:14:10 +01:00
DorotaC
cf9daedb18 wayland IME: Discard completed delete request
Co-authored-by: dcz <gilapfco.dcz@porcupinefactory.org>
2026-02-07 00:31:07 +09:00
isan
5218e11e55 reafactor(orbital): Reimplement Orbital support using std::fs and redox_event (#4470)
Implemented Send and Sync for EventQueue
2026-02-03 16:23:40 +01:00
John Nunley
b8f28efd04 wayland: Move hash algorithm to foldhash
At the moment, the wayland code uses ahash to perform hashing
in its various hash mas. This was done because ahash was seen as
the best default in the Rust community at the time. However, most
Rust crates (including `hashbrown`) have since moved to using
foldhash instead.

This move is done for two primary reasons:

- This reduces the number of dependencies in the tree for most GUI
  projects. As other projects use foldhash now, this removes ahash
  (as well as its five dependencies) from the tree.
- In most cases, foldhash is faster than ahash.

Signed-off-by: John Nunley <dev@notgull.net>
2026-01-29 09:15:25 +09:00
dependabot[bot]
eced28824b chore: bump taiki-e/cache-cargo-install-action (#4456) 2026-01-28 19:34:50 +01:00
Jack
b3c7635bba Add Apple Pencil support on iOS (#4449)
With force, altitude, and azimuth data handling
2026-01-28 19:24:38 +01:00
Mads Marquart
4aac778d2a chore: appease clippy (#4471) 2026-01-28 19:12:39 +01:00
Colin Finck
043c8635a9 Upgrade windows-sys dependency to 0.61 (#4464) 2026-01-20 20:49:03 +01:00
dependabot[bot]
da6220060e chore: bump the github-actions version 2025-12-21 19:44:09 +09:00
Anhad Singh
50f9b84f6b fix(winit-orbital): handle EINTR when reading from event_socket
Signed-off-by: Anhad Singh <andypython@protonmail.com>
2025-12-18 18:42:54 +09:00
itsamine27
56333064ac winit-win32: Fix ABI mismatch in INIT_MAIN_THREAD_ID
Fixes #4435.
2025-12-16 22:50:14 +09:00
Silico_Biomancer
de78ffdfed winit-x11: replace xfixes with x11rb in set_hittest
The xfixes implementation is not that reliable and rather simple to
replace, so use x11rb to implement the same functionality.

Fixes #4120.
Co-authored-by: avitran0 <holyhades64@gmail.com>
2025-11-25 11:06:39 +09:00
Mads Marquart
2d56ed1099 Fix issue template labels (#4421) 2025-11-24 13:50:34 +01:00
Diggory Hardy
7b1dd52ad1 winit-core/ime: implement Error for ImeSurroundingTextError 2025-11-20 19:18:48 +09:00
richerfu
6a46610632 feat(core): add keyboard for ohos 2025-11-17 15:56:20 +09:00
Kirill Chibisov
27233f575f Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2025-11-17 15:45:12 +09:00
Kirill Chibisov
f2ba68771f winit-core: fix set_ime_allowed always panicing 2025-11-16 16:39:15 +09:00
Mads Marquart
cc23fbf3be winit/event_loop: Add register_app and run_app_never_return
To allow users to explicitly choose the run semantics that they want.
2025-11-16 16:25:44 +09:00
Mads Marquart
c98d880a1c winit-web: return immediately from run_app on web
This avoids using JavaScript exceptions to support `EventLoop::run_app`
on the web, which is a huge hack, and doesn't work with the Exception
Handling Proposal for WebAssembly:
https://github.com/WebAssembly/exception-handling

This needs the application handler passed to `run_app` to be `'static`,
but that works better on iOS too anyhow (since you can't accidentally
forget to pass in state that then wouldn't be dropped when terminating).
2025-11-16 16:25:44 +09:00
Kirill Chibisov
165c163f01 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2025-11-16 13:53:23 +09:00
Kirill Chibisov
0a59e4b905 chore: fix clippy on X11 2025-11-16 13:07:23 +09:00
ShikiSuen
d837c88855 winit-appkit: fix tests on systems without MonitorId(1)
Use `CGMainDisplayID` instead of `1` to make tests pass on systems
without `MonitorId(1)`.
2025-11-06 14:46:28 +09:00
Dan Harris
82eab465e0 macOS: fix a crash when dragging non-file content onto window
Winit only supports text, thus we should ignore the rest
instead of crashing.
2025-11-05 13:20:01 +09:00
Diggory Hardy
a9c189a423 winit-win32: prevent inner size reported as (0,0) when minimized 2025-11-01 23:39:57 +09:00
Diggory Hardy
9d9d21cfdb winit-core: revise MouseButton type
Unify the values of `MouseButton` and thus remove `Other` variant in
limit possible buttons to 32, which was picked based on platform
capabilities, where 32 is the highest.

For the reference, SDL has identical limit.
2025-11-01 21:00:39 +09:00
Kirill Chibisov
779f52a21f chore: latest typos 2025-11-01 21:00:39 +09:00
mfluehr
7a21858d29 windows: don't confine hidden cursor 2025-11-01 13:03:11 +09:00
Kirill Chibisov
a3f7e6566a ci: fix eslint version to 9.38.0
9.39.0 has an issue with unified-signatures.

Links: https://github.com/eslint/eslint/issues/20272
2025-11-01 12:52:30 +09:00
DorotaC
f41897cfa4 examples/ime: fix crash on wayland
When pressing Ctrl+Space, KeyEvent::text_with_all_modifiers would return
\0. That would get included in the text-input text. When an input method
gets activated, the invalid string returning \0 would get sent as
surrounding text, resulting in a Wayland protocol error and crashing the
application.
2025-10-29 13:40:30 +09:00
Timon
bd6fef1d80 wayland: add prefer_csd attribute 2025-10-24 21:35:51 +09:00
Kirill Chibisov
42d256e926 ci: fix version of smol_str 2025-10-24 21:01:20 +09:00
Pavel Strakhov
03dad26c43 x11: ignore mouse scroll button release events 2025-10-24 20:07:10 +09:00
Kirill Chibisov
1e7ab0cd25 wayland: handle wl_seat v10 repeated state
Fixes #4382.
2025-10-20 00:01:36 +09:00
Kirill Chibisov
c333003514 Bump MSRV to 1.85 and edition to 2024 2025-10-20 00:01:36 +09:00
Arthur Cosentino
10f21090ce x11: fix deadlock in request_ime_update 2025-10-09 16:57:20 +09:00
Timon
5575f51483 wayland: add wayland-csd-adwaita-notitlebar feature
Addition to already present `notitle` feature.
2025-10-08 12:11:55 +09:00
Ian Douglas Scott
2ede84ab2f wayland: handle axis_value120 scroll events
This can be tested with the `application` example, looking at the events
shown for mouse wheel movement.

`wl_pointer::axis_discrete` isn't sent in version 8 or higher of
`wl_pointer`. And `sctk` doesn't convert the `value120` events, so
on compositors advertising version 8, only pixel scroll events were
being sent.

This sends `MouseScrollDelta::LineDelta` with a fractional value,
without doing any accumulation. Given `LineDelta` contains `f32` values,
this presumably is expected?

Though it might be good to change the definition of `MouseScrollDelta`
to include both discrete and pixel values, when the compositor sends
both. I'm not familiar with how this works on non-Wayland backends
though.
2025-10-08 11:53:57 +09:00
Kirill Chibisov
f046e778aa winit-core:winit: add tablet input support
The API is integrated into the `WindowEvent::Pointer*` API and is
present in form of `TabletTool` variant on corresponding data entries.

For now implemented for Web, Windows, and with limitations for Wayland.

Fixes #99.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2025-10-07 21:42:36 +09:00
sachharine
ffcdf80192 chore: fix typos causing dead links 2025-10-07 19:25:41 +09:00
Mads Marquart
b811e9d878 chore: update to objc2 frameworks v0.3.2 2025-10-07 17:56:05 +09:00
Kirill Chibisov
d815bc089c chore: fix nightly clippy 2025-10-07 17:56:05 +09:00
Kirill Chibisov
66283a79bd wayland: reduce amount of empty preedits
If we send empty preedit before, don't send it again.
2025-09-13 06:53:29 +09:00
Alan Everett
3be30affe4 wayland: support for pinch, rotation, and pan gestures
Co-Authored-By: linkmauve <linkmauve@linkmauve.fr>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2025-09-07 11:45:35 +09:00
Kirill Chibisov
a68f1a664b winit-wayland: bump sctk and sctk-adwaita 2025-09-06 22:25:24 +09:00
DorotaC
6de5041a94 winit-core/ime: add more purposes and content hints
Also move IME example into `ime.rs` thus making IME integrations more
easily comprehendible.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2025-09-06 13:27:55 +09:00
Mads Marquart
014fb68a26 Fix using Rust-Analyzer on non-macOS platforms (#4347) 2025-09-05 22:32:20 +02:00
Mads Marquart
9a03dacbde linux: align scancode conversions with Chromium 2025-09-05 10:17:34 +09:00
Jeremy Soller
4d9302b33c Add borderless fullscreen mode for orbital (#4343) 2025-09-04 22:14:41 +02:00
moooozi
488c036a05 win32: disable DPI re-adjustments on Windows 11
For earlier Windows 10 builds (pre-22000), a workaround was necessary
to fix dragging window onto a monitor with different DPI. This commit makes
the old DPI workaround  to only apply conditionally on affected Windows versions.

Fixes #4041.
2025-08-24 12:57:31 +09:00
Tony
a4af50ec13 win32: refresh title bar on Window::set_theme 2025-08-23 21:55:54 +09:00
Tony
317d62fb93 win32: account for mouse wheel speed setting
Also adds a method to toggle this behavior during runtime.
2025-08-23 21:38:56 +09:00
dependabot[bot]
b13b39aa0b chore: bump ci deps versions 2025-08-23 21:19:46 +09:00
Sanjay
abea6e64e4 macOS: default menu uses bundle name
Use the bundle name in the default menu or fall back
to using the process name as before.
2025-08-23 20:35:45 +09:00
Jeremiah S
d6f7a28499 macOS: fix crash due during window drop
On macOS 26+ the window drop was leading to unwrap, since
events were coming after the window was already destroyed,
while it sounds rather strange, guard against such things just
in case.

Fixes #4333.
2025-08-15 09:52:23 +09:00
Kirill Chibisov
bd98561b38 chore: fix typos from recent typos-cli (#4329) 2025-08-12 16:30:34 +09:00
John Nunley
ca6f523924 m: remove self from CODEOWNERS
This is making official what's basically been the
case for the past few months at this point. I no
longer have the capacity to effectively review
and merge PRs for the Windows and X11 backends.

Thanks again for having me.

Signed-off-by: John Nunley <dev@notgull.net>
2025-08-04 02:50:06 +09:00
DorotaC
d7fdfb1bca winit-core: add Ime::DeleteSurroundingText API
This completes the basic API required for e.g. Wayland.
2025-08-02 19:17:27 +09:00
Kirill Chibisov
120f21a010 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2025-07-27 22:13:06 +09:00
Robert Wallis
5904529ba1 macOS: fix runtime crash on macos26 "type code 'q', but found 'Q'"
Fixes #4299.
2025-07-20 17:04:04 +09:00
DorotaC
e7a6034b55 winit-core: add surrounding_text for IME
Allow communicating surrounding text to IME to better handle user input
and account for content around for preedit.
2025-07-13 14:57:10 +09:00
DorotaC
eb66c25980 chore: fix typos
Latest typos found more issues.
2025-07-03 08:30:22 +09:00
DorotaC
0ccb8a9f87 example: fix toggling IME 2025-06-30 11:51:46 +09:00
DorotaC
abed32eb80 winit-core/window: wrap ImeCapabilities in struct
To prevent user from using `::all()` and thus writing not forward
compatible code wrap the bitflags struct and provide simpler interface
to it.
2025-06-29 13:53:47 +09:00
DorotaC
08907148ec winit-core/window: add Window::request_ime_update
Allow updating IME state atomically to make it easier for platforms
where it's atomic by its nature, like Wayland. The old API is marked
as deprecated and is routed to the new atomic API.

Co-authored-by: dcz <gilapfco.dcz@porcupinefactory.org>
2025-06-28 13:14:20 +09:00
Kirill Chibisov
fa0795a50c examples/application: fix running on wasm
Was using `time` unconditionally.
2025-06-24 19:15:46 +09:00
Enn3Developer
fe1eab07ae wayland: add xdg_toplevel_icon_v1 support
Closes: #3859.
2025-06-22 19:40:05 +09:00
Kirill Chibisov
552c7a6252 winit: silence wasm on nightly
The lint is needed for stable, but is no longer present on nightly, so
silence it for the time being.
2025-06-22 19:29:27 +09:00
Jeremy Soller
50c0180af3 winit-orbital: update to new path format 2025-06-17 14:46:09 +09:00
Mads Marquart
4f33643509 chore: make git checkout build on Windows with symlinks disabled 2025-06-10 07:37:36 +09:00
Kirill Chibisov
0b21c55b72 winit-core/as_any: fix Box<AsAny> casting
The casting was doing an incorrect check on the `ref` instead of
actually trying to downcast a ref as `cast_ref` does. So use `cast_ref`
to check whether we can safely `cast` to owned type.
2025-06-08 22:11:02 +09:00
Mads Marquart
e1bccb68d8 chore: use a shared version number for all winit-* crates 2025-06-08 09:22:58 +09:00
Mads Marquart
e540062ac0 iOS: Avoid RefCell and static mut (#4255)
* iOS: Refactor queued_gpu_redraws out from AppStateImpl

To allow AppStateImpl to be Copy, and to move redraws into the window in
the future.

* iOS AppState: Avoid RefCell and static mut

Instead, prefer Cell and Copy types, as those will never have crashes
on re-entrancy / if forgetting to make a state transition.
2025-06-07 23:16:41 +02:00
Martin Fischer
f1e0f6c646 icon: add PartialEq and Hash for RgbaIcon 2025-06-08 01:18:55 +09:00
Mads Marquart
3218316420 Document platform-specific modules 2025-06-07 22:43:38 +09:00
Mads Marquart
2900ecab93 winit-core/keyboard: use keyboard_types
Closes #2394.
2025-06-07 16:47:47 +09:00
Kirill Chibisov
3a84da6951 Move winit itself to crate
That way we use top-level Cargo.toml only for the workspace management
purposes.
2025-06-07 13:07:12 +09:00
Mads Marquart
478427b0bd Remove the need for cfg_aliases in winit-core (#4271) 2025-06-06 13:24:01 +02:00
Mads Marquart
b0f26c79ff Fix CI (#4270)
* Fix typos
* Remove OsError in winit-web
2025-06-05 13:10:30 +02:00
Kirill Chibisov
969237f422 Bump rustix to 1.0.0 2025-05-27 16:31:11 +09:00
Mads Marquart
e542a78deb Move Web backend to winit-web 2025-05-26 14:56:00 +09:00
Kirill Chibisov
2d4b9938f0 ci/deny: add rustix
Will take a while to move to 1.0 for everyone.
2025-05-26 14:56:00 +09:00
Mads Marquart
8ad016362a chore: move event loop recreation check into backends themselves 2025-05-26 13:48:52 +09:00
Mads Marquart
5f2c7350e9 Move AppKit (macOS) backend to winit-appkit (#4248) 2025-05-25 17:37:40 +02:00
Mads Marquart
256bbe949e Move X11 backend to winit-x11 (#4253) 2025-05-25 17:24:00 +02:00
Mads Marquart
1126e9ea2f Move Wayland backend to winit-wayland (#4252) 2025-05-25 16:48:07 +02:00
Mads Marquart
927af44aa4 Move UIKit backend to winit-uikit 2025-05-25 23:19:30 +09:00
Mads Marquart
0adc0898f0 Move shared code to a new crate winit-common 2025-05-25 20:41:28 +09:00
Mads Marquart
3b986f5583 Move Windows backend to winit-win32 2025-05-25 12:13:25 +09:00
Mads Marquart
b1f8d778a1 Move Android backend to winit-android (#4250) 2025-05-24 13:29:53 +02:00
Mads Marquart
04482d5a2e fix: Allow unknown bit-depth on macOS (#4190)
It is unclear what values CGDisplayModeCopyPixelEncoding is allowed to
return, so let's make sure to handle unknown cases.
2025-05-23 16:07:09 +02:00
Mads Marquart
3e50911adb macOS: Remove panic wrapper (#4147)
This is unnecessary nowadays, 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 in rustc.
2025-05-23 15:53:12 +02:00
Evgeny
f51a470872 doc: add info on sticky vs toggle modifier behavior (#4251) 2025-05-22 21:57:10 +02:00
Mads Marquart
47b938dbe7 Split Orbital backend out into winit-orbital (#4243) 2025-05-21 13:12:55 +02:00
Kirill Chibisov
e2b883d215 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2025-05-21 18:04:10 +09:00
Evgeny
38fd3c6a99 winit-core/keyboard: clarify modifier docs
Emphasize the difference between logical and physical state, reference sticky mods.
2025-05-21 17:40:28 +09:00
Mads Marquart
5190472bee chore: use workspace dependencies everywhere
To reduce the amount of duplication in 'Cargo.toml's when we split into
different crates.
2025-05-21 15:45:12 +09:00
Mads Marquart
eab03dca80 Move EventLoopExt* to winit-core (#4228)
* Move EventLoopExtPumpEvents and PumpStatus to winit-core
* Move EventLoopExtRunOnDemand to winit-core
2025-05-20 16:56:53 +02:00
Kirill Chibisov
59e3dda89f wayland: fix pump events's loop drop deadlock 2025-05-17 13:23:01 +09:00
Mads Marquart
c846f67bcb chore: import from winit-core instead of the top-level crate
Reproduce with:
find ./src/platform_impl -type f -exec sed -i '' 's/crate::/winit_core::/g' {} \;
find ./src/platform_impl -type f -exec sed -i '' 's/winit_core::platform/crate::platform/g' {} \;
find ./src/platform_impl -type f -exec sed -i '' 's/winit_core::dpi::/dpi::/g' {} \;
cargo +nightly fmt
2025-05-17 11:26:09 +09:00
Mads Marquart
03c01e038b chose: appease clippy 2025-05-17 10:56:51 +09:00
Varphone Wong
ed4ebd4242 windows: Fix crash in for Windows versions < 17763
In Windows versions < 17763, `GetProcAddress("#132")` from `uxtheme.dll`
also returns a non-null pointer. However, the function does not match
the expected `extern "system" fn() -> bool` prototype, which causes a
crash when it is called.

This fix ensures compatibility by adding proper checks to prevent such
crashes on older Windows versions.
2025-05-14 21:31:48 +09:00
Kirill Chibisov
b5921d89f2 winit-core: add top-level doc 2025-05-14 21:18:44 +09:00
Kirill Chibisov
b5a6a4e616 ci: test winit-core 2025-05-14 21:18:44 +09:00
Kirill Chibisov
9598eb371c winit-core: fix tests 2025-05-14 21:18:44 +09:00
Kirill Chibisov
634b9baea2 winit-core: drop all cfg except web 2025-05-14 21:18:44 +09:00
Kirill Chibisov
cf5e422dc8 winit-core: drop broken docs
Generally, winit-core doesn't know about underlying platforms, though,
some general information which will true for any implementation was
left in place.
2025-05-14 21:18:44 +09:00
Kirill Chibisov
276597e009 winit-core: cleanup event loop docs 2025-05-14 21:18:44 +09:00
Kirill Chibisov
c0b737de4a winit-core: move application 2025-05-14 21:18:44 +09:00
Kirill Chibisov
79fa4061cb winit-core: move event 2025-05-14 21:18:44 +09:00
Kirill Chibisov
056421546a winit-core: move ActiveEventLoop 2025-05-14 21:18:44 +09:00
Kirill Chibisov
b4c5b76155 winit-core: move window
Create `WindowAttributes` for respective platform specific window
attributes in `winit` due to move of `WindowAttributes`.
2025-05-14 21:18:44 +09:00
Kirill Chibisov
c8b9a86885 winit-core: partially split event_loop 2025-05-14 21:18:44 +09:00
Kirill Chibisov
fe2df61884 winit-core: move error 2025-05-14 21:18:44 +09:00
Kirill Chibisov
446482367b winit-core: move cursor 2025-05-14 21:18:44 +09:00
Kirill Chibisov
cbb29ab526 winit-core: move icon 2025-05-14 21:18:44 +09:00
Kirill Chibisov
a491c2abed winit-core: move keyboard 2025-05-14 21:18:44 +09:00
Kirill Chibisov
3142355417 winit-core: move monitor handle 2025-05-14 21:18:44 +09:00
Kirill Chibisov
3493a20173 winit-core: new crate + split out as_any 2025-05-14 21:18:44 +09:00
Kirill Chibisov
bf0bde8067 ci/deny: allow scripts in zerocopy 2025-05-14 21:18:44 +09:00
Kirill Chibisov
519947463f Bump MSRV to 1.80 2025-05-05 21:55:12 +09:00
Kirill Chibisov
8c36ed4900 x11: drop dead code
Fixes #4214.
2025-05-04 00:03:31 +09:00
Bruce Mitchener
7b2c9d42b4 Fix typos from updated typos tool (#4213) 2025-05-03 13:38:15 +02:00
306 changed files with 12010 additions and 9831 deletions

31
.github/CODEOWNERS vendored
View File

@@ -1,31 +1,26 @@
# Android # Android
/src/platform/android.rs @MarijnS95 /winit-android @MarijnS95
/src/platform_impl/android @MarijnS95
# Apple (AppKit + UIKit) # Apple (AppKit + UIKit)
/src/platform/ios.rs @madsmtm /winit-appkit @madsmtm
/src/platform/macos.rs @madsmtm /winit-uikit @madsmtm
/src/platform_impl/apple @madsmtm /winit-common/src/core_foundation @madsmtm
/winit-common/src/event_handler.rs @madsmtm
# Unix # XKB
/src/platform_impl/linux/mod.rs @kchibisov /winit-common/src/xkb @kchibisov
# Wayland # Wayland
/src/platform/wayland.rs @kchibisov /winit-wayland @kchibisov
/src/platform_impl/linux/wayland @kchibisov
# X11 # X11
/src/platform/x11.rs @kchibisov @notgull /winit-x11 @kchibisov
/src/platform_impl/linux/x11 @kchibisov @notgull
# Web # Web
/src/platform/web.rs @daxpedda /winit-web @daxpedda
/src/platform_impl/web @daxpedda
# Windows # Windows (Win32) (UNOWNED)
/src/platform/windows.rs @notgull #/winit-win32
/src/platform_impl/windows @notgull
# Orbital (Redox OS) # Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51 /winit-orbital @jackpot51
/src/platform_impl/orbital @jackpot51

View File

@@ -1,8 +1,8 @@
name: MacOS bug name: macOS bug
description: Create a macOS-specific bug report description: Create a macOS-specific bug report
labels: labels:
- B - bug - B - bug
- DS - macos - DS - appkit
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -1,8 +1,8 @@
name: iOS bug name: iOS bug
description: Create an iOS-specific bug report description: Create an iOS/UIKit-specific bug report
labels: labels:
- B - bug - B - bug
- DS - ios - DS - uikit
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -2,7 +2,7 @@ name: Windows bug
description: Create a Windows-specific bug report description: Create a Windows-specific bug report
labels: labels:
- B - bug - B - bug
- DS - windows - DS - win32
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -55,7 +55,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.73'] toolchain: [stable, nightly, '1.85']
platform: platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, } - { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -66,7 +66,7 @@ jobs:
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' } - { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' } - { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } - { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, } - { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
@@ -82,14 +82,14 @@ jobs:
- toolchain: nightly - toolchain: nightly
platform: { name: 'Windows 32bit GNU' } platform: { name: 'Windows 32bit GNU' }
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.73' - toolchain: '1.85'
platform: { name: 'Android' } platform: { name: 'Android' }
# Redox OS doesn't follow MSRV # Redox OS doesn't follow MSRV
- toolchain: '1.73' - toolchain: '1.85'
platform: { name: 'Redox OS' } platform: { name: 'Redox OS' }
include: include:
- toolchain: '1.73' - toolchain: '1.85'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' }
- toolchain: 'nightly' - toolchain: 'nightly'
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile } platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile }
- toolchain: 'nightly' - toolchain: 'nightly'
@@ -124,7 +124,7 @@ jobs:
# the cache has been downloaded. # the cache has been downloaded.
# #
# This could be avoided if we added Cargo.lock to the repository. # This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v4 uses: actions/cache/restore@v6
with: with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci # https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: | path: |
@@ -136,7 +136,7 @@ jobs:
- name: Generate lockfile - name: Generate lockfile
# Also updates the crates.io index # Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0 run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0 && cargo update -p wayland-protocols --precise 0.32.12
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -145,7 +145,7 @@ jobs:
- name: Cache cargo-apk - name: Cache cargo-apk
if: contains(matrix.platform.target, 'android') if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache id: cargo-apk-cache
uses: actions/cache@v4 uses: actions/cache@v6
with: with:
path: ~/.cargo/bin/cargo-apk path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version # Change this key if we update the required cargo-apk version
@@ -160,7 +160,7 @@ jobs:
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true') if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: taiki-e/cache-cargo-install-action@v2 - uses: taiki-e/cache-cargo-install-action@v3
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly' if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
with: with:
tool: wasm-bindgen-cli tool: wasm-bindgen-cli
@@ -179,23 +179,70 @@ jobs:
- name: Build crate - name: Build crate
run: cargo $CMD build $OPTIONS run: cargo $CMD build $OPTIONS
- name: Test winit core
run: cargo test -p winit-core
- name: Test winit Android
if: contains(matrix.platform.target, 'android')
run: cargo $CMD test -p winit-android --features native-activity --no-run
- name: Test winit Common (EventHandler)
run: cargo $CMD test -p winit-common --features event-handler --no-run
- name: Test winit Common (CF)
if: contains(matrix.platform.target, 'apple')
run: cargo $CMD test -p winit-common --features core-foundation --no-run
- name: Test winit Common (XKB)
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-common --features xkb,x11,wayland --no-run
- name: Test winit AppKit
if: contains(matrix.platform.target, 'macos')
run: cargo $CMD test -p winit-appkit $OPTIONS
- name: Test winit Orbital
if: contains(matrix.platform.target, 'redox')
run: cargo test -p winit-orbital
- name: Test winit UIKit
if: contains(matrix.platform.target, 'ios')
# TODO: Run on Simulator
run: cargo $CMD test -p winit-uikit $OPTIONS --no-run
- name: Test winit Web
if: contains(matrix.platform.target, 'wasm')
run: cargo $CMD test -p winit-web $OPTIONS --no-run
- name: Test winit Win32
if: contains(matrix.platform.target, 'windows')
run: cargo $CMD test -p winit-win32 $OPTIONS
- name: Test winit X11
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-x11 --target=${{ matrix.platform.target }}
- name: Test winit Wayland
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-wayland --target=${{ matrix.platform.target }}
# Test only on Linux x86_64, so we avoid spending unnecessary CI hours. # Test only on Linux x86_64, so we avoid spending unnecessary CI hours.
- name: Test dpi crate - name: Test dpi crate
if: > if: >
contains(matrix.platform.name, 'Linux 64bit') && contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.85'
run: cargo test -p dpi run: cargo test -p dpi
- name: Check dpi crate (no_std) - name: Check dpi crate (no_std)
if: > if: >
contains(matrix.platform.name, 'Linux 64bit') && contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.85'
run: cargo check -p dpi --no-default-features run: cargo check -p dpi --no-default-features
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.85'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test --no-run $OPTIONS
- name: Run tests - name: Run tests
@@ -204,7 +251,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && (!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.85'
run: cargo $CMD test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
@@ -214,7 +261,7 @@ jobs:
- name: Build tests with serde enabled - name: Build tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.85'
run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
@@ -223,7 +270,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && (!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73' matrix.toolchain != '1.85'
run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde
- name: Check docs.rs documentation - name: Check docs.rs documentation
@@ -234,7 +281,7 @@ jobs:
# See restore step above # See restore step above
- name: Save cache of cargo folder - name: Save cache of cargo folder
uses: actions/cache/save@v4 uses: actions/cache/save@v6
with: with:
path: | path: |
~/.cargo/registry/index/ ~/.cargo/registry/index/
@@ -243,30 +290,14 @@ jobs:
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }} key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny: cargo-deny:
name: Run cargo-deny on ${{ matrix.platform.name }} name: Run cargo-deny
runs-on: ubuntu-latest runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: aarch64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'Web', target: wasm32-unknown-unknown }
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v2
with: with:
command: check
log-level: error log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}
eslint: eslint:
name: ESLint name: ESLint
@@ -274,14 +305,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./src/platform_impl/web/script working-directory: ./winit-web/src/script
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
- name: Setup NPM - name: Setup NPM
run: npm install run: npm install
- name: Run ESLint - name: Run ESLint
run: npx eslint run: npx eslint@9.38.0
swc: swc:
name: Minimize JavaScript name: Minimize JavaScript
@@ -289,7 +320,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./src/platform_impl/web/script working-directory: ./winit-web/src/script
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1

View File

@@ -19,7 +19,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
@@ -32,7 +32,7 @@ jobs:
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v5 uses: actions/configure-pages@v6
- name: Fix permissions - name: Fix permissions
run: | run: |
@@ -41,10 +41,10 @@ jobs:
done done
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v5
with: with:
path: target/doc path: target/doc
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v5

View File

@@ -3,6 +3,6 @@ Changelog entries should be put in the [`changelog::unreleased`].
The changelog can also be viewed [on docs.rs][docs_rs] or [on the current The changelog can also be viewed [on docs.rs][docs_rs] or [on the current
master docs][master_docs]. master docs][master_docs].
[`changelog::unreleased`]: src/changelog/unreleased.md [`changelog::unreleased`]: winit/src/changelog/unreleased.md
[docs_rs]: https://docs.rs/winit/latest/winit/changelog/index.html [docs_rs]: https://docs.rs/winit/latest/winit/changelog/index.html
[master_docs]: https://rust-windowing.github.io/winit/winit/changelog/index.html [master_docs]: https://rust-windowing.github.io/winit/winit/changelog/index.html

View File

@@ -13,6 +13,13 @@ To save your time it's wise to check already opened [pull requests][prs] and
accepted, however larger new API proposals should go into the issue first. When accepted, however larger new API proposals should go into the issue first. When
in doubt contact us on [Matrix][matrix] or via opening an issue. in doubt contact us on [Matrix][matrix] or via opening an issue.
### Windows
To run the examples on Windows, you must have symlinks enabled, see
[this][git-windows-symlinks].
[git-windows-symlinks]: https://gitforwindows.org/symbolic-links.html
### Submitting your work and handling review ### Submitting your work and handling review
All patches have to be sent on Github as [pull requests][prs]. To simplify your All patches have to be sent on Github as [pull requests][prs]. To simplify your

View File

@@ -1,403 +1,97 @@
[package] [workspace]
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"] default-members = ["winit"]
categories = ["gui"] members = ["dpi", "winit*"]
description = "Cross-platform window creation library." resolver = "2"
documentation = "https://docs.rs/winit"
edition.workspace = true
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
keywords = ["windowing"]
license.workspace = true
name = "winit"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version = "0.30.10"
[package.metadata.docs.rs] [workspace.package]
features = [ edition = "2024"
"serde", license = "Apache-2.0"
"mint", repository = "https://github.com/rust-windowing/winit"
# Enabled to get docs to compile rust-version = "1.85"
"android-native-activity", version = "0.31.0-beta.2"
]
# These are all tested in CI
rustdoc-args = ["--cfg", "docsrs"]
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"aarch64-apple-ios",
# Android
"aarch64-linux-android",
# Web
"wasm32-unknown-unknown",
]
# Features are documented in either `lib.rs` or under `winit::platform`. [workspace.dependencies]
[features] # Workspace dependencies.
android-game-activity = ["android-activity/game-activity"] # `winit` has no version here to allow using it in dev deps for docs.
android-native-activity = ["android-activity/native-activity"] winit = { path = "winit" }
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] winit-android = { version = "=0.31.0-beta.2", path = "winit-android" }
mint = ["dpi/mint"] winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" }
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"] winit-common = { version = "=0.31.0-beta.2", path = "winit-common" }
wayland = [ winit-core = { version = "=0.31.0-beta.2", path = "winit-core" }
"wayland-client", winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" }
"wayland-backend", winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" }
"wayland-protocols", winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false }
"wayland-protocols-plasma", winit-web = { version = "=0.31.0-beta.2", path = "winit-web" }
"sctk", winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" }
"ahash", winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" }
"memmap2",
]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
wayland-dlopen = ["wayland-backend/dlopen"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
[build-dependencies] # Core dependencies.
cfg_aliases = "0.2.1"
[dependencies]
bitflags = "2" bitflags = "2"
cfg_aliases = "0.2.1"
cursor-icon = "1.1.0" cursor-icon = "1.1.0"
dpi = { version = "0.1.2", path = "dpi" } dpi = { version = "0.1.2", path = "dpi" }
keyboard-types = "0.8.0"
mint = "0.5.6"
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true } serde = { version = "1", features = ["serde_derive"] }
smol_str = "0.3" smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false } tracing = { version = "0.1.40", default-features = false }
[dev-dependencies] # Dev dependencies.
image = { version = "0.25.0", default-features = false, features = ["png"] } image = { version = "0.25.0", default-features = false }
tracing = { version = "0.1.40", default-features = false, features = ["log"] } softbuffer = { version = "0.4.8", default-features = false, features = [
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer = { version = "0.4.6", default-features = false, features = [
"x11", "x11",
"x11-dlopen", "x11-dlopen",
"wayland", "wayland",
"wayland-dlopen", "wayland-dlopen",
] } ] }
tracing-subscriber = "0.3.18"
# Android # Android dependencies.
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0" android-activity = "0.6.0"
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false } ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit # Apple dependencies.
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.6.1" block2 = "0.6.1"
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] } dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] }
objc2 = "0.6.1" objc2 = { version = "0.6.1", features = ["relax-sign-encoding"] }
objc2-app-kit = { version = "0.3.2", default-features = false }
objc2-core-foundation = { version = "0.3.2", default-features = false }
objc2-core-graphics = { version = "0.3.2", default-features = false }
objc2-core-video = { version = "0.3.2", default-features = false }
objc2-foundation = { version = "0.3.2", default-features = false }
objc2-ui-kit = { version = "0.3.2", default-features = false }
# AppKit # Windows dependencies.
[target.'cfg(target_os = "macos")'.dependencies]
objc2-app-kit = { version = "0.3.1", default-features = false, features = [
"std",
"objc2-core-foundation",
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
"NSEvent",
"NSGraphics",
"NSGraphicsContext",
"NSImage",
"NSImageRep",
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPanel",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSToolbar",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"CFBase",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { version = "0.3.1", default-features = false, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { version = "0.3.1", default-features = false, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
] }
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"CFCGTypes",
"CFBase",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.3.1", default-features = false, features = [
"std",
"objc2-core-foundation",
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
"UIResponder",
"UIRotationGestureRecognizer",
"UIScreen",
"UIScreenMode",
"UITapGestureRecognizer",
"UITouch",
"UITraitCollection",
"UIView",
"UIViewController",
"UIWindow",
] }
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"
windows-sys = { version = "0.59.0", features = [ windows-sys = "0.61"
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
] }
# Linux # Linux dependencies.
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies] bytemuck = { version = "1.13.1", default-features = false }
ahash = { version = "0.8.7", features = ["no-rng"], optional = true } calloop = "0.14.3"
bytemuck = { version = "1.13.1", default-features = false, optional = true } foldhash = { version = "0.2.0", default-features = false, features = ["std"] }
calloop = "0.13.0"
libc = "0.2.64" libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true } memmap2 = "0.9.0"
percent-encoding = { version = "2.0", optional = true } percent-encoding = "2.0"
rustix = { version = "0.38.4", default-features = false, features = [ rustix = { version = "1.0.7", default-features = false }
"std", x11-dl = "2.19.1"
"system", x11rb = { version = "0.13.0", default-features = false }
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.10", optional = true }
wayland-protocols = { version = "0.32.8", features = ["staging"], optional = true }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"], optional = true }
x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code",
"cursor",
"dl-libxcb",
"randr",
"resource_manager",
"sync",
"xinput",
"xkb",
], optional = true }
xkbcommon-dl = "0.4.2" xkbcommon-dl = "0.4.2"
# Orbital # Orbital dependencies.
[target.'cfg(target_os = "redox")'.dependencies] libredox = "0.1.12"
orbclient = { version = "0.3.47", default-features = false } orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.5.7" redox_event = { package = "redox_event", version = "0.4.5" }
# Web # Web dependencies.
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70"
pin-project = "1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70", features = [
"AbortController",
"AbortSignal",
"Blob",
"BlobPropertyBag",
"console",
"CssStyleDeclaration",
"Document",
"DomException",
"DomRect",
"DomRectReadOnly",
"Element",
"Event",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlHtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
"ImageBitmapRenderingContext",
"ImageData",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent",
"MediaQueryList",
"MessageChannel",
"MessagePort",
"Navigator",
"Node",
"OrientationLockType",
"OrientationType",
"PageTransitionEvent",
"Permissions",
"PermissionState",
"PermissionStatus",
"PointerEvent",
"PremultiplyAlpha",
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"Screen",
"ScreenOrientation",
"Url",
"VisibilityState",
"WheelEvent",
"Window",
"Worker",
] }
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1" atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false } concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
js-sys = "0.3.70"
pin-project = "1"
tracing-web = "0.1" tracing-web = "0.1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"
web-time = "1"
[[example]] web_sys = { package = "web-sys", version = "0.3.70" }
doc-scrape-examples = true
name = "window"
[[example]]
name = "child_window"
[workspace]
members = ["dpi"]
resolver = "2"
[workspace.package]
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit"
rust-version = "1.73"
[workspace.dependencies]
mint = "0.5.6"
serde = { version = "1", features = ["serde_derive"] }

View File

@@ -8,7 +8,7 @@
```toml ```toml
[dependencies] [dependencies]
winit = "0.30.10" winit = "0.31.0-beta.2"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -33,13 +33,13 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
show something on the window you need to use the platform-specific getters provided by winit, or show something on the window you need to use the platform-specific getters provided by winit, or
another library. another library.
## CONTRIBUING ## CONTRIBUTING
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md). For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
## MSRV Policy ## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to This crate's Minimum Supported Rust Version (MSRV) is **1.85**. Changes to
the MSRV will be accompanied by a minor version bump. the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following As a **tentative** policy, the upper bound of the MSRV is given by the following

View File

@@ -1,16 +1,27 @@
disallowed-methods = [ # Using allow-invalid because this is platform-specific code
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, disallowed-macros = [
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" }, { path = "std::print", reason = "works badly on web", replacement = "tracing::info" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "std::println", reason = "works badly on web", replacement = "tracing::info" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, { path = "std::eprint", reason = "works badly on web", replacement = "tracing::error" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "std::eprintln", reason = "works badly on web", replacement = "tracing::error" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" }, { path = "std::dbg", reason = "leftover debugging aid, remove it or use tracing" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" }, ]
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" }, disallowed-methods = [
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" }, { allow-invalid = true, path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" }, { allow-invalid = true, path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" }, { allow-invalid = true, path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" }, { allow-invalid = true, path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" }, { allow-invalid = true, path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::window", reason = "is not available in every context" }, { allow-invalid = true, path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" },
{ allow-invalid = true, path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" },
{ allow-invalid = true, path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" },
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::window", reason = "is not available in every context" },
] ]

View File

@@ -1,27 +1,25 @@
# https://embarkstudios.github.io/cargo-deny # https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny # cargo install cargo-deny
# cargo update && cargo deny --target aarch64-apple-ios check # cargo update && cargo deny check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph] [graph]
all-features = true all-features = true
exclude-dev = true exclude-dev = true
targets = [ targets = [
{ triple = "aarch64-apple-darwin" }, "aarch64-apple-darwin",
{ triple = "aarch64-apple-ios" }, "aarch64-apple-ios",
{ triple = "aarch64-linux-android" }, "aarch64-linux-android",
{ triple = "i686-pc-windows-gnu" }, "i686-pc-windows-gnu",
{ triple = "i686-pc-windows-msvc" }, "i686-pc-windows-msvc",
{ triple = "i686-unknown-linux-gnu" }, "i686-unknown-linux-gnu",
{ triple = "wasm32-unknown-unknown", features = [ { triple = "wasm32-unknown-unknown", features = [
"atomics", "atomics",
] }, ] },
{ triple = "x86_64-apple-darwin" }, "x86_64-apple-darwin",
{ triple = "x86_64-apple-ios" }, "x86_64-apple-ios",
{ triple = "x86_64-pc-windows-gnu" }, "x86_64-pc-windows-gnu",
{ triple = "x86_64-pc-windows-msvc" }, "x86_64-pc-windows-msvc",
{ triple = "x86_64-unknown-linux-gnu" }, "x86_64-unknown-linux-gnu",
{ triple = "x86_64-unknown-redox" }, "x86_64-unknown-redox",
] ]
[licenses] [licenses]
@@ -32,41 +30,32 @@ allow = [
"ISC", # https://tldrlegal.com/license/isc-license "ISC", # https://tldrlegal.com/license/isc-license
"MIT", # https://tldrlegal.com/license/mit-license "MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html "Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
"Zlib", # https://spdx.org/licenses/Zlib.html
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/
] ]
confidence-threshold = 1.0 confidence-threshold = 1.0
private = { ignore = true } private = { ignore = true }
[bans] [bans]
multiple-versions = "deny" multiple-versions = "deny"
skip = [{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" }] skip = [
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed { crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" },
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
{ crate = "objc2@0.5", reason = "used by crossfont" },
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
]
skip-tree = [
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" },
]
[bans.build] [bans.build]
bypass = [
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] },
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] },
# `crossfont` still depends (partially transitively) on `winapi`.
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
]
include-archives = true include-archives = true
interpreted = "deny" interpreted = "deny"
[[bans.build.bypass]]
allow = [
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_i686_msvc"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_x86_64_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_x86_64_msvc"

View File

@@ -1,18 +0,0 @@
# Image Attribution
These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.
## `coordinate-systems*`
These files are created by [Mads Marquart](https://github.com/madsmtm) using
[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -64,7 +64,7 @@
//! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] #![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))]
#![cfg_attr(feature = "std", forbid(unsafe_code))] #![cfg_attr(feature = "std", forbid(unsafe_code))]
#![no_std] #![no_std]
@@ -84,36 +84,18 @@ pub trait Pixel: Copy + Into<f64> {
} }
} }
impl Pixel for u8 { macro_rules! pixel_int_impl {
fn from_f64(f: f64) -> Self { ($($t:ty),*) => {$(
round(f) as u8 impl Pixel for $t {
} fn from_f64(f: f64) -> Self {
} round(f) as $t
impl Pixel for u16 { }
fn from_f64(f: f64) -> Self { }
round(f) as u16 )*}
}
}
impl Pixel for u32 {
fn from_f64(f: f64) -> Self {
round(f) as u32
}
}
impl Pixel for i8 {
fn from_f64(f: f64) -> Self {
round(f) as i8
}
}
impl Pixel for i16 {
fn from_f64(f: f64) -> Self {
round(f) as i16
}
}
impl Pixel for i32 {
fn from_f64(f: f64) -> Self {
round(f) as i32
}
} }
pixel_int_impl!(u8, u16, u32, i8, i16, i32);
impl Pixel for f32 { impl Pixel for f32 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
f as f32 f as f32
@@ -378,6 +360,48 @@ impl<P: Pixel> From<LogicalUnit<P>> for PixelUnit {
} }
} }
macro_rules! vec2_from_impls {
($t:ident, $a:ident, $b:ident, $mint_ty:ident) => {
impl<P: Pixel, X: Pixel> From<(X, X)> for $t<P> {
fn from(($a, $b): (X, X)) -> Self {
Self::new($a.cast(), $b.cast())
}
}
impl<P: Pixel, X: Pixel> From<$t<P>> for (X, X) {
fn from(p: $t<P>) -> Self {
(p.$a.cast(), p.$b.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for $t<P> {
fn from([$a, $b]: [X; 2]) -> Self {
Self::new($a.cast(), $b.cast())
}
}
impl<P: Pixel, X: Pixel> From<$t<P>> for [X; 2] {
fn from(p: $t<P>) -> Self {
[p.$a.cast(), p.$b.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::$mint_ty<P>> for $t<P> {
fn from(p: mint::$mint_ty<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<$t<P>> for mint::$mint_ty<P> {
fn from(p: $t<P>) -> Self {
Self { x: p.$a, y: p.$b }
}
}
};
}
/// A position represented in logical pixels. /// A position represented in logical pixels.
/// ///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the /// The position is stored as floats, so please be careful. Casting floats to integers truncates the
@@ -420,43 +444,7 @@ impl<P: Pixel> LogicalPosition<P> {
} }
} }
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> { vec2_from_impls!(LogicalPosition, x, y, Point2);
fn from((x, y): (X, X)) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
fn from(p: LogicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
fn from(p: LogicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(p: LogicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A position represented in physical pixels. /// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -496,43 +484,7 @@ impl<P: Pixel> PhysicalPosition<P> {
} }
} }
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> { vec2_from_impls!(PhysicalPosition, x, y, Point2);
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
fn from(p: PhysicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
fn from(p: PhysicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(p: PhysicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A size represented in logical pixels. /// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -572,43 +524,7 @@ impl<P: Pixel> LogicalSize<P> {
} }
} }
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> { vec2_from_impls!(LogicalSize, width, height, Vector2);
fn from((x, y): (X, X)) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
fn from(s: LogicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
fn from(s: LogicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 { x: s.width, y: s.height }
}
}
/// A size represented in physical pixels. /// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -645,43 +561,7 @@ impl<P: Pixel> PhysicalSize<P> {
} }
} }
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> { vec2_from_impls!(PhysicalSize, width, height, Vector2);
fn from((x, y): (X, X)) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
fn from(s: PhysicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
fn from(s: PhysicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 { x: s.width, y: s.height }
}
}
/// A size that's either physical or logical. /// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]

1
examples Symbolic link
View File

@@ -0,0 +1 @@
winit/examples

View File

@@ -1,152 +0,0 @@
//! Fill the window buffer with a solid color.
//!
//! Launching a window without drawing to it has unpredictable results varying from platform to
//! platform. In order to have well-defined examples, this module provides an easy way to
//! fill the window buffer with a solid color.
//!
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
#[allow(unused_imports)]
pub use platform::cleanup_window;
#[allow(unused_imports)]
pub use platform::fill_window;
#[allow(unused_imports)]
pub use platform::fill_window_with_animated_color;
#[allow(unused_imports)]
pub use platform::fill_window_with_color;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform {
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use softbuffer::{Context, Surface};
use winit::window::{Window, WindowId};
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
}
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: RefCell<Context<&'static dyn Window>>,
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
}
impl GraphicsContext {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(w)
})
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
}
}
fn create_surface(
&mut self,
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
}
}
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color
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(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 fill_window_with_animated_color(window: &dyn Window, start: std::time::Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
}
}
#[cfg(any(target_os = "android", target_os = "ios"))]
mod platform {
#[allow(dead_code)]
pub fn fill_window(_window: &dyn winit::window::Window) {
// 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 fill_window_with_animated_color(
_window: &dyn winit::window::Window,
_start: std::time::Instant,
) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
}

View File

@@ -1,586 +0,0 @@
//! The [`EventLoop`] struct and assorted supporting types, including
//! [`ControlFlow`].
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::fmt;
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
#[cfg(web_platform)]
use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::utils::{impl_dyn_casting, AsAny};
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
/// initializes everything that will be required to create windows. For example on Linux creating
/// an event loop opens a connection to the X or Wayland server.
///
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// the [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
#[derive(Debug)]
pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
/// Object that allows building the event loop.
///
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::builder`].
#[derive(Default, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopBuilder {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder {
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
/// and only once per application.***
///
/// Calling this function will result in display backend initialisation.
///
/// ## Panics
///
/// Attempting to create the event loop off the main thread will panic. This
/// restriction isn't strictly necessary on all platforms, but is imposed to
/// eliminate any nasty surprises when porting to platforms that require it.
/// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant
/// [`platform`] module if the target platform supports creating an event
/// loop on any thread.
///
/// ## Platform-specific
///
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or
/// `DISPLAY` respectively when building the event loop.
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
///
/// [`platform`]: crate::platform
#[cfg_attr(
android_platform,
doc = "[`.with_android_app(app)`]: \
crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(android_platform),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
return Err(EventLoopError::RecreationAttempt);
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
Ok(EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
_marker: PhantomData,
})
}
#[cfg(web_platform)]
pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
}
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the
/// display's native refresh rate should instead use [`Poll`] and the VSync functionality
/// of a graphics API to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
}
impl ControlFlow {
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
}
}
impl EventLoop {
/// Create the event loop.
///
/// This is an alias of `EventLoop::builder().build()`.
#[inline]
pub fn new() -> Result<EventLoop, EventLoopError> {
Self::builder().build()
}
/// Start building a new event loop.
///
/// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation.
///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline]
pub fn builder() -> EventLoopBuilder {
EventLoopBuilder { platform_specific: Default::default() }
}
}
impl EventLoop {
/// Run the application with the event loop on the calling thread.
///
/// ## Event loop flow
///
/// This function internally handles the different parts of a traditional event-handling loop.
/// You can imagine this method as being implemented like this:
///
/// ```rust,ignore
/// let mut start_cause = StartCause::Init;
///
/// // Run the event loop.
/// while !event_loop.exiting() {
/// // Wake up.
/// app.new_events(event_loop, start_cause);
///
/// // Indicate that surfaces can now safely be created.
/// if start_cause == StartCause::Init {
/// app.can_create_surfaces(event_loop);
/// }
///
/// // Handle proxy wake-up event.
/// if event_loop.proxy_wake_up_set() {
/// event_loop.proxy_wake_up_clear();
/// app.proxy_wake_up(event_loop);
/// }
///
/// // Handle actions done by the user / system such as moving the cursor, resizing the
/// // window, changing the window theme, etc.
/// for event in event_loop.events() {
/// match event {
/// window event => app.window_event(event_loop, window_id, event),
/// device event => app.device_event(event_loop, device_id, event),
/// }
/// }
///
/// // Handle redraws.
/// for window_id in event_loop.pending_redraws() {
/// app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
/// }
///
/// // Done handling events, wait until we're woken up again.
/// app.about_to_wait(event_loop);
/// start_cause = event_loop.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`], but
/// it should give you an idea of how things fit together.
///
/// ## Platform-specific
///
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
/// exception (that Rust doesn't see) that will also mean that the rest of the function is
/// never executed and any values not passed to this function will *not* be dropped.
///
/// Web applications are recommended to use
#[cfg_attr(
web_platform,
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^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.
///
/// This function won't be available with `target_feature = "exception-handling"`.
///
/// [^1]: `spawn_app()` is only available on the Web platform.
///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run_app()`]: Self::run_app()
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop.window_target().create_proxy()
}
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
self.event_loop.window_target().owned_display_handle()
}
/// Change if or when [`DeviceEvent`]s are captured.
///
/// See [`ActiveEventLoop::listen_device_events`] for details.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, allowed: DeviceEvents) {
let _span = tracing::debug_span!(
"winit::EventLoop::listen_device_events",
allowed = ?allowed
)
.entered();
self.event_loop.window_target().listen_device_events(allowed)
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.event_loop.window_target().set_control_flow(control_flow);
}
/// Create custom cursor.
///
/// ## Platform-specific
///
/// **iOS / Android / Orbital:** Unsupported.
pub fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError> {
self.event_loop.window_target().create_custom_cursor(custom_cursor)
}
}
impl HasDisplayHandle for EventLoop {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsFd for EventLoop {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsRawFd for EventLoop {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
fn create_proxy(&self) -> EventLoopProxy;
/// Create the window.
///
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
///
/// ## Platform-specific
///
/// - **Web:** The window is created but not inserted into the Web page automatically. Please
/// see the Web platform module for more information.
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<Box<dyn Window>, RequestError>;
/// Create custom cursor.
///
/// ## Platform-specific
///
/// **iOS / Android / Orbital:** Unsupported.
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError>;
/// Returns the list of all the monitors available on the system.
///
/// ## Platform-specific
///
/// **Web:** Only returns the current monitor without
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
/// Returns the primary monitor of the system.
///
/// Returns `None` if it can't identify any monitor as a primary one.
///
/// ## Platform-specific
///
/// - **Wayland:** Always returns `None`.
/// - **Web:** Always returns `None` without
#[cfg_attr(
web_platform,
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")]
fn primary_monitor(&self) -> Option<MonitorHandle>;
/// Change if or when [`DeviceEvent`]s are captured.
///
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
/// this at runtime to explicitly capture them again.
///
/// ## Platform-specific
///
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
fn listen_device_events(&self, allowed: DeviceEvents);
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
fn system_theme(&self) -> Option<Theme>;
/// Sets the [`ControlFlow`].
fn set_control_flow(&self, control_flow: ControlFlow);
/// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow;
/// Stop the event loop.
///
/// ## Platform-specific
///
/// ### iOS
///
/// It is not possible to programmatically exit/quit an application on iOS, so this function is
/// a no-op there. See also [this technical Q&A][qa1561].
///
/// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
fn exit(&self);
/// Returns whether the [`EventLoop`] is about to stop.
///
/// Set by [`exit()`][Self::exit].
fn exiting(&self) -> bool;
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
fn owned_display_handle(&self) -> OwnedDisplayHandle;
/// Get the raw-window-handle handle.
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
}
impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.rwh_06_handle().display_handle()
}
}
impl_dyn_casting!(ActiveEventLoop);
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
/// needed.
///
/// For all platforms, this is one of the following:
///
/// - A zero-sized type that is likely optimized out.
/// - A reference-counted pointer to the underlying type.
#[derive(Clone)]
pub struct OwnedDisplayHandle {
pub(crate) handle: Arc<dyn HasDisplayHandle>,
}
impl OwnedDisplayHandle {
pub(crate) fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
Self { handle }
}
}
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.handle.display_handle()
}
}
impl fmt::Debug for OwnedDisplayHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
}
}
impl PartialEq for OwnedDisplayHandle {
fn eq(&self, other: &Self) -> bool {
match (self.display_handle(), other.display_handle()) {
(Ok(lhs), Ok(rhs)) => lhs == rhs,
_ => false,
}
}
}
impl Eq for OwnedDisplayHandle {}
pub(crate) trait EventLoopProxyProvider: Send + Sync + fmt::Debug {
/// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self);
}
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone, Debug)]
pub struct EventLoopProxy {
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
}
impl EventLoopProxy {
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called.
///
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.proxy.wake_up();
}
pub(crate) fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
Self { proxy }
}
}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeviceEvents {
/// Report device events regardless of window focus.
Always,
/// Only capture device events while the window is focused.
#[default]
WhenFocused,
/// Never capture device events.
Never,
}
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarios could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AsyncRequestSerial {
serial: usize,
}
impl AsyncRequestSerial {
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: We rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
//! Contains traits with platform-specific methods in them.
//!
//! Only the modules corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)]
pub mod android;
#[cfg(ios_platform)]
pub mod ios;
#[cfg(macos_platform)]
pub mod macos;
#[cfg(orbital_platform)]
pub mod orbital;
#[cfg(any(x11_platform, wayland_platform))]
pub mod startup_notify;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(web_platform)]
pub mod web;
#[cfg(windows_platform)]
pub mod windows;
#[cfg(x11_platform)]
pub mod x11;
#[allow(unused_imports)]
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
docsrs,
))]
pub mod run_on_demand;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
docsrs,
))]
pub mod pump_events;
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
pub mod scancode;

View File

@@ -1,6 +0,0 @@
//! # Orbital / Redox OS
//!
//! Redox OS has some functionality not yet present that will be implemented
//! when its orbital display server provides it.
// There are no Orbital specific traits yet.

View File

@@ -1,111 +0,0 @@
//! # Wayland
//!
//! **Note:** Windows don't appear on Wayland until you draw/present to them.
//!
//! By default, Winit loads system libraries using `dlopen`. This can be
//! disabled by disabling the `"wayland-dlopen"` cargo feature.
//!
//! ## Client-side decorations
//!
//! Winit provides client-side decorations by default, but the behaviour can
//! be controlled with the following feature flags:
//!
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use std::ffi::c_void;
use std::ptr::NonNull;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::platform_impl::wayland::Window;
pub use crate::window::Theme;
use crate::window::{Window as CoreWindow, WindowAttributes};
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait ActiveEventLoopExtWayland {
/// True if the [`ActiveEventLoop`] uses Wayland.
fn is_wayland(&self) -> bool;
}
impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ {
#[inline]
fn is_wayland(&self) -> bool {
self.cast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some()
}
}
/// Additional methods on [`EventLoop`] that are specific to Wayland.
pub trait EventLoopExtWayland {
/// True if the [`EventLoop`] uses Wayland.
fn is_wayland(&self) -> bool;
}
impl EventLoopExtWayland for EventLoop {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
pub trait EventLoopBuilderExtWayland {
/// Force using Wayland.
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtWayland for EventLoopBuilder {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to Wayland.
///
/// [`Window`]: crate::window::Window
pub trait WindowExtWayland {
/// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window.
fn xdg_toplevel(&self) -> Option<NonNull<c_void>>;
}
impl WindowExtWayland for dyn CoreWindow + '_ {
#[inline]
fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
self.cast_ref::<Window>()?.xdg_toplevel()
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland {
/// Build window with the given name.
///
/// The `general` name sets an application ID, which should match the `.desktop`
/// file distributed with your program. The `instance` is a `no-op`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
}
impl WindowAttributesExtWayland for WindowAttributes {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name =
Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
self
}
}

View File

@@ -1,23 +0,0 @@
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod observer;
mod view;
mod window;
mod window_delegate;
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;
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;

View File

@@ -1,286 +0,0 @@
//! Utilities for working with `CFRunLoop`.
//!
//! See Apple's documentation on Run Loops for details:
//! <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 objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverCallBack,
CFRunLoopObserverContext, CFRunLoopTimer,
};
use tracing::error;
use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo};
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-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
panic_info: *mut c_void,
) {
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::AfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`");
},
_ => 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-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
panic_info: *mut c_void,
) {
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`");
},
CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */
_ => unreachable!(),
}
});
}
}
#[derive(Debug)]
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(CFRunLoop::main().unwrap())
}
pub fn wakeup(&self) {
self.0.wake_up();
}
unsafe fn add_observer(
&self,
flags: CFRunLoopActivity,
// The lower the value, the sooner this will run
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) {
let observer =
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) }
.unwrap();
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// 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) {
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { self.0.perform_block(Some(mode), Some(&block)) }
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
version: 0,
retain: None,
release: None,
copyDescription: None,
};
run_loop.add_observer(
CFRunLoopActivity::AfterWaiting,
CFIndex::MIN,
Some(control_flow_begin_handler),
&mut context as *mut _,
);
run_loop.add_observer(
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
CFIndex::MAX,
Some(control_flow_end_handler),
&mut context as *mut _,
);
}
}
#[derive(Debug)]
pub struct EventLoopWaker {
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
/// easily check if the next_fire_date needs updating.
start_instant: Instant,
/// This is what the `NextFireDate` has been set to.
/// `None` corresponds to `waker.stop()` and `start_instant` is used
/// for `waker.start()`
next_fire_date: Option<Instant>,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
self.timer.invalidate();
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
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 = CFRunLoopTimer::new(
None,
f64::MAX,
0.000_000_1,
0,
0,
Some(wakeup_main_loop),
ptr::null_mut(),
)
.unwrap();
CFRunLoop::main().unwrap().add_timer(Some(&timer), kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
pub fn stop(&mut self) {
if self.next_fire_date.is_some() {
self.next_fire_date = None;
self.timer.set_next_fire_date(f64::MAX);
}
}
pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant);
self.timer.set_next_fire_date(f64::MIN);
}
}
pub fn start_at(&mut self, instant: Option<Instant>) {
let now = Instant::now();
match instant {
Some(instant) if now >= instant => {
self.start();
},
Some(instant) => {
if self.next_fire_date != Some(instant) {
self.next_fire_date = Some(instant);
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
self.timer.set_next_fire_date(current + fsecs);
}
},
None => {
self.stop();
},
}
}
}

View File

@@ -1,40 +0,0 @@
use objc2_core_graphics::CGError;
use tracing::trace;
use crate::error::OsError;
macro_rules! trace_scope {
($s:literal) => {
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
};
}
pub(crate) struct TraceGuard {
module_path: &'static str,
called_from_fn: &'static str,
}
impl TraceGuard {
#[inline]
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
trace!(target = module_path, "Triggered `{}`", called_from_fn);
Self { module_path, called_from_fn }
}
}
impl Drop for TraceGuard {
#[inline]
fn drop(&mut self) {
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

@@ -1,16 +0,0 @@
//! Apple/Darwin-specific implementations
#[cfg(target_os = "macos")]
mod appkit;
mod event_handler;
mod event_loop_proxy;
mod notification_center;
#[cfg(not(target_os = "macos"))]
mod uikit;
#[allow(unused_imports)]
#[cfg(target_os = "macos")]
pub use self::appkit::*;
#[allow(unused_imports)]
#[cfg(not(target_os = "macos"))]
pub use self::uikit::*;

View File

@@ -1,25 +0,0 @@
#![allow(clippy::let_unit_value)]
mod app_state;
mod event_loop;
mod monitor;
mod view;
mod view_controller;
mod window;
use std::fmt;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
#[derive(Debug)]
pub enum OsError {}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "os error")
}
}

View File

@@ -1 +0,0 @@
pub mod xkb;

View File

@@ -1,30 +0,0 @@
//! Winit's Wayland backend.
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::window::WindowId;
mod event_loop;
mod output;
mod seat;
mod state;
mod types;
mod window;
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use window::Window;
/// Get the WindowId out of the surface.
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId::from_raw(surface.id().as_ptr() as usize)
}
/// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -1,200 +0,0 @@
use std::ops::Deref;
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::state::WinitState;
use crate::window::ImePurpose;
#[derive(Debug)]
pub struct TextInputState {
text_input_manager: ZwpTextInputManagerV3,
}
impl TextInputState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { text_input_manager })
}
}
impl Deref for TextInputState {
type Target = ZwpTextInputManagerV3;
fn deref(&self) -> &Self::Target {
&self.text_input_manager
}
}
impl Dispatch<ZwpTextInputManagerV3, GlobalData, WinitState> for TextInputState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpTextInputManagerV3,
_event: <ZwpTextInputManagerV3 as Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
fn event(
state: &mut WinitState,
text_input: &ZwpTextInputV3,
event: <ZwpTextInputV3 as Proxy>::Event,
data: &TextInputData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let windows = state.windows.get_mut();
let mut text_input_data = data.inner.lock().unwrap();
match event {
TextInputEvent::Enter { surface } => {
let window_id = wayland::make_wid(&surface);
text_input_data.surface = Some(surface);
let mut window = match windows.get(&window_id) {
Some(window) => window.lock().unwrap(),
None => return,
};
if window.ime_allowed() {
text_input.enable();
text_input.set_content_type_by_purpose(window.ime_purpose());
text_input.commit();
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
window.text_input_entered(text_input);
},
TextInputEvent::Leave { surface } => {
text_input_data.surface = None;
// Always issue a disable.
text_input.disable();
text_input.commit();
let window_id = wayland::make_wid(&surface);
// XXX this check is essential, because `leave` could have a
// reference to nil surface...
let mut window = match windows.get(&window_id) {
Some(window) => window.lock().unwrap(),
None => return,
};
window.text_input_left(text_input);
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
},
TextInputEvent::PreeditString { text, cursor_begin, cursor_end } => {
let text = text.unwrap_or_default();
let cursor_begin = usize::try_from(cursor_begin)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
let cursor_end = usize::try_from(cursor_end)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
text_input_data.pending_preedit = Some(Preedit { text, cursor_begin, cursor_end })
},
TextInputEvent::CommitString { text } => {
text_input_data.pending_preedit = None;
text_input_data.pending_commit = text;
},
TextInputEvent::Done { .. } => {
let window_id = match text_input_data.surface.as_ref() {
Some(surface) => wayland::make_wid(surface),
None => return,
};
// 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() {
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Send preedit.
if let Some(preedit) = text_input_data.pending_preedit.take() {
let cursor_range =
preedit.cursor_begin.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
window_id,
);
}
},
TextInputEvent::DeleteSurroundingText { .. } => {
// Not handled.
},
_ => {},
}
}
}
pub trait ZwpTextInputV3Ext {
fn set_content_type_by_purpose(&self, purpose: ImePurpose);
}
impl ZwpTextInputV3Ext for ZwpTextInputV3 {
fn set_content_type_by_purpose(&self, purpose: ImePurpose) {
let (hint, purpose) = match purpose {
ImePurpose::Normal => (ContentHint::None, ContentPurpose::Normal),
ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password),
ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal),
};
self.set_content_type(hint, purpose);
}
}
/// The Data associated with the text input.
#[derive(Default)]
pub struct TextInputData {
inner: std::sync::Mutex<TextInputDataInner>,
}
#[derive(Default)]
pub struct TextInputDataInner {
/// The `WlSurface` we're performing input to.
surface: Option<WlSurface>,
/// The commit to submit on `done`.
pending_commit: Option<String>,
/// The preedit to submit on `done`.
pending_preedit: Option<Preedit>,
}
/// The state of the preedit.
struct Preedit {
text: String,
cursor_begin: Option<usize>,
cursor_end: Option<usize>,
}
delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState);
delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState);

View File

@@ -1,68 +0,0 @@
use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool};
use crate::cursor::{CursorImage, CustomCursorProvider};
// Wrap in our own type to not impl trait on global type.
#[derive(Debug)]
pub struct WaylandCustomCursor(pub(crate) CursorImage);
impl CustomCursorProvider for WaylandCustomCursor {
fn is_animated(&self) -> bool {
false
}
}
#[derive(Debug)]
pub enum SelectedCursor {
Named(CursorIcon),
Custom(CustomCursor),
}
impl Default for SelectedCursor {
fn default() -> Self {
Self::Named(Default::default())
}
}
#[derive(Debug)]
pub struct CustomCursor {
pub buffer: Buffer,
pub w: i32,
pub h: i32,
pub hotspot_x: i32,
pub hotspot_y: i32,
}
impl CustomCursor {
pub(crate) fn new(pool: &mut SlotPool, image: &WaylandCustomCursor) -> Self {
let image = &image.0;
let (buffer, canvas) = pool
.create_buffer(
image.width as i32,
image.height as i32,
4 * (image.width as i32),
Format::Argb8888,
)
.unwrap();
for (canvas_chunk, rgba) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4)) {
// Alpha in buffer is premultiplied.
let alpha = rgba[3] as f32 / 255.;
let r = (rgba[0] as f32 * alpha) as u32;
let g = (rgba[1] as f32 * alpha) as u32;
let b = (rgba[2] as f32 * alpha) as u32;
let color = ((rgba[3] as u32) << 24) + (r << 16) + (g << 8) + b;
let array: &mut [u8; 4] = canvas_chunk.try_into().unwrap();
*array = color.to_le_bytes();
}
CustomCursor {
buffer,
w: image.width as i32,
h: image.height as i32,
hotspot_x: image.hotspot_x as i32,
hotspot_y: image.hotspot_y as i32,
}
}
}

View File

@@ -1,36 +0,0 @@
#![allow(clippy::assertions_on_constants)]
use super::*;
use crate::icon::{Pixel, RgbaIcon, PIXEL_SIZE};
impl Pixel {
pub fn to_packed_argb(&self) -> Cardinal {
let mut cardinal = 0;
assert!(CARDINAL_SIZE >= PIXEL_SIZE);
let as_bytes = &mut cardinal as *mut _ as *mut u8;
unsafe {
*as_bytes.offset(0) = self.b;
*as_bytes.offset(1) = self.g;
*as_bytes.offset(2) = self.r;
*as_bytes.offset(3) = self.a;
}
cardinal
}
}
impl RgbaIcon {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (self.width * self.height) as usize);
let mut data = Vec::with_capacity(pixel_count);
data.push(self.width as Cardinal);
data.push(self.height as Cardinal);
let pixels = self.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.add(pixel_index) };
data.push(pixel.to_packed_argb());
}
data
}
}

View File

@@ -1,128 +0,0 @@
#![cfg(target_os = "redox")]
use std::{fmt, str};
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::window::Window;
mod event_loop;
mod window;
#[derive(Debug)]
struct RedoxSocket {
fd: usize,
}
impl RedoxSocket {
fn event() -> syscall::Result<Self> {
Self::open_raw("event:")
}
fn orbital(properties: &WindowProperties<'_>) -> syscall::Result<Self> {
Self::open_raw(&format!("{properties}"))
}
// Paths should be checked to ensure they are actually sockets and not normal files. If a
// non-socket path is used, it could cause read and write to not function as expected. For
// example, the seek would change in a potentially unpredictable way if either read or write
// were called at the same time by multiple threads.
fn open_raw(path: &str) -> syscall::Result<Self> {
let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?;
Ok(Self { fd })
}
fn read(&self, buf: &mut [u8]) -> syscall::Result<()> {
let count = syscall::read(self.fd, buf)?;
if count == buf.len() {
Ok(())
} else {
Err(syscall::Error::new(syscall::EINVAL))
}
}
fn write(&self, buf: &[u8]) -> syscall::Result<()> {
let count = syscall::write(self.fd, buf)?;
if count == buf.len() {
Ok(())
} else {
Err(syscall::Error::new(syscall::EINVAL))
}
}
fn fpath<'a>(&self, buf: &'a mut [u8]) -> syscall::Result<&'a str> {
let count = syscall::fpath(self.fd, buf)?;
str::from_utf8(&buf[..count]).map_err(|_err| syscall::Error::new(syscall::EINVAL))
}
}
impl Drop for RedoxSocket {
fn drop(&mut self) {
let _ = syscall::close(self.fd);
}
}
#[derive(Debug)]
pub struct TimeSocket(RedoxSocket);
impl TimeSocket {
fn open() -> syscall::Result<Self> {
RedoxSocket::open_raw("time:4").map(Self)
}
// Read current time.
fn current_time(&self) -> syscall::Result<syscall::TimeSpec> {
let mut timespec = syscall::TimeSpec::default();
self.0.read(&mut timespec)?;
Ok(timespec)
}
// Write a timeout.
fn timeout(&self, timespec: &syscall::TimeSpec) -> syscall::Result<()> {
self.0.write(timespec)
}
// Wake immediately.
fn wake(&self) -> syscall::Result<()> {
// Writing a default TimeSpec will always trigger a time event.
self.timeout(&syscall::TimeSpec::default())
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;
struct WindowProperties<'a> {
flags: &'a str,
x: i32,
y: i32,
w: u32,
h: u32,
title: &'a str,
}
impl<'a> WindowProperties<'a> {
fn new(path: &'a str) -> Self {
// orbital:flags/x/y/w/h/t
let mut parts = path.splitn(6, '/');
let flags = parts.next().unwrap_or("");
let x = parts.next().map_or(0, |part| part.parse::<i32>().unwrap_or(0));
let y = parts.next().map_or(0, |part| part.parse::<i32>().unwrap_or(0));
let w = parts.next().map_or(0, |part| part.parse::<u32>().unwrap_or(0));
let h = parts.next().map_or(0, |part| part.parse::<u32>().unwrap_or(0));
let title = parts.next().unwrap_or("");
Self { flags, x, y, w, h, title }
}
}
impl fmt::Display for WindowProperties<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"orbital:{}/{}/{}/{}/{}/{}",
self.flags, self.x, self.y, self.w, self.h, self.title
)
}
}

View File

@@ -1,10 +0,0 @@
use std::fmt;
#[derive(Debug)]
pub struct OsError(pub String);
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@@ -1,86 +0,0 @@
use super::{backend, HasMonitorPermissionFuture, MonitorPermissionFuture};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError};
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
mod proxy;
pub(crate) mod runner;
mod state;
mod window_target;
pub(crate) use window_target::ActiveEventLoop;
#[derive(Debug)]
pub struct EventLoop {
elw: ActiveEventLoop,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
Ok(EventLoop { elw: ActiveEventLoop::new() })
}
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 app = unsafe {
std::mem::transmute::<
Box<dyn ApplicationHandler + '_>,
Box<dyn ApplicationHandler + 'static>,
>(app)
};
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 '!'
backend::throw(
"Using exceptions for control flow, don't mind me. This isn't actually an error!",
);
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app), true);
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.elw
}
pub fn set_poll_strategy(&self, strategy: PollStrategy) {
self.elw.set_poll_strategy(strategy);
}
pub fn poll_strategy(&self) -> PollStrategy {
self.elw.poll_strategy()
}
pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.elw.set_wait_until_strategy(strategy);
}
pub fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.elw.wait_until_strategy()
}
pub fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
self.elw.has_multiple_screens()
}
pub(crate) fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
self.elw.request_detailed_monitor_permission()
}
pub fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture {
self.elw.runner.monitor().has_detailed_monitor_permission_async()
}
}

View File

@@ -1,525 +0,0 @@
use smol_str::SmolStr;
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
impl Key {
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
Key::Named(match kav {
"Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))),
"Dead" => return Key::Dead(None),
"Alt" => NamedKey::Alt,
"AltGraph" => NamedKey::AltGraph,
"CapsLock" => NamedKey::CapsLock,
"Control" => NamedKey::Control,
"Fn" => NamedKey::Fn,
"FnLock" => NamedKey::FnLock,
"NumLock" => NamedKey::NumLock,
"ScrollLock" => NamedKey::ScrollLock,
"Shift" => NamedKey::Shift,
"Symbol" => NamedKey::Symbol,
"SymbolLock" => NamedKey::SymbolLock,
#[allow(deprecated)]
"Super" => NamedKey::Super,
#[allow(deprecated)]
"Hyper" => NamedKey::Hyper,
"Meta" => NamedKey::Meta,
"Enter" => NamedKey::Enter,
"Tab" => NamedKey::Tab,
"ArrowDown" => NamedKey::ArrowDown,
"ArrowLeft" => NamedKey::ArrowLeft,
"ArrowRight" => NamedKey::ArrowRight,
"ArrowUp" => NamedKey::ArrowUp,
"End" => NamedKey::End,
"Home" => NamedKey::Home,
"PageDown" => NamedKey::PageDown,
"PageUp" => NamedKey::PageUp,
"Backspace" => NamedKey::Backspace,
"Clear" => NamedKey::Clear,
"Copy" => NamedKey::Copy,
"CrSel" => NamedKey::CrSel,
"Cut" => NamedKey::Cut,
"Delete" => NamedKey::Delete,
"EraseEof" => NamedKey::EraseEof,
"ExSel" => NamedKey::ExSel,
"Insert" => NamedKey::Insert,
"Paste" => NamedKey::Paste,
"Redo" => NamedKey::Redo,
"Undo" => NamedKey::Undo,
"Accept" => NamedKey::Accept,
"Again" => NamedKey::Again,
"Attn" => NamedKey::Attn,
"Cancel" => NamedKey::Cancel,
"ContextMenu" => NamedKey::ContextMenu,
"Escape" => NamedKey::Escape,
"Execute" => NamedKey::Execute,
"Find" => NamedKey::Find,
"Help" => NamedKey::Help,
"Pause" => NamedKey::Pause,
"Play" => NamedKey::Play,
"Props" => NamedKey::Props,
"Select" => NamedKey::Select,
"ZoomIn" => NamedKey::ZoomIn,
"ZoomOut" => NamedKey::ZoomOut,
"BrightnessDown" => NamedKey::BrightnessDown,
"BrightnessUp" => NamedKey::BrightnessUp,
"Eject" => NamedKey::Eject,
"LogOff" => NamedKey::LogOff,
"Power" => NamedKey::Power,
"PowerOff" => NamedKey::PowerOff,
"PrintScreen" => NamedKey::PrintScreen,
"Hibernate" => NamedKey::Hibernate,
"Standby" => NamedKey::Standby,
"WakeUp" => NamedKey::WakeUp,
"AllCandidates" => NamedKey::AllCandidates,
"Alphanumeric" => NamedKey::Alphanumeric,
"CodeInput" => NamedKey::CodeInput,
"Compose" => NamedKey::Compose,
"Convert" => NamedKey::Convert,
"FinalMode" => NamedKey::FinalMode,
"GroupFirst" => NamedKey::GroupFirst,
"GroupLast" => NamedKey::GroupLast,
"GroupNext" => NamedKey::GroupNext,
"GroupPrevious" => NamedKey::GroupPrevious,
"ModeChange" => NamedKey::ModeChange,
"NextCandidate" => NamedKey::NextCandidate,
"NonConvert" => NamedKey::NonConvert,
"PreviousCandidate" => NamedKey::PreviousCandidate,
"Process" => NamedKey::Process,
"SingleCandidate" => NamedKey::SingleCandidate,
"HangulMode" => NamedKey::HangulMode,
"HanjaMode" => NamedKey::HanjaMode,
"JunjaMode" => NamedKey::JunjaMode,
"Eisu" => NamedKey::Eisu,
"Hankaku" => NamedKey::Hankaku,
"Hiragana" => NamedKey::Hiragana,
"HiraganaKatakana" => NamedKey::HiraganaKatakana,
"KanaMode" => NamedKey::KanaMode,
"KanjiMode" => NamedKey::KanjiMode,
"Katakana" => NamedKey::Katakana,
"Romaji" => NamedKey::Romaji,
"Zenkaku" => NamedKey::Zenkaku,
"ZenkakuHankaku" => NamedKey::ZenkakuHankaku,
"Soft1" => NamedKey::Soft1,
"Soft2" => NamedKey::Soft2,
"Soft3" => NamedKey::Soft3,
"Soft4" => NamedKey::Soft4,
"ChannelDown" => NamedKey::ChannelDown,
"ChannelUp" => NamedKey::ChannelUp,
"Close" => NamedKey::Close,
"MailForward" => NamedKey::MailForward,
"MailReply" => NamedKey::MailReply,
"MailSend" => NamedKey::MailSend,
"MediaClose" => NamedKey::MediaClose,
"MediaFastForward" => NamedKey::MediaFastForward,
"MediaPause" => NamedKey::MediaPause,
"MediaPlay" => NamedKey::MediaPlay,
"MediaPlayPause" => NamedKey::MediaPlayPause,
"MediaRecord" => NamedKey::MediaRecord,
"MediaRewind" => NamedKey::MediaRewind,
"MediaStop" => NamedKey::MediaStop,
"MediaTrackNext" => NamedKey::MediaTrackNext,
"MediaTrackPrevious" => NamedKey::MediaTrackPrevious,
"New" => NamedKey::New,
"Open" => NamedKey::Open,
"Print" => NamedKey::Print,
"Save" => NamedKey::Save,
"SpellCheck" => NamedKey::SpellCheck,
"Key11" => NamedKey::Key11,
"Key12" => NamedKey::Key12,
"AudioBalanceLeft" => NamedKey::AudioBalanceLeft,
"AudioBalanceRight" => NamedKey::AudioBalanceRight,
"AudioBassBoostDown" => NamedKey::AudioBassBoostDown,
"AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle,
"AudioBassBoostUp" => NamedKey::AudioBassBoostUp,
"AudioFaderFront" => NamedKey::AudioFaderFront,
"AudioFaderRear" => NamedKey::AudioFaderRear,
"AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext,
"AudioTrebleDown" => NamedKey::AudioTrebleDown,
"AudioTrebleUp" => NamedKey::AudioTrebleUp,
"AudioVolumeDown" => NamedKey::AudioVolumeDown,
"AudioVolumeUp" => NamedKey::AudioVolumeUp,
"AudioVolumeMute" => NamedKey::AudioVolumeMute,
"MicrophoneToggle" => NamedKey::MicrophoneToggle,
"MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown,
"MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp,
"MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute,
"SpeechCorrectionList" => NamedKey::SpeechCorrectionList,
"SpeechInputToggle" => NamedKey::SpeechInputToggle,
"LaunchApplication1" => NamedKey::LaunchApplication1,
"LaunchApplication2" => NamedKey::LaunchApplication2,
"LaunchCalendar" => NamedKey::LaunchCalendar,
"LaunchContacts" => NamedKey::LaunchContacts,
"LaunchMail" => NamedKey::LaunchMail,
"LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer,
"LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer,
"LaunchPhone" => NamedKey::LaunchPhone,
"LaunchScreenSaver" => NamedKey::LaunchScreenSaver,
"LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet,
"LaunchWebBrowser" => NamedKey::LaunchWebBrowser,
"LaunchWebCam" => NamedKey::LaunchWebCam,
"LaunchWordProcessor" => NamedKey::LaunchWordProcessor,
"BrowserBack" => NamedKey::BrowserBack,
"BrowserFavorites" => NamedKey::BrowserFavorites,
"BrowserForward" => NamedKey::BrowserForward,
"BrowserHome" => NamedKey::BrowserHome,
"BrowserRefresh" => NamedKey::BrowserRefresh,
"BrowserSearch" => NamedKey::BrowserSearch,
"BrowserStop" => NamedKey::BrowserStop,
"AppSwitch" => NamedKey::AppSwitch,
"Call" => NamedKey::Call,
"Camera" => NamedKey::Camera,
"CameraFocus" => NamedKey::CameraFocus,
"EndCall" => NamedKey::EndCall,
"GoBack" => NamedKey::GoBack,
"GoHome" => NamedKey::GoHome,
"HeadsetHook" => NamedKey::HeadsetHook,
"LastNumberRedial" => NamedKey::LastNumberRedial,
"Notification" => NamedKey::Notification,
"MannerMode" => NamedKey::MannerMode,
"VoiceDial" => NamedKey::VoiceDial,
"TV" => NamedKey::TV,
"TV3DMode" => NamedKey::TV3DMode,
"TVAntennaCable" => NamedKey::TVAntennaCable,
"TVAudioDescription" => NamedKey::TVAudioDescription,
"TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown,
"TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp,
"TVContentsMenu" => NamedKey::TVContentsMenu,
"TVDataService" => NamedKey::TVDataService,
"TVInput" => NamedKey::TVInput,
"TVInputComponent1" => NamedKey::TVInputComponent1,
"TVInputComponent2" => NamedKey::TVInputComponent2,
"TVInputComposite1" => NamedKey::TVInputComposite1,
"TVInputComposite2" => NamedKey::TVInputComposite2,
"TVInputHDMI1" => NamedKey::TVInputHDMI1,
"TVInputHDMI2" => NamedKey::TVInputHDMI2,
"TVInputHDMI3" => NamedKey::TVInputHDMI3,
"TVInputHDMI4" => NamedKey::TVInputHDMI4,
"TVInputVGA1" => NamedKey::TVInputVGA1,
"TVMediaContext" => NamedKey::TVMediaContext,
"TVNetwork" => NamedKey::TVNetwork,
"TVNumberEntry" => NamedKey::TVNumberEntry,
"TVPower" => NamedKey::TVPower,
"TVRadioService" => NamedKey::TVRadioService,
"TVSatellite" => NamedKey::TVSatellite,
"TVSatelliteBS" => NamedKey::TVSatelliteBS,
"TVSatelliteCS" => NamedKey::TVSatelliteCS,
"TVSatelliteToggle" => NamedKey::TVSatelliteToggle,
"TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog,
"TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital,
"TVTimer" => NamedKey::TVTimer,
"AVRInput" => NamedKey::AVRInput,
"AVRPower" => NamedKey::AVRPower,
"ColorF0Red" => NamedKey::ColorF0Red,
"ColorF1Green" => NamedKey::ColorF1Green,
"ColorF2Yellow" => NamedKey::ColorF2Yellow,
"ColorF3Blue" => NamedKey::ColorF3Blue,
"ColorF4Grey" => NamedKey::ColorF4Grey,
"ColorF5Brown" => NamedKey::ColorF5Brown,
"ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle,
"Dimmer" => NamedKey::Dimmer,
"DisplaySwap" => NamedKey::DisplaySwap,
"DVR" => NamedKey::DVR,
"Exit" => NamedKey::Exit,
"FavoriteClear0" => NamedKey::FavoriteClear0,
"FavoriteClear1" => NamedKey::FavoriteClear1,
"FavoriteClear2" => NamedKey::FavoriteClear2,
"FavoriteClear3" => NamedKey::FavoriteClear3,
"FavoriteRecall0" => NamedKey::FavoriteRecall0,
"FavoriteRecall1" => NamedKey::FavoriteRecall1,
"FavoriteRecall2" => NamedKey::FavoriteRecall2,
"FavoriteRecall3" => NamedKey::FavoriteRecall3,
"FavoriteStore0" => NamedKey::FavoriteStore0,
"FavoriteStore1" => NamedKey::FavoriteStore1,
"FavoriteStore2" => NamedKey::FavoriteStore2,
"FavoriteStore3" => NamedKey::FavoriteStore3,
"Guide" => NamedKey::Guide,
"GuideNextDay" => NamedKey::GuideNextDay,
"GuidePreviousDay" => NamedKey::GuidePreviousDay,
"Info" => NamedKey::Info,
"InstantReplay" => NamedKey::InstantReplay,
"Link" => NamedKey::Link,
"ListProgram" => NamedKey::ListProgram,
"LiveContent" => NamedKey::LiveContent,
"Lock" => NamedKey::Lock,
"MediaApps" => NamedKey::MediaApps,
"MediaAudioTrack" => NamedKey::MediaAudioTrack,
"MediaLast" => NamedKey::MediaLast,
"MediaSkipBackward" => NamedKey::MediaSkipBackward,
"MediaSkipForward" => NamedKey::MediaSkipForward,
"MediaStepBackward" => NamedKey::MediaStepBackward,
"MediaStepForward" => NamedKey::MediaStepForward,
"MediaTopMenu" => NamedKey::MediaTopMenu,
"NavigateIn" => NamedKey::NavigateIn,
"NavigateNext" => NamedKey::NavigateNext,
"NavigateOut" => NamedKey::NavigateOut,
"NavigatePrevious" => NamedKey::NavigatePrevious,
"NextFavoriteChannel" => NamedKey::NextFavoriteChannel,
"NextUserProfile" => NamedKey::NextUserProfile,
"OnDemand" => NamedKey::OnDemand,
"Pairing" => NamedKey::Pairing,
"PinPDown" => NamedKey::PinPDown,
"PinPMove" => NamedKey::PinPMove,
"PinPToggle" => NamedKey::PinPToggle,
"PinPUp" => NamedKey::PinPUp,
"PlaySpeedDown" => NamedKey::PlaySpeedDown,
"PlaySpeedReset" => NamedKey::PlaySpeedReset,
"PlaySpeedUp" => NamedKey::PlaySpeedUp,
"RandomToggle" => NamedKey::RandomToggle,
"RcLowBattery" => NamedKey::RcLowBattery,
"RecordSpeedNext" => NamedKey::RecordSpeedNext,
"RfBypass" => NamedKey::RfBypass,
"ScanChannelsToggle" => NamedKey::ScanChannelsToggle,
"ScreenModeNext" => NamedKey::ScreenModeNext,
"Settings" => NamedKey::Settings,
"SplitScreenToggle" => NamedKey::SplitScreenToggle,
"STBInput" => NamedKey::STBInput,
"STBPower" => NamedKey::STBPower,
"Subtitle" => NamedKey::Subtitle,
"Teletext" => NamedKey::Teletext,
"VideoModeNext" => NamedKey::VideoModeNext,
"Wink" => NamedKey::Wink,
"ZoomToggle" => NamedKey::ZoomToggle,
"F1" => NamedKey::F1,
"F2" => NamedKey::F2,
"F3" => NamedKey::F3,
"F4" => NamedKey::F4,
"F5" => NamedKey::F5,
"F6" => NamedKey::F6,
"F7" => NamedKey::F7,
"F8" => NamedKey::F8,
"F9" => NamedKey::F9,
"F10" => NamedKey::F10,
"F11" => NamedKey::F11,
"F12" => NamedKey::F12,
"F13" => NamedKey::F13,
"F14" => NamedKey::F14,
"F15" => NamedKey::F15,
"F16" => NamedKey::F16,
"F17" => NamedKey::F17,
"F18" => NamedKey::F18,
"F19" => NamedKey::F19,
"F20" => NamedKey::F20,
"F21" => NamedKey::F21,
"F22" => NamedKey::F22,
"F23" => NamedKey::F23,
"F24" => NamedKey::F24,
"F25" => NamedKey::F25,
"F26" => NamedKey::F26,
"F27" => NamedKey::F27,
"F28" => NamedKey::F28,
"F29" => NamedKey::F29,
"F30" => NamedKey::F30,
"F31" => NamedKey::F31,
"F32" => NamedKey::F32,
"F33" => NamedKey::F33,
"F34" => NamedKey::F34,
"F35" => NamedKey::F35,
string => return Key::Character(SmolStr::new(string)),
})
}
}
impl PhysicalKey {
pub fn from_key_code_attribute_value(kcav: &str) -> Self {
PhysicalKey::Code(match kcav {
"Backquote" => KeyCode::Backquote,
"Backslash" => KeyCode::Backslash,
"BracketLeft" => KeyCode::BracketLeft,
"BracketRight" => KeyCode::BracketRight,
"Comma" => KeyCode::Comma,
"Digit0" => KeyCode::Digit0,
"Digit1" => KeyCode::Digit1,
"Digit2" => KeyCode::Digit2,
"Digit3" => KeyCode::Digit3,
"Digit4" => KeyCode::Digit4,
"Digit5" => KeyCode::Digit5,
"Digit6" => KeyCode::Digit6,
"Digit7" => KeyCode::Digit7,
"Digit8" => KeyCode::Digit8,
"Digit9" => KeyCode::Digit9,
"Equal" => KeyCode::Equal,
"IntlBackslash" => KeyCode::IntlBackslash,
"IntlRo" => KeyCode::IntlRo,
"IntlYen" => KeyCode::IntlYen,
"KeyA" => KeyCode::KeyA,
"KeyB" => KeyCode::KeyB,
"KeyC" => KeyCode::KeyC,
"KeyD" => KeyCode::KeyD,
"KeyE" => KeyCode::KeyE,
"KeyF" => KeyCode::KeyF,
"KeyG" => KeyCode::KeyG,
"KeyH" => KeyCode::KeyH,
"KeyI" => KeyCode::KeyI,
"KeyJ" => KeyCode::KeyJ,
"KeyK" => KeyCode::KeyK,
"KeyL" => KeyCode::KeyL,
"KeyM" => KeyCode::KeyM,
"KeyN" => KeyCode::KeyN,
"KeyO" => KeyCode::KeyO,
"KeyP" => KeyCode::KeyP,
"KeyQ" => KeyCode::KeyQ,
"KeyR" => KeyCode::KeyR,
"KeyS" => KeyCode::KeyS,
"KeyT" => KeyCode::KeyT,
"KeyU" => KeyCode::KeyU,
"KeyV" => KeyCode::KeyV,
"KeyW" => KeyCode::KeyW,
"KeyX" => KeyCode::KeyX,
"KeyY" => KeyCode::KeyY,
"KeyZ" => KeyCode::KeyZ,
"Minus" => KeyCode::Minus,
"Period" => KeyCode::Period,
"Quote" => KeyCode::Quote,
"Semicolon" => KeyCode::Semicolon,
"Slash" => KeyCode::Slash,
"AltLeft" => KeyCode::AltLeft,
"AltRight" => KeyCode::AltRight,
"Backspace" => KeyCode::Backspace,
"CapsLock" => KeyCode::CapsLock,
"ContextMenu" => KeyCode::ContextMenu,
"ControlLeft" => KeyCode::ControlLeft,
"ControlRight" => KeyCode::ControlRight,
"Enter" => KeyCode::Enter,
"MetaLeft" => KeyCode::MetaLeft,
"MetaRight" => KeyCode::MetaRight,
"ShiftLeft" => KeyCode::ShiftLeft,
"ShiftRight" => KeyCode::ShiftRight,
"Space" => KeyCode::Space,
"Tab" => KeyCode::Tab,
"Convert" => KeyCode::Convert,
"KanaMode" => KeyCode::KanaMode,
"Lang1" => KeyCode::Lang1,
"Lang2" => KeyCode::Lang2,
"Lang3" => KeyCode::Lang3,
"Lang4" => KeyCode::Lang4,
"Lang5" => KeyCode::Lang5,
"NonConvert" => KeyCode::NonConvert,
"Delete" => KeyCode::Delete,
"End" => KeyCode::End,
"Help" => KeyCode::Help,
"Home" => KeyCode::Home,
"Insert" => KeyCode::Insert,
"PageDown" => KeyCode::PageDown,
"PageUp" => KeyCode::PageUp,
"ArrowDown" => KeyCode::ArrowDown,
"ArrowLeft" => KeyCode::ArrowLeft,
"ArrowRight" => KeyCode::ArrowRight,
"ArrowUp" => KeyCode::ArrowUp,
"NumLock" => KeyCode::NumLock,
"Numpad0" => KeyCode::Numpad0,
"Numpad1" => KeyCode::Numpad1,
"Numpad2" => KeyCode::Numpad2,
"Numpad3" => KeyCode::Numpad3,
"Numpad4" => KeyCode::Numpad4,
"Numpad5" => KeyCode::Numpad5,
"Numpad6" => KeyCode::Numpad6,
"Numpad7" => KeyCode::Numpad7,
"Numpad8" => KeyCode::Numpad8,
"Numpad9" => KeyCode::Numpad9,
"NumpadAdd" => KeyCode::NumpadAdd,
"NumpadBackspace" => KeyCode::NumpadBackspace,
"NumpadClear" => KeyCode::NumpadClear,
"NumpadClearEntry" => KeyCode::NumpadClearEntry,
"NumpadComma" => KeyCode::NumpadComma,
"NumpadDecimal" => KeyCode::NumpadDecimal,
"NumpadDivide" => KeyCode::NumpadDivide,
"NumpadEnter" => KeyCode::NumpadEnter,
"NumpadEqual" => KeyCode::NumpadEqual,
"NumpadHash" => KeyCode::NumpadHash,
"NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd,
"NumpadMemoryClear" => KeyCode::NumpadMemoryClear,
"NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall,
"NumpadMemoryStore" => KeyCode::NumpadMemoryStore,
"NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract,
"NumpadMultiply" => KeyCode::NumpadMultiply,
"NumpadParenLeft" => KeyCode::NumpadParenLeft,
"NumpadParenRight" => KeyCode::NumpadParenRight,
"NumpadStar" => KeyCode::NumpadStar,
"NumpadSubtract" => KeyCode::NumpadSubtract,
"Escape" => KeyCode::Escape,
"Fn" => KeyCode::Fn,
"FnLock" => KeyCode::FnLock,
"PrintScreen" => KeyCode::PrintScreen,
"ScrollLock" => KeyCode::ScrollLock,
"Pause" => KeyCode::Pause,
"BrowserBack" => KeyCode::BrowserBack,
"BrowserFavorites" => KeyCode::BrowserFavorites,
"BrowserForward" => KeyCode::BrowserForward,
"BrowserHome" => KeyCode::BrowserHome,
"BrowserRefresh" => KeyCode::BrowserRefresh,
"BrowserSearch" => KeyCode::BrowserSearch,
"BrowserStop" => KeyCode::BrowserStop,
"Eject" => KeyCode::Eject,
"LaunchApp1" => KeyCode::LaunchApp1,
"LaunchApp2" => KeyCode::LaunchApp2,
"LaunchMail" => KeyCode::LaunchMail,
"MediaPlayPause" => KeyCode::MediaPlayPause,
"MediaSelect" => KeyCode::MediaSelect,
"MediaStop" => KeyCode::MediaStop,
"MediaTrackNext" => KeyCode::MediaTrackNext,
"MediaTrackPrevious" => KeyCode::MediaTrackPrevious,
"Power" => KeyCode::Power,
"Sleep" => KeyCode::Sleep,
"AudioVolumeDown" => KeyCode::AudioVolumeDown,
"AudioVolumeMute" => KeyCode::AudioVolumeMute,
"AudioVolumeUp" => KeyCode::AudioVolumeUp,
"WakeUp" => KeyCode::WakeUp,
#[allow(deprecated)]
"Super" => KeyCode::Super,
#[allow(deprecated)]
"Hyper" => KeyCode::Hyper,
#[allow(deprecated)]
"Turbo" => KeyCode::Turbo,
"Abort" => KeyCode::Abort,
"Resume" => KeyCode::Resume,
"Suspend" => KeyCode::Suspend,
"Again" => KeyCode::Again,
"Copy" => KeyCode::Copy,
"Cut" => KeyCode::Cut,
"Find" => KeyCode::Find,
"Open" => KeyCode::Open,
"Paste" => KeyCode::Paste,
"Props" => KeyCode::Props,
"Select" => KeyCode::Select,
"Undo" => KeyCode::Undo,
"Hiragana" => KeyCode::Hiragana,
"Katakana" => KeyCode::Katakana,
"F1" => KeyCode::F1,
"F2" => KeyCode::F2,
"F3" => KeyCode::F3,
"F4" => KeyCode::F4,
"F5" => KeyCode::F5,
"F6" => KeyCode::F6,
"F7" => KeyCode::F7,
"F8" => KeyCode::F8,
"F9" => KeyCode::F9,
"F10" => KeyCode::F10,
"F11" => KeyCode::F11,
"F12" => KeyCode::F12,
"F13" => KeyCode::F13,
"F14" => KeyCode::F14,
"F15" => KeyCode::F15,
"F16" => KeyCode::F16,
"F17" => KeyCode::F17,
"F18" => KeyCode::F18,
"F19" => KeyCode::F19,
"F20" => KeyCode::F20,
"F21" => KeyCode::F21,
"F22" => KeyCode::F22,
"F23" => KeyCode::F23,
"F24" => KeyCode::F24,
"F25" => KeyCode::F25,
"F26" => KeyCode::F26,
"F27" => KeyCode::F27,
"F28" => KeyCode::F28,
"F29" => KeyCode::F29,
"F30" => KeyCode::F30,
"F31" => KeyCode::F31,
"F32" => KeyCode::F32,
"F33" => KeyCode::F33,
"F34" => KeyCode::F34,
"F35" => KeyCode::F35,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
})
}
}

View File

@@ -1,44 +0,0 @@
// Brief introduction to the internals of the Web backend:
// The Web backend used to support both wasm-bindgen and stdweb as methods of binding to the
// environment. Because they are both supporting the same underlying APIs, the actual Web bindings
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
//
// When adding support for new events or interactions with the browser, first consult trusted
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
// Once you have decided on the relevant Web APIs, add support to both backends.
//
// The backend is used by the rest of the module to implement Winit's business logic, which forms
// the rest of the code. 'device', 'error', 'monitor', and 'window' define Web-specific structures
// for winit's cross-platform structures. They are all relatively simple translations.
//
// The event_loop module handles listening for and processing events. 'Proxy' implements
// EventLoopProxy and 'WindowTarget' implements ActiveEventLoop. WindowTarget also handles
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
// compliant way.
// TODO: FP, remove when <https://github.com/rust-lang/rust-clippy/issues/12377> is fixed.
#![allow(clippy::empty_docs)]
mod r#async;
mod cursor;
mod error;
mod event;
mod event_loop;
mod keyboard;
mod lock;
mod main_thread;
mod monitor;
mod web_sys;
mod window;
pub(crate) use cursor::CustomCursorFuture;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{
HasMonitorPermissionFuture, MonitorHandle, MonitorPermissionFuture, OrientationLockFuture,
};
use self::web_sys as backend;
pub use self::window::{PlatformSpecificWindowAttributes, Window};

View File

@@ -1,126 +0,0 @@
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
pub(crate) use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
pub(crate) use self::icon::{RaiiIcon, SelectedCursor};
pub(crate) use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::Window;
use crate::event::DeviceId;
use crate::icon::Icon;
use crate::platform::windows::{BackdropType, Color, CornerPreference};
#[derive(Clone, Debug)]
pub struct PlatformSpecificWindowAttributes {
pub owner: Option<HWND>,
pub menu: Option<HMENU>,
pub taskbar_icon: Option<Icon>,
pub no_redirection_bitmap: bool,
pub drag_and_drop: bool,
pub skip_taskbar: bool,
pub class_name: String,
pub decoration_shadow: bool,
pub backdrop_type: BackdropType,
pub clip_children: bool,
pub border_color: Option<Color>,
pub title_background_color: Option<Color>,
pub title_text_color: Option<Color>,
pub corner_preference: Option<CornerPreference>,
}
impl Default for PlatformSpecificWindowAttributes {
fn default() -> Self {
Self {
owner: None,
menu: None,
taskbar_icon: None,
no_redirection_bitmap: false,
drag_and_drop: true,
skip_taskbar: false,
class_name: "Window Class".to_string(),
decoration_shadow: false,
backdrop_type: BackdropType::default(),
clip_children: true,
border_color: None,
title_background_color: None,
title_text_color: None,
corner_preference: None,
}
}
}
unsafe impl Send for PlatformSpecificWindowAttributes {}
unsafe impl Sync for PlatformSpecificWindowAttributes {}
fn wrap_device_id(id: u32) -> DeviceId {
DeviceId::from_raw(id as i64)
}
#[inline(always)]
const fn get_xbutton_wparam(x: u32) -> u16 {
hiword(x)
}
#[inline(always)]
const fn get_x_lparam(x: u32) -> i16 {
loword(x) as _
}
#[inline(always)]
const fn get_y_lparam(x: u32) -> i16 {
hiword(x) as _
}
#[inline(always)]
pub(crate) const fn primarylangid(lgid: u16) -> u16 {
lgid & 0x3ff
}
#[inline(always)]
pub(crate) const fn loword(x: u32) -> u16 {
(x & 0xffff) as u16
}
#[inline(always)]
const fn hiword(x: u32) -> u16 {
((x >> 16) & 0xffff) as u16
}
#[inline(always)]
unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
#[cfg(target_pointer_width = "64")]
return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW(hwnd, nindex) };
#[cfg(target_pointer_width = "32")]
return unsafe {
windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongW(hwnd, nindex) as isize
};
}
#[inline(always)]
unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize {
#[cfg(target_pointer_width = "64")]
return unsafe {
windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW(hwnd, nindex, dwnewlong)
};
#[cfg(target_pointer_width = "32")]
return unsafe {
windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongW(hwnd, nindex, dwnewlong as i32)
as isize
};
}
#[macro_use]
mod util;
mod dark_mode;
mod definitions;
mod dpi;
mod drop_handler;
mod event_loop;
mod icon;
mod ime;
mod keyboard;
mod keyboard_layout;
mod monitor;
pub(crate) mod raw_input;
mod window;
mod window_state;

35
winit-android/Cargo.toml Normal file
View File

@@ -0,0 +1,35 @@
[package]
description = "Winit's Android backend"
documentation = "https://docs.rs/winit-android"
edition.workspace = true
license.workspace = true
name = "winit-android"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
game-activity = ["android-activity/game-activity"]
native-activity = ["android-activity/native-activity"]
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde", "winit-core/serde"]
[dependencies]
bitflags.workspace = true
dpi.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
tracing.workspace = true
winit-core.workspace = true
# Platform-specific
[target.'cfg(target_os = "android")'.dependencies]
android-activity.workspace = true
ndk.workspace = true
[dev-dependencies]
winit.workspace = true
[package.metadata.docs.rs]
features = ["serde", "native-activity"]
targets = ["aarch64-linux-android"]

1
winit-android/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

1
winit-android/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -1,4 +1,5 @@
use std::cell::Cell; use std::cell::Cell;
use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -8,26 +9,26 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{ use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
}; };
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
use winit_core::application::ApplicationHandler;
use crate::application::ApplicationHandler; use winit_core::cursor::{Cursor, CustomCursor, CustomCursorSource};
use crate::cursor::Cursor; use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; use winit_core::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use crate::error::{EventLoopError, NotSupportedError, RequestError}; use winit_core::event_loop::pump_events::PumpStatus;
use crate::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter}; use winit_core::event_loop::{
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle, OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::platform::pump_events::PumpStatus; use winit_core::window::{
use crate::window::{ self, CursorGrabMode, ImeCapabilities, ImePurpose, ImeRequest, ImeRequestError,
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowLevel,
}; };
mod keycodes; use crate::keycodes;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true); static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@@ -43,7 +44,7 @@ struct SharedFlagSetter {
flag: Arc<AtomicBool>, flag: Arc<AtomicBool>,
} }
impl SharedFlagSetter { impl SharedFlagSetter {
pub fn set(&self) -> bool { fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok() self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
} }
} }
@@ -58,21 +59,21 @@ struct SharedFlag {
// we just need to know at the start of a main loop iteration if a redraw // we just need to know at the start of a main loop iteration if a redraw
// was queued and be able to read and clear the state atomically) // was queued and be able to read and clear the state atomically)
impl SharedFlag { impl SharedFlag {
pub fn new() -> Self { fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) } Self { flag: Arc::new(AtomicBool::new(false)) }
} }
pub fn setter(&self) -> SharedFlagSetter { fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() } SharedFlagSetter { flag: self.flag.clone() }
} }
pub fn get_and_reset(&self) -> bool { fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel) self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct RedrawRequester { struct RedrawRequester {
flag: SharedFlagSetter, flag: SharedFlagSetter,
waker: AndroidAppWaker, waker: AndroidAppWaker,
} }
@@ -88,7 +89,7 @@ impl RedrawRequester {
RedrawRequester { flag: flag.setter(), waker } RedrawRequester { flag: flag.setter(), waker }
} }
pub fn request_redraw(&self) { fn request_redraw(&self) {
if self.flag.set() { if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag // Only explicitly try to wake up the main loop when the flag
// value changes // value changes
@@ -99,7 +100,7 @@ impl RedrawRequester {
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
pub(crate) android_app: AndroidApp, pub android_app: AndroidApp,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
redraw_flag: SharedFlag, redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>` loop_running: bool, // Dispatched `NewEvents<Init>`
@@ -112,9 +113,9 @@ pub struct EventLoop {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes { pub struct PlatformSpecificEventLoopAttributes {
pub(crate) android_app: Option<AndroidApp>, pub android_app: Option<AndroidApp>,
pub(crate) ignore_volume_keys: bool, pub ignore_volume_keys: bool,
} }
impl Default for PlatformSpecificEventLoopAttributes { impl Default for PlatformSpecificEventLoopAttributes {
@@ -127,9 +128,13 @@ impl Default for PlatformSpecificEventLoopAttributes {
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0); const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop { impl EventLoop {
pub(crate) fn new( pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
attributes: &PlatformSpecificEventLoopAttributes, static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
) -> Result<Self, EventLoopError> { if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
// For better cross-platformness.
return Err(EventLoopError::RecreationAttempt);
}
let android_app = attributes.android_app.as_ref().expect( let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \ "An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android", Android",
@@ -159,7 +164,7 @@ impl EventLoop {
}) })
} }
pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop { pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target &self.window_target
} }
@@ -468,16 +473,23 @@ impl EventLoop {
&mut self.combining_accent, &mut self.combining_accent,
); );
let logical_key = keycodes::to_logical(key_char, keycode);
let text = if state == event::ElementState::Pressed {
logical_key.to_text().map(smol_str::SmolStr::new)
} else {
None
};
let event = event::WindowEvent::KeyboardInput { let event = event::WindowEvent::KeyboardInput {
device_id: Some(DeviceId::from_raw(key.device_id() as i64)), device_id: Some(DeviceId::from_raw(key.device_id() as i64)),
event: event::KeyEvent { event: event::KeyEvent {
state, state,
physical_key: keycodes::to_physical_key(keycode), physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode), logical_key,
location: keycodes::to_location(keycode), location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0, repeat: key.repeat_count() > 0,
text: None, text: text.clone(),
text_with_all_modifiers: None, text_with_all_modifiers: text,
key_without_modifiers: keycodes::to_logical(key_char, keycode), key_without_modifiers: keycodes::to_logical(key_char, keycode),
}, },
is_synthetic: false, is_synthetic: false,
@@ -495,10 +507,6 @@ impl EventLoop {
input_status input_status
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
mut app: A, mut app: A,
@@ -752,8 +760,9 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
pub struct PlatformSpecificWindowAttributes; pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Window { pub struct Window {
app: AndroidApp, app: AndroidApp,
ime_capabilities: Mutex<Option<ImeCapabilities>>,
redraw_requester: RedrawRequester, redraw_requester: RedrawRequester,
} }
@@ -764,14 +773,18 @@ impl Window {
) -> Result<Self, RequestError> { ) -> Result<Self, RequestError> {
// FIXME this ignores requested window attributes // FIXME this ignores requested window attributes
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() }) Ok(Self {
app: el.app.clone(),
ime_capabilities: Default::default(),
redraw_requester: el.redraw_requester.clone(),
})
} }
pub fn config(&self) -> ConfigurationRef { pub(crate) fn config(&self) -> ConfigurationRef {
self.app.config() self.app.config()
} }
pub fn content_rect(&self) -> Rect { pub(crate) fn content_rect(&self) -> Rect {
self.app.content_rect() self.app.content_rect()
} }
@@ -928,16 +941,37 @@ impl CoreWindow for Window {
fn set_window_level(&self, _level: WindowLevel) {} fn set_window_level(&self, _level: WindowLevel) {}
fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {} fn set_window_icon(&self, _window_icon: Option<winit_core::icon::Icon>) {}
fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
fn set_ime_allowed(&self, allowed: bool) { fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
if allowed { let mut current_caps = self.ime_capabilities.lock().unwrap();
self.app.show_soft_input(true); match request {
} else { ImeRequest::Enable(enable) => {
self.app.hide_soft_input(true); let (capabilities, _) = enable.into_raw();
if current_caps.is_some() {
return Err(ImeRequestError::AlreadyEnabled);
}
*current_caps = Some(capabilities);
self.app.show_soft_input(true);
},
ImeRequest::Update(_) => {
if current_caps.is_none() {
return Err(ImeRequestError::NotEnabled);
}
},
ImeRequest::Disable => {
*current_caps = None;
self.app.hide_soft_input(true);
},
} }
Ok(())
}
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
*self.ime_capabilities.lock().unwrap()
} }
fn set_ime_purpose(&self, _purpose: ImePurpose) {} fn set_ime_purpose(&self, _purpose: ImePurpose) {}
@@ -1000,16 +1034,6 @@ impl CoreWindow for Window {
} }
} }
#[derive(Default, Clone, Debug)]
pub struct OsError;
use std::fmt::{self, Display, Formatter};
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
}
}
fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> { fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() { if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _) PhysicalSize::new(native_window.width() as _, native_window.height() as _)

View File

@@ -1,7 +1,8 @@
use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode};
use android_activity::AndroidApp; use android_activity::AndroidApp;
use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; use winit_core::keyboard::{
Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
};
pub fn to_physical_key(keycode: Keycode) -> PhysicalKey { pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
PhysicalKey::Code(match keycode { PhysicalKey::Code(match keycode {
@@ -108,6 +109,7 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::MediaStop => KeyCode::MediaStop, Keycode::MediaStop => KeyCode::MediaStop,
Keycode::MediaNext => KeyCode::MediaTrackNext, Keycode::MediaNext => KeyCode::MediaTrackNext,
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious, Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
Keycode::MediaEject => KeyCode::Eject,
Keycode::Plus => KeyCode::Equal, Keycode::Plus => KeyCode::Equal,
Keycode::Minus => KeyCode::Minus, Keycode::Minus => KeyCode::Minus,
@@ -130,7 +132,11 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
// These are exactly the same // These are exactly the same
Keycode::ScrollLock => KeyCode::ScrollLock, Keycode::ScrollLock => KeyCode::ScrollLock,
Keycode::Eisu => KeyCode::Lang2,
Keycode::Muhenkan => KeyCode::NonConvert,
Keycode::Henkan => KeyCode::Convert,
Keycode::Yen => KeyCode::IntlYen, Keycode::Yen => KeyCode::IntlYen,
Keycode::Ro => KeyCode::IntlRo,
Keycode::Kana => KeyCode::Lang1, Keycode::Kana => KeyCode::Lang1,
Keycode::KatakanaHiragana => KeyCode::KanaMode, Keycode::KatakanaHiragana => KeyCode::KanaMode,
@@ -153,6 +159,14 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep? Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp, Keycode::Wakeup => KeyCode::WakeUp,
Keycode::CapsLock => KeyCode::CapsLock,
Keycode::Help => KeyCode::Help,
Keycode::Back => KeyCode::BrowserBack,
Keycode::Forward => KeyCode::BrowserForward,
Keycode::Refresh => KeyCode::BrowserRefresh,
Keycode::Search => KeyCode::BrowserSearch,
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())), keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
}) })
} }

View File

@@ -62,17 +62,26 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building //! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be: //! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml` //! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.10", //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version =
//! features = [ "android-native-activity" ] }` //! "0.31.0-beta.2", features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! 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 //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
//! logging as above). //! logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your //! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above). //! event loop (as shown above).
#![cfg(target_os = "android")]
mod event_loop;
mod keycodes;
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
use winit_core::window::Window as CoreWindow;
use self::activity::{AndroidApp, ConfigurationRef, Rect}; use self::activity::{AndroidApp, ConfigurationRef, Rect};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; pub use crate::event_loop::{
use crate::window::{Window, WindowAttributes}; ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
PlatformSpecificWindowAttributes, Window,
};
/// Additional methods on [`EventLoop`] that are specific to Android. /// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid { pub trait EventLoopExtAndroid {
@@ -80,12 +89,6 @@ pub trait EventLoopExtAndroid {
fn android_app(&self) -> &AndroidApp; fn android_app(&self) -> &AndroidApp;
} }
impl EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android. /// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid { pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop. /// Get the [`AndroidApp`] which was used to create this event loop.
@@ -99,30 +102,25 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef; fn config(&self) -> ConfigurationRef;
} }
impl WindowExtAndroid for dyn Window + '_ { impl WindowExtAndroid for dyn CoreWindow + '_ {
fn content_rect(&self) -> Rect { fn content_rect(&self) -> Rect {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<Window>().unwrap();
window.content_rect() window.content_rect()
} }
fn config(&self) -> ConfigurationRef { fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<Window>().unwrap();
window.config() window.config()
} }
} }
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtAndroid for dyn CoreActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp { fn android_app(&self) -> &AndroidApp {
let event_loop = self.cast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap(); let event_loop = self.cast_ref::<ActiveEventLoop>().unwrap();
&event_loop.app &event_loop.app
} }
} }
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid { pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop /// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
/// ///
@@ -135,18 +133,6 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self; fn handle_volume_keys(&mut self) -> &mut Self;
} }
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API /// Re-export of the `android_activity` API
/// ///
/// Winit re-exports the `android_activity` API for convenience so that most /// Winit re-exports the `android_activity` API for convenience so that most
@@ -175,16 +161,5 @@ pub mod activity {
// feature enabled, so we avoid inlining it so that they're forced to view // feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page. // it on the crate's own docs.rs page.
#[doc(no_inline)] #[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*; pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
} }

117
winit-appkit/Cargo.toml Normal file
View File

@@ -0,0 +1,117 @@
[package]
description = "Winit's Appkit / macOS backend"
documentation = "https://docs.rs/winit-appkit"
edition.workspace = true
license.workspace = true
name = "winit-appkit"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde"]
[dependencies]
bitflags.workspace = true
dpi.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
tracing.workspace = true
winit-core.workspace = true
# Platform-specific
[target.'cfg(target_vendor = "apple")'.dependencies]
block2.workspace = true
dispatch2 = { workspace = true, features = ["std", "objc2"] }
objc2.workspace = true
objc2-app-kit = { workspace = true, features = [
"std",
"objc2-core-foundation",
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
"NSEvent",
"NSGraphics",
"NSGraphicsContext",
"NSImage",
"NSImageRep",
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPanel",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSTrackingArea",
"NSToolbar",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-core-foundation = { workspace = true, features = [
"std",
"block2",
"CFBase",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { workspace = true, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { workspace = true, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { workspace = true, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
[dev-dependencies]
winit.workspace = true
[package.metadata.docs.rs]
all-features = true
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin"]

1
winit-appkit/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

1
winit-appkit/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -1,17 +1,18 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell; use std::cell::Cell;
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::{mem, ptr};
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel}; use objc2::runtime::{Imp, Sel};
use objc2::sel; use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker; use objc2_foundation::MainThreadMarker;
use tracing::trace_span;
use winit_core::event::{DeviceEvent, ElementState};
use super::app_state::AppState; use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState};
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent); type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);
@@ -21,6 +22,10 @@ static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
}; };
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) { extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
// This can be a bit noisy, since `event` is fairly large. Note that you can use
// `RUST_LOG='trace,winit_appkit::app=warn'` if you're debugging and want TRACE-level logs but
// not this.
let _entered = trace_span!("sendEvent:", ?event).entered();
let mtm = MainThreadMarker::from(app); let mtm = MainThreadMarker::from(app);
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
@@ -30,8 +35,8 @@ extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent)
// For posterity, there are some undocumented event types // For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155) // (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here. // but that doesn't really matter here.
let event_type = unsafe { event.r#type() }; let event_type = event.r#type();
let modifier_flags = unsafe { event.modifierFlags() }; let modifier_flags = event.modifierFlags();
if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) { if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) {
if let Some(key_window) = app.keyWindow() { if let Some(key_window) = app.keyWindow() {
key_window.sendEvent(event); key_window.sendEvent(event);
@@ -75,9 +80,7 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) }; let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything. // 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. if ptr::fn_addr_eq(overridden, method.implementation()) {
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
return; return;
} }
@@ -98,15 +101,15 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
} }
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) { fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let event_type = unsafe { event.r#type() }; let event_type = event.r#type();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match event_type { match event_type {
NSEventType::MouseMoved NSEventType::MouseMoved
| NSEventType::LeftMouseDragged | NSEventType::LeftMouseDragged
| NSEventType::OtherMouseDragged | NSEventType::OtherMouseDragged
| NSEventType::RightMouseDragged => { | NSEventType::RightMouseDragged => {
let delta_x = unsafe { event.deltaX() } as f64; let delta_x = event.deltaX() as f64;
let delta_y = unsafe { event.deltaY() } as f64; let delta_y = event.deltaY() as f64;
if delta_x != 0.0 || delta_y != 0.0 { if delta_x != 0.0 || delta_y != 0.0 {
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
@@ -117,7 +120,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
} }
}, },
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = unsafe { event.buttonNumber() } as u32; let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button { app.device_event(event_loop, None, DeviceEvent::Button {
button, button,
@@ -126,7 +129,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
}); });
}, },
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = unsafe { event.buttonNumber() } as u32; let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button { app.device_event(event_loop, None, DeviceEvent::Button {
button, button,
@@ -141,7 +144,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{define_class, msg_send, ClassType}; use objc2::{ClassType, define_class, msg_send};
use objc2_app_kit::NSResponder; use objc2_app_kit::NSResponder;
use objc2_foundation::NSObject; use objc2_foundation::NSObject;
@@ -153,16 +156,14 @@ mod tests {
let Some(mtm) = MainThreadMarker::new() else { return }; let Some(mtm) = MainThreadMarker::new() else { return };
// Create a new application, without making it the shared application. // Create a new application, without making it the shared application.
let app = unsafe { NSApplication::new(mtm) }; let app = NSApplication::new(mtm);
override_send_event(&app); override_send_event(&app);
// Test calling twice works. // Test calling twice works.
override_send_event(&app); override_send_event(&app);
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState. // FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
// unsafe { // let event = super::super::event::dummy_event().unwrap();
// let event = super::super::event::dummy_event().unwrap(); // app.sendEvent(&event)
// app.sendEvent(&event)
// }
} }
#[test] #[test]

View File

@@ -1,6 +1,6 @@
use std::cell::{Cell, OnceCell, RefCell}; use std::cell::{Cell, OnceCell, RefCell};
use std::mem; use std::mem;
use std::rc::{Rc, Weak}; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
@@ -8,16 +8,16 @@ use dispatch2::MainThreadBound;
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification; use objc2_foundation::NSNotification;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
use winit_core::event::{StartCause, WindowEvent};
use winit_core::event_loop::ControlFlow;
use winit_core::window::WindowId;
use super::super::event_handler::EventHandler; use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately};
use super::super::event_loop_proxy::EventLoopProxy;
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::menu; use super::menu;
use super::observer::{EventLoopWaker, RunLoop}; use super::observer::EventLoopWaker;
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
#[derive(Debug)] #[derive(Debug)]
pub(super) struct AppState { pub(super) struct AppState {
@@ -25,7 +25,7 @@ pub(super) struct AppState {
activation_policy: Option<NSApplicationActivationPolicy>, activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
run_loop: RunLoop, run_loop: MainRunLoop,
event_loop_proxy: Arc<EventLoopProxy>, event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler, event_handler: EventHandler,
stop_on_launch: Cell<bool>, stop_on_launch: Cell<bool>,
@@ -58,7 +58,7 @@ impl AppState {
activation_policy: Option<NSApplicationActivationPolicy>, activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
) -> Rc<Self> { ) -> Option<Rc<Self>> {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || { let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
})); }));
@@ -68,7 +68,7 @@ impl AppState {
activation_policy, activation_policy,
default_menu, default_menu,
activate_ignoring_other_apps, activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm), run_loop: MainRunLoop::get(mtm),
event_loop_proxy, event_loop_proxy,
event_handler: EventHandler::new(), event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false), stop_on_launch: Cell::new(false),
@@ -85,8 +85,7 @@ impl AppState {
pending_redraw: RefCell::new(vec![]), pending_redraw: RefCell::new(vec![]),
}); });
GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once"); GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
this
} }
pub fn get(mtm: MainThreadMarker) -> Rc<Self> { pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
@@ -100,7 +99,6 @@ impl AppState {
// NOTE: This notification will, globally, only be emitted once, // NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates. // no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) { pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationDidFinishLaunchingNotification");
self.is_launched.set(true); self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
@@ -118,7 +116,7 @@ impl AppState {
// - https://github.com/rust-windowing/winit/issues/261 // - https://github.com/rust-windowing/winit/issues/261
// - https://github.com/rust-windowing/winit/issues/3958 // - https://github.com/rust-windowing/winit/issues/3958
let is_bundled = let is_bundled =
unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() }; NSRunningApplication::currentApplication().bundleIdentifier().is_some();
if !is_bundled { if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular); app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
} }
@@ -155,7 +153,6 @@ impl AppState {
} }
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) { pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification");
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
notify_windows_of_exit(&app); notify_windows_of_exit(&app);
self.event_handler.terminate(); self.event_handler.terminate();
@@ -266,7 +263,7 @@ impl AppState {
if !pending_redraw.contains(&window_id) { if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id); pending_redraw.push(window_id);
} }
self.run_loop.wakeup(); self.run_loop.wake_up();
} }
#[track_caller] #[track_caller]
@@ -309,13 +306,10 @@ impl AppState {
} }
// Called by RunLoopObserver after finishing waiting for new events // Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) { pub fn wakeup(self: &Rc<Self>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779 // 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() { // (we have registered to observe all modes, including modal event loops).
if !self.event_handler.ready() || !self.is_running() {
return; return;
} }
@@ -341,15 +335,10 @@ impl AppState {
} }
// Called by RunLoopObserver before waiting for new events // Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) { pub fn cleared(self: &Rc<Self>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779 // 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 have registered to observe all modes, including modal event loops).
// we're about to return to the `CFRunLoop` to poll for new events? if !self.event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
return; return;
} }

View File

@@ -4,15 +4,16 @@ use std::sync::OnceLock;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType}; use objc2::{AllocAnyThread, ClassType, available, msg_send, sel};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_app_kit::{
use objc2_foundation::{ NSBitmapImageRep, NSCursor, NSCursorFrameResizeDirections, NSCursorFrameResizePosition,
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, NSDeviceRGBColorSpace, NSImage,
}; };
use objc2_foundation::{
use crate::cursor::{CursorImage, CustomCursorProvider, CustomCursorSource}; NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string,
use crate::error::{NotSupportedError, RequestError}; };
use crate::window::CursorIcon; use winit_core::cursor::{CursorIcon, CursorImage, CustomCursorProvider, CustomCursorSource};
use winit_core::error::{NotSupportedError, RequestError};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Retained<NSCursor>); pub struct CustomCursor(pub(crate) Retained<NSCursor>);
@@ -33,7 +34,7 @@ impl CustomCursor {
let cursor = match cursor { let cursor = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image, CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => { CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into()) return Err(NotSupportedError::new("unsupported cursor kind").into());
}, },
}; };
@@ -42,8 +43,8 @@ impl CustomCursor {
} }
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, RequestError> { pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, RequestError> {
let width = cursor.width; let width = cursor.width();
let height = cursor.height; let height = cursor.height();
let bitmap = unsafe { let bitmap = unsafe {
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel( NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
@@ -60,15 +61,14 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCurso
32, 32,
) )
}.ok_or_else(|| os_error!("parent view should be installed in a window"))?; }.ok_or_else(|| os_error!("parent view should be installed in a window"))?;
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) }; let bitmap_data =
bitmap_data.copy_from_slice(&cursor.rgba); unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.buffer().len()) };
bitmap_data.copy_from_slice(cursor.buffer());
let image = unsafe { let image = NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()));
NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into())) image.addRepresentation(&bitmap);
};
unsafe { image.addRepresentation(&bitmap) };
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64); let hotspot = NSPoint::new(cursor.hotspot_x() as f64, cursor.hotspot_y() as f64);
Ok(NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)) Ok(NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot))
} }
@@ -141,12 +141,9 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// TODO: Handle PLists better // TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe { #[allow(deprecated)]
msg_send![ let info: Retained<NSDictionary<NSObject, NSObject>> =
<NSDictionary<NSObject, NSObject>>::class(), unsafe { NSDictionary::dictionaryWithContentsOfFile(&info_path) }.unwrap();
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0; let mut x = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hotx")) { if let Some(n) = info.objectForKey(ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() { if let Ok(n) = n.downcast::<NSNumber>() {
@@ -210,23 +207,155 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(), CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(), CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
CursorIcon::Crosshair => NSCursor::crosshairCursor(), CursorIcon::Crosshair => NSCursor::crosshairCursor(),
CursorIcon::EResize => NSCursor::resizeRightCursor(), CursorIcon::EResize => {
CursorIcon::NResize => NSCursor::resizeUpCursor(), if available!(macos = 15.0) {
CursorIcon::WResize => NSCursor::resizeLeftCursor(), NSCursor::frameResizeCursorFromPosition_inDirections(
CursorIcon::SResize => NSCursor::resizeDownCursor(), NSCursorFrameResizePosition::Right,
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(), NSCursorFrameResizeDirections::Outward,
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(), )
} else {
NSCursor::resizeRightCursor()
}
},
CursorIcon::NResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Top,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeUpCursor()
}
},
CursorIcon::WResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Left,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeLeftCursor()
}
},
CursorIcon::SResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Bottom,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeDownCursor()
}
},
CursorIcon::EwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Right,
NSCursorFrameResizeDirections::All,
)
} else {
NSCursor::resizeLeftRightCursor()
}
},
CursorIcon::NsResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Top,
NSCursorFrameResizeDirections::All,
)
} else {
NSCursor::resizeUpDownCursor()
}
},
CursorIcon::NeResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopRight,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeNorthEastCursor()
}
},
CursorIcon::NwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopLeft,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeNorthWestCursor()
}
},
CursorIcon::SeResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::BottomRight,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeSouthEastCursor()
}
},
CursorIcon::SwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::BottomLeft,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeSouthWestCursor()
}
},
CursorIcon::NeswResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopRight,
NSCursorFrameResizeDirections::All,
)
} else {
_windowResizeNorthEastSouthWestCursor()
}
},
CursorIcon::NwseResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopLeft,
NSCursorFrameResizeDirections::All,
)
} else {
_windowResizeNorthWestSouthEastCursor()
}
},
CursorIcon::ColResize => {
if available!(macos = 15.0) {
NSCursor::columnResizeCursor()
} else {
NSCursor::resizeLeftRightCursor()
}
},
CursorIcon::RowResize => {
if available!(macos = 15.0) {
NSCursor::rowResizeCursor()
} else {
NSCursor::resizeUpDownCursor()
}
},
CursorIcon::ZoomIn => {
if available!(macos = 15.0) {
NSCursor::zoomInCursor()
} else {
_zoomInCursor()
}
},
CursorIcon::ZoomOut => {
if available!(macos = 15.0) {
NSCursor::zoomOutCursor()
} else {
_zoomOutCursor()
}
},
CursorIcon::Help => _helpCursor(), 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(),
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
// This is the wrong semantics for `Wait`, but it's the same as // This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome. // what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(), CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),

View File

@@ -6,14 +6,14 @@ use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_core_foundation::{CFData, CFRetained}; use objc2_core_foundation::{CFData, CFRetained};
use objc2_foundation::NSPoint; use objc2_foundation::NSPoint;
use smol_str::SmolStr; use smol_str::SmolStr;
use winit_core::event::{ElementState, KeyEvent, Modifiers};
use super::ffi; use winit_core::keyboard::{
use crate::event::{ElementState, KeyEvent, Modifiers};
use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey, PhysicalKey,
}; };
use super::ffi;
/// Ignores ALL modifiers. /// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key { pub fn get_modifierless_char(scancode: u16) -> Key {
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else { let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
@@ -67,9 +67,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
// Ignores all modifiers except for SHIFT (yes, even ALT is ignored). // Ignores all modifiers except for SHIFT (yes, even ALT is ignored).
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
let string = unsafe { ns_event.charactersIgnoringModifiers() } let string = ns_event.charactersIgnoringModifiers().map(|s| s.to_string()).unwrap_or_default();
.map(|s| s.to_string())
.unwrap_or_default();
if string.is_empty() { if string.is_empty() {
// Probably a dead key // Probably a dead key
let first_char = modifierless_chars.chars().next(); let first_char = modifierless_chars.chars().next();
@@ -85,7 +83,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
use ElementState::{Pressed, Released}; use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released }; let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() }; let scancode = ns_event.keyCode();
let mut physical_key = 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. // NOTE: The logical key should heed both SHIFT and ALT if possible.
@@ -95,7 +93,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
// * Pressing CTRL SHIFT A: logical key should also be "A" // * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best. // This is not easy to tease out of `NSEvent`, but we do our best.
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); let characters = ns_event.characters().map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() { let text_with_all_modifiers = if characters.is_empty() {
None None
} else { } else {
@@ -111,13 +109,13 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
// `get_modifierless_char/key_without_modifiers` ignores ALL modifiers. // `get_modifierless_char/key_without_modifiers` ignores ALL modifiers.
let key_without_modifiers = get_modifierless_char(scancode); let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = unsafe { ns_event.modifierFlags() }; let modifiers = ns_event.modifierFlags();
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control); let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
let has_cmd = modifiers.contains(NSEventModifierFlags::Command); let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
let logical_key = match text_with_all_modifiers.as_ref() { let logical_key = match text_with_all_modifiers.as_ref() {
// Only checking for ctrl and cmd here, not checking for alt because we DO want to // Only checking for ctrl and cmd here, not checking for alt because we DO want to
// include its effect in the key. For example if -on the Germay layout- one // include its effect in the key. For example if -on the German layout- one
// presses alt+8, the logical key should be "{" // presses alt+8, the logical key should be "{"
// Also not checking if this is a release event because then this issue would // Also not checking if this is a release event because then this issue would
// still affect the key release. // still affect the key release.
@@ -302,15 +300,15 @@ const NX_DEVICERALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x000000
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000); const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000);
pub(super) fn lalt_pressed(event: &NSEvent) -> bool { pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
unsafe { event.modifierFlags() }.contains(NX_DEVICELALTKEYMASK) event.modifierFlags().contains(NX_DEVICELALTKEYMASK)
} }
pub(super) fn ralt_pressed(event: &NSEvent) -> bool { pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
unsafe { event.modifierFlags() }.contains(NX_DEVICERALTKEYMASK) event.modifierFlags().contains(NX_DEVICERALTKEYMASK)
} }
pub(super) fn event_mods(event: &NSEvent) -> Modifiers { pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let flags = unsafe { event.modifierFlags() }; let flags = event.modifierFlags();
let mut state = ModifiersState::empty(); let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty(); let mut pressed_mods = ModifiersKeys::empty();
@@ -330,12 +328,11 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK)); pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK)); pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK));
Modifiers { state, pressed_mods } Modifiers::new(state, pressed_mods)
} }
pub(super) fn dummy_event() -> Option<Retained<NSEvent>> { pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
unsafe { NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEventType::ApplicationDefined, NSEventType::ApplicationDefined,
NSPoint::new(0.0, 0.0), NSPoint::new(0.0, 0.0),
NSEventModifierFlags(0), NSEventModifierFlags(0),
@@ -346,10 +343,9 @@ pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
0, 0,
0, 0,
) )
}
} }
pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> { pub fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
let code = match physical_key { let code = match physical_key {
PhysicalKey::Code(code) => code, PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => return None, PhysicalKey::Unidentified(_) => return None,
@@ -481,7 +477,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
} }
} }
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do: // Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc // 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 // https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h

View File

@@ -1,75 +1,38 @@
use std::any::Any; use std::rc::Rc;
use std::cell::Cell;
use std::fmt;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::rc::{Rc, Weak};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{Retained, autoreleasepool};
use objc2::runtime::ProtocolObject; use objc2::runtime::ProtocolObject;
use objc2::{available, MainThreadMarker}; use objc2::{MainThreadMarker, available};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow, NSApplicationWillTerminateNotification, NSWindow,
}; };
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::Theme;
use super::super::notification_center::create_observer;
use super::app::override_send_event; use super::app::override_send_event;
use super::app_state::AppState; use super::app_state::AppState;
use super::cursor::CustomCursor; use super::cursor::CustomCursor;
use super::event::dummy_event; use super::event::dummy_event;
use super::monitor; use super::monitor;
use super::observer::setup_control_flow_observers; use crate::ActivationPolicy;
use crate::application::ApplicationHandler; use crate::window::Window;
use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
}
impl fmt::Debug for PanicInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PanicInfo").finish_non_exhaustive()
}
}
// 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)] #[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
@@ -102,8 +65,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_window( fn create_window(
&self, &self,
window_attributes: crate::window::WindowAttributes, window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn crate::window::Window>, RequestError> { ) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?)) Ok(Box::new(Window::new(self, window_attributes)?))
} }
@@ -122,7 +85,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
) )
} }
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> { fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor(); let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor))) Some(CoreMonitorHandle(Arc::new(monitor)))
} }
@@ -182,7 +145,6 @@ pub struct EventLoop {
app_state: Rc<AppState>, app_state: Rc<AppState>,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
panic_info: Rc<PanicInfo>,
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated; // 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. // the system instead cleans it up next time it would have posted a notification to it.
@@ -190,13 +152,17 @@ pub struct EventLoop {
// Though we do still need to keep the observers around to prevent them from being deallocated. // Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_before_waiting_observer: MainRunLoopObserver,
_after_waiting_observer: MainRunLoopObserver,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes { pub struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: Option<ActivationPolicy>, pub activation_policy: Option<ActivationPolicy>,
pub(crate) default_menu: bool, pub default_menu: bool,
pub(crate) activate_ignoring_other_apps: bool, pub activate_ignoring_other_apps: bool,
} }
impl Default for PlatformSpecificEventLoopAttributes { impl Default for PlatformSpecificEventLoopAttributes {
@@ -206,9 +172,7 @@ impl Default for PlatformSpecificEventLoopAttributes {
} }
impl EventLoop { impl EventLoop {
pub(crate) fn new( pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new() let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!"); .expect("on macOS, `EventLoop` must be created on the main thread!");
@@ -224,7 +188,8 @@ impl EventLoop {
activation_policy, activation_policy,
attributes.default_menu, attributes.default_menu,
attributes.activate_ignoring_other_apps, attributes.activate_ignoring_other_apps,
); )
.ok_or_else(|| EventLoopError::RecreationAttempt)?;
// Initialize the application (if it has not already been). // Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm); let app = NSApplication::sharedApplication(mtm);
@@ -232,7 +197,7 @@ impl EventLoop {
// Override `sendEvent:` on the application to forward to our application state. // Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app); override_send_event(&app);
let center = unsafe { NSNotificationCenter::defaultCenter() }; let center = NSNotificationCenter::defaultCenter();
let weak_app_state = Rc::downgrade(&app_state); let weak_app_state = Rc::downgrade(&app_state);
let _did_finish_launching_observer = create_observer( let _did_finish_launching_observer = create_observer(
@@ -240,6 +205,7 @@ impl EventLoop {
// `applicationDidFinishLaunching:` // `applicationDidFinishLaunching:`
unsafe { NSApplicationDidFinishLaunchingNotification }, unsafe { NSApplicationDidFinishLaunchingNotification },
move |notification| { move |notification| {
let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() { if let Some(app_state) = weak_app_state.upgrade() {
app_state.did_finish_launching(notification); app_state.did_finish_launching(notification);
} }
@@ -252,22 +218,55 @@ impl EventLoop {
// `applicationWillTerminate:` // `applicationWillTerminate:`
unsafe { NSApplicationWillTerminateNotification }, unsafe { NSApplicationWillTerminateNotification },
move |notification| { move |notification| {
let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() { if let Some(app_state) = weak_app_state.upgrade() {
app_state.will_terminate(notification); app_state.will_terminate(notification);
} }
}, },
); );
let panic_info: Rc<PanicInfo> = Default::default(); let main_loop = MainRunLoop::get(mtm);
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info)); let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
// Tracing observers have the lowest and highest orderings.
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
main_loop.add_observer(start, mode);
main_loop.add_observer(end, mode);
});
let app_state_clone = Rc::clone(&app_state);
let _before_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure
// it is processed after other observers.
CFIndex::MAX - 1,
move |_| app_state_clone.cleared(),
);
main_loop.add_observer(&_before_waiting_observer, mode);
let app_state_clone = Rc::clone(&app_state);
let _after_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::AfterWaiting,
true,
// Queued with the second-highest priority (tracing observers use the highest) to
// ensure it is processed before other observers.
CFIndex::MIN + 1,
move |_| app_state_clone.wakeup(),
);
main_loop.add_observer(&_after_waiting_observer, mode);
Ok(EventLoop { Ok(EventLoop {
app, app,
app_state: app_state.clone(), app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm }, window_target: ActiveEventLoop { app_state, mtm },
panic_info,
_did_finish_launching_observer, _did_finish_launching_observer,
_will_terminate_observer, _will_terminate_observer,
_tracing_observers,
_before_waiting_observer,
_after_waiting_observer,
}) })
} }
@@ -275,10 +274,6 @@ impl EventLoop {
&self.window_target &self.window_target
} }
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 // 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 // `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 // time and so a layered implementation would end up using a lot of CPU due to
@@ -305,15 +300,6 @@ impl EventLoop {
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing. // NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
self.app.run(); self.app.run();
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the `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);
}
self.app_state.internal_exit() self.app_state.internal_exit()
}) })
}); });
@@ -369,15 +355,6 @@ impl EventLoop {
self.app.run(); self.app.run();
} }
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the 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);
}
if self.app_state.exiting() { if self.app_state.exiting() {
self.app_state.internal_exit(); self.app_state.internal_exit();
PumpStatus::Exit(0) PumpStatus::Exit(0)
@@ -422,29 +399,3 @@ pub(super) fn notify_windows_of_exit(app: &NSApplication) {
window.close(); window.close();
} }
} }
/// 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
},
}
}

View File

@@ -6,13 +6,13 @@ use std::ffi::c_void;
use objc2::ffi::NSInteger; use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2_core_foundation::{cf_type, CFString, CFUUID}; use objc2_core_foundation::{CFString, CFUUID, cf_type};
use objc2_core_graphics::CGDirectDisplayID; use objc2_core_graphics::CGDirectDisplayID;
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB"; pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB"; pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB"; pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. // `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13. // However, that framework was only introduced "publicly" in macOS 10.13.
@@ -22,14 +22,14 @@ pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
// `ApplicationServices`, see: // `ApplicationServices`, see:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG // 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")] #[link(name = "ApplicationServices", kind = "framework")]
extern "C" { unsafe extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID; pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID; pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID;
} }
#[link(name = "CoreGraphics", kind = "framework")] #[link(name = "CoreGraphics", kind = "framework")]
extern "C" { unsafe extern "C" {
// Wildly used private APIs; Apple uses them for their Terminal.app. // Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject; pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius( pub fn CGSSetWindowBackgroundBlurRadius(
@@ -60,7 +60,7 @@ pub const kUCKeyActionDisplay: u16 = 3;
pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1; pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
#[link(name = "Carbon", kind = "framework")] #[link(name = "Carbon", kind = "framework")]
extern "C" { unsafe extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString; pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
#[allow(non_snake_case)] #[allow(non_snake_case)]

View File

@@ -17,13 +17,12 @@
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is //! Instead, Winit guarantees that it will not register an application delegate, so the solution is
//! to register your own application delegate, as outlined in the following example (see //! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information). //! `objc2-app-kit` for more detailed information).
#![cfg_attr(target_os = "macos", doc = "```")] //! ```
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained; //! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject; //! use objc2::runtime::ProtocolObject;
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly}; //! use objc2::{DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; //! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol}; //! use objc2_foundation::{NSArray, NSObject, NSObjectProtocol, NSURL};
//! use winit::event_loop::EventLoop; //! use winit::event_loop::EventLoop;
//! //!
//! define_class!( //! define_class!(
@@ -64,17 +63,39 @@
//! Ok(()) //! Ok(())
//! } //! }
//! ``` //! ```
#![cfg(target_vendor = "apple")] // TODO: Remove once `objc2` allows compiling on all platforms
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod observer;
mod view;
mod window;
mod window_delegate;
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[doc(inline)]
pub use winit_core::application::macos::ApplicationHandlerExtMacOS;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::monitor::MonitorHandle;
use winit_core::window::{PlatformWindowAttributes, Window};
use crate::application::ApplicationHandler; pub use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder}; use self::event_loop::ActiveEventLoop as AppKitActiveEventLoop;
use crate::monitor::MonitorHandle; pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
use crate::platform_impl::MonitorHandle as MacOsMonitorHandle; use self::monitor::MonitorHandle as AppKitMonitorHandle;
use crate::window::{Window, WindowAttributes, WindowId}; use self::window::Window as AppKitWindow;
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS { pub trait WindowExtMacOS {
@@ -170,109 +191,109 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for dyn Window + '_ { impl WindowExtMacOS for dyn Window + '_ {
#[inline] #[inline]
fn simple_fullscreen(&self) -> bool { fn simple_fullscreen(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.simple_fullscreen()) window.maybe_wait_on_main(|w| w.simple_fullscreen())
} }
#[inline] #[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen)) window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
} }
#[inline] #[inline]
fn has_shadow(&self) -> bool { fn has_shadow(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.has_shadow()) window.maybe_wait_on_main(|w| w.has_shadow())
} }
#[inline] #[inline]
fn set_has_shadow(&self, has_shadow: bool) { fn set_has_shadow(&self, has_shadow: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow)); window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow));
} }
#[inline] #[inline]
fn set_tabbing_identifier(&self, identifier: &str) { fn set_tabbing_identifier(&self, identifier: &str) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier)) window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
} }
#[inline] #[inline]
fn tabbing_identifier(&self) -> String { fn tabbing_identifier(&self) -> String {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.tabbing_identifier()) window.maybe_wait_on_main(|w| w.tabbing_identifier())
} }
#[inline] #[inline]
fn select_next_tab(&self) { fn select_next_tab(&self) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.select_next_tab()); window.maybe_wait_on_main(|w| w.select_next_tab());
} }
#[inline] #[inline]
fn select_previous_tab(&self) { fn select_previous_tab(&self) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.select_previous_tab()); window.maybe_wait_on_main(|w| w.select_previous_tab());
} }
#[inline] #[inline]
fn select_tab_at_index(&self, index: usize) { fn select_tab_at_index(&self, index: usize) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.select_tab_at_index(index)); window.maybe_wait_on_main(move |w| w.select_tab_at_index(index));
} }
#[inline] #[inline]
fn num_tabs(&self) -> usize { fn num_tabs(&self) -> usize {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.num_tabs()) window.maybe_wait_on_main(|w| w.num_tabs())
} }
#[inline] #[inline]
fn is_document_edited(&self) -> bool { fn is_document_edited(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.is_document_edited()) window.maybe_wait_on_main(|w| w.is_document_edited())
} }
#[inline] #[inline]
fn set_document_edited(&self, edited: bool) { fn set_document_edited(&self, edited: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_document_edited(edited)); window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
} }
#[inline] #[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt)); window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
} }
#[inline] #[inline]
fn option_as_alt(&self) -> OptionAsAlt { fn option_as_alt(&self) -> OptionAsAlt {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.option_as_alt()) window.maybe_wait_on_main(|w| w.option_as_alt())
} }
#[inline] #[inline]
fn set_borderless_game(&self, borderless_game: bool) { fn set_borderless_game(&self, borderless_game: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
} }
#[inline] #[inline]
fn is_borderless_game(&self) -> bool { fn is_borderless_game(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.is_borderless_game()) window.maybe_wait_on_main(|w| w.is_borderless_game())
} }
#[inline] #[inline]
fn set_unified_titlebar(&self, unified_titlebar: bool) { fn set_unified_titlebar(&self, unified_titlebar: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar)) window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
} }
#[inline] #[inline]
fn unified_titlebar(&self) -> bool { fn unified_titlebar(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar()) window.maybe_wait_on_main(|w| w.unified_titlebar())
} }
} }
@@ -292,7 +313,7 @@ pub enum ActivationPolicy {
Prohibited, Prohibited,
} }
/// Additional methods on [`WindowAttributes`] that are specific to MacOS. /// Window attributes that are specific to MacOS.
/// ///
/// **Note:** Properties dealing with the titlebar will be overwritten by the /// **Note:** Properties dealing with the titlebar will be overwritten by the
/// [`WindowAttributes::with_decorations`] method: /// [`WindowAttributes::with_decorations`] method:
@@ -301,127 +322,158 @@ pub enum ActivationPolicy {
/// - `with_titlebar_hidden` /// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden` /// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view` /// - `with_fullsize_content_view`
pub trait WindowAttributesExtMacOS { ///
/// [`WindowAttributes::with_decorations`]: crate::window::WindowAttributes::with_decorations
#[derive(Clone, Debug, PartialEq)]
pub struct WindowAttributesMacOS {
pub(crate) movable_by_window_background: bool,
pub(crate) titlebar_transparent: bool,
pub(crate) title_hidden: bool,
pub(crate) titlebar_hidden: bool,
pub(crate) titlebar_buttons_hidden: bool,
pub(crate) fullsize_content_view: bool,
pub(crate) disallow_hidpi: bool,
pub(crate) has_shadow: bool,
pub(crate) accepts_first_mouse: bool,
pub(crate) tabbing_identifier: Option<String>,
pub(crate) option_as_alt: OptionAsAlt,
pub(crate) borderless_game: bool,
pub(crate) unified_titlebar: bool,
pub(crate) panel: bool,
}
impl WindowAttributesMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar. /// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self; #[inline]
pub fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.movable_by_window_background = movable_by_window_background;
self
}
/// Makes the titlebar transparent and allows the content to appear behind it. /// Makes the titlebar transparent and allows the content to appear behind it.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self; #[inline]
/// Hides the window title. pub fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
fn with_title_hidden(self, title_hidden: bool) -> Self; self.titlebar_transparent = titlebar_transparent;
self
}
/// Hides the window titlebar. /// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self; #[inline]
pub fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.titlebar_hidden = titlebar_hidden;
self
}
/// Hides the window titlebar buttons. /// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self; #[inline]
pub fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
/// Hides the window title.
#[inline]
pub fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.title_hidden = title_hidden;
self
}
/// Makes the window content appear behind the titlebar. /// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self; #[inline]
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self; pub fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
fn with_has_shadow(self, has_shadow: bool) -> Self; self.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
pub fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
pub fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.has_shadow = has_shadow;
self
}
/// Window accepts click-through mouse events. /// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self; #[inline]
pub fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.accepts_first_mouse = accepts_first_mouse;
self
}
/// Defines the window tabbing identifier. /// Defines the window tabbing identifier.
/// ///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier> /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn with_tabbing_identifier(self, identifier: &str) -> Self; #[inline]
pub fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
/// Set how the <kbd>Option</kbd> keys are interpreted. /// Set how the <kbd>Option</kbd> keys are interpreted.
/// ///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; #[inline]
pub fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.option_as_alt = option_as_alt;
self
}
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set. /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
fn with_borderless_game(self, borderless_game: bool) -> Self; #[inline]
pub fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.borderless_game = borderless_game;
self
}
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set. /// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self; #[inline]
pub fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.unified_titlebar = unified_titlebar;
self
}
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of /// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
/// [`NSWindow`]. /// [`NSWindow`].
/// ///
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc /// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?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 /// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
fn with_panel(self, panel: bool) -> Self; #[inline]
pub fn with_panel(mut self, panel: bool) -> Self {
self.panel = panel;
self
}
} }
impl WindowAttributesExtMacOS for WindowAttributes { impl Default for WindowAttributesMacOS {
#[inline] #[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { fn default() -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background; Self {
self movable_by_window_background: false,
titlebar_transparent: false,
title_hidden: false,
titlebar_hidden: false,
titlebar_buttons_hidden: false,
fullsize_content_view: false,
disallow_hidpi: false,
has_shadow: true,
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
unified_titlebar: false,
panel: false,
}
} }
}
#[inline] impl PlatformWindowAttributes for WindowAttributesMacOS {
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self { fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
self.platform_specific.titlebar_transparent = titlebar_transparent; Box::from(self.clone())
self
}
#[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.platform_specific.title_hidden = title_hidden;
self
}
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.platform_specific.option_as_alt = option_as_alt;
self
}
#[inline]
fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.platform_specific.borderless_game = borderless_game;
self
}
#[inline]
fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}
#[inline]
fn with_panel(mut self, panel: bool) -> Self {
self.platform_specific.panel = panel;
self
} }
} }
@@ -480,26 +532,6 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self; fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
} }
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = Some(activation_policy);
self
}
#[inline]
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
self.platform_specific.default_menu = enable;
self
}
#[inline]
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
self.platform_specific.activate_ignoring_other_apps = ignore;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS. /// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS { pub trait MonitorHandleExtMacOS {
/// Returns a pointer to the NSScreen representing this monitor. /// Returns a pointer to the NSScreen representing this monitor.
@@ -508,7 +540,7 @@ pub trait MonitorHandleExtMacOS {
impl MonitorHandleExtMacOS for MonitorHandle { impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
let monitor = self.cast_ref::<MacOsMonitorHandle>().unwrap(); let monitor = self.cast_ref::<AppKitMonitorHandle>().unwrap();
// SAFETY: We only use the marker to get a pointer // SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _) monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
@@ -533,30 +565,26 @@ pub trait ActiveEventLoopExtMacOS {
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) { fn hide_application(&self) {
let event_loop = self let event_loop =
.cast_ref::<crate::platform_impl::ActiveEventLoop>() self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
.expect("non macOS event loop on macOS");
event_loop.hide_application() event_loop.hide_application()
} }
fn hide_other_applications(&self) { fn hide_other_applications(&self) {
let event_loop = self let event_loop =
.cast_ref::<crate::platform_impl::ActiveEventLoop>() self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
.expect("non macOS event loop on macOS");
event_loop.hide_other_applications() event_loop.hide_other_applications()
} }
fn set_allows_automatic_window_tabbing(&self, enabled: bool) { fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
let event_loop = self let event_loop =
.cast_ref::<crate::platform_impl::ActiveEventLoop>() self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
.expect("non macOS event loop on macOS");
event_loop.set_allows_automatic_window_tabbing(enabled); event_loop.set_allows_automatic_window_tabbing(enabled);
} }
fn allows_automatic_window_tabbing(&self) -> bool { fn allows_automatic_window_tabbing(&self) -> bool {
let event_loop = self let event_loop =
.cast_ref::<crate::platform_impl::ActiveEventLoop>() self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
.expect("non macOS event loop on macOS");
event_loop.allows_automatic_window_tabbing() event_loop.allows_automatic_window_tabbing()
} }
} }
@@ -580,52 +608,3 @@ pub enum OptionAsAlt {
#[default] #[default]
None, None,
} }
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
///
/// This can be registered with [`ApplicationHandler::macos_handler`].
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
/// The system interpreted a keypress as a standard key binding command.
///
/// Examples include inserting tabs and newlines, or moving the insertion point, see
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
/// editing related.
///
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
///
/// The `action` parameter contains the string representation of the selector. Examples include
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
///
/// # Example
///
/// ```ignore
/// impl ApplicationHandlerExtMacOS for App {
/// fn standard_key_binding(
/// &mut self,
/// event_loop: &dyn ActiveEventLoop,
/// window_id: WindowId,
/// action: &str,
/// ) {
/// match action {
/// "moveBackward:" => self.cursor.position -= 1,
/// "moveForward:" => self.cursor.position += 1,
/// _ => {} // Ignore other actions
/// }
/// }
/// }
/// ```
///
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
#[doc(alias = "doCommandBySelector:")]
fn standard_key_binding(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
let _ = event_loop;
let _ = window_id;
let _ = action;
}
}

View File

@@ -1,8 +1,8 @@
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{sel, MainThreadMarker}; use objc2::{MainThreadMarker, sel};
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, NSProcessInfo, NSString}; use objc2_foundation::{NSBundle, NSProcessInfo, NSString, ns_string};
struct KeyEquivalent<'a> { struct KeyEquivalent<'a> {
key: &'a NSString, key: &'a NSString,
@@ -16,7 +16,10 @@ pub fn initialize(app: &NSApplication) {
menubar.addItem(&app_menu_item); menubar.addItem(&app_menu_item);
let app_menu = NSMenu::new(mtm); let app_menu = NSMenu::new(mtm);
let process_name = NSProcessInfo::processInfo().processName(); let process_name = match NSBundle::mainBundle().name() {
Some(bundle_name) => bundle_name,
None => NSProcessInfo::processInfo().processName(),
};
// About menu item // About menu item
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name); let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
@@ -79,7 +82,7 @@ pub fn initialize(app: &NSApplication) {
app_menu.addItem(&quit_item); app_menu.addItem(&quit_item);
app_menu_item.setSubmenu(Some(&app_menu)); app_menu_item.setSubmenu(Some(&app_menu));
unsafe { app.setServicesMenu(Some(&services_menu)) }; app.setServicesMenu(Some(&services_menu));
app.setMainMenu(Some(&menubar)); app.setMainMenu(Some(&menubar));
} }

View File

@@ -6,22 +6,22 @@ use std::ptr::NonNull;
use std::{fmt, ptr}; use std::{fmt, ptr};
use dispatch2::run_on_main; use dispatch2::run_on_main;
use objc2::rc::Retained; use dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2_app_kit::NSScreen; use objc2_app_kit::NSScreen;
use objc2_core_foundation::{CFArray, CFRetained, CFUUID}; use objc2_core_foundation::{CFArray, CFRetained, CFUUID};
use objc2_core_graphics::{ use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode, CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID, CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID,
}; };
use objc2_core_video::{kCVReturnSuccess, CVDisplayLink, CVTimeFlags}; use objc2_core_video::{CVDisplayLink, CVTimeFlags, kCVReturnSuccess};
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect}; use objc2_foundation::{NSNumber, NSPoint, NSRect, ns_string};
use tracing::warn; use tracing::warn;
use winit_core::monitor::{MonitorHandleProvider, VideoMode};
use super::ffi; use super::ffi;
use super::util::cgerr; use super::util::cgerr;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandleProvider, VideoMode};
#[derive(Clone)] #[derive(Clone)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
@@ -65,31 +65,33 @@ impl VideoModeHandle {
native_mode: NativeDisplayMode, native_mode: NativeDisplayMode,
refresh_rate_millihertz: Option<NonZeroU32>, refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self { ) -> Self {
unsafe { // The bit-depth is basically always 32 since macOS 10.12.
#[allow(deprecated)] #[allow(deprecated)]
let pixel_encoding = let pixel_encoding =
CGDisplayMode::pixel_encoding(Some(&native_mode.0)).unwrap().to_string(); CGDisplayMode::pixel_encoding(Some(&native_mode.0)).unwrap().to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32 NonZeroU16::new(32)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
16 NonZeroU16::new(16)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
30 NonZeroU16::new(30)
} else { } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO64BitDirectPixels) {
unimplemented!() NonZeroU16::new(64)
}; } else {
warn!(?pixel_encoding, "unknown bit depth");
None
};
let mode = VideoMode { let mode = VideoMode::new(
size: PhysicalSize::new( PhysicalSize::new(
CGDisplayMode::pixel_width(Some(&native_mode.0)) as u32, CGDisplayMode::pixel_width(Some(&native_mode.0)) as u32,
CGDisplayMode::pixel_height(Some(&native_mode.0)) as u32, CGDisplayMode::pixel_height(Some(&native_mode.0)) as u32,
), ),
refresh_rate_millihertz, bit_depth,
bit_depth: NonZeroU16::new(bit_depth), refresh_rate_millihertz,
}; );
VideoModeHandle { mode, monitor: monitor.clone(), native_mode } Self { mode, monitor: monitor.clone(), native_mode }
}
} }
} }
@@ -133,11 +135,11 @@ impl MonitorHandle {
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> { fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode = let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.display_id()) }.unwrap()); NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()).unwrap());
refresh_rate_millihertz(self.display_id(), &current_display_mode) refresh_rate_millihertz(self.display_id(), &current_display_mode)
} }
pub fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> + 'static {
let refresh_rate_millihertz = self.refresh_rate_millihertz(); let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone(); let monitor = self.clone();
@@ -155,7 +157,7 @@ impl MonitorHandle {
}; };
modes.into_iter().map(move |mode| { modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = unsafe { CGDisplayMode::refresh_rate(Some(&mode)) }; let cg_refresh_rate_hertz = CGDisplayMode::refresh_rate(Some(&mode));
// CGDisplayModeGetRefreshRate returns 0.0 for any display that // CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT // isn't a CRT
@@ -198,7 +200,7 @@ impl MonitorHandleProvider for MonitorHandle {
// //
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126> // <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
fn name(&self) -> Option<std::borrow::Cow<'_, str>> { fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
let screen_num = unsafe { CGDisplayModelNumber(self.display_id()) }; let screen_num = CGDisplayModelNumber(self.display_id());
Some(format!("Monitor #{screen_num}").into()) Some(format!("Monitor #{screen_num}").into())
} }
@@ -206,7 +208,7 @@ impl MonitorHandleProvider for MonitorHandle {
// This is already in screen coordinates. If we were using `NSScreen`, // This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed: // then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame()) // flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.display_id()) }; let bounds = CGDisplayBounds(self.display_id());
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y); let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor())) Some(position.to_physical(self.scale_factor()))
} }
@@ -221,8 +223,7 @@ impl MonitorHandleProvider for MonitorHandle {
} }
fn current_video_mode(&self) -> Option<VideoMode> { fn current_video_mode(&self) -> Option<VideoMode> {
let mode = let mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()).unwrap());
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.display_id()) }.unwrap());
let refresh_rate_millihertz = refresh_rate_millihertz(self.display_id(), &mode); let refresh_rate_millihertz = refresh_rate_millihertz(self.display_id(), &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode) Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
} }
@@ -260,7 +261,7 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
pub fn primary_monitor() -> MonitorHandle { pub fn primary_monitor() -> MonitorHandle {
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap. // Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
MonitorHandle::new(unsafe { CGMainDisplayID() }).expect("invalid display ID") MonitorHandle::new(CGMainDisplayID()).expect("invalid display ID")
} }
impl fmt::Debug for MonitorHandle { impl fmt::Debug for MonitorHandle {
@@ -311,40 +312,38 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
// It is intentional that we use `CGMainDisplayID` (as opposed to // It is intentional that we use `CGMainDisplayID` (as opposed to
// `NSScreen::mainScreen`), because that's what the screen coordinates // `NSScreen::mainScreen`), because that's what the screen coordinates
// are relative to, no matter which display the window is currently on. // are relative to, no matter which display the window is currently on.
let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height; let main_screen_height = CGDisplayBounds(CGMainDisplayID()).size.height;
let y = main_screen_height - frame.size.height - frame.origin.y; let y = main_screen_height - frame.size.height - frame.origin.y;
NSPoint::new(frame.origin.x, y) NSPoint::new(frame.origin.x, y)
} }
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> { fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
unsafe { let refresh_rate = CGDisplayMode::refresh_rate(Some(&mode.0));
let refresh_rate = CGDisplayMode::refresh_rate(Some(&mode.0)); if refresh_rate > 0.0 {
if refresh_rate > 0.0 { return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
#[allow(deprecated)]
if CVDisplayLink::create_with_cg_display(id, NonNull::from(&mut display_link))
!= kCVReturnSuccess
{
return None;
}
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
#[allow(deprecated)]
let time = display_link.nominal_output_video_refresh_period();
// This value is indefinite if an invalid display link was specified
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
return None;
}
(time.timeScale as i64)
.checked_div(time.timeValue)
.map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new)
} }
let mut display_link = std::ptr::null_mut();
#[allow(deprecated)]
if unsafe { CVDisplayLink::create_with_cg_display(id, NonNull::from(&mut display_link)) }
!= kCVReturnSuccess
{
return None;
}
let display_link = unsafe { CFRetained::from_raw(NonNull::new(display_link).unwrap()) };
#[allow(deprecated)]
let time = display_link.nominal_output_video_refresh_period();
// This value is indefinite if an invalid display link was specified
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
return None;
}
(time.timeScale as i64)
.checked_div(time.timeValue)
.map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new)
} }
#[cfg(test)] #[cfg(test)]
@@ -353,8 +352,10 @@ mod tests {
#[test] #[test]
fn uuid_stable() { fn uuid_stable() {
let handle_a = MonitorHandle::new(1).unwrap(); let primary_id = CGMainDisplayID();
let handle_b = MonitorHandle::new(1).unwrap();
let handle_a = MonitorHandle::new(primary_id).unwrap();
let handle_b = MonitorHandle::new(primary_id).unwrap();
assert_eq!(handle_a, handle_b); assert_eq!(handle_a, handle_b);
assert_eq!(handle_a.display_id(), handle_b.display_id()); assert_eq!(handle_a.display_id(), handle_b.display_id());
assert_eq!(handle_a.uuid(), handle_b.uuid()); assert_eq!(handle_a.uuid(), handle_b.uuid());
@@ -369,8 +370,10 @@ mod tests {
/// Test the MonitorHandle::new fallback. /// Test the MonitorHandle::new fallback.
#[test] #[test]
fn monitorhandle_from_zero() { fn monitorhandle_from_zero() {
let primary_id = CGMainDisplayID();
let handle0 = MonitorHandle::new(0).unwrap(); let handle0 = MonitorHandle::new(0).unwrap();
let handle1 = MonitorHandle::new(1).unwrap(); let handle1 = MonitorHandle::new(primary_id).unwrap();
assert_eq!(handle0, handle1); assert_eq!(handle0, handle1);
assert_eq!(handle0.display_id(), handle1.display_id()); assert_eq!(handle0.display_id(), handle1.display_id());
assert_eq!(handle0.uuid(), handle1.uuid()); assert_eq!(handle0.uuid(), handle1.uuid());
@@ -391,7 +394,7 @@ mod tests {
// Assertion failed: (did_initialize), function CGS_REQUIRE_INIT, file CGInitialization.c, line 44. // Assertion failed: (did_initialize), function CGS_REQUIRE_INIT, file CGInitialization.c, line 44.
// ``` // ```
// See https://github.com/JXA-Cookbook/JXA-Cookbook/issues/27#issuecomment-277517668 // See https://github.com/JXA-Cookbook/JXA-Cookbook/issues/27#issuecomment-277517668
let _ = unsafe { CGMainDisplayID() }; let _ = CGMainDisplayID();
let handle = MonitorHandle(CFUUID::new(None).unwrap()); let handle = MonitorHandle(CFUUID::new(None).unwrap());
assert_eq!(handle.display_id(), 0); assert_eq!(handle.display_id(), 0);

View File

@@ -0,0 +1,87 @@
use std::ffi::c_void;
use std::ptr;
use std::time::Instant;
use objc2_core_foundation::{
CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, kCFRunLoopCommonModes,
};
#[derive(Debug)]
pub struct EventLoopWaker {
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
/// easily check if the next_fire_date needs updating.
start_instant: Instant,
/// This is what the `NextFireDate` has been set to.
/// `None` corresponds to `waker.stop()` and `start_instant` is used
/// for `waker.start()`
next_fire_date: Option<Instant>,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
self.timer.invalidate();
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
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 = CFRunLoopTimer::new(
None,
f64::MAX,
0.000_000_1,
0,
0,
Some(wakeup_main_loop),
ptr::null_mut(),
)
.unwrap();
CFRunLoop::main().unwrap().add_timer(Some(&timer), kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
pub fn stop(&mut self) {
if self.next_fire_date.is_some() {
self.next_fire_date = None;
self.timer.set_next_fire_date(f64::MAX);
}
}
pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant);
self.timer.set_next_fire_date(f64::MIN);
}
}
pub fn start_at(&mut self, instant: Option<Instant>) {
let now = Instant::now();
match instant {
Some(instant) if now >= instant => {
self.start();
},
Some(instant) => {
if self.next_fire_date != Some(instant) {
self.next_fire_date = Some(instant);
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
self.timer.set_next_fire_date(current + fsecs);
}
},
None => {
self.stop();
},
}
}
}

11
winit-appkit/src/util.rs Normal file
View File

@@ -0,0 +1,11 @@
use objc2_core_graphics::CGError;
use winit_core::error::OsError;
macro_rules! os_error {
($error:expr) => {{ winit_core::error::OsError::new(line!(), file!(), $error) }};
}
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success { Ok(()) } else { Err(os_error!(format!("CGError {err:?}"))) }
}

View File

@@ -1,20 +1,28 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use dpi::{LogicalPosition, PhysicalSize};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker}; use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingRectTag, NSView, NSWindow, NSTrackingAreaOptions, NSView, NSViewLayerContentsRedrawPolicy, NSWindow,
}; };
use objc2_core_foundation::CGRect;
use objc2_foundation::{ use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
}; };
use tracing::{debug_span, trace_span};
use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use winit_core::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use winit_core::window::ImeCapabilities;
use super::app_state::AppState; use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor}; use super::cursor::{default_cursor, invisible_cursor};
@@ -23,13 +31,7 @@ use super::event::{
scancode_to_physicalkey, scancode_to_physicalkey,
}; };
use super::window::window_id; use super::window::window_id;
use crate::dpi::{LogicalPosition, LogicalSize}; use crate::OptionAsAlt;
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
#[derive(Debug)] #[derive(Debug)]
struct CursorState { struct CursorState {
@@ -117,19 +119,17 @@ pub struct ViewState {
ime_size: Cell<NSSize>, ime_size: Cell<NSSize>,
modifiers: Cell<Modifiers>, modifiers: Cell<Modifiers>,
phys_modifiers: RefCell<HashMap<Key, ModLocationMask>>, phys_modifiers: RefCell<HashMap<Key, ModLocationMask>>,
tracking_rect: Cell<Option<NSTrackingRectTag>>,
ime_state: Cell<ImeState>, ime_state: Cell<ImeState>,
input_source: RefCell<String>, input_source: RefCell<String>,
/// True iff the application wants IME events. /// True iff the application wants IME events.
/// ///
/// Can be set using `set_ime_allowed` /// Can be set using `set_ime_allowed`
ime_allowed: Cell<bool>, ime_capabilities: Cell<Option<ImeCapabilities>>,
/// True if the current key event should be forwarded /// True if the current key event should be forwarded
/// to the application, even during IME /// to the application, even during IME
forward_key_to_app: Cell<bool>, forward_key_to_app: Cell<bool>,
marked_text: RefCell<Retained<NSMutableAttributedString>>, marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool, accepts_first_mouse: bool,
@@ -151,49 +151,35 @@ define_class!(
true true
} }
#[unsafe(method(viewDidMoveToWindow))]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
}
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))] #[unsafe(method(viewFrameDidChangeNotification:))]
fn frame_did_change(&self, _notification: Option<&AnyObject>) { fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification"); let _entered = debug_span!("NSViewFrameDidChangeNotification").entered();
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because: // 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 // 1. When a new window is created as a tab, the frame size may change without a window
// resize occurring. // resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height). // size (includes tab height).
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); self.surface_resized();
let size = logical_size.to_physical::<u32>(self.scale_factor()); // During live resize, AppKit may not let the normal event loop reach its next redraw
self.queue_event(WindowEvent::SurfaceResized(size)); // point before stretching the current layer contents. Redraw immediately after the
// app has observed the new surface size.
self.redraw_during_live_resize();
}
#[unsafe(method(viewDidChangeBackingProperties))]
fn view_did_change_backing_properties(&self) {
let _entered = debug_span!("viewDidChangeBackingProperties").entered();
// Moving between displays or changing scale can alter the drawable backing size
// without a matching frame-size change.
self.surface_resized();
self.redraw_during_live_resize();
} }
#[unsafe(method(drawRect:))] #[unsafe(method(drawRect:))]
fn draw_rect(&self, _rect: NSRect) { fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:"); let _entered = debug_span!("drawRect:").entered();
self.ivars().app_state.handle_redraw(window_id(&self.window())); self.ivars().app_state.handle_redraw(window_id(&self.window()));
@@ -202,7 +188,7 @@ define_class!(
#[unsafe(method(acceptsFirstResponder))] #[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool { fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder"); let _entered = trace_span!("acceptsFirstResponder").entered();
true true
} }
@@ -216,13 +202,13 @@ define_class!(
// extension for using `NSTouchBar` // extension for using `NSTouchBar`
#[unsafe(method_id(touchBar))] #[unsafe(method_id(touchBar))]
fn touch_bar(&self) -> Option<Retained<NSObject>> { fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar"); let _entered = debug_span!("touchBar").entered();
None None
} }
#[unsafe(method(resetCursorRects))] #[unsafe(method(resetCursorRects))]
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); let _entered = debug_span!("resetCursorRects").entered();
let bounds = self.bounds(); let bounds = self.bounds();
let cursor_state = self.ivars().cursor_state.borrow(); let cursor_state = self.ivars().cursor_state.borrow();
// We correctly invoke `addCursorRect` only from inside `resetCursorRects` // We correctly invoke `addCursorRect` only from inside `resetCursorRects`
@@ -237,13 +223,13 @@ define_class!(
unsafe impl NSTextInputClient for WinitView { unsafe impl NSTextInputClient for WinitView {
#[unsafe(method(hasMarkedText))] #[unsafe(method(hasMarkedText))]
fn has_marked_text(&self) -> bool { fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText"); let _entered = debug_span!("hasMarkedText").entered();
self.ivars().marked_text.borrow().length() > 0 self.ivars().marked_text.borrow().length() > 0
} }
#[unsafe(method(markedRange))] #[unsafe(method(markedRange))]
fn marked_range(&self) -> NSRange { fn marked_range(&self) -> NSRange {
trace_scope!("markedRange"); let _entered = debug_span!("markedRange").entered();
let length = self.ivars().marked_text.borrow().length(); let length = self.ivars().marked_text.borrow().length();
if length > 0 { if length > 0 {
NSRange::new(0, length) NSRange::new(0, length)
@@ -255,7 +241,7 @@ define_class!(
#[unsafe(method(selectedRange))] #[unsafe(method(selectedRange))]
fn selected_range(&self) -> NSRange { fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange"); let _entered = debug_span!("selectedRange").entered();
// Documented to return `{NSNotFound, 0}` if there is no selection. // Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0) NSRange::new(NSNotFound as NSUInteger, 0)
} }
@@ -268,7 +254,7 @@ define_class!(
_replacement_range: NSRange, _replacement_range: NSRange,
) { ) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:"); let _entered = debug_span!("setMarkedText:selectedRange:replacementRange:").entered();
let (marked_text, string) = if let Some(string) = let (marked_text, string) = if let Some(string) =
string.downcast_ref::<NSAttributedString>() string.downcast_ref::<NSAttributedString>()
@@ -290,33 +276,38 @@ define_class!(
self.queue_event(WindowEvent::Ime(Ime::Enabled)); self.queue_event(WindowEvent::Ime(Ime::Enabled));
} }
if unsafe { self.hasMarkedText() } { if self.hasMarkedText() {
self.ivars().ime_state.set(ImeState::Preedit); self.ivars().ime_state.set(ImeState::Preedit);
} else { } else {
// In case the preedit was cleared, set IME into the Ground state. // In case the preedit was cleared, set IME into the Ground state.
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
} }
let string = string.to_string();
let cursor_range = if string.is_empty() { let cursor_range = if string.is_empty() {
// An empty string basically means that there's no preedit, so indicate that by // An empty string basically means that there's no preedit, so indicate that by
// sending a `None` cursor range. // sending a `None` cursor range.
None None
} else { } else {
// Convert the selected range from UTF-16 indices to UTF-8 indices. // Convert the selected range from UTF-16 code unit indices to UTF-8 byte
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) }; // offsets. `utf16_to_utf8_offset` is defensive: it snaps an offset that would
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) }; // split a surrogate pair down to the character boundary and clamps an
let lowerbound_utf8 = sub_string_a.len(); // out-of-bounds offset to the string length, so no `NSRangeException` is
let upperbound_utf8 = sub_string_b.len(); // possible and the resulting range can never be inverted (`lower <= upper`).
// IMEs are known to send both mid-surrogate and out-of-bounds offsets (e.g.
// native Pinyin, see https://github.com/alacritty/alacritty/issues/8791).
let lowerbound_utf8 = utf16_to_utf8_offset(&string, selected_range.location);
let upperbound_utf8 = utf16_to_utf8_offset(&string, selected_range.end());
Some((lowerbound_utf8, upperbound_utf8)) Some((lowerbound_utf8, upperbound_utf8))
}; };
// Send WindowEvent for updating marked text // Send WindowEvent for updating marked text
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range))); self.queue_event(WindowEvent::Ime(Ime::Preedit(string, cursor_range)));
} }
#[unsafe(method(unmarkText))] #[unsafe(method(unmarkText))]
fn unmark_text(&self) { fn unmark_text(&self) {
trace_scope!("unmarkText"); let _entered = debug_span!("unmarkText").entered();
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
let input_context = self.inputContext().expect("input context"); let input_context = self.inputContext().expect("input context");
@@ -333,7 +324,7 @@ define_class!(
#[unsafe(method_id(validAttributesForMarkedText))] #[unsafe(method_id(validAttributesForMarkedText))]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> { fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText"); let _entered = trace_span!("validAttributesForMarkedText").entered();
NSArray::new() NSArray::new()
} }
@@ -343,13 +334,14 @@ define_class!(
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> Option<Retained<NSAttributedString>> { ) -> Option<Retained<NSAttributedString>> {
trace_scope!("attributedSubstringForProposedRange:actualRange:"); let _entered =
trace_span!("attributedSubstringForProposedRange:actualRange:").entered();
None None
} }
#[unsafe(method(characterIndexForPoint:))] #[unsafe(method(characterIndexForPoint:))]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:"); let _entered = debug_span!("characterIndexForPoint:").entered();
0 0
} }
@@ -359,16 +351,23 @@ define_class!(
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> NSRect { ) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:"); let _entered = debug_span!("firstRectForCharacterRange:actualRange:").entered();
// Guard when the view is no longer in a window during teardown.
let Some(window) = (**self).window() else {
return CGRect::ZERO;
};
// Return value is expected to be in screen coordinates, so we need a conversion
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 let view_rect = self.convertRect_toView(rect, None);
self.window().convertRectToScreen(self.convertRect_toView(rect, None)) window.convertRectToScreen(view_rect)
} }
#[unsafe(method(insertText:replacementRange:))] #[unsafe(method(insertText:replacementRange:))]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:"); let _entered = debug_span!("insertText:replacementRange:").entered();
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() { let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
string.string().to_string() string.string().to_string()
@@ -382,7 +381,7 @@ define_class!(
let is_control = string.chars().next().is_some_and(|c| c.is_control()); let is_control = string.chars().next().is_some_and(|c| c.is_control());
// Commit only if we have marked text. // Commit only if we have marked text.
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control { if self.hasMarkedText() && self.is_ime_enabled() && !is_control {
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
self.queue_event(WindowEvent::Ime(Ime::Commit(string))); self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.ivars().ime_state.set(ImeState::Committed); self.ivars().ime_state.set(ImeState::Committed);
@@ -393,7 +392,7 @@ define_class!(
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C. // "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[unsafe(method(doCommandBySelector:))] #[unsafe(method(doCommandBySelector:))]
fn do_command_by_selector(&self, command: Sel) { fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:"); let _entered = debug_span!("doCommandBySelector:").entered();
// We shouldn't forward any character from just committed text, since we'll end up // 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 // sending it twice with some IMEs like Korean one. We'll also always send
@@ -405,8 +404,7 @@ define_class!(
self.ivars().forward_key_to_app.set(true); self.ivars().forward_key_to_app.set(true);
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit if self.hasMarkedText() && self.ivars().ime_state.get() == ImeState::Preedit {
{
// Leave preedit so that we also report the key-up for this key. // Leave preedit so that we also report the key-up for this key.
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
} }
@@ -433,7 +431,7 @@ define_class!(
impl WinitView { impl WinitView {
#[unsafe(method(keyDown:))] #[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) { fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:"); let _entered = debug_span!("keyDown:").entered();
{ {
let mut prev_input_source = self.ivars().input_source.borrow_mut(); let mut prev_input_source = self.ivars().input_source.borrow_mut();
let current_input_source = self.current_input_source(); let current_input_source = self.current_input_source();
@@ -456,9 +454,9 @@ define_class!(
// we must send the `KeyboardInput` event during IME if it triggered // we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input // `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application) // is not handled by IME and should be handled by the application)
if self.ivars().ime_allowed.get() { if self.ivars().ime_capabilities.get().is_some() {
let events_for_nsview = NSArray::from_slice(&[&*event]); let events_for_nsview = NSArray::from_slice(&[&*event]);
unsafe { self.interpretKeyEvents(&events_for_nsview) }; self.interpretKeyEvents(&events_for_nsview);
// If the text was committed we must treat the next keyboard event as IME related. // If the text was committed we must treat the next keyboard event as IME related.
if self.ivars().ime_state.get() == ImeState::Committed { if self.ivars().ime_state.get() == ImeState::Committed {
@@ -481,7 +479,7 @@ define_class!(
}; };
if !had_ime_input || self.ivars().forward_key_to_app.get() { if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }); let key_event = create_key_event(&event, true, event.isARepeat());
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
event: key_event, event: key_event,
@@ -492,7 +490,7 @@ define_class!(
#[unsafe(method(keyUp:))] #[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) { fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:"); let _entered = debug_span!("keyUp:").entered();
let event = replace_event(event, self.option_as_alt()); let event = replace_event(event, self.option_as_alt());
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
@@ -509,14 +507,14 @@ define_class!(
#[unsafe(method(flagsChanged:))] #[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) { fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:"); let _entered = debug_span!("flagsChanged:").entered();
self.update_modifiers(event, true); self.update_modifiers(event, true);
} }
#[unsafe(method(insertTab:))] #[unsafe(method(insertTab:))]
fn insert_tab(&self, _sender: Option<&AnyObject>) { fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:"); let _entered = debug_span!("insertTab:").entered();
let window = self.window(); let window = self.window();
if let Some(first_responder) = window.firstResponder() { if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self { if *first_responder == ***self {
@@ -527,7 +525,7 @@ define_class!(
#[unsafe(method(insertBackTab:))] #[unsafe(method(insertBackTab:))]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) { fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:"); let _entered = debug_span!("insertBackTab:").entered();
let window = self.window(); let window = self.window();
if let Some(first_responder) = window.firstResponder() { if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self { if *first_responder == ***self {
@@ -541,14 +539,14 @@ define_class!(
#[unsafe(method(cancelOperation:))] #[unsafe(method(cancelOperation:))]
fn cancel_operation(&self, _sender: Option<&AnyObject>) { fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self); let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:"); let _entered = debug_span!("cancelOperation:").entered();
let event = NSApplication::sharedApplication(mtm) let event = NSApplication::sharedApplication(mtm)
.currentEvent() .currentEvent()
.expect("could not find current event"); .expect("could not find current event");
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }); let event = create_key_event(&event, true, event.isARepeat());
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
@@ -570,71 +568,73 @@ define_class!(
#[unsafe(method(mouseDown:))] #[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:"); let _entered = debug_span!("mouseDown:").entered();
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(mouseUp:))] #[unsafe(method(mouseUp:))]
fn mouse_up(&self, event: &NSEvent) { fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:"); let _entered = debug_span!("mouseUp:").entered();
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[unsafe(method(rightMouseDown:))] #[unsafe(method(rightMouseDown:))]
fn right_mouse_down(&self, event: &NSEvent) { fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:"); let _entered = debug_span!("rightMouseDown:").entered();
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(rightMouseUp:))] #[unsafe(method(rightMouseUp:))]
fn right_mouse_up(&self, event: &NSEvent) { fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:"); let _entered = debug_span!("rightMouseUp:").entered();
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[unsafe(method(otherMouseDown:))] #[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) { fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:"); let _entered = debug_span!("otherMouseDown:").entered();
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(otherMouseUp:))] #[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) { fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:"); let _entered = debug_span!("otherMouseUp:").entered();
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
// No tracing on these because that would be overly verbose
#[unsafe(method(mouseMoved:))] #[unsafe(method(mouseMoved:))]
fn mouse_moved(&self, event: &NSEvent) { fn mouse_moved(&self, event: &NSEvent) {
let _entered = debug_span!("mouseMoved:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(mouseDragged:))] #[unsafe(method(mouseDragged:))]
fn mouse_dragged(&self, event: &NSEvent) { fn mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("mouseDragged:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(rightMouseDragged:))] #[unsafe(method(rightMouseDragged:))]
fn right_mouse_dragged(&self, event: &NSEvent) { fn right_mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("rightMouseDragged:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(otherMouseDragged:))] #[unsafe(method(otherMouseDragged:))]
fn other_mouse_dragged(&self, event: &NSEvent) { fn other_mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("otherMouseDragged:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(mouseEntered:))] #[unsafe(method(mouseEntered:))]
fn mouse_entered(&self, event: &NSEvent) { fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:"); let _entered = debug_span!("mouseEntered:").entered();
let position = self.mouse_view_point(event).to_physical(self.scale_factor()); let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -648,7 +648,7 @@ define_class!(
#[unsafe(method(mouseExited:))] #[unsafe(method(mouseExited:))]
fn mouse_exited(&self, event: &NSEvent) { fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:"); let _entered = debug_span!("mouseExited:").entered();
let position = self.mouse_view_point(event).to_physical(self.scale_factor()); let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -662,13 +662,13 @@ define_class!(
#[unsafe(method(scrollWheel:))] #[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) { fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:"); let _entered = debug_span!("scrollWheel:").entered();
self.mouse_motion(event); self.mouse_motion(event);
let delta = { let delta = {
let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) }; let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
if unsafe { event.hasPreciseScrollingDeltas() } { if event.hasPreciseScrollingDeltas() {
let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor()); let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor());
MouseScrollDelta::PixelDelta(delta) MouseScrollDelta::PixelDelta(delta)
} else { } else {
@@ -681,10 +681,10 @@ define_class!(
// momentum phase is recorded (or rather, the started/ended cases of the // momentum phase is recorded (or rather, the started/ended cases of the
// momentum phase) then we report the touch phase. // momentum phase) then we report the touch phase.
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } { let phase = match event.momentumPhase() {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended, NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended,
_ => match unsafe { event.phase() } { _ => match event.phase() {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended, NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended,
_ => TouchPhase::Moved, _ => TouchPhase::Moved,
@@ -701,12 +701,12 @@ define_class!(
#[unsafe(method(magnifyWithEvent:))] #[unsafe(method(magnifyWithEvent:))]
fn magnify_with_event(&self, event: &NSEvent) { fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:"); let _entered = debug_span!("magnifyWithEvent:").entered();
self.mouse_motion(event); self.mouse_motion(event);
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } { let phase = match event.phase() {
NSEventPhase::Began => TouchPhase::Started, NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Changed => TouchPhase::Moved, NSEventPhase::Changed => TouchPhase::Moved,
NSEventPhase::Cancelled => TouchPhase::Cancelled, NSEventPhase::Cancelled => TouchPhase::Cancelled,
@@ -716,14 +716,14 @@ define_class!(
self.queue_event(WindowEvent::PinchGesture { self.queue_event(WindowEvent::PinchGesture {
device_id: None, device_id: None,
delta: unsafe { event.magnification() }, delta: event.magnification(),
phase, phase,
}); });
} }
#[unsafe(method(smartMagnifyWithEvent:))] #[unsafe(method(smartMagnifyWithEvent:))]
fn smart_magnify_with_event(&self, event: &NSEvent) { fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:"); let _entered = debug_span!("smartMagnifyWithEvent:").entered();
self.mouse_motion(event); self.mouse_motion(event);
@@ -732,12 +732,12 @@ define_class!(
#[unsafe(method(rotateWithEvent:))] #[unsafe(method(rotateWithEvent:))]
fn rotate_with_event(&self, event: &NSEvent) { fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:"); let _entered = debug_span!("rotateWithEvent:").entered();
self.mouse_motion(event); self.mouse_motion(event);
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } { let phase = match event.phase() {
NSEventPhase::Began => TouchPhase::Started, NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Changed => TouchPhase::Moved, NSEventPhase::Changed => TouchPhase::Moved,
NSEventPhase::Cancelled => TouchPhase::Cancelled, NSEventPhase::Cancelled => TouchPhase::Cancelled,
@@ -747,19 +747,19 @@ define_class!(
self.queue_event(WindowEvent::RotationGesture { self.queue_event(WindowEvent::RotationGesture {
device_id: None, device_id: None,
delta: unsafe { event.rotation() }, delta: event.rotation(),
phase, phase,
}); });
} }
#[unsafe(method(pressureChangeWithEvent:))] #[unsafe(method(pressureChangeWithEvent:))]
fn pressure_change_with_event(&self, event: &NSEvent) { fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:"); let _entered = debug_span!("pressureChangeWithEvent:").entered();
self.queue_event(WindowEvent::TouchpadPressure { self.queue_event(WindowEvent::TouchpadPressure {
device_id: None, device_id: None,
pressure: unsafe { event.pressure() }, pressure: event.pressure(),
stage: unsafe { event.stage() } as i64, stage: event.stage() as i64,
}); });
} }
@@ -768,13 +768,13 @@ define_class!(
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[unsafe(method(_wantsKeyDownForEvent:))] #[unsafe(method(_wantsKeyDownForEvent:))]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:"); let _entered = debug_span!("_wantsKeyDownForEvent:").entered();
true true
} }
#[unsafe(method(acceptsFirstMouse:))] #[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:"); let _entered = debug_span!("acceptsFirstMouse:").entered();
self.ivars().accepts_first_mouse self.ivars().accepts_first_mouse
} }
} }
@@ -794,19 +794,65 @@ impl WinitView {
ime_size: Default::default(), ime_size: Default::default(),
modifiers: Default::default(), modifiers: Default::default(),
phys_modifiers: Default::default(), phys_modifiers: Default::default(),
tracking_rect: Default::default(),
ime_state: Default::default(), ime_state: Default::default(),
input_source: Default::default(), input_source: Default::default(),
ime_allowed: Default::default(), ime_capabilities: Default::default(),
forward_key_to_app: Default::default(), forward_key_to_app: Default::default(),
marked_text: Default::default(), marked_text: Default::default(),
accepts_first_mouse, accepts_first_mouse,
option_as_alt: Cell::new(option_as_alt), option_as_alt: Cell::new(option_as_alt),
}); });
let this: Retained<Self> = unsafe { msg_send![super(this), init] }; let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();
// Ask AppKit to redisplay the layer while the view is being resized so layer-backed
// surfaces keep painting.
this.setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy::DuringViewResize);
// `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and
// `mouseExited:`.
//
// `MouseMoved` enables receiving events through `mouseMoved:`
//
// We do not set `CursorUpdate` because it is part of the "flexible" alternative to
// `cursorRect` based cursor image updates, and we currently still use
// `cursorRect`s. We also can't really switch to this approach because "The
// cursorUpdate(with:) message is not sent when the NSTrackingCursorUpdate option is
// specified along with [`ActiveAlways`]."
//
// `ActiveAlways` indicates we want to receive events when the window is not
// focused ("key window" in Cocoa terms), which matches the behavior on other
// platforms.
//
// We do not set `AssumeInside` because we want to avoid emitting `Left` events without a
// correspondering `Entered` to our consumers, and not setting this flag tells AppKit to
// handle this for us by synthesizing entry and exit events in some cases.
//
// `InVisibleRect` instructs the tracking area's `owner` (our `NSView`) to ignore the value
// we provide in `rect` and keep the tracking area's bounds up to date with the
// current view bounds automatically.
//
// We do not set `EnabledDuringMouseDrag` to match the platform behavior on Windows
// and Wayland, since neither emit events while being dragged over with an empty
// cursor without focus.
//
// See also https://developer.apple.com/documentation/appkit/nstrackingareaoptions.
// Safety: the type of `owner` should be `NSView` and is.
// The type of `user_info` is irrelevant because it is None.
this.addTrackingArea(&*unsafe {
NSTrackingArea::initWithRect_options_owner_userInfo(
NSTrackingArea::alloc(),
NSRect::ZERO,
NSTrackingAreaOptions::MouseEnteredAndExited
| NSTrackingAreaOptions::MouseMoved
| NSTrackingAreaOptions::ActiveAlways
| NSTrackingAreaOptions::InVisibleRect,
Some(&this),
None,
)
});
this this
} }
@@ -821,6 +867,37 @@ impl WinitView {
}); });
} }
fn surface_resized(&self) {
let Some(window) = (**self).window() else {
return;
};
let size = self.surface_size();
let window_id = window_id(&window);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::SurfaceResized(size));
});
}
/// Returns the drawable size from the view's backing-coordinate bounds.
pub(super) fn surface_size(&self) -> PhysicalSize<u32> {
// The view bounds are authoritative for full-size content views and during live resize.
// Deriving this from the window frame can exclude custom titlebar content or be stale.
let backing_bounds = self.convertRectToBacking(self.bounds());
PhysicalSize::new(
backing_bounds.size.width.round().max(0.0) as u32,
backing_bounds.size.height.round().max(0.0) as u32,
)
}
fn redraw_during_live_resize(&self) {
let Some(window) = (**self).window() else {
return;
};
if window.inLiveResize() {
self.ivars().app_state.handle_redraw(window_id(&window));
}
}
fn scale_factor(&self) -> f64 { fn scale_factor(&self) -> f64 {
self.window().backingScaleFactor() as f64 self.window().backingScaleFactor() as f64
} }
@@ -858,23 +935,37 @@ impl WinitView {
false false
} }
} }
pub(super) fn enable_ime(&self, capabilities: ImeCapabilities) {
// This seems reasonable but the prior behavior of `set_ime_allowed` doesn't do this
// (it was also broken but let's not break things worse)
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) { // if self.ivars().ime_capabilities.get().is_none() {
if self.ivars().ime_allowed.get() == ime_allowed { // self.ivars().ime_state.set(ImeState::Ground);
return; // }
}
self.ivars().ime_allowed.set(ime_allowed);
if self.ivars().ime_allowed.get() {
return;
}
// Clear markedText
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
// why are we disabling things in an enable fn? who knows. it's what the previous one did
// though
if self.ivars().ime_state.get() != ImeState::Disabled { if self.ivars().ime_state.get() != ImeState::Disabled {
self.ivars().ime_state.set(ImeState::Disabled); self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled)); self.queue_event(WindowEvent::Ime(Ime::Disabled));
} }
self.ivars().ime_capabilities.set(Some(capabilities));
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
}
pub(super) fn disable_ime(&self) {
// see above
self.ivars().ime_capabilities.set(None);
if self.ivars().ime_state.get() != ImeState::Disabled {
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
// we probably don't need to do this, but again this mirrors the prior behavior of
// `set_ime_allowed`
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
}
pub(super) fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.ivars().ime_capabilities.get()
} }
pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) { pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) {
@@ -917,8 +1008,8 @@ impl WinitView {
// later will work though, since the flags are attached to the event and contain valid // later will work though, since the flags are attached to the event and contain valid
// information. // information.
'send_event: { 'send_event: {
if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 { if is_flags_changed_event && ns_event.keyCode() != 0 {
let scancode = unsafe { ns_event.keyCode() }; let scancode = ns_event.keyCode();
let physical_key = scancode_to_physicalkey(scancode as u32); let physical_key = scancode_to_physicalkey(scancode as u32);
let logical_key = code_to_key(physical_key, scancode); let logical_key = code_to_key(physical_key, scancode);
@@ -1050,7 +1141,7 @@ impl WinitView {
|| view_point.x > frame.size.width || view_point.x > frame.size.width
|| view_point.y > frame.size.height || view_point.y > frame.size.height
{ {
let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() }; let mouse_buttons_down = NSEvent::pressedMouseButtons();
if mouse_buttons_down == 0 { if mouse_buttons_down == 0 {
// Point is outside of the client area (view) and no buttons are pressed // Point is outside of the client area (view) and no buttons are pressed
return; return;
@@ -1068,7 +1159,7 @@ impl WinitView {
} }
fn mouse_view_point(&self, event: &NSEvent) -> LogicalPosition<f64> { fn mouse_view_point(&self, event: &NSEvent) -> LogicalPosition<f64> {
let window_point = unsafe { event.locationInWindow() }; let window_point = event.locationInWindow();
let view_point = self.convertPoint_fromView(window_point, None); let view_point = self.convertPoint_fromView(window_point, None);
LogicalPosition::new(view_point.x, view_point.y) LogicalPosition::new(view_point.x, view_point.y)
@@ -1082,21 +1173,18 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
// For the other events, it's always set to 0. // For the other events, it's always set to 0.
// MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons, // MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons,
// but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications. // but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications.
match unsafe { event.buttonNumber() } { let b: isize = event.buttonNumber();
0 => MouseButton::Left, b.try_into()
1 => MouseButton::Right, .ok()
2 => MouseButton::Middle, .and_then(MouseButton::try_from_u8)
3 => MouseButton::Back, .expect("expected MacOS button number in the range 0..=31")
4 => MouseButton::Forward,
n => MouseButton::Other(n as u16),
}
} }
// NOTE: to get option as alt working we need to rewrite events // NOTE: to get option as alt working we need to rewrite events
// we're getting from the operating system, which makes it // we're getting from the operating system, which makes it
// impossible to provide such events as extra in `KeyEvent`. // impossible to provide such events as extra in `KeyEvent`.
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEvent> { fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEvent> {
let ev_mods = event_mods(event).state; let ev_mods = event_mods(event).state();
let ignore_alt_characters = match option_as_alt { let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true, OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
OptionAsAlt::OnlyRight if ralt_pressed(event) => true, OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
@@ -1106,12 +1194,10 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
&& !ev_mods.meta_key(); && !ev_mods.meta_key();
if ignore_alt_characters { if ignore_alt_characters {
let ns_chars = unsafe { let ns_chars =
event.charactersIgnoringModifiers().expect("expected characters to be non-null") event.charactersIgnoringModifiers().expect("expected characters to be non-null");
};
unsafe { NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
event.r#type(), event.r#type(),
event.locationInWindow(), event.locationInWindow(),
event.modifierFlags(), event.modifierFlags(),
@@ -1124,8 +1210,96 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
event.keyCode(), event.keyCode(),
) )
.unwrap() .unwrap()
}
} else { } else {
event.copy() event.copy()
} }
} }
/// Convert a UTF-16 code unit offset into the corresponding UTF-8 byte offset within `s`.
///
/// IMEs are not required to send well-formed offsets, so this is defensive: an offset that
/// would split a surrogate pair is snapped down to the start of that character, and an
/// out-of-bounds offset is clamped to the end of the string (e.g. native Pinyin sends
/// out-of-bounds indices, see <https://github.com/alacritty/alacritty/issues/8791>).
///
/// The mapping is monotone non-decreasing, so applying it to the location and end of an
/// `NSRange` (where `location <= end`) can never produce an inverted byte range.
fn utf16_to_utf8_offset(s: &str, utf16_offset: usize) -> usize {
let mut utf16_pos = 0;
for (utf8_pos, ch) in s.char_indices() {
if utf16_pos >= utf16_offset {
return utf8_pos;
}
utf16_pos += ch.len_utf16();
// The target offset lands strictly inside this character's UTF-16 representation,
// i.e. it splits a surrogate pair: snap down to the character boundary.
if utf16_pos > utf16_offset {
return utf8_pos;
}
}
s.len()
}
#[cfg(test)]
mod tests {
use super::*;
/// Apply the UTF-16 -> UTF-8 conversion to both ends of a `selectedRange {loc, len}`,
/// mirroring what `set_marked_text` does for the emitted `Ime::Preedit` cursor range.
fn convert(s: &str, loc: usize, len: usize) -> (usize, usize) {
(utf16_to_utf8_offset(s, loc), utf16_to_utf8_offset(s, loc + len))
}
#[test]
fn mid_surrogate_offset_snaps_down() {
// "😀a": 😀 is one char = 2 UTF-16 units = 4 UTF-8 bytes; offset 1 is mid-pair.
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 1), 0);
// Offset 2 is the boundary just after the pair.
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 2), 4);
}
#[test]
fn no_longer_inverted() {
// "a😀b" with selectedRange {1,1}: previously emitted (1, 0) -- lower > upper, a
// slice-panic vector. The boundary-snapping conversion keeps lower <= upper.
assert_eq!(convert("a\u{1F600}b", 1, 1), (1, 1));
}
#[test]
fn prefix_preserved_on_mid_pair_collapse() {
// "a😀b" with selectedRange {2,0}: previously collapsed to (0, 0), discarding the
// valid "a" prefix; now snaps to the char boundary after "a".
assert_eq!(convert("a\u{1F600}b", 2, 0), (1, 1));
}
#[test]
fn out_of_bounds_clamps_to_len() {
// Subsumes the #4494 `.min(len)` clamp: an out-of-bounds offset maps to the string
// length instead of triggering an NSRangeException.
assert_eq!(convert("\u{1F600}a", 99, 0), (5, 5));
}
#[test]
fn well_formed_inputs_are_identity() {
// The common case (well-formed boundary indices) must be byte-for-byte unchanged.
assert_eq!(convert("a\u{1F600}b", 3, 0), (5, 5));
assert_eq!(convert("a\u{1F600}b", 4, 0), (6, 6));
// BMP multi-byte (Japanese): each char is 1 UTF-16 unit and 3 UTF-8 bytes.
assert_eq!(convert("\u{3053}\u{3093}", 1, 1), (3, 6));
}
#[test]
fn monotone_non_decreasing() {
// Sweep every UTF-16 offset (including out-of-bounds) over a string mixing BMP and
// non-BMP characters and assert the conversion never goes backwards, which is what
// guarantees `lower <= upper` for any `NSRange`.
let s = "a\u{1F600}b\u{3053}\u{1F4A9}c";
let mut prev = 0;
for off in 0..=20 {
let cur = utf16_to_utf8_offset(s, off);
assert!(cur >= prev, "non-monotone at offset {off}: {cur} < {prev}");
assert!(cur <= s.len(), "offset {off} mapped past end: {cur} > {}", s.len());
prev = cur;
}
}
}

View File

@@ -4,19 +4,22 @@ use std::sync::Arc;
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use dpi::{Position, Size}; use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{Retained, autoreleasepool};
use objc2::{define_class, MainThreadMarker, Message}; use objc2::{MainThreadMarker, Message, define_class};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow}; use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject; use objc2_foundation::NSObject;
use tracing::trace_span;
use winit_core::cursor::Cursor;
use winit_core::error::RequestError;
use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
ImeCapabilities, ImeRequest, ImeRequestError, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
use super::event_loop::ActiveEventLoop; use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate; use super::window_delegate::WindowDelegate;
use crate::error::RequestError;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::window::{
Cursor, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes,
WindowButtons, WindowId, WindowLevel,
};
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Window { pub(crate) struct Window {
@@ -91,7 +94,7 @@ impl rwh_06::HasWindowHandle for Window {
} }
impl CoreWindow for Window { impl CoreWindow for Window {
fn id(&self) -> crate::window::WindowId { fn id(&self) -> winit_core::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id()) self.maybe_wait_on_main(|delegate| delegate.id())
} }
@@ -231,16 +234,12 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon)); self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
} }
fn set_ime_cursor_area(&self, position: Position, size: Size) { fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size)); self.maybe_wait_on_main(|delegate| delegate.request_ime_update(request))
} }
fn set_ime_allowed(&self, allowed: bool) { fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed)); self.maybe_wait_on_main(|delegate| delegate.ime_capabilities())
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
} }
fn focus_window(&self) { fn focus_window(&self) {
@@ -279,7 +278,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position)) self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
} }
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> { fn set_cursor_grab(
&self,
mode: winit_core::window::CursorGrabMode,
) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode)) self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
} }
@@ -293,7 +295,7 @@ impl CoreWindow for Window {
fn drag_resize_window( fn drag_resize_window(
&self, &self,
direction: crate::window::ResizeDirection, direction: winit_core::window::ResizeDirection,
) -> Result<(), RequestError> { ) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?) Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
} }
@@ -349,13 +351,13 @@ define_class!(
impl WinitWindow { impl WinitWindow {
#[unsafe(method(canBecomeMainWindow))] #[unsafe(method(canBecomeMainWindow))]
fn can_become_main_window(&self) -> bool { fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow"); let _entered = trace_span!("canBecomeMainWindow").entered();
true true
} }
#[unsafe(method(canBecomeKeyWindow))] #[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool { fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow"); let _entered = trace_span!("canBecomeKeyWindow").entered();
true true
} }
} }
@@ -373,7 +375,7 @@ define_class!(
// it doesn't if window doesn't have NSWindowStyleMask::Titled // it doesn't if window doesn't have NSWindowStyleMask::Titled
#[unsafe(method(canBecomeKeyWindow))] #[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool { fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow"); let _entered = trace_span!("canBecomeKeyWindow").entered();
true true
} }
} }

55
winit-common/Cargo.toml Normal file
View File

@@ -0,0 +1,55 @@
[package]
description = "Winit implementation helpers"
documentation = "https://docs.rs/winit-common"
edition.workspace = true
license.workspace = true
name = "winit-common"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
# Event Handler
event-handler = []
# XKB
wayland = ["dep:memmap2"]
x11 = ["xkbcommon-dl?/x11", "dep:x11-dl"]
xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
# CoreFoundation
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
# Foundation
foundation = ["dep:block2", "dep:objc2", "dep:objc2-foundation"]
[dependencies]
smol_str = { workspace = true, optional = true }
tracing.workspace = true
winit-core.workspace = true
# XKB
memmap2 = { workspace = true, optional = true }
x11-dl = { workspace = true, optional = true }
xkbcommon-dl = { workspace = true, optional = true }
# Foundation / CoreFoundation
block2 = { workspace = true, optional = true }
objc2 = { workspace = true, optional = true }
objc2-core-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"objc2",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"NSNotification",
"NSString",
"NSOperation",
] }
[package.metadata.docs.rs]
all-features = true

1
winit-common/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

1
winit-common/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -3,10 +3,9 @@ use std::sync::Arc;
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2_core_foundation::{ use objc2_core_foundation::{
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext, kCFRunLoopCommonModes,
}; };
use winit_core::event_loop::EventLoopProxyProvider;
use crate::event_loop::EventLoopProxyProvider;
/// A waker that signals a `CFRunLoopSource` on the main thread. /// A waker that signals a `CFRunLoopSource` on the main thread.
/// ///
@@ -15,7 +14,7 @@ use crate::event_loop::EventLoopProxyProvider;
/// ///
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>. /// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct EventLoopProxy { pub struct EventLoopProxy {
source: CFRetained<CFRunLoopSource>, source: CFRetained<CFRunLoopSource>,
/// Cached value of `CFRunLoopGetMain`. /// Cached value of `CFRunLoopGetMain`.
main_loop: CFRetained<CFRunLoop>, main_loop: CFRetained<CFRunLoop>,
@@ -29,7 +28,7 @@ impl EventLoopProxy {
/// Create a new proxy, registering it to be performed on the main thread. /// Create a new proxy, registering it to be performed on the main thread.
/// ///
/// The provided closure should call `proxy_wake_up` on the application. /// The provided closure should call `proxy_wake_up` on the application.
pub(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self { pub 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 // 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). // atomic (`Retained`/`CFRetained` would be valid alternatives too).
let signaller = Arc::new(signaller); let signaller = Arc::new(signaller);
@@ -100,8 +99,7 @@ impl EventLoopProxy {
// FIXME(madsmtm): Use this on macOS too. // FIXME(madsmtm): Use this on macOS too.
// More difficult there, since the user can re-start the event loop. // More difficult there, since the user can re-start the event loop.
#[cfg_attr(target_os = "macos", allow(dead_code))] pub fn invalidate(&self) {
pub(crate) fn invalidate(&self) {
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that // 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. // we only need to register a single source even if there's multiple proxies in use.
self.source.invalidate(); self.source.invalidate();

View File

@@ -0,0 +1,136 @@
//! Various utilities for interfacing with the main run loop.
//!
//! These allow for using closures without the `Send + Sync` requirement that is otherwise required
//! when interfacing with run loops.
//!
//! See also <https://github.com/madsmtm/objc2/issues/696> for work on this at a lower level.
use std::cell::Cell;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
use tracing::{Span, error};
use super::MainRunLoopObserver;
/// Wrapper around [`CFRunLoop::main`].
#[derive(Debug)]
pub struct MainRunLoop {
/// This is on the main thread.
_mtm: MainThreadMarker,
/// A cached reference to the main run loop.
main_run_loop: CFRetained<CFRunLoop>,
}
impl MainRunLoop {
/// Get the main run loop.
pub fn get(_mtm: MainThreadMarker) -> Self {
let main_run_loop = CFRunLoop::main().unwrap();
Self { _mtm, main_run_loop }
}
/// Get a reference to the underlying [`CFRunLoop`].
pub fn run_loop(&self) -> &CFRunLoop {
&self.main_run_loop
}
/// Wake the main run loop.
pub fn wake_up(&self) {
self.main_run_loop.wake_up();
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]` to queue a closure to run
/// the next time runloop sources are processed.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// 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) {
// We use this to run a closure asynchronously at a later point, so it also makes sense to
// re-enter the current span when running the queued closure.
let span = Span::current();
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
// Running this block happens inside a `CFRunLoopSource`, but the spans that we emit for
// that are (intentionally) overwritten by entering the span from before.
let _enter = span.enter();
debug_assert!(MainThreadMarker::new().is_some());
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
let _ = self._mtm;
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
//
// Additionally, we have a `MainThreadMarker` here, which means we know we're on the main
// thread. We also know that the run loop is the main-thread run loop, so scheduling a
// non-`Send` block to that is allowed.
unsafe { self.main_run_loop.perform_block(Some(mode), Some(&block)) }
}
/// Add an observer to the main run loop.
pub fn add_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
// Accessing the `MainObserver`'s observer is fine here, since we're adding it to the main
// run loop (which is on the same thread that the observer was created on).
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
}
/// Remove an observer from the main run loop.
///
/// This is also done automatically when the [`MainRunLoopObserver`] is dropped.
pub fn remove_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
// Same as in `add_observer`, accessing the main loop's observer is fine.
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
}
}

View File

@@ -0,0 +1,55 @@
use block2::RcBlock;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
CFIndex, CFRetained, CFRunLoopActivity, CFRunLoopObserver, kCFAllocatorDefault,
};
/// A [`CFRunLoopObserver`] on the main thread.
///
/// This type has "ownership" semantics, and invalidates the observer when dropped.
#[derive(Debug)]
pub struct MainRunLoopObserver {
/// This must be private, otherwise the user might add it to an arbitrary run loop (but this
/// observer is not designed to only be on the main thread).
pub(crate) observer: CFRetained<CFRunLoopObserver>,
}
impl MainRunLoopObserver {
/// Create a new run loop observer that observes the main run loop.
pub fn new(
mtm: MainThreadMarker,
activities: CFRunLoopActivity,
repeats: bool,
// The lower the value, the sooner this will run (inverse of a "priority").
order: CFIndex,
callback: impl Fn(CFRunLoopActivity) + 'static,
) -> Self {
let block = RcBlock::new(move |_: *mut _, activity| {
debug_assert!(MainThreadMarker::new().is_some());
callback(activity)
});
let _ = mtm;
// SAFETY: The callback is not Send + Sync, which would normally be unsound, but since we
// restrict the callback to only ever be on the main thread (by taking `MainThreadMarker`,
// and in `MainRunLoop::add_observer`), the callback doesn't have to be thread safe.
let observer = unsafe {
CFRunLoopObserver::with_handler(
kCFAllocatorDefault,
activities.0,
repeats,
order,
Some(&block),
)
}
.unwrap();
Self { observer }
}
}
impl Drop for MainRunLoopObserver {
fn drop(&mut self) {
self.observer.invalidate();
}
}

View File

@@ -0,0 +1,14 @@
//! Various wrappers around [`CFRunLoop`][objc2_core_foundation::CFRunLoop].
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
mod event_loop_proxy;
mod main_run_loop;
mod main_run_loop_observer;
mod tracing_observers;
pub use self::event_loop_proxy::*;
pub use self::main_run_loop::*;
pub use self::main_run_loop_observer::*;
pub use self::tracing_observers::*;

View File

@@ -0,0 +1,146 @@
use std::cell::RefCell;
use std::rc::Rc;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFIndex, CFRunLoop, CFRunLoopActivity, kCFRunLoopDefaultMode};
use tracing::span::EnteredSpan;
use tracing::{Level, error, field, span_enabled, trace_span};
use crate::core_foundation::MainRunLoopObserver;
/// Create two run loop observers that add TRACE-level [spans][tracing::span].
///
/// This is useful when debugging run loops, it makes it easier to see in which run loop activity an
/// event is triggered inside (if any).
///
/// When debugging these interactions, it can also be useful to configure your tracing subscriber
/// with `.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)` to emit events upon
/// entering and exiting these stages.
pub fn tracing_observers(
mtm: MainThreadMarker,
) -> Option<(MainRunLoopObserver, MainRunLoopObserver)> {
// Observers are a bit costly, so don't create them if the tracing-level for this module is
// configured to disable them.
if !span_enabled!(Level::TRACE) {
return None;
}
/// The state that we think the runloop is currently in.
///
/// The order of activities we observe if waiting twice looks something like:
/// - CFRunLoopActivity::Entry
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::BeforeWaiting
/// - CFRunLoopActivity::AfterWaiting
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::BeforeWaiting
/// - CFRunLoopActivity::AfterWaiting
/// - CFRunLoopActivity::Exit
///
/// And if not waiting, it looks something like:
/// - CFRunLoopActivity::Entry
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::Exit
#[derive(Default)]
#[allow(unused)] // EnteredSpans are kept around
enum RunLoopState {
/// Currently processing `Entry`/`Exit` observers.
#[default]
Entered,
/// Currently processing timers or `BeforeTimers` observers.
Timers(EnteredSpan),
/// Currently processing sources or `BeforeSources` observers.
Sources(EnteredSpan),
/// Currently waiting or processing `BeforeWaiting`/`AfterWaiting` observers.
Waiting(EnteredSpan),
}
// A list of currently entered (outer) spans and their state.
//
// This is a list because runloops can be run recursively.
let spans: Rc<RefCell<Vec<(EnteredSpan, RunLoopState)>>> = Rc::new(RefCell::new(Vec::new()));
let spans_clone = Rc::clone(&spans);
// An observer at the start of run loop activities.
let activities = CFRunLoopActivity::Entry
| CFRunLoopActivity::BeforeTimers
| CFRunLoopActivity::BeforeSources
| CFRunLoopActivity::BeforeWaiting;
let start = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MIN, move |activity| {
match activity {
// Add an outer span for each runloop iteration.
CFRunLoopActivity::Entry => {
let span = trace_span!("inside runloop", mode = field::Empty);
// Get the mode dynamically, the observer may added to multiple different modes.
let mode = CFRunLoop::current().unwrap().current_mode().unwrap();
// Mode isn't interesting if it's the default mode.
if &*mode != unsafe { kCFRunLoopDefaultMode }.unwrap() {
span.record("mode", field::display(mode));
}
let entered = span.entered();
spans.borrow_mut().push((entered, RunLoopState::Entered));
},
// Add inner spans that help inspecting the state the runloop is in.
CFRunLoopActivity::BeforeTimers => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Timers(trace_span!("processing timers").entered());
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::BeforeSources => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Sources(trace_span!("processing sources").entered());
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::BeforeWaiting => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Waiting(trace_span!("waiting").entered());
} else {
error!("unbalanced observer invocations");
}
},
activity => unreachable!("unexpected activity: {activity:?}"),
}
});
// An observer at the end of run loop activities.
let activities = CFRunLoopActivity::AfterWaiting | CFRunLoopActivity::Exit;
let end = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MAX, move |activity| {
match activity {
CFRunLoopActivity::AfterWaiting => {
if let Some((_, state)) = spans_clone.borrow_mut().last_mut() {
// Transition from the waiting state to the initial state.
*state = RunLoopState::Entered;
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::Exit => {
if let Some((span, state)) = spans_clone.borrow_mut().pop() {
drop(state); // Explicitly exit and drop inner span.
drop(span); // Explicitly exit and drop outer span.
} else {
error!("unbalanced observer invocations");
}
},
activity => unreachable!("unexpected activity: {activity:?}"),
}
});
Some((start, end))
}

View File

@@ -1,12 +1,12 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::{fmt, mem}; use std::{fmt, mem};
use crate::application::ApplicationHandler; use winit_core::application::ApplicationHandler;
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access /// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
/// to it within the execution of a closure. /// to it within the execution of a closure.
#[derive(Default)] #[derive(Default)]
pub(crate) struct EventHandler { pub struct EventHandler {
/// This can be in the following states: /// This can be in the following states:
/// - Not registered by the event loop, or terminated (None). /// - Not registered by the event loop, or terminated (None).
/// - Present (Some(handler)). /// - Present (Some(handler)).
@@ -26,7 +26,7 @@ impl fmt::Debug for EventHandler {
} }
impl EventHandler { impl EventHandler {
pub(crate) fn new() -> Self { pub fn new() -> Self {
Self { inner: RefCell::new(None) } Self { inner: RefCell::new(None) }
} }
@@ -35,7 +35,7 @@ impl EventHandler {
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates /// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
/// to store the handler in a thread local, such that it can be accessed /// to store the handler in a thread local, such that it can be accessed
/// from within the closure. /// from within the closure.
pub(crate) fn set<'handler, R>( pub fn set<'handler, R>(
&self, &self,
app: Box<dyn ApplicationHandler + 'handler>, app: Box<dyn ApplicationHandler + 'handler>,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
@@ -79,6 +79,10 @@ impl EventHandler {
// Allowed, happens if the handler was cleared manually // Allowed, happens if the handler was cleared manually
// elsewhere (such as in `applicationWillTerminate:`). // elsewhere (such as in `applicationWillTerminate:`).
}, },
// We use `eprintln!` here over `tracing::error!`, since we're going to abort
// immediately after this, and it'd be annoying for the user if they didn't get
// any feedback on that if they don't have a tracing subscriber.
#[allow(clippy::disallowed_macros)]
Err(_) => { Err(_) => {
// Note: This is not expected to ever happen, this // Note: This is not expected to ever happen, this
// module generally controls the `RefCell`, and // module generally controls the `RefCell`, and
@@ -104,18 +108,17 @@ impl EventHandler {
// soundness. // soundness.
} }
#[cfg(target_os = "macos")] pub fn in_use(&self) -> bool {
pub(crate) fn in_use(&self) -> bool {
self.inner.try_borrow().is_err() self.inner.try_borrow().is_err()
} }
pub(crate) fn ready(&self) -> bool { pub fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_))) matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
} }
pub(crate) fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) { pub fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
match self.inner.try_borrow_mut().as_deref_mut() { match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(ref mut user_app)) => { Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here, // It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is // so that `in_use` can properly detect that the handler is
// still in use. // still in use.
@@ -137,7 +140,7 @@ impl EventHandler {
} }
} }
pub(crate) fn terminate(&self) { pub fn terminate(&self) {
match self.inner.try_borrow_mut().as_deref_mut() { match self.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => { Ok(data @ Some(_)) => {
let handler = data.take(); let handler = data.take();

View File

@@ -0,0 +1,3 @@
mod notification_center;
pub use self::notification_center::*;

10
winit-common/src/lib.rs Normal file
View File

@@ -0,0 +1,10 @@
//! Winit implementation helpers.
#[cfg(feature = "core-foundation")]
pub mod core_foundation;
#[cfg(feature = "event-handler")]
pub mod event_handler;
#[cfg(feature = "foundation")]
pub mod foundation;
#[cfg(feature = "xkb")]
pub mod xkb;

View File

@@ -12,7 +12,7 @@ use xkbcommon_dl::{
xkb_compose_status, xkb_compose_table, xkb_keysym_t, xkb_compose_status, xkb_compose_table, xkb_keysym_t,
}; };
use super::{XkbContext, XKBCH}; use super::{XKBCH, XkbContext};
#[derive(Debug)] #[derive(Debug)]
pub struct XkbComposeTable { pub struct XkbComposeTable {

View File

@@ -4,20 +4,22 @@ use std::ffi::c_char;
use std::ops::Deref; use std::ops::Deref;
use std::ptr::{self, NonNull}; use std::ptr::{self, NonNull};
#[cfg(x11_platform)] use winit_core::keyboard::{
Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
};
#[cfg(feature = "x11")]
use x11_dl::xlib_xcb::xcb_connection_t; use x11_dl::xlib_xcb::xcb_connection_t;
use xkb::XKB_MOD_INVALID; use xkb::XKB_MOD_INVALID;
use xkbcommon_dl::{ use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t, self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
xkb_layout_index_t, xkb_mod_index_t, xkb_layout_index_t, xkb_mod_index_t,
}; };
#[cfg(wayland_platform)] #[cfg(feature = "wayland")]
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; #[cfg(feature = "x11")]
#[cfg(x11_platform)] use super::XKBXH;
use crate::platform_impl::common::xkb::XKBXH; use super::{XKBH, XkbContext};
use crate::platform_impl::common::xkb::{XkbContext, XKBH};
/// Map the raw X11-style keycode to the `KeyCode` enum. /// Map the raw X11-style keycode to the `KeyCode` enum.
/// ///
@@ -35,8 +37,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
// I can only hope they agree on what the keycodes mean. // I can only hope they agree on what the keycodes mean.
// //
// The mapping here is heavily influenced by Firefox' source: // The mapping here is heavily influenced by Firefox' and Chromium's sources:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h // - https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
// - https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// //
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are // Some of the keycodes are likely superfluous for our purposes, and some are ones which are
// difficult to test the correctness of, or discover the purpose of. Because of this, they've // difficult to test the correctness of, or discover the purpose of. Because of this, they've
@@ -157,11 +160,11 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
113 => KeyCode::AudioVolumeMute, 113 => KeyCode::AudioVolumeMute,
114 => KeyCode::AudioVolumeDown, 114 => KeyCode::AudioVolumeDown,
115 => KeyCode::AudioVolumeUp, 115 => KeyCode::AudioVolumeUp,
// 116 => KeyCode::POWER, 116 => KeyCode::Power,
117 => KeyCode::NumpadEqual, 117 => KeyCode::NumpadEqual,
// 118 => KeyCode::KPPLUSMINUS, // 118 => KeyCode::KPPLUSMINUS,
119 => KeyCode::Pause, 119 => KeyCode::Pause,
// 120 => KeyCode::SCALE, 120 => KeyCode::ShowAllWindows,
121 => KeyCode::NumpadComma, 121 => KeyCode::NumpadComma,
122 => KeyCode::Lang1, 122 => KeyCode::Lang1,
123 => KeyCode::Lang2, 123 => KeyCode::Lang2,
@@ -183,7 +186,7 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 139 => KeyCode::MENU, // 139 => KeyCode::MENU,
140 => KeyCode::LaunchApp2, // CALC 140 => KeyCode::LaunchApp2, // CALC
// 141 => KeyCode::SETUP, // 141 => KeyCode::SETUP,
// 142 => KeyCode::SLEEP, 142 => KeyCode::Sleep,
143 => KeyCode::WakeUp, 143 => KeyCode::WakeUp,
144 => KeyCode::LaunchApp1, // FILE 144 => KeyCode::LaunchApp1, // FILE
// 145 => KeyCode::SENDFILE, // 145 => KeyCode::SENDFILE,
@@ -208,8 +211,8 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
164 => KeyCode::MediaPlayPause, 164 => KeyCode::MediaPlayPause,
165 => KeyCode::MediaTrackPrevious, 165 => KeyCode::MediaTrackPrevious,
166 => KeyCode::MediaStop, 166 => KeyCode::MediaStop,
// 167 => KeyCode::RECORD, 167 => KeyCode::MediaRecord,
// 168 => KeyCode::REWIND, 168 => KeyCode::MediaRewind,
// 169 => KeyCode::PHONE, // 169 => KeyCode::PHONE,
// 170 => KeyCode::ISO, // 170 => KeyCode::ISO,
171 => KeyCode::MediaSelect, // CONFIG 171 => KeyCode::MediaSelect, // CONFIG
@@ -220,8 +223,8 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 176 => KeyCode::EDIT, // 176 => KeyCode::EDIT,
// 177 => KeyCode::SCROLLUP, // 177 => KeyCode::SCROLLUP,
// 178 => KeyCode::SCROLLDOWN, // 178 => KeyCode::SCROLLDOWN,
// 179 => KeyCode::KPLEFTPAREN, 179 => KeyCode::NumpadParenLeft,
// 180 => KeyCode::KPRIGHTPAREN, 180 => KeyCode::NumpadParenRight,
// 181 => KeyCode::NEW, // 181 => KeyCode::NEW,
// 182 => KeyCode::REDO, // 182 => KeyCode::REDO,
183 => KeyCode::F13, 183 => KeyCode::F13,
@@ -237,14 +240,14 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
193 => KeyCode::F23, 193 => KeyCode::F23,
194 => KeyCode::F24, 194 => KeyCode::F24,
// 200 => KeyCode::PLAYCD, // 200 => KeyCode::PLAYCD,
// 201 => KeyCode::PAUSECD, 201 => KeyCode::MediaPause,
// 202 => KeyCode::PROG3, // 202 => KeyCode::PROG3,
// 203 => KeyCode::PROG4, // 203 => KeyCode::PROG4,
// 204 => KeyCode::DASHBOARD, // 204 => KeyCode::DASHBOARD,
// 205 => KeyCode::SUSPEND, // 205 => KeyCode::SUSPEND,
// 206 => KeyCode::CLOSE, // 206 => KeyCode::CLOSE,
// 207 => KeyCode::PLAY, 207 => KeyCode::MediaPlay,
// 208 => KeyCode::FASTFORWARD, 208 => KeyCode::MediaFastForward,
// 209 => KeyCode::BASSBOOST, // 209 => KeyCode::BASSBOOST,
// 210 => KeyCode::PRINT, // 210 => KeyCode::PRINT,
// 211 => KeyCode::HP, // 211 => KeyCode::HP,
@@ -260,16 +263,16 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 221 => KeyCode::SHOP, // 221 => KeyCode::SHOP,
// 222 => KeyCode::ALTERASE, // 222 => KeyCode::ALTERASE,
// 223 => KeyCode::CANCEL, // 223 => KeyCode::CANCEL,
// 224 => KeyCode::BRIGHTNESSDOW, 224 => KeyCode::BrightnessDown,
// 225 => KeyCode::BRIGHTNESSU, 225 => KeyCode::BrightnessUp,
// 226 => KeyCode::MEDIA, // 226 => KeyCode::MEDIA,
// 227 => KeyCode::SWITCHVIDEOMODE, 227 => KeyCode::DisplayToggleIntExt,
// 228 => KeyCode::KBDILLUMTOGGLE, 228 => KeyCode::KeyboardBacklightToggle,
// 229 => KeyCode::KBDILLUMDOWN, // 229 => KeyCode::KBDILLUMDOWN,
// 230 => KeyCode::KBDILLUMUP, // 230 => KeyCode::KBDILLUMUP,
// 231 => KeyCode::SEND, 231 => KeyCode::MailSend,
// 232 => KeyCode::REPLY, 232 => KeyCode::MailReply,
// 233 => KeyCode::FORWARDMAIL, 233 => KeyCode::MailForward,
// 234 => KeyCode::SAVE, // 234 => KeyCode::SAVE,
// 235 => KeyCode::DOCUMENTS, // 235 => KeyCode::DOCUMENTS,
// 236 => KeyCode::BATTERY, // 236 => KeyCode::BATTERY,
@@ -284,7 +287,14 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 245 => KeyCode::DISPLAY_OFF, // 245 => KeyCode::DISPLAY_OFF,
// 246 => KeyCode::WWAN, // 246 => KeyCode::WWAN,
// 247 => KeyCode::RFKILL, // 247 => KeyCode::RFKILL,
// 248 => KeyCode::KEY_MICMUTE, 248 => KeyCode::MicrophoneMuteToggle,
372 => KeyCode::ZoomToggle,
579 => KeyCode::LaunchControlPanel,
580 => KeyCode::SelectTask,
581 => KeyCode::LaunchScreenSaver,
583 => KeyCode::LaunchAssistant,
584 => KeyCode::KeyboardLayoutSelect,
633 => KeyCode::PrivacyScreenToggle,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)), _ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)),
}) })
} }
@@ -413,8 +423,10 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::AudioVolumeMute => Some(113), KeyCode::AudioVolumeMute => Some(113),
KeyCode::AudioVolumeDown => Some(114), KeyCode::AudioVolumeDown => Some(114),
KeyCode::AudioVolumeUp => Some(115), KeyCode::AudioVolumeUp => Some(115),
KeyCode::Power => Some(116),
KeyCode::NumpadEqual => Some(117), KeyCode::NumpadEqual => Some(117),
KeyCode::Pause => Some(119), KeyCode::Pause => Some(119),
KeyCode::ShowAllWindows => Some(120),
KeyCode::NumpadComma => Some(121), KeyCode::NumpadComma => Some(121),
KeyCode::Lang1 => Some(122), KeyCode::Lang1 => Some(122),
KeyCode::Lang2 => Some(123), KeyCode::Lang2 => Some(123),
@@ -434,6 +446,7 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::Cut => Some(137), KeyCode::Cut => Some(137),
KeyCode::Help => Some(138), KeyCode::Help => Some(138),
KeyCode::LaunchApp2 => Some(140), KeyCode::LaunchApp2 => Some(140),
KeyCode::Sleep => Some(142),
KeyCode::WakeUp => Some(143), KeyCode::WakeUp => Some(143),
KeyCode::LaunchApp1 => Some(144), KeyCode::LaunchApp1 => Some(144),
KeyCode::LaunchMail => Some(155), KeyCode::LaunchMail => Some(155),
@@ -445,9 +458,13 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::MediaPlayPause => Some(164), KeyCode::MediaPlayPause => Some(164),
KeyCode::MediaTrackPrevious => Some(165), KeyCode::MediaTrackPrevious => Some(165),
KeyCode::MediaStop => Some(166), KeyCode::MediaStop => Some(166),
KeyCode::MediaRecord => Some(167),
KeyCode::MediaRewind => Some(168),
KeyCode::MediaSelect => Some(171), KeyCode::MediaSelect => Some(171),
KeyCode::BrowserHome => Some(172), KeyCode::BrowserHome => Some(172),
KeyCode::BrowserRefresh => Some(173), KeyCode::BrowserRefresh => Some(173),
KeyCode::NumpadParenLeft => Some(179),
KeyCode::NumpadParenRight => Some(180),
KeyCode::F13 => Some(183), KeyCode::F13 => Some(183),
KeyCode::F14 => Some(184), KeyCode::F14 => Some(184),
KeyCode::F15 => Some(185), KeyCode::F15 => Some(185),
@@ -460,7 +477,26 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::F22 => Some(192), KeyCode::F22 => Some(192),
KeyCode::F23 => Some(193), KeyCode::F23 => Some(193),
KeyCode::F24 => Some(194), KeyCode::F24 => Some(194),
KeyCode::MediaPause => Some(201),
KeyCode::MediaPlay => Some(207),
KeyCode::MediaFastForward => Some(208),
KeyCode::BrowserSearch => Some(217), KeyCode::BrowserSearch => Some(217),
KeyCode::BrightnessDown => Some(224),
KeyCode::BrightnessUp => Some(225),
KeyCode::DisplayToggleIntExt => Some(227),
KeyCode::KeyboardBacklightToggle => Some(228),
KeyCode::MailSend => Some(231),
KeyCode::MailReply => Some(232),
KeyCode::MailForward => Some(233),
// PhysicalKey::Unidentified(NativeKeyCode::Unidentified) => Some(240),
KeyCode::MicrophoneMuteToggle => Some(248),
KeyCode::ZoomToggle => Some(372),
KeyCode::LaunchControlPanel => Some(579),
KeyCode::SelectTask => Some(580),
KeyCode::LaunchScreenSaver => Some(581),
KeyCode::LaunchAssistant => Some(583),
KeyCode::KeyboardLayoutSelect => Some(584),
KeyCode::PrivacyScreenToggle => Some(633),
_ => None, _ => None,
} }
} }
@@ -945,7 +981,7 @@ pub struct XkbKeymap {
} }
impl XkbKeymap { impl XkbKeymap {
#[cfg(wayland_platform)] #[cfg(feature = "wayland")]
pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> { pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> {
let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? }; let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? };
@@ -962,7 +998,7 @@ impl XkbKeymap {
Some(Self::new_inner(keymap, 0)) Some(Self::new_inner(keymap, 0))
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn from_x11_keymap( pub fn from_x11_keymap(
context: &XkbContext, context: &XkbContext,
xcb: *mut xcb_connection_t, xcb: *mut xcb_connection_t,
@@ -995,7 +1031,7 @@ impl XkbKeymap {
Self { keymap, _mods_indices: mods_indices, _core_keyboard_id } Self { keymap, _mods_indices: mods_indices, _core_keyboard_id }
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn mods_indices(&self) -> ModsIndices { pub fn mods_indices(&self) -> ModsIndices {
self._mods_indices self._mods_indices
} }
@@ -1016,11 +1052,7 @@ impl XkbKeymap {
&mut keysyms, &mut keysyms,
); );
if count == 1 { if count == 1 { *keysyms } else { 0 }
*keysyms
} else {
0
}
} }
} }
@@ -1047,7 +1079,7 @@ impl Deref for XkbKeymap {
} }
/// Modifier index in the keymap. /// Modifier index in the keymap.
#[cfg_attr(not(x11_platform), allow(dead_code))] #[cfg_attr(not(feature = "x11"), allow(dead_code))]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct ModsIndices { pub struct ModsIndices {
pub shift: Option<xkb_mod_index_t>, pub shift: Option<xkb_mod_index_t>,
@@ -1064,10 +1096,6 @@ fn mod_index_for_name(keymap: NonNull<xkb_keymap>, name: &[u8]) -> Option<xkb_mo
unsafe { unsafe {
let mod_index = let mod_index =
(XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char); (XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char);
if mod_index == XKB_MOD_INVALID { if mod_index == XKB_MOD_INVALID { None } else { Some(mod_index) }
None
} else {
Some(mod_index)
}
} }
} }

View File

@@ -1,41 +1,39 @@
use std::ops::Deref; use std::ops::Deref;
use std::os::raw::c_char; use std::os::raw::c_char;
#[cfg(wayland_platform)] #[cfg(feature = "wayland")]
use std::os::unix::io::OwnedFd; use std::os::unix::io::OwnedFd;
use std::ptr::{self, NonNull}; use std::ptr::{self, NonNull};
use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use smol_str::SmolStr; use smol_str::SmolStr;
use tracing::warn; use winit_core::event::{ElementState, KeyEvent};
use winit_core::keyboard::{Key, KeyLocation};
use xkbcommon_dl::{ use xkbcommon_dl::{
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle, self as xkb, XkbCommon, XkbCommonCompose, xkb_compose_status, xkb_context, xkb_context_flags,
xkbcommon_handle, XkbCommon, XkbCommonCompose, xkbcommon_compose_handle, xkbcommon_handle,
}; };
#[cfg(x11_platform)] #[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; 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::utils::Lazy;
mod compose; mod compose;
mod keymap; mod keymap;
mod state; mod state;
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable}; use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
#[cfg(x11_platform)]
pub use keymap::raw_keycode_to_physicalkey;
use keymap::XkbKeymap; use keymap::XkbKeymap;
#[cfg(feature = "x11")]
pub use keymap::raw_keycode_to_physicalkey;
pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey}; pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
pub use state::XkbState; pub use state::XkbState;
// TODO: Wire this up without using a static `AtomicBool`. // TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle); static XKBH: LazyLock<&'static XkbCommon> = LazyLock::new(xkbcommon_handle);
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle); static XKBCH: LazyLock<&'static XkbCommonCompose> = LazyLock::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); static XKBXH: LazyLock<&'static xkb::x11::XkbCommonX11> = LazyLock::new(xkbcommon_x11_handle);
#[inline(always)] #[inline(always)]
pub fn reset_dead_keys() { pub fn reset_dead_keys() {
@@ -51,7 +49,7 @@ pub enum Error {
#[derive(Debug)] #[derive(Debug)]
pub struct Context { pub struct Context {
// NOTE: field order matters. // NOTE: field order matters.
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub core_keyboard_id: i32, pub core_keyboard_id: i32,
state: Option<XkbState>, state: Option<XkbState>,
keymap: Option<XkbKeymap>, keymap: Option<XkbKeymap>,
@@ -85,7 +83,7 @@ impl Context {
keymap: None, keymap: None,
compose_state1, compose_state1,
compose_state2, compose_state2,
#[cfg(x11_platform)] #[cfg(feature = "x11")]
core_keyboard_id: 0, core_keyboard_id: 0,
_compose_table: compose_table, _compose_table: compose_table,
context, context,
@@ -126,23 +124,23 @@ impl Context {
self.keymap.as_mut() self.keymap.as_mut()
} }
#[cfg(wayland_platform)] #[cfg(feature = "wayland")]
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) { pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
let keymap = XkbKeymap::from_fd(&self.context, fd, size); let keymap = XkbKeymap::from_fd(&self.context, fd, size);
let state = keymap.as_ref().and_then(XkbState::new_wayland); let state = keymap.as_ref().and_then(XkbState::new_wayland);
if keymap.is_none() || state.is_none() { if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap"); tracing::warn!("failed to update xkb keymap");
} }
self.state = state; self.state = state;
self.keymap = keymap; self.keymap = keymap;
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) { pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id); let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
let state = keymap.as_ref().and_then(|keymap| XkbState::new_x11(xcb, keymap)); let state = keymap.as_ref().and_then(|keymap| XkbState::new_x11(xcb, keymap));
if keymap.is_none() || state.is_none() { if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap"); tracing::warn!("failed to update xkb keymap");
} }
self.state = state; self.state = state;
self.keymap = keymap; self.keymap = keymap;
@@ -161,7 +159,7 @@ impl Context {
/// Key builder context with the user provided xkb state. /// Key builder context with the user provided xkb state.
/// ///
/// Should be used when the original context must not be altered. /// Should be used when the original context must not be altered.
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn key_context_with_state<'a>( pub fn key_context_with_state<'a>(
&'a mut self, &'a mut self,
state: &'a mut XkbState, state: &'a mut XkbState,
@@ -312,11 +310,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = keymap::keysym_location(keysym); let location = keymap::keysym_location(keysym);
let key = keymap::keysym_to_key(keysym); let key = keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) { if matches!(key, Key::Unidentified(_)) { Err((key, location)) } else { Ok((key, location)) }
Err((key, location))
} else {
Ok((key, location))
}
} }
pub fn text(&mut self) -> Option<SmolStr> { pub fn text(&mut self) -> Option<SmolStr> {
@@ -325,7 +319,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
// The current behaviour makes it so composing a character overrides attempts to input a // The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option // control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour. // if someone specifically wants the opposite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> { pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() { match self.composed_text() {
Ok(text) => text, Ok(text) => text,

View File

@@ -4,16 +4,16 @@ use std::os::raw::c_char;
use std::ptr::NonNull; use std::ptr::NonNull;
use smol_str::SmolStr; use smol_str::SmolStr;
#[cfg(x11_platform)] #[cfg(feature = "x11")]
use x11_dl::xlib_xcb::xcb_connection_t; use x11_dl::xlib_xcb::xcb_connection_t;
use xkbcommon_dl::{ use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component, self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
}; };
use crate::platform_impl::common::xkb::keymap::XkbKeymap; #[cfg(feature = "x11")]
#[cfg(x11_platform)] use super::XKBXH;
use crate::platform_impl::common::xkb::XKBXH; use super::keymap::XkbKeymap;
use crate::platform_impl::common::xkb::{make_string_with, XKBH}; use super::{XKBH, make_string_with};
#[derive(Debug)] #[derive(Debug)]
pub struct XkbState { pub struct XkbState {
@@ -22,13 +22,13 @@ pub struct XkbState {
} }
impl XkbState { impl XkbState {
#[cfg(wayland_platform)] #[cfg(feature = "wayland")]
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> { pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?; let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
Some(Self::new_inner(state)) Some(Self::new_inner(state))
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> { pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
let state = unsafe { let state = unsafe {
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id) (XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
@@ -52,7 +52,7 @@ impl XkbState {
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) } unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t { pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe { unsafe {
(XKBH.xkb_state_serialize_mods)( (XKBH.xkb_state_serialize_mods)(
@@ -62,7 +62,7 @@ impl XkbState {
} }
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t { pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe { unsafe {
(XKBH.xkb_state_serialize_mods)( (XKBH.xkb_state_serialize_mods)(
@@ -72,7 +72,7 @@ impl XkbState {
} }
} }
#[cfg(x11_platform)] #[cfg(feature = "x11")]
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t { pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe { unsafe {
(XKBH.xkb_state_serialize_mods)( (XKBH.xkb_state_serialize_mods)(
@@ -153,12 +153,13 @@ impl Drop for XkbState {
} }
} }
/// Represents the current state of the keyboard modifiers /// Represents the current logical state of the keyboard modifiers
/// ///
/// Each field of this struct represents a modifier and is `true` if this modifier is active. /// Each field of this struct represents a modifier and is `true` if this modifier is active.
/// ///
/// For some modifiers, this means that the key is currently pressed, others are toggled /// For some modifiers, this means that the key is logically pressed, others are toggled (like Caps
/// (like caps lock). /// Lock). But physically the key can be in any state (for example, released Shift when
/// it's active as a sticky modifier or released Caps Lock when it's toggled on)
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ModifiersState { pub struct ModifiersState {
/// The "control" key /// The "control" key
@@ -177,13 +178,13 @@ pub struct ModifiersState {
pub num_lock: bool, pub num_lock: bool,
} }
impl From<ModifiersState> for crate::keyboard::ModifiersState { impl From<ModifiersState> for winit_core::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState { fn from(mods: ModifiersState) -> winit_core::keyboard::ModifiersState {
let mut to_mods = crate::keyboard::ModifiersState::empty(); let mut to_mods = winit_core::keyboard::ModifiersState::empty();
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); to_mods.set(winit_core::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); to_mods.set(winit_core::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); to_mods.set(winit_core::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::META, mods.logo); to_mods.set(winit_core::keyboard::ModifiersState::META, mods.logo);
to_mods to_mods
} }
} }

39
winit-core/Cargo.toml Normal file
View File

@@ -0,0 +1,39 @@
[package]
authors = ["The winit contributors", "Kirill Chibisov <contact@kchibisov.com>"]
categories = ["gui"]
description = "winit core API."
documentation = "https://docs.rs/winit-core"
edition.workspace = true
keywords = ["windowing"]
license.workspace = true
name = "winit-core"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
serde = [
"dep:serde",
"bitflags/serde",
"cursor-icon/serde",
"dpi/serde",
"keyboard-types/serde",
"smol_str/serde",
]
[dependencies]
bitflags.workspace = true
cursor-icon.workspace = true
dpi.workspace = true
keyboard-types.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
# `wasm32-unknown-unknown` and `wasm32-none`, but not `wasm32-wasi`.
[target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies]
web-time.workspace = true
[dev-dependencies]
winit.workspace = true

1
winit-core/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

1
winit-core/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -0,0 +1,52 @@
use crate::application::ApplicationHandler;
use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId;
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
///
/// This can be registered with [`ApplicationHandler::macos_handler`].
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
/// The system interpreted a keypress as a standard key binding command.
///
/// Examples include inserting tabs and newlines, or moving the insertion point, see
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
/// editing related.
///
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
///
/// The `action` parameter contains the string representation of the selector. Examples include
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
///
/// # Example
///
/// ```ignore
/// impl ApplicationHandlerExtMacOS for App {
/// fn standard_key_binding(
/// &mut self,
/// event_loop: &dyn ActiveEventLoop,
/// window_id: WindowId,
/// action: &str,
/// ) {
/// match action {
/// "moveBackward:" => self.cursor.position -= 1,
/// "moveForward:" => self.cursor.position += 1,
/// _ => {} // Ignore other actions
/// }
/// }
/// }
/// ```
///
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
#[doc(alias = "doCommandBySelector:")]
fn standard_key_binding(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
let _ = event_loop;
let _ = window_id;
let _ = action;
}
}

View File

@@ -2,21 +2,11 @@
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop; use crate::event_loop::ActiveEventLoop;
#[cfg(macos_platform)]
use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId; use crate::window::WindowId;
pub mod macos;
/// The handler of application-level events. /// The handler of application-level events.
///
/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when
/// events are delivered.
///
/// This is [dropped] when the event loop is shut down. Note that this only works if you're passing
/// the entire state to [`EventLoop::run_app`] (passing `&mut app` won't work).
///
/// [the top-level docs]: crate
/// [`EventLoop::run_app`]: crate::event_loop::EventLoop::run_app
/// [dropped]: std::ops::Drop
pub trait ApplicationHandler { pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed. /// Emitted when new events arrive from the OS to be processed.
/// ///
@@ -135,8 +125,9 @@ pub trait ApplicationHandler {
/// use std::thread; /// use std::thread;
/// use std::time::Duration; /// use std::time::Duration;
/// ///
/// use winit::application::ApplicationHandler; /// use winit::event_loop::EventLoop;
/// use winit::event_loop::{ActiveEventLoop, EventLoop}; /// use winit_core::application::ApplicationHandler;
/// use winit_core::event_loop::ActiveEventLoop;
/// ///
/// struct MyApp { /// struct MyApp {
/// receiver: mpsc::Receiver<u64>, /// receiver: mpsc::Receiver<u64>,
@@ -210,9 +201,7 @@ pub trait ApplicationHandler {
/// Emitted when the OS sends an event to a device. /// Emitted when the OS sends an event to a device.
/// ///
/// For this to be called, it must be enabled with [`EventLoop::listen_device_events`]. /// Whether device events are delivered depends on the backend in use.
///
/// [`EventLoop::listen_device_events`]: crate::event_loop::EventLoop::listen_device_events
fn device_event( fn device_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
@@ -350,9 +339,8 @@ pub trait ApplicationHandler {
/// The macOS-specific handler. /// The macOS-specific handler.
/// ///
/// The return value from this should not change at runtime. /// The return value from this should not change at runtime.
#[cfg(macos_platform)]
#[inline(always)] #[inline(always)]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
None None
} }
} }
@@ -419,9 +407,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
} }
#[cfg(macos_platform)]
#[inline] #[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
(**self).macos_handler() (**self).macos_handler()
} }
} }
@@ -488,9 +475,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
} }
#[cfg(macos_platform)]
#[inline] #[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
(**self).macos_handler() (**self).macos_handler()
} }
} }

View File

@@ -1,32 +1,4 @@
// A poly-fill for `lazy_cell`
// Replace with std::sync::LazyLock when https://github.com/rust-lang/rust/issues/109736 is stabilized.
// This isn't used on every platform, which can come up as dead code warnings.
#![allow(dead_code)]
use std::any::Any; use std::any::Any;
use std::ops::Deref;
use std::sync::OnceLock;
pub(crate) struct Lazy<T> {
cell: OnceLock<T>,
init: fn() -> T,
}
impl<T> Lazy<T> {
pub const fn new(f: fn() -> T) -> Self {
Self { cell: OnceLock::new(), init: f }
}
}
impl<T> Deref for Lazy<T> {
type Target = T;
#[inline]
fn deref(&self) -> &'_ T {
self.cell.get_or_init(self.init)
}
}
// NOTE: This is `pub`, but isn't actually exposed outside the crate. // NOTE: This is `pub`, but isn't actually exposed outside the crate.
// NOTE: Marked as `#[doc(hidden)]` and underscored, because they can be quite difficult to use // NOTE: Marked as `#[doc(hidden)]` and underscored, because they can be quite difficult to use
@@ -59,6 +31,7 @@ impl<T: Any> AsAny for T {
} }
} }
#[macro_export]
macro_rules! impl_dyn_casting { macro_rules! impl_dyn_casting {
($trait:ident) => { ($trait:ident) => {
impl dyn $trait + '_ { impl dyn $trait + '_ {
@@ -82,8 +55,7 @@ macro_rules! impl_dyn_casting {
/// ///
/// Returns `Err` with `self` if the object was not from that backend. /// Returns `Err` with `self` if the object was not from that backend.
pub fn cast<T: $trait>(self: Box<Self>) -> Result<Box<T>, Box<Self>> { pub fn cast<T: $trait>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
let reference: &dyn std::any::Any = self.__as_any(); if self.cast_ref::<T>().is_some() {
if reference.is::<T>() {
let this: Box<dyn std::any::Any> = self.__into_any(); let this: Box<dyn std::any::Any> = self.__into_any();
// Unwrap is okay, we just checked the type of `self` is `T`. // Unwrap is okay, we just checked the type of `self` is `T`.
Ok(this.downcast::<T>().unwrap()) Ok(this.downcast::<T>().unwrap())
@@ -95,4 +67,26 @@ macro_rules! impl_dyn_casting {
}; };
} }
pub(crate) use impl_dyn_casting; pub use impl_dyn_casting;
#[cfg(test)]
mod tests {
use super::AsAny;
struct Foo;
trait FooTrait: AsAny {}
impl FooTrait for Foo {}
impl_dyn_casting!(FooTrait);
#[test]
fn dyn_casting() {
let foo_owned: Box<dyn FooTrait> = Box::new(Foo);
assert!(foo_owned.cast::<Foo>().is_ok());
let mut foo = Foo;
let foo_ref: &mut dyn FooTrait = &mut foo;
assert!((foo_ref).cast_ref::<Foo>().is_some());
assert!((&&&&foo_ref).cast_ref::<Foo>().is_some());
assert!(foo_ref.cast_mut::<Foo>().is_some());
}
}

View File

@@ -5,9 +5,10 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use cursor_icon::CursorIcon; #[doc(inline)]
pub use cursor_icon::CursorIcon;
use crate::utils::{impl_dyn_casting, AsAny}; use crate::as_any::AsAny;
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`]. /// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048; pub const MAX_CURSOR_SIZE: u16 = 2048;
@@ -50,10 +51,10 @@ impl From<CustomCursor> for Cursor {
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use winit::event_loop::ActiveEventLoop; /// # use winit_core::event_loop::ActiveEventLoop;
/// # use winit::window::Window; /// # use winit_core::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) { /// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit::window::CustomCursorSource; /// use winit_core::cursor::CustomCursorSource;
/// ///
/// let w = 10; /// let w = 10;
/// let h = 10; /// let h = 10;
@@ -75,7 +76,7 @@ impl From<CustomCursor> for Cursor {
/// # } /// # }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CustomCursor(pub(crate) Arc<dyn CustomCursorProvider>); pub struct CustomCursor(pub Arc<dyn CustomCursorProvider>);
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync { pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync {
/// Whether a cursor was backed by animation. /// Whether a cursor was backed by animation.
@@ -235,7 +236,6 @@ impl fmt::Display for BadAnimation {
impl Error for BadAnimation {} impl Error for BadAnimation {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[allow(dead_code)]
pub struct CursorImage { pub struct CursorImage {
pub(crate) rgba: Vec<u8>, pub(crate) rgba: Vec<u8>,
pub(crate) width: u16, pub(crate) width: u16,
@@ -277,6 +277,30 @@ impl CursorImage {
Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y }) Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
} }
pub fn buffer(&self) -> &[u8] {
self.rgba.as_slice()
}
pub fn buffer_mut(&mut self) -> &mut [u8] {
self.rgba.as_mut_slice()
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn hotspot_x(&self) -> u16 {
self.hotspot_x
}
pub fn hotspot_y(&self) -> u16 {
self.hotspot_y
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -297,4 +321,16 @@ impl CursorAnimation {
Ok(Self { duration, cursors }) Ok(Self { duration, cursors })
} }
pub fn duration(&self) -> Duration {
self.duration
}
pub fn cursors(&self) -> &[CustomCursor] {
self.cursors.as_slice()
}
pub fn into_raw(self) -> (Duration, Vec<CustomCursor>) {
(self.duration, self.cursors)
}
} }

View File

@@ -19,7 +19,13 @@ pub enum EventLoopError {
impl fmt::Display for EventLoopError { impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"), Self::RecreationAttempt => {
write!(
f,
"EventLoop can't be recreated, only a single instance of it is supported (for \
cross-platform compatibility)"
)
},
Self::Os(err) => err.fmt(f), Self::Os(err) => err.fmt(f),
Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"), Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
Self::NotSupported(err) => err.fmt(f), Self::NotSupported(err) => err.fmt(f),
@@ -29,11 +35,7 @@ impl fmt::Display for EventLoopError {
impl Error for EventLoopError { impl Error for EventLoopError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self { if let Self::Os(err) = self { err.source() } else { None }
err.source()
} else {
None
}
} }
} }
@@ -72,11 +74,7 @@ impl Display for RequestError {
} }
impl Error for RequestError { impl Error for RequestError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self { if let Self::Os(err) = self { err.source() } else { None }
err.source()
} else {
None
}
} }
} }
@@ -100,7 +98,7 @@ pub struct NotSupportedError {
} }
impl NotSupportedError { impl NotSupportedError {
pub(crate) fn new(reason: &'static str) -> Self { pub fn new(reason: &'static str) -> Self {
Self { reason } Self { reason }
} }
} }
@@ -121,8 +119,7 @@ pub struct OsError {
} }
impl OsError { impl OsError {
#[allow(dead_code)] pub fn new(
pub(crate) fn new(
line: u32, line: u32,
file: &'static str, file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>, error: impl Into<Box<dyn Error + Send + Sync + 'static>>,
@@ -144,7 +141,5 @@ impl Error for OsError {
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! os_error { macro_rules! os_error {
($error:expr) => {{ ($error:expr) => {{ crate::error::OsError::new(line!(), file!(), $error) }};
crate::error::OsError::new(line!(), file!(), $error)
}};
} }

View File

@@ -1,16 +1,16 @@
//! The event enums and assorted supporting types. //! The event enums and assorted supporting types.
use std::cell::LazyCell;
use std::cmp::Ordering;
use std::f64;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
use std::time::Instant;
use dpi::{PhysicalPosition, PhysicalSize};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smol_str::SmolStr; use smol_str::SmolStr;
#[cfg(web_platform)]
use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::Instant;
use crate::error::RequestError; use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}; use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
@@ -46,10 +46,6 @@ pub enum StartCause {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent { pub enum WindowEvent {
/// The activation token was delivered back and now could be used. /// The activation token was delivered back and now could be used.
#[cfg_attr(not(any(x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links))]
/// Delivered in response to [`request_activation_token`].
///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken }, ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
/// The size of the window's surface has changed. /// The size of the window's surface has changed.
@@ -160,6 +156,8 @@ pub enum WindowEvent {
Ime(Ime), Ime(Ime),
/// The pointer has moved on the window. /// The pointer has moved on the window.
///
/// Should be emitted regardless of window focus.
PointerMoved { PointerMoved {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
@@ -188,6 +186,8 @@ pub enum WindowEvent {
}, },
/// The pointer has entered the window. /// The pointer has entered the window.
///
/// Should be emitted regardless of window focus.
PointerEntered { PointerEntered {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
@@ -213,6 +213,8 @@ pub enum WindowEvent {
}, },
/// The pointer has left the window. /// The pointer has left the window.
///
/// Should be emitted regardless of window focus.
PointerLeft { PointerLeft {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
@@ -267,11 +269,25 @@ pub enum WindowEvent {
button: ButtonSource, button: ButtonSource,
}, },
/// Multi-finger hold gesture on the touchpad or touchscreen without movement.
///
/// The `phase` field indicates the lifecycle of the hold gesture:
/// - `Started`: One or more fingers are in contact with the touchpad/touchscreen.
/// - `Ended`: All fingers have been lifted from the touchpad/touchscreen.
/// - `Cancelled`: The hold gesture was interrupted, for example when another finger touches the
/// touchpad (causing a new `Started` event with more fingers), or when movement begins and
/// transitions to other gestures like pinch, pan, or rotation.
///
/// ## Platform-specific
///
/// - Only available on **Wayland**.
HoldGesture { device_id: Option<DeviceId>, phase: TouchPhase },
/// Two-finger pinch gesture, often used for magnification. /// Two-finger pinch gesture, often used for magnification.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS** and **iOS**. /// - Only available on **macOS**, **iOS**, and **Wayland**.
/// - On iOS, not recognized by default. It must be enabled when needed. /// - On iOS, not recognized by default. It must be enabled when needed.
PinchGesture { PinchGesture {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
@@ -287,7 +303,7 @@ pub enum WindowEvent {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **iOS**. /// - Only available on **iOS** and **Wayland**.
/// - On iOS, not recognized by default. It must be enabled when needed. /// - On iOS, not recognized by default. It must be enabled when needed.
PanGesture { PanGesture {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
@@ -323,7 +339,7 @@ pub enum WindowEvent {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Only available on **macOS** and **iOS**. /// - Only available on **macOS**, **iOS**, and **Wayland**.
/// - On iOS, not recognized by default. It must be enabled when needed. /// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture { RotationGesture {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
@@ -442,6 +458,7 @@ pub enum PointerKind {
/// ///
/// **macOS:** Unsupported. /// **macOS:** Unsupported.
Touch(FingerId), Touch(FingerId),
TabletTool(TabletToolKind),
Unknown, Unknown,
} }
@@ -492,6 +509,13 @@ pub enum PointerSource {
/// force will be 0.5 when a button is pressed or 0.0 otherwise. /// force will be 0.5 when a button is pressed or 0.0 otherwise.
force: Option<Force>, force: Option<Force>,
}, },
TabletTool {
/// Describes as which tool kind the interaction happened.
kind: TabletToolKind,
/// Describes how the tool was held and used.
data: TabletToolData,
},
Unknown, Unknown,
} }
@@ -500,6 +524,7 @@ impl From<PointerSource> for PointerKind {
match source { match source {
PointerSource::Mouse => Self::Mouse, PointerSource::Mouse => Self::Mouse,
PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id), PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id),
PointerSource::TabletTool { kind, .. } => Self::TabletTool(kind),
PointerSource::Unknown => Self::Unknown, PointerSource::Unknown => Self::Unknown,
} }
} }
@@ -509,8 +534,21 @@ impl From<PointerSource> for PointerKind {
/// ///
/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the /// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
/// system. /// system.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ButtonSource { pub enum ButtonSource {
/// ## Platform-specific
///
/// ### macOS
///
/// Users may expect holding [<kbd>CTRL</kbd>](ModifiersState::CONTROL) while
/// clicking [`MouseButton::Left`] to result in a "secondary" click, but the way these
/// clicks behave natively is slightly different from how a physical secondary
/// button press would, depending on the content under the cursor when clicked. If
/// applications want this behavior they should implement it themselves by interpreting
/// [`Left`](MouseButton::Left) clicks as secondary when
/// [<kbd>CTRL</kbd>](ModifiersState::CONTROL) is held and their internal logic deems it
/// appropriate for the content under the pointer.
/// See also https://github.com/rust-windowing/winit/issues/4469.
Mouse(MouseButton), Mouse(MouseButton),
/// See [`PointerSource::Touch`] for more details. /// See [`PointerSource::Touch`] for more details.
/// ///
@@ -521,25 +559,27 @@ pub enum ButtonSource {
finger_id: FingerId, finger_id: FingerId,
force: Option<Force>, force: Option<Force>,
}, },
TabletTool {
kind: TabletToolKind,
button: TabletToolButton,
data: TabletToolData,
},
/// A pointer button of unknown source.
///
/// Codes are undefined and may not be reproducible across platforms or winit versions.
Unknown(u16), Unknown(u16),
} }
impl ButtonSource { impl ButtonSource {
/// Convert any [`ButtonSource`] to an equivalent [`MouseButton`]. If a pointer type has no /// Try to convert a [`ButtonSource`] to an equivalent [`MouseButton`]. If a pointer type has no
/// special handling in an application, this method can be used to handle it like any generic /// special handling in an application, this method can be used to handle it like any generic
/// mouse input. /// mouse input.
pub fn mouse_button(self) -> MouseButton { pub fn mouse_button(self) -> Option<MouseButton> {
match self { match self {
ButtonSource::Mouse(mouse) => mouse, ButtonSource::Mouse(mouse) => Some(mouse),
ButtonSource::Touch { .. } => MouseButton::Left, ButtonSource::Touch { .. } => Some(MouseButton::Left),
ButtonSource::Unknown(button) => match button { ButtonSource::TabletTool { button, .. } => button.into(),
0 => MouseButton::Left, ButtonSource::Unknown(_) => None,
1 => MouseButton::Middle,
2 => MouseButton::Right,
3 => MouseButton::Back,
4 => MouseButton::Forward,
_ => MouseButton::Other(button),
},
} }
} }
} }
@@ -563,16 +603,14 @@ impl DeviceId {
/// Convert the [`DeviceId`] into the underlying integer. /// Convert the [`DeviceId`] into the underlying integer.
/// ///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic. /// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
#[allow(dead_code)] pub const fn into_raw(self) -> i64 {
pub(crate) const fn into_raw(self) -> i64 {
self.0 self.0
} }
/// Construct a [`DeviceId`] from the underlying integer. /// Construct a [`DeviceId`] from the underlying integer.
/// ///
/// This should only be called with integers returned from [`DeviceId::into_raw`]. /// This should only be called with integers returned from [`DeviceId::into_raw`].
#[allow(dead_code)] pub const fn from_raw(id: i64) -> Self {
pub(crate) const fn from_raw(id: i64) -> Self {
Self(id) Self(id)
} }
} }
@@ -588,16 +626,14 @@ impl FingerId {
/// Convert the [`FingerId`] into the underlying integer. /// Convert the [`FingerId`] into the underlying integer.
/// ///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic. /// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
#[allow(dead_code)] pub const fn into_raw(self) -> usize {
pub(crate) const fn into_raw(self) -> usize {
self.0 self.0
} }
/// Construct a [`FingerId`] from the underlying integer. /// Construct a [`FingerId`] from the underlying integer.
/// ///
/// This should only be called with integers returned from [`FingerId::into_raw`]. /// This should only be called with integers returned from [`FingerId::into_raw`].
#[allow(dead_code)] pub const fn from_raw(id: usize) -> Self {
pub(crate) const fn from_raw(id: usize) -> Self {
Self(id) Self(id)
} }
} }
@@ -622,17 +658,8 @@ pub enum DeviceEvent {
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used /// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used
/// and browser support is available, see /// and browser support is available.
#[cfg_attr(
web_platform,
doc = "[`ActiveEventLoopExtWeb::is_cursor_lock_raw()`][crate::platform::web::ActiveEventLoopExtWeb::is_cursor_lock_raw()]."
)]
#[cfg_attr(
not(web_platform),
doc = "`ActiveEventLoopExtWeb::is_cursor_lock_raw()`."
)]
/// ///
#[rustfmt::skip]
/// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked /// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
PointerMotion { PointerMotion {
/// (x, y) change in position in unspecified units. /// (x, y) change in position in unspecified units.
@@ -765,12 +792,12 @@ pub struct KeyEvent {
/// ///
/// # Example /// # Example
/// ///
/// In games, you often want to ignore repated key events - this can be /// In games, you often want to ignore repeated key events - this can be
/// done by ignoring events where this property is set. /// done by ignoring events where this property is set.
/// ///
/// ```no_run /// ```no_run
/// use winit::event::{ElementState, KeyEvent, WindowEvent}; /// use winit_core::event::{ElementState, KeyEvent, WindowEvent};
/// use winit::keyboard::{KeyCode, PhysicalKey}; /// use winit_core::keyboard::{KeyCode, PhysicalKey};
/// # let window_event = WindowEvent::RedrawRequested; // To make the example compile /// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
/// match window_event { /// match window_event {
/// WindowEvent::KeyboardInput { /// WindowEvent::KeyboardInput {
@@ -790,13 +817,14 @@ pub struct KeyEvent {
/// ``` /// ```
pub repeat: bool, pub repeat: bool,
/// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd>. /// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd> and may
/// produce ASCII control characters.
/// ///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`. /// For example, pressing <kbd>Ctrl</kbd>+<kbd>space</kbd> produces `Some("\x00")`.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Android:** Unimplemented, this field is always the same value as `text`. /// - **Android:** This field is always the same value as `text`.
/// - **iOS:** 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`. /// - **Web:** Unsupported, this field is always the same value as `text`.
pub text_with_all_modifiers: Option<SmolStr>, pub text_with_all_modifiers: Option<SmolStr>,
@@ -825,54 +853,59 @@ pub struct KeyEvent {
pub struct Modifiers { pub struct Modifiers {
pub(crate) state: ModifiersState, pub(crate) state: ModifiersState,
// NOTE: Currently pressed modifiers keys. // NOTE: Currently active modifiers keys (logically, but not necessarily physically, pressed).
// //
// The field providing a metadata, it shouldn't be used as a source of truth. // The field providing a metadata, it shouldn't be used as a source of truth.
pub(crate) pressed_mods: ModifiersKeys, pub(crate) pressed_mods: ModifiersKeys,
} }
impl Modifiers { impl Modifiers {
/// The state of the modifiers. /// Create a new modifiers from state and pressed mods.
pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
Self { state, pressed_mods }
}
/// The logical state of the modifiers.
pub fn state(&self) -> ModifiersState { pub fn state(&self) -> ModifiersState {
self.state self.state
} }
/// The state of the left shift key. /// The logical state of the left shift key.
pub fn lshift_state(&self) -> ModifiersKeyState { pub fn lshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LSHIFT) self.mod_state(ModifiersKeys::LSHIFT)
} }
/// The state of the right shift key. /// The logical state of the right shift key.
pub fn rshift_state(&self) -> ModifiersKeyState { pub fn rshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RSHIFT) self.mod_state(ModifiersKeys::RSHIFT)
} }
/// The state of the left alt key. /// The logical state of the left alt key.
pub fn lalt_state(&self) -> ModifiersKeyState { pub fn lalt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LALT) self.mod_state(ModifiersKeys::LALT)
} }
/// The state of the right alt key. /// The logical state of the right alt key.
pub fn ralt_state(&self) -> ModifiersKeyState { pub fn ralt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RALT) self.mod_state(ModifiersKeys::RALT)
} }
/// The state of the left control key. /// The logical state of the left control key.
pub fn lcontrol_state(&self) -> ModifiersKeyState { pub fn lcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LCONTROL) self.mod_state(ModifiersKeys::LCONTROL)
} }
/// The state of the right control key. /// The logical state of the right control key.
pub fn rcontrol_state(&self) -> ModifiersKeyState { pub fn rcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RCONTROL) self.mod_state(ModifiersKeys::RCONTROL)
} }
/// The state of the left super key. /// The logical state of the left super key.
pub fn lsuper_state(&self) -> ModifiersKeyState { pub fn lsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LMETA) self.mod_state(ModifiersKeys::LMETA)
} }
/// The state of the right super key. /// The logical state of the right super key.
pub fn rsuper_state(&self) -> ModifiersKeyState { pub fn rsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RMETA) self.mod_state(ModifiersKeys::RMETA)
} }
@@ -894,6 +927,8 @@ impl From<ModifiersState> for Modifiers {
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
/// ///
/// The `Ime` events must be applied in the order they arrive.
///
/// This is also called a "composition event". /// This is also called a "composition event".
/// ///
/// Most keypresses using a latin-like keyboard layout simply generate a /// Most keypresses using a latin-like keyboard layout simply generate a
@@ -950,7 +985,7 @@ pub enum Ime {
/// position. When it's `None`, the cursor should be hidden. When `String` is an empty string /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
/// this indicates that preedit was cleared. /// this indicates that preedit was cleared.
/// ///
/// The cursor position is byte-wise indexed. /// The cursor position is byte-wise indexed, assuming UTF-8.
Preedit(String, Option<(usize, usize)>), Preedit(String, Option<(usize, usize)>),
/// Notifies when text should be inserted into the editor widget. /// Notifies when text should be inserted into the editor widget.
@@ -958,6 +993,20 @@ pub enum Ime {
/// Right before this event winit will send empty [`Self::Preedit`] event. /// Right before this event winit will send empty [`Self::Preedit`] event.
Commit(String), Commit(String),
/// Delete text surrounding the cursor or selection.
///
/// This event does not affect either the pre-edit string.
/// This means that the application must first remove the pre-edit,
/// then execute the deletion, then insert the removed text back.
///
/// This event assumes text is stored in UTF-8.
DeleteSurrounding {
/// Bytes to remove before the selection
before_bytes: usize,
/// Bytes to remove after the selection
after_bytes: usize,
},
/// Notifies when the IME was disabled. /// Notifies when the IME was disabled.
/// ///
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
@@ -971,15 +1020,24 @@ pub enum Ime {
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase { pub enum TouchPhase {
/// Initial touch contact or gesture start, for example when one or more fingers touch the
/// screen or touchpad.
Started, Started,
/// The touch contact point changed, for example without lifting the finger.
Moved, Moved,
/// All touch contact points have been lifted from the touchscreen or touchpad.
///
/// This event is important as it should clear any state or event in flight that was
/// generated by the preceding `Started` and `Moved` events.
Ended, Ended,
/// The event was cancelled and should cancel any event in flight and clear state.
Cancelled, Cancelled,
} }
/// Describes the force of a touch event /// Describes the force of a touch event
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[doc(alias = "Pressure")]
pub enum Force { pub enum Force {
/// On iOS, the force is calibrated so that the same number corresponds to /// On iOS, the force is calibrated so that the same number corresponds to
/// roughly the same amount of pressure on the screen regardless of the /// roughly the same amount of pressure on the screen regardless of the
@@ -990,7 +1048,7 @@ pub enum Force {
/// ///
/// The force reported by Apple Pencil is measured along the axis of the /// The force reported by Apple Pencil is measured along the axis of the
/// pencil. If you want a force perpendicular to the device, you need to /// pencil. If you want a force perpendicular to the device, you need to
/// calculate this value using the `altitude_angle` value. /// calculate this value using the [`TabletToolAngle::altitude`] value.
force: f64, force: f64,
/// The maximum possible force for a touch. /// The maximum possible force for a touch.
/// ///
@@ -1011,9 +1069,17 @@ impl Force {
/// Instead of normalizing the force, you should prefer to handle /// Instead of normalizing the force, you should prefer to handle
/// [`Force::Calibrated`] so that the amount of force the user has to apply is /// [`Force::Calibrated`] so that the amount of force the user has to apply is
/// consistent across devices. /// consistent across devices.
pub fn normalized(&self) -> f64 { ///
/// Passing in a [`TabletToolAngle`], returns the perpendicular force.
pub fn normalized(&self, angle: Option<TabletToolAngle>) -> f64 {
match self { match self {
Force::Calibrated { force, max_possible_force } => force / max_possible_force, Force::Calibrated { force, max_possible_force } => {
let force = match angle {
Some(TabletToolAngle { altitude, .. }) => force / altitude.sin(),
None => *force,
};
force / max_possible_force
},
Force::Normalized(force) => *force, Force::Normalized(force) => *force,
} }
} }
@@ -1025,6 +1091,260 @@ pub type AxisId = u32;
/// Identifier for a specific button on some device. /// Identifier for a specific button on some device.
pub type ButtonId = u32; pub type ButtonId = u32;
/// Tablet of the tablet tool.
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[non_exhaustive]
pub enum TabletToolKind {
#[default]
Pen,
Eraser,
Brush,
Pencil,
Airbrush,
Finger,
Mouse,
Lens,
}
#[derive(Default, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct TabletToolData {
/// The force applied to the tool against the surface.
///
/// When the force information is not available, [`None`] is returned.
///
/// ## Platform-specific
///
/// **Web:** Has no mechanism to detect support, so this will always be [`Some`].
pub force: Option<Force>,
/// Represents normalized tangential pressure, also known as barrel pressure. In the range of
/// -1 to 1. 0 means no tangential pressure is applied. [`None`] means backend or device has no
/// support.
///
/// ## Platform-specific
///
/// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value
/// of 0.
pub tangential_force: Option<f32>,
/// The clockwise rotation in degrees of a tool around its own major axis. E.g. twisting a pen
/// around its length. In the range of 0 to 359. [`None`] means backend or device has no
/// support.
///
/// ## Platform-specific
///
/// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value
/// of 0.
pub twist: Option<u16>,
/// The plane angle in degrees. [`None`] means backend or device has no support.
///
/// ## Platform-specific
///
/// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with default
/// values.
pub tilt: Option<TabletToolTilt>,
/// The angular position in radians. [`None`] means backend or device has no support.
///
/// ## Platform-specific
///
/// **Web:** Has no mechanism to detect device support, so this will always be [`Some`] with
/// default values unless browser support is lacking.
pub angle: Option<TabletToolAngle>,
}
impl TabletToolData {
/// Returns [`TabletToolTilt`] if present or calculates it from [`TabletToolAngle`].
pub fn tilt(self) -> Option<TabletToolTilt> {
if let Some(tilt) = self.tilt { Some(tilt) } else { self.angle.map(TabletToolAngle::tilt) }
}
/// Returns [`TabletToolAngle`] if present or calculates it from [`TabletToolTilt`].
pub fn angle(self) -> Option<TabletToolAngle> {
if let Some(angle) = self.angle {
Some(angle)
} else {
self.tilt.map(TabletToolTilt::angle)
}
}
}
/// The plane angle in degrees of a tool.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct TabletToolTilt {
/// The plane angle in degrees between the surface Y-Z plane and the plane containing the tool
/// and the surface Y axis. Positive values are to the right. In the range of -90 to 90. 0
/// means the tool is perpendicular to the surface and is the default.
///
/// ![Tilt X](https://raw.githubusercontent.com/rust-windowing/winit/master/winit/docs/res/tool_tilt_x.webp)
///
/// <sub>
/// For image attribution, see the
/// <a href="https://github.com/rust-windowing/winit/blob/master/winit/docs/ATTRIBUTION.md">
/// ATTRIBUTION.md
/// </a>
/// file.
/// </sub>
pub x: i8,
/// The plane angle in degrees between the surface X-Z plane and the plane containing the tool
/// and the surface X axis. Positive values are towards the user. In the range of -90 to
/// 90. 0 means the tool is perpendicular to the surface and is the default.
///
/// ![Tilt Y](https://raw.githubusercontent.com/rust-windowing/winit/master/winit/docs/res/tool_tilt_y.webp)
///
/// <sub>
/// For image attribution, see the
/// <a href="https://github.com/rust-windowing/winit/blob/master/winit/docs/ATTRIBUTION.md">
/// ATTRIBUTION.md
/// </a>
/// file.
/// </sub>
pub y: i8,
}
impl TabletToolTilt {
pub fn angle(self) -> TabletToolAngle {
// See <https://www.w3.org/TR/2024/WD-pointerevents3-20240326/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle>.
use std::f64::consts::*;
const PI_0_5: f64 = FRAC_PI_2;
const PI_1_5: f64 = 3. * FRAC_PI_2;
const PI_2: f64 = 2. * PI;
let x = LazyCell::new(|| f64::from(self.x).to_radians());
let y = LazyCell::new(|| f64::from(self.y).to_radians());
let mut azimuth = 0.;
if self.x == 0 {
match self.y.cmp(&0) {
Ordering::Greater => azimuth = PI_0_5,
Ordering::Less => azimuth = PI_1_5,
Ordering::Equal => (),
}
} else if self.y == 0 {
if self.x < 0 {
azimuth = PI;
}
} else if self.x.abs() == 90 || self.y.abs() == 90 {
// not enough information to calculate azimuth
azimuth = 0.;
} else {
// Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90
azimuth = f64::atan2(y.tan(), x.tan());
if azimuth < 0. {
azimuth += PI_2;
}
}
let altitude;
if self.x.abs() == 90 || self.y.abs() == 90 {
altitude = 0.;
} else if self.x == 0 {
altitude = PI_0_5 - y.abs();
} else if self.y == 0 {
altitude = PI_0_5 - x.abs();
} else {
// Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90
altitude = f64::atan(1. / f64::sqrt(x.tan().powi(2) + y.tan().powi(2)));
}
TabletToolAngle { altitude, azimuth }
}
}
/// The angular position in radians of a tool.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct TabletToolAngle {
/// The altitude angle in radians between the tools perpendicular position to the surface and
/// the surface X-Y plane. In the range of 0, parallel to the surface, to π/2, perpendicular to
/// the surface. π/2 means the tool is perpendicular to the surface and is the default.
///
/// ![Altitude angle](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_altitude.webp)
///
/// <sub>
/// For image attribution, see the
/// <a href="https://github.com/rust-windowing/winit/blob/master/docs/res/ATTRIBUTION.md">
/// ATTRIBUTION.md
/// </a>
/// file.
/// </sub>
pub altitude: f64,
/// The azimuth angle in radiants representing the rotation between the major axis of the tool
/// and the surface X-Y plane. In the range of 0, 3 o'clock, progressively increasing clockwise
/// to 2π. 0 means the tool is at 3 o'clock or is perpendicular to the surface (`altitude` of
/// π/2) and is the default.
///
/// ![Azimuth angle](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_azimuth.webp)
///
/// <sub>
/// For image attribution, see the
/// <a href="https://github.com/rust-windowing/winit/blob/master/docs/res/ATTRIBUTION.md">
/// ATTRIBUTION.md
/// </a>
/// file.
/// </sub>
pub azimuth: f64,
}
impl Default for TabletToolAngle {
fn default() -> Self {
Self { altitude: f64::consts::FRAC_2_PI, azimuth: 0. }
}
}
impl TabletToolAngle {
pub fn tilt(self) -> TabletToolTilt {
// See <https://www.w3.org/TR/2024/WD-pointerevents3-20240326/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle>.
use std::f64::consts::*;
const PI_0_5: f64 = FRAC_PI_2;
const PI_1_5: f64 = 3. * FRAC_PI_2;
const PI_2: f64 = 2. * PI;
let mut x = 0.;
let mut y = 0.;
if self.altitude == 0. {
if self.azimuth == 0. || self.azimuth == PI_2 {
x = FRAC_PI_2;
} else if self.azimuth == PI_0_5 {
y = FRAC_PI_2;
} else if self.azimuth == PI {
x = -FRAC_PI_2;
} else if self.azimuth == PI_1_5 {
y = -FRAC_PI_2;
} else if self.azimuth > 0. && self.azimuth < PI_0_5 {
x = FRAC_PI_2;
y = FRAC_PI_2;
} else if self.azimuth > PI_0_5 && self.azimuth < PI {
x = -FRAC_PI_2;
y = FRAC_PI_2;
} else if self.azimuth > PI && self.azimuth < PI_1_5 {
x = -FRAC_PI_2;
y = -FRAC_PI_2;
} else if self.azimuth > PI_1_5 && self.azimuth < PI_2 {
x = FRAC_PI_2;
y = -FRAC_PI_2;
}
}
if self.altitude != 0. {
let altitude = self.altitude.tan();
x = f64::atan(f64::cos(self.azimuth) / altitude);
y = f64::atan(f64::sin(self.azimuth) / altitude);
}
TabletToolTilt { x: x.to_degrees().round() as i8, y: y.to_degrees().round() as i8 }
}
}
/// Describes the input state of a key. /// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1040,23 +1360,138 @@ impl ElementState {
} }
} }
/// Describes a button of a mouse controller. /// Identifies a button of a mouse controller.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **macOS:** `Back` and `Forward` might not work with all hardware. /// The first three buttons should be supported on all platforms.
/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them. /// [`Self::Back`] and [`Self::Forward`] are supported on most platforms
/// (when using a compatible mouse).
///
/// - **Android, iOS:** Currently not supported.
/// - **Orbital:** Only left/right/middle buttons are supported at this time.
/// - **Web, Windows:** Supports left/right/middle/back/forward buttons.
/// - **Wayland:** Supports buttons 0..=15.
/// - **macOS:** Supports all button variants.
/// - **X11:** Technically supports further buttons than this (0..=250), these are emitted in
/// `ButtonSource::Unknown`.
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum MouseButton { pub enum MouseButton {
Left, /// The primary (usually left) button
Right, Left = 0,
Middle, /// The secondary (usually right) button
Back, Right = 1,
Forward, /// The tertiary (usually middle) button
Middle = 2,
/// The first side button, frequently assigned a back function
Back = 3,
/// The second side button, frequently assigned a forward function
Forward = 4,
/// The sixth button
Button6 = 5,
/// The seventh button
Button7 = 6,
/// The eighth button
Button8 = 7,
/// The ninth button
Button9 = 8,
/// The tenth button
Button10 = 9,
/// The eleventh button
Button11 = 10,
/// The twelfth button
Button12 = 11,
/// The thirteenth button
Button13 = 12,
/// The fourteenth button
Button14 = 13,
/// The fifteenth button
Button15 = 14,
/// The sixteenth button
Button16 = 15,
Button17 = 16,
Button18 = 17,
Button19 = 18,
Button20 = 19,
Button21 = 20,
Button22 = 21,
Button23 = 22,
Button24 = 23,
Button25 = 24,
Button26 = 25,
Button27 = 26,
Button28 = 27,
Button29 = 28,
Button30 = 29,
Button31 = 30,
Button32 = 31,
}
impl MouseButton {
/// Construct from a `u8` if within the range `0..=31`
pub fn try_from_u8(b: u8) -> Option<MouseButton> {
Some(match b {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Back,
4 => MouseButton::Forward,
5 => MouseButton::Button6,
6 => MouseButton::Button7,
7 => MouseButton::Button8,
8 => MouseButton::Button9,
9 => MouseButton::Button10,
10 => MouseButton::Button11,
11 => MouseButton::Button12,
12 => MouseButton::Button13,
13 => MouseButton::Button14,
14 => MouseButton::Button15,
15 => MouseButton::Button16,
16 => MouseButton::Button17,
17 => MouseButton::Button18,
18 => MouseButton::Button19,
19 => MouseButton::Button20,
20 => MouseButton::Button21,
21 => MouseButton::Button22,
22 => MouseButton::Button23,
23 => MouseButton::Button24,
24 => MouseButton::Button25,
25 => MouseButton::Button26,
26 => MouseButton::Button27,
27 => MouseButton::Button28,
28 => MouseButton::Button29,
29 => MouseButton::Button30,
30 => MouseButton::Button31,
31 => MouseButton::Button32,
_ => return None,
})
}
}
/// Describes a button of a tool, e.g. a pen.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum TabletToolButton {
Contact,
Barrel,
Other(u16), Other(u16),
} }
impl From<TabletToolButton> for Option<MouseButton> {
fn from(tool: TabletToolButton) -> Self {
Some(match tool {
TabletToolButton::Contact => MouseButton::Left,
TabletToolButton::Barrel => MouseButton::Right,
TabletToolButton::Other(1) => MouseButton::Middle,
TabletToolButton::Other(3) => MouseButton::Back,
TabletToolButton::Other(4) => MouseButton::Forward,
TabletToolButton::Other(_) => return None,
})
}
}
/// Describes a difference in the mouse scroll wheel state. /// Describes a difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1091,8 +1526,7 @@ pub struct SurfaceSizeWriter {
} }
impl SurfaceSizeWriter { impl SurfaceSizeWriter {
#[cfg(not(orbital_platform))] pub fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
pub(crate) fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_surface_size } Self { new_surface_size }
} }
@@ -1108,6 +1542,15 @@ impl SurfaceSizeWriter {
Err(RequestError::Ignored) Err(RequestError::Ignored)
} }
} }
/// Get the currently stashed surface size.
pub fn surface_size(&self) -> Result<PhysicalSize<u32>, RequestError> {
if let Some(inner) = self.new_surface_size.upgrade() {
Ok(*inner.lock().unwrap())
} else {
Err(RequestError::Ignored)
}
}
} }
impl PartialEq for SurfaceSizeWriter { impl PartialEq for SurfaceSizeWriter {
@@ -1122,7 +1565,8 @@ impl Eq for SurfaceSizeWriter {}
mod tests { mod tests {
use std::collections::{BTreeSet, HashSet}; use std::collections::{BTreeSet, HashSet};
use crate::dpi::PhysicalPosition; use dpi::PhysicalPosition;
use crate::event; use crate::event;
macro_rules! foreach_event { macro_rules! foreach_event {
@@ -1178,7 +1622,7 @@ mod tests {
primary: true, primary: true,
state: event::ElementState::Pressed, state: event::ElementState::Pressed,
position: (0, 0).into(), position: (0, 0).into(),
button: event::MouseButton::Other(0).into(), button: event::ButtonSource::Unknown(0),
}); });
with_window_event(PointerButton { with_window_event(PointerButton {
device_id: None, device_id: None,
@@ -1231,16 +1675,85 @@ mod tests {
}); });
} }
#[test]
fn test_tilt_angle_conversions() {
use std::f64::consts::*;
use event::{TabletToolAngle, TabletToolTilt};
// See <https://github.com/web-platform-tests/wpt/blob/5af3e9c2a2aba76ade00f0dbc3486e50a74a4506/pointerevents/pointerevent_tiltX_tiltY_to_azimuth_altitude.html#L11-L23>.
const TILT_TO_ANGLE: &[(TabletToolTilt, TabletToolAngle)] = &[
(TabletToolTilt { x: 0, y: 0 }, TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }),
(TabletToolTilt { x: 0, y: 90 }, TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }),
(TabletToolTilt { x: 0, y: -90 }, TabletToolAngle {
altitude: 0.,
azimuth: 3. * FRAC_PI_2,
}),
(TabletToolTilt { x: 90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
(TabletToolTilt { x: 90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
(TabletToolTilt { x: 90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
(TabletToolTilt { x: -90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: PI }),
(TabletToolTilt { x: -90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
(TabletToolTilt { x: -90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
(TabletToolTilt { x: 0, y: 45 }, TabletToolAngle {
altitude: FRAC_PI_4,
azimuth: FRAC_PI_2,
}),
(TabletToolTilt { x: 0, y: -45 }, TabletToolAngle {
altitude: FRAC_PI_4,
azimuth: 3. * FRAC_PI_2,
}),
(TabletToolTilt { x: 45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }),
(TabletToolTilt { x: -45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }),
];
for (tilt, angle) in TILT_TO_ANGLE {
assert_eq!(tilt.angle(), *angle, "{tilt:?}");
}
// See <https://github.com/web-platform-tests/wpt/blob/5af3e9c2a2aba76ade00f0dbc3486e50a74a4506/pointerevents/pointerevent_tiltX_tiltY_to_azimuth_altitude.html#L38-L46>.
const ANGLE_TO_TILT: &[(TabletToolAngle, TabletToolTilt)] = &[
(TabletToolAngle { altitude: 0., azimuth: 0. }, TabletToolTilt { x: 90, y: 0 }),
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }, TabletToolTilt { x: 45, y: 0 }),
(TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }, TabletToolTilt { x: 0, y: 0 }),
(TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }, TabletToolTilt { x: 0, y: 90 }),
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 }, TabletToolTilt {
x: 0,
y: 45,
}),
(TabletToolAngle { altitude: 0., azimuth: PI }, TabletToolTilt { x: -90, y: 0 }),
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }, TabletToolTilt { x: -45, y: 0 }),
(TabletToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 }, TabletToolTilt {
x: 0,
y: -90,
}),
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 }, TabletToolTilt {
x: 0,
y: -45,
}),
];
for (angle, tilt) in ANGLE_TO_TILT {
assert_eq!(angle.tilt(), *tilt, "{angle:?}");
}
}
#[test] #[test]
fn test_force_normalize() { fn test_force_normalize() {
let force = event::Force::Normalized(0.0); let force = event::Force::Normalized(0.0);
assert_eq!(force.normalized(), 0.0); assert_eq!(force.normalized(None), 0.0);
let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 }; let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
assert_eq!(force2.normalized(), 2.0); assert_eq!(force2.normalized(None), 2.0);
let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 }; let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
assert_eq!(force3.normalized(), 2.0); assert_eq!(
force3.normalized(Some(event::TabletToolAngle {
altitude: std::f64::consts::PI / 2.0,
azimuth: 0.
})),
2.0
);
} }
#[allow(clippy::clone_on_copy)] #[allow(clippy::clone_on_copy)]

View File

@@ -0,0 +1,291 @@
pub mod never_return;
pub mod pump_events;
pub mod register;
pub mod run_on_demand;
use std::fmt::{self, Debug};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
use crate::Instant;
use crate::as_any::AsAny;
use crate::cursor::{CustomCursor, CustomCursorSource};
use crate::error::RequestError;
use crate::monitor::MonitorHandle;
use crate::window::{Theme, Window, WindowAttributes};
pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
fn create_proxy(&self) -> EventLoopProxy;
/// Create the window.
///
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
///
/// ## Platform-specific
///
/// - **Web:** The window is created but not inserted into the Web page automatically. Please
/// see the Web platform module for more information.
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<Box<dyn Window>, RequestError>;
/// Create custom cursor.
///
/// ## Platform-specific
///
/// **iOS / Android / Orbital:** Unsupported.
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError>;
/// Returns the list of all the monitors available on the system.
///
/// ## Platform-specific
///
/// **Web:** Only returns the current monitor without `detailed monitor permissions`.
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
/// Returns the primary monitor of the system.
///
/// Returns `None` if it can't identify any monitor as a primary one.
///
/// ## Platform-specific
///
/// - **Wayland:** Always returns `None`.
/// - **Web:** Always returns `None` without `detailed monitor permissions`.
fn primary_monitor(&self) -> Option<MonitorHandle>;
/// Change if or when [`DeviceEvent`]s are captured.
///
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
/// this at runtime to explicitly capture them again.
///
/// ## Platform-specific
///
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
fn listen_device_events(&self, allowed: DeviceEvents);
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
fn system_theme(&self) -> Option<Theme>;
/// Sets the [`ControlFlow`].
fn set_control_flow(&self, control_flow: ControlFlow);
/// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow;
/// Stop the event loop.
///
/// ## Platform-specific
///
/// ### iOS
///
/// It is not possible to programmatically exit/quit an application on iOS, so this function is
/// a no-op there. See also [this technical Q&A][qa1561].
///
/// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
fn exit(&self);
/// Returns whether the [`ActiveEventLoop`] is about to stop.
///
/// Set by [`exit()`][Self::exit].
fn exiting(&self) -> bool;
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
fn owned_display_handle(&self) -> OwnedDisplayHandle;
/// Get the raw-window-handle handle.
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
}
impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.rwh_06_handle().display_handle()
}
}
impl_dyn_casting!(ActiveEventLoop);
/// Control the [`ActiveEventLoop`], possibly from a different thread, without referencing it
/// directly.
#[derive(Clone, Debug)]
pub struct EventLoopProxy {
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
}
impl EventLoopProxy {
/// Wake up the [`ActiveEventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called.
///
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: crate::application::ApplicationHandler::proxy_wake_up
/// [`ApplicationHandler::proxy_wake_up()`]: crate::application::ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.proxy.wake_up();
}
pub fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
Self { proxy }
}
}
pub trait EventLoopProxyProvider: Send + Sync + Debug {
/// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self);
}
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the underlying event loop type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
/// needed.
///
/// For all platforms, this is one of the following:
///
/// - A zero-sized type that is likely optimized out.
/// - A reference-counted pointer to the underlying type.
#[derive(Clone)]
pub struct OwnedDisplayHandle {
pub(crate) handle: Arc<dyn HasDisplayHandle + Send + Sync>,
}
impl OwnedDisplayHandle {
pub fn new(handle: Arc<dyn HasDisplayHandle + Send + Sync>) -> Self {
Self { handle }
}
}
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.handle.display_handle()
}
}
impl fmt::Debug for OwnedDisplayHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
}
}
impl PartialEq for OwnedDisplayHandle {
fn eq(&self, other: &Self) -> bool {
match (self.display_handle(), other.display_handle()) {
(Ok(lhs), Ok(rhs)) => lhs == rhs,
_ => false,
}
}
}
impl Eq for OwnedDisplayHandle {}
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the
/// display's native refresh rate should instead use [`Poll`] and the VSync functionality
/// of a graphics API to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
}
impl ControlFlow {
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
}
}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeviceEvents {
/// Report device events regardless of window focus.
Always,
/// Only capture device events while the window is focused.
#[default]
WhenFocused,
/// Never capture device events.
Never,
}
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarios could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AsyncRequestSerial {
serial: usize,
}
impl AsyncRequestSerial {
pub fn get() -> Self {
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: We rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}

View File

@@ -0,0 +1,14 @@
use crate::application::ApplicationHandler;
/// Additional methods on `EventLoop` for platforms whose run method never return.
pub trait EventLoopExtNeverReturn {
/// Run the event loop and call `process::exit` once finished.
///
/// ## Platform-specific
///
/// - **iOS**: This registers the callbacks with the system and calls `UIApplicationMain`.
/// - **macOS**: Unimplemented (TODO: Should call `NSApplicationMain`).
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
/// - **Web**: Impossible to support properly.
fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> !;
}

View File

@@ -1,8 +1,8 @@
use std::time::Duration; use std::time::Duration;
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::event_loop::EventLoop;
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] for pumping events within an external event loop /// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents { pub trait EventLoopExtPumpEvents {
/// Pump the `EventLoop` to check for and dispatch pending events. /// Pump the `EventLoop` to check for and dispatch pending events.
@@ -106,16 +106,6 @@ pub trait EventLoopExtPumpEvents {
) -> PumpStatus; ) -> PumpStatus;
} }
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
}
}
/// The return status for `pump_events` /// The return status for `pump_events`
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PumpStatus { pub enum PumpStatus {

View File

@@ -0,0 +1,17 @@
use crate::application::ApplicationHandler;
/// Additional methods on `EventLoop` that registers it with the system event loop.
pub trait EventLoopExtRegister {
/// Initialize and register the application with the system's event loop, such that the
/// callbacks will be run later.
///
/// ## Platform-specific
///
/// - **Web**: Once the event loop has been destroyed, it's possible to reinitialize another
/// event loop by calling this function again. This can be useful if you want to recreate the
/// event loop while the WebAssembly module is still loaded. For example, this can be used to
/// recreate the event loop when switching between tabs on a single page application.
/// - **iOS/macOS**: Unimplemented.
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
fn register_app<A: ApplicationHandler + 'static>(self, app: A);
}

View File

@@ -1,9 +1,9 @@
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::EventLoopError; use crate::error::EventLoopError;
use crate::event_loop::EventLoop;
#[cfg(doc)] #[cfg(doc)]
use crate::{ use crate::{
event_loop::ActiveEventLoop, platform::pump_events::EventLoopExtPumpEvents, window::Window, event_loop::{ActiveEventLoop, pump_events::EventLoopExtPumpEvents},
window::Window,
}; };
/// Additional methods on [`EventLoop`] to return control flow to the caller. /// Additional methods on [`EventLoop`] to return control flow to the caller.
@@ -11,9 +11,8 @@ pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread. /// Run the application with the event loop on the calling thread.
/// ///
/// Unlike [`EventLoop::run_app`], 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 /// state and it is possible to return control back to the caller without consuming the
/// consuming the `EventLoop` (by using [`exit()`]) and /// `EventLoop` (by using [`exit()`]) and so the event loop can be re-run after it has exit.
/// so the event loop can be re-run after it has exit.
/// ///
/// It's expected that each run of the loop will be for orthogonal instantiations of your /// It's expected that each run of the loop will be for orthogonal instantiations of your
/// Winit application, but internally each instantiation may re-use some common window /// Winit application, but internally each instantiation may re-use some common window
@@ -30,14 +29,7 @@ pub trait EventLoopExtRunOnDemand {
/// ///
/// # Caveats /// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return /// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web /// to the caller (specifically this is impossible on iOS and Web).
/// backend it is possible to use
#[cfg_attr(
web_platform,
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop. /// - No [`Window`] state can be carried between separate runs of the event loop.
/// ///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -56,28 +48,7 @@ pub trait EventLoopExtRunOnDemand {
/// are delivered via callbacks based on an event loop that is internal to the browser itself. /// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. /// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
/// ///
/// [^1]: `spawn_app()` is only available on the Web platforms.
///
/// [`exit()`]: ActiveEventLoop::exit() /// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>; fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
} }
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app_on_demand(app)
}
}
/// ```compile_fail
/// use winit::event_loop::EventLoop;
/// use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
///
/// let mut event_loop = EventLoop::new().unwrap();
/// event_loop.run_on_demand(|_, _| {
/// // Attempt to run the event loop re-entrantly; this must fail.
/// event_loop.run_on_demand(|_, _| {});
/// });
/// ```
#[allow(dead_code)]
fn test_run_on_demand_cannot_access_event_loop() {}

View File

@@ -1,30 +1,29 @@
use std::error::Error; use std::error::Error;
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, io, mem}; use std::{fmt, io, mem};
use crate::utils::{impl_dyn_casting, AsAny}; use crate::as_any::AsAny;
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>(); pub(crate) const PIXEL_SIZE: usize = mem::size_of::<u32>();
/// An icon used for the window titlebar, taskbar, etc. /// An icon used for the window titlebar, taskbar, etc.
#[allow(dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Icon(pub(crate) Arc<dyn IconProvider>); pub struct Icon(pub Arc<dyn IconProvider>);
// TODO remove that once split. // TODO remove that once split.
pub trait IconProvider: AsAny + fmt::Debug + Send + Sync {} pub trait IconProvider: AsAny + fmt::Debug + Send + Sync {}
impl_dyn_casting!(IconProvider); impl Deref for Icon {
type Target = dyn IconProvider;
#[repr(C)] fn deref(&self) -> &Self::Target {
#[derive(Debug)] self.0.as_ref()
pub(crate) struct Pixel { }
pub(crate) r: u8,
pub(crate) g: u8,
pub(crate) b: u8,
pub(crate) a: u8,
} }
impl_dyn_casting!(IconProvider);
#[derive(Debug)] #[derive(Debug)]
/// An error produced when using [`RgbaIcon::new`] with invalid arguments. /// An error produced when using [`RgbaIcon::new`] with invalid arguments.
pub enum BadIcon { pub enum BadIcon {
@@ -61,7 +60,7 @@ impl fmt::Display for BadIcon {
impl Error for BadIcon {} impl Error for BadIcon {}
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RgbaIcon { pub struct RgbaIcon {
pub(crate) width: u32, pub(crate) width: u32,
pub(crate) height: u32, pub(crate) height: u32,

486
winit-core/src/keyboard.rs Normal file
View File

@@ -0,0 +1,486 @@
//! Types related to the keyboard.
use bitflags::bitflags;
pub use keyboard_types::{Code as KeyCode, Location as KeyLocation, NamedKey};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier
///
/// The exact values vary from platform to platform (which is part of why this is a per-platform
/// enum), but the values are primarily tied to the key's physical location on the keyboard.
///
/// This enum is primarily used to store raw keycodes when Winit doesn't map a given native
/// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we
/// haven't mapped for you yet, this lets you use use [`KeyCode`] to:
///
/// - Correctly match key press and release events.
/// - On non-Web platforms, support assigning keybinds to virtually any key through a UI.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKeyCode {
Unidentified,
/// An Android "scancode".
Android(u32),
/// A macOS "scancode".
MacOS(u16),
/// A Windows "scancode".
Windows(u16),
/// An XKB "keycode".
Xkb(u32),
/// An OpenHarmony "scancode".
Ohos(u32),
}
impl std::fmt::Debug for NativeKeyCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use NativeKeyCode::{Android, MacOS, Ohos, Unidentified, Windows, Xkb};
let mut debug_tuple;
match self {
Unidentified => {
debug_tuple = f.debug_tuple("Unidentified");
},
Android(code) => {
debug_tuple = f.debug_tuple("Android");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
MacOS(code) => {
debug_tuple = f.debug_tuple("MacOS");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Windows(code) => {
debug_tuple = f.debug_tuple("Windows");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Xkb(code) => {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Ohos(code) => {
debug_tuple = f.debug_tuple("OpenHarmony");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
}
debug_tuple.finish()
}
}
/// Contains the platform-native logical key identifier
///
/// Exactly what that means differs from platform to platform, but the values are to some degree
/// tied to the currently active keyboard layout. The same key on the same keyboard may also report
/// different values on different platforms, which is one of the reasons this is a per-platform
/// enum.
///
/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical
/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user
/// define keybinds which work in the presence of identifiers we haven't mapped for you yet.
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKey {
Unidentified,
/// An Android "keycode", which is similar to a "virtual-key code" on Windows.
Android(u32),
/// A macOS "scancode". There does not appear to be any direct analogue to either keysyms or
/// "virtual-key" codes in macOS, so we report the scancode instead.
MacOS(u16),
/// A Windows "virtual-key code".
Windows(u16),
/// An XKB "keysym".
Xkb(u32),
/// A "key value string".
Web(SmolStr),
/// An OpenHarmony "keycode", which is similar to a "virtual-key code" on Windows.
Ohos(u32),
}
impl std::fmt::Debug for NativeKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use NativeKey::{Android, MacOS, Ohos, Unidentified, Web, Windows, Xkb};
let mut debug_tuple;
match self {
Unidentified => {
debug_tuple = f.debug_tuple("Unidentified");
},
Android(code) => {
debug_tuple = f.debug_tuple("Android");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
MacOS(code) => {
debug_tuple = f.debug_tuple("MacOS");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Windows(code) => {
debug_tuple = f.debug_tuple("Windows");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Xkb(code) => {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Web(code) => {
debug_tuple = f.debug_tuple("Web");
debug_tuple.field(code);
},
Ohos(code) => {
debug_tuple = f.debug_tuple("OpenHarmony");
debug_tuple.field(code);
},
}
debug_tuple.finish()
}
}
impl From<NativeKeyCode> for NativeKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
match code {
NativeKeyCode::Unidentified => NativeKey::Unidentified,
NativeKeyCode::Android(x) => NativeKey::Android(x),
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
NativeKeyCode::Ohos(x) => NativeKey::Ohos(x),
}
}
}
impl PartialEq<NativeKey> for NativeKeyCode {
#[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
NativeKey::from(*self) == *rhs
}
}
impl PartialEq<NativeKeyCode> for NativeKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
rhs == self
}
}
/// Represents the location of a physical key.
///
/// Winit will not emit [`KeyCode::Unidentified`] when it cannot recognize the key, instead it will
/// emit [`PhysicalKey::Unidentified`] with additional data about the key.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PhysicalKey {
/// A known key code
Code(KeyCode),
/// This variant is used when the key cannot be translated to a [`KeyCode`]
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
}
impl From<KeyCode> for PhysicalKey {
#[inline]
fn from(code: KeyCode) -> Self {
PhysicalKey::Code(code)
}
}
impl From<PhysicalKey> for KeyCode {
#[inline]
fn from(key: PhysicalKey) -> Self {
match key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => KeyCode::Unidentified,
}
}
}
impl From<NativeKeyCode> for PhysicalKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
PhysicalKey::Unidentified(code)
}
}
impl PartialEq<KeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &KeyCode) -> bool {
match self {
PhysicalKey::Code(code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for KeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
impl PartialEq<NativeKeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
match self {
PhysicalKey::Unidentified(code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for NativeKeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
/// Key represents the meaning of a keypress.
///
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
/// additions:
/// - All simple variants are wrapped under the `Named` variant
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A simple (unparameterised) action
Named(NamedKey),
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
}
impl From<NamedKey> for Key {
#[inline]
fn from(action: NamedKey) -> Self {
Key::Named(action)
}
}
impl From<NativeKey> for Key {
#[inline]
fn from(code: NativeKey) -> Self {
Key::Unidentified(code)
}
}
impl<Str> PartialEq<NamedKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NamedKey) -> bool {
match self {
Key::Named(a) => a == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &str) -> bool {
match self {
Key::Character(s) => s == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self == *rhs
}
}
impl<Str> PartialEq<NativeKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
match self {
Key::Unidentified(code) => code == rhs,
_ => false,
}
}
}
impl<Str> PartialEq<Key<Str>> for NativeKey {
#[inline]
fn eq(&self, rhs: &Key<Str>) -> bool {
rhs == self
}
}
impl Key<SmolStr> {
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
/// `Key`. All other variants remain unchanged.
pub fn as_ref(&self) -> Key<&str> {
match self {
Key::Named(a) => Key::Named(*a),
Key::Character(ch) => Key::Character(ch.as_str()),
Key::Dead(d) => Key::Dead(*d),
Key::Unidentified(u) => Key::Unidentified(u.clone()),
}
}
}
impl Key {
/// Convert a key to its approximate textual equivalent.
///
/// # Examples
///
/// ```
/// # #[cfg(target_family = "wasm")]
/// # wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
/// # #[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
/// # fn main() {
/// use winit_core::keyboard::{Key, NamedKey};
///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
/// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
/// # }
/// ```
pub fn to_text(&self) -> Option<&str> {
match self {
Key::Named(action) => match action {
NamedKey::Enter => Some("\r"),
NamedKey::Backspace => Some("\x08"),
NamedKey::Tab => Some("\t"),
NamedKey::Escape => Some("\x1b"),
_ => None,
},
Key::Character(ch) => Some(ch.as_str()),
_ => None,
}
}
}
bitflags! {
/// Represents the current logical state of the keyboard modifiers
///
/// Each flag represents a modifier and is set if this modifier is active.
///
/// Note that the modifier key can be physically released with the modifier
/// still being marked as active, as in the case of sticky modifiers.
/// See [`ModifiersKeyState`] for more details on what "sticky" means.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModifiersState: u32 {
/// The "shift" key.
const SHIFT = 0b100;
/// The "control" key.
const CONTROL = 0b100 << 3;
/// The "alt" key.
const ALT = 0b100 << 6;
/// This is the "windows" key on PC and "command" key on Mac.
const META = 0b100 << 9;
#[deprecated = "use META instead"]
const SUPER = Self::META.bits();
}
}
impl ModifiersState {
/// Returns whether the shift modifier is active.
pub fn shift_key(&self) -> bool {
self.intersects(Self::SHIFT)
}
/// Returns whether the control modifier is active.
pub fn control_key(&self) -> bool {
self.intersects(Self::CONTROL)
}
/// Returns whether the alt modifier is active.
pub fn alt_key(&self) -> bool {
self.intersects(Self::ALT)
}
/// Returns whether the meta modifier is active.
pub fn meta_key(&self) -> bool {
self.intersects(Self::META)
}
}
/// The logical state of the particular modifiers key.
///
/// NOTE: while the modifier can only be in a binary active/inactive state, it might be helpful to
/// note the context re. how its state changes by physical key events.
///
/// `↓` / `↑` denote physical press/release[^1]:
///
/// | Type | Activated | Deactivated | Comment |
/// | ----------------- | :-----------------: | :---------: | ------- |
/// | __Regular__ | `↓` | `↑` | Active while being held |
/// | __Sticky__ | `↓` | `↓` unless lock is enabled<br>`↓`/`↑`[^2] __non__-sticky key | Temporarily "stuck"; other `Sticky` keys have no effect |
/// | __Sticky Locked__ | `↓` <br>if `Sticky` | `↓` | Similar to `Toggle`, but deactivating `↓` turns on `Regular` effect |
/// | __Toggle__ | `↓` | `↓` | `↑` from the activating `↓` has no effect |
///
/// `Sticky` effect avoids the need to press and hold multiple modifiers for a single shortcut and
/// is usually a platform-wide option that affects modifiers _commonly_ used in shortcuts:
/// <kbd>Shift</kbd>, <kbd>Control</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>.
///
/// `Toggle` type is typically a property of a modifier, for example, <kbd>Caps Lock</kbd>.
///
/// These active states are __not__ differentiated here.
///
/// [^1]: For virtual/on-screen keyboards physical press/release can be a mouse click or a finger tap or a voice command.
/// [^2]: platform-dependent
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModifiersKeyState {
/// The particular modifier is active or logically, but not necessarily physically, pressed.
Pressed,
/// The state of the key is unknown.
///
/// Can include cases when the key is active or logically pressed, for example, when a sticky
/// **Shift** is active, the OS might not preserve information that it was activated by
/// RightShift, so the state of [`ModifiersKeys::RSHIFT`] will be unknown while the state
/// of [`ModifiersState::SHIFT`] will be active.
#[default]
Unknown,
}
// NOTE: the exact modifier key is not used to represent modifiers state in the
// first place due to a fact that modifiers state could be changed without any
// key being pressed and on some platforms like Wayland/X11 which key resulted
// in modifiers change is hidden, also, not that it really matters.
//
// The reason this API is even exposed is mostly to provide a way for users
// to treat modifiers differently based on their position, which is required
// on macOS due to their AltGr/Option situation.
bitflags! {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModifiersKeys: u8 {
const LSHIFT = 0b0000_0001;
const RSHIFT = 0b0000_0010;
const LCONTROL = 0b0000_0100;
const RCONTROL = 0b0000_1000;
const LALT = 0b0001_0000;
const RALT = 0b0010_0000;
const LMETA = 0b0100_0000;
const RMETA = 0b1000_0000;
#[deprecated = "use LMETA instead"]
const LSUPER = Self::LMETA.bits();
#[deprecated = "use RMETA instead"]
const RSUPER = Self::RMETA.bits();
}
}

29
winit-core/src/lib.rs Normal file
View File

@@ -0,0 +1,29 @@
//! # Core types for Winit
//!
//! Platform-agnostic types and traits useful when implementing Winit backends,
//! or otherwise interfacing with Winit from library code.
//!
//! See the [`winit`] crate for the full user-facing API.
//!
//! [`winit`]: https://docs.rs/winit
#[macro_use]
pub mod as_any;
pub mod cursor;
#[macro_use]
pub mod error;
pub mod application;
pub mod event;
pub mod event_loop;
pub mod icon;
pub mod keyboard;
pub mod monitor;
pub mod window;
// `Instant` is not actually available on `wasm32-unknown-unknown`, the `std` implementation there
// is a stub. And `wasm32-none` doesn't even have `std`. Instead, we use `web_time::Instant`.
#[cfg(not(all(target_family = "wasm", any(target_os = "unknown", target_os = "none"))))]
pub(crate) use std::time::Instant;
#[cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))]
pub(crate) use web_time::Instant;

View File

@@ -11,8 +11,9 @@ use std::num::{NonZeroU16, NonZeroU32};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use crate::dpi::{PhysicalPosition, PhysicalSize}; use dpi::{PhysicalPosition, PhysicalSize};
use crate::utils::{impl_dyn_casting, AsAny};
use crate::as_any::AsAny;
/// Handle to a monitor. /// Handle to a monitor.
/// ///
@@ -28,24 +29,13 @@ use crate::utils::{impl_dyn_casting, AsAny};
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **Web:** A [`MonitorHandle`] created without /// **Web:** A [`MonitorHandle`] created without `detailed monitor permissions`
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
/// will always represent the current monitor the browser window is in instead of a specific /// will always represent the current monitor the browser window is in instead of a specific
/// monitor. See /// monitor.
#[cfg_attr(
web_platform,
doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]"
)]
#[cfg_attr(not(web_platform), doc = "`MonitorHandleExtWeb::is_detailed()`")]
/// to check.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MonitorHandle(pub(crate) Arc<dyn MonitorHandleProvider>); pub struct MonitorHandle(pub Arc<dyn MonitorHandleProvider>);
impl Deref for MonitorHandle { impl Deref for MonitorHandle {
type Target = dyn MonitorHandleProvider; type Target = dyn MonitorHandleProvider;
@@ -87,14 +77,10 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// ///
/// Returns `None` if the monitor doesn't exist anymore or the name couldn't be obtained. /// Returns `None` if the monitor doesn't exist anymore or the name couldn't be obtained.
/// ///
///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **Web:** Always returns [`None`] without /// **Web:** Always returns [`None`] without `detailed monitor permissions`.
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
fn name(&self) -> Option<Cow<'_, str>>; fn name(&self) -> Option<Cow<'_, str>>;
/// Returns the top-left corner position of the monitor in desktop coordinates. /// Returns the top-left corner position of the monitor in desktop coordinates.
@@ -105,12 +91,7 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **Web:** Always returns [`None`] without /// **Web:** Always returns [`None`] without `detailed monitor permissions`.
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
fn position(&self) -> Option<PhysicalPosition<i32>>; fn position(&self) -> Option<PhysicalPosition<i32>>;
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// Returns the scale factor of the underlying monitor. To map logical pixels to physical
@@ -118,19 +99,9 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// ///
/// See the [`dpi`] module for more information. /// See the [`dpi`] module for more information.
/// ///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`]. /// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0. /// - **Web:** Always returns `0.0` without `detailed_monitor_permissions`.
/// - **Web:** Always returns `0.0` without
#[cfg_attr(
web_platform,
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")]
/// ///
#[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor /// [`Window::scale_factor`]: crate::window::Window::scale_factor
fn scale_factor(&self) -> f64; fn scale_factor(&self) -> f64;
@@ -161,6 +132,14 @@ pub struct VideoMode {
} }
impl VideoMode { impl VideoMode {
pub fn new(
size: PhysicalSize<u32>,
bit_depth: Option<NonZeroU16>,
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
Self { size, bit_depth, refresh_rate_millihertz }
}
/// Returns the resolution of this video mode. This **must not** be used to create your /// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead. /// rendering surface. Use [`Window::surface_size()`] instead.
/// ///
@@ -172,6 +151,13 @@ impl VideoMode {
/// Returns the bit depth of this video mode, as in how many bits you have /// 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 /// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not. /// systems, depending on whether the alpha channel is counted or not.
///
/// # Platform-specific
///
/// - **macOS**: Video modes do not control the bit depth of the monitor, so this often defaults
/// to 32.
/// - **iOS**: Always returns `None`.
/// - **Wayland**: Always returns `None`.
pub fn bit_depth(&self) -> Option<NonZeroU16> { pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth self.bit_depth
} }

File diff suppressed because it is too large Load Diff

26
winit-orbital/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
description = "Winit's Orbital/Redox backend"
documentation = "https://docs.rs/winit-orbital"
edition.workspace = true
license.workspace = true
name = "winit-orbital"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde"]
[dependencies]
bitflags.workspace = true
dpi.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
tracing.workspace = true
winit-core.workspace = true
# Platform-specific
libredox.workspace = true
orbclient.workspace = true
redox_event.workspace = true

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