Compare commits

..

164 Commits

Author SHA1 Message Date
Osspial
429bbfade0 Release Alpha 4 (#1187)
* Release Alpha 4. TODO CHANGELOG RELEASE DATE

* Add changelog date
2019-10-18 13:25:12 -04:00
Osspial
28e3c35547 Prevent EventLoop from getting initialized outside the main thread in cross-platform functions (#1186)
* Prevent EventLoop from getting initialized outside the main thread

This only applies to the cross-platform functions. We expose functions
to do this in a platform-specific manner, when available.

* Add CHANGELOG entry

* Formatting has changed since the latest stable update...

* Fix error spacing

* Unix: Prevent initializing EventLoop outside main thread

* Updates libc dependency to 0.2.64, as required by BSD platforms

* Update CHANGELOG.md for Linux implementation

* Finish sentence

* Consolidate documentation
2019-10-18 11:51:06 -04:00
Steven Sheldon
af3ef52252 Fix so the compiler can infer msg_send! return types (#1227)
* Fix so the compiler can infer msg_send! return type

Currently, due to a quirk in Rust's type inference interacting with the
structure of the msg_send! macro, a () return type will be inferred when
the compiler cannot otherwise determine the return type. This behavior
is expected to change, and in the future could resolve to a ! return
type, which results in undefined behavior.

Linting has previously been added for this in rust-lang/rust#39216, but
it did not catch these cases due to SSheldon/rust-objc#62. An upcoming
version of objc will be fixed to stop hiding these errors, at which
point they will become compile errors.

This change fixes these errors and allows winit to compile with the
fixed version of objc.

* Bump cocoa to 0.19.1
2019-10-18 11:33:40 -04:00
Osspial
2b5f9c52a6 Always dispatch a RedrawRequested event after creating a new window (#1175) 2019-10-17 10:59:07 -04:00
Ryan G
5631cc2528 Remove test files that stuck around on accident (#1226) 2019-10-16 22:46:16 -04:00
Aleksi Juvani
de33a92a1b Fix panic upon closing the app on iOS (#1168) 2019-10-16 18:52:10 -04:00
Ryan Goldstein
1c6353aa3a Merge branch 'web' 2019-10-16 16:09:39 -04:00
Marcus Willock
08cb950226 Link WindowBuilder documentation to corresponding Window methods (fixes #1070) (#1216)
* fixes #1070

* Continuing adding documentation links for #1070
2019-10-16 12:18:02 -04:00
Kirill Chibisov
765225d918 Wayland: Fix panic when calling set_cursor_grab from a different thread than evlp's one. (#1206)
This commit also start following X11 behavior on cursor icon update when
cursor is invisible.

Fixes regression introdced in 5ced36e319
2019-10-16 12:16:23 -04:00
Ryan Goldstein
35bc65f6fa Remove usage of derivative 2019-10-13 14:36:54 -04:00
Ryan Goldstein
676268d461 Merge remote-tracking branch 'upstream/master' into web 2019-10-13 14:09:52 -04:00
Osspial
34dce8069f Test all windows targets and allow nightly Appveyor failures (#1222)
* Test all windows targets

* Allow nightly failures
2019-10-13 13:19:46 -04:00
Ryan G
f62bb33317 Merge branch 'master' into web 2019-10-12 23:51:34 -04:00
Ryan G
a557b3cfb6 Add web targets to travis (#1220)
* Add web targets to travis

* Fix some bash syntax

* Avoid crash on providing no features

* Fix syntax error

* Syntax?

* Fix pleas

* Fix features check

* Add a comment

* Add nightly builds
2019-10-12 23:44:30 -04:00
Ryan G
3ff4834bd5 Add web fullscreen support (#1142)
Adds fullscreen using native web APIs to the stdweb and web-sys backends.

Due to limitations of browser APIs, requests for fullscreen can only be fulfilled during a short-lived user-triggered event. This commit does automatically handle that under the hood, but it does introduce unavoidable latency in full-screening the canvas.
2019-10-11 11:45:07 -04:00
Ryan G
bedb889693 Fix the event key code variable name (#1219) 2019-10-11 11:41:49 -04:00
Ryan G
157ca9cd17 Update the feature matrix for wasm (#1218) 2019-10-09 19:49:12 -04:00
Osspial
6f5e7e170c Update raw-window-handle to 0.3 (#1215) 2019-10-05 21:34:27 -04:00
Osspial
df7571b369 Improve interaction between fullscreen windows and monitor-switching keyboard shortcuts (#1171)
* Update to 1.38.0 formatting

* Improve fullscreen window monitor switching on Windows

* Format
2019-10-05 16:23:30 -04:00
Aleksi Juvani
d69e41eba8 Update fullscreen state on macOS before entering fullscreen (#1196) 2019-10-05 16:23:06 -04:00
Benjamin Saunders
2d41a7d1b0 Fix use-after-free in XConnection::get_output_info (#1211) 2019-10-05 15:00:54 -04:00
Antonino Siena
42e0ccfa1c Implemented a HINSTANCE getter function for Windows (#1213)
* Expose HINSTANCE now using a getter function

* Missing changes

* remove unused import

* Required changes for the PR

* Rust fmt

* Use GetWindowLong

* Use GetWindowLong
2019-10-05 14:52:40 -04:00
Lúcás Meier
55640a91ae Use consistent return types for available_monitors() (#1207)
* [#1111] Use consistent return types for available_monitors()

Always use `impl Iterator<Item = MonitorHandle>` instead of
`AvailableMonitorsIter`. Fix an example that used the Debug
implementation of `AvailableMonitorsIter`.

* [#1111] Update changelog

* [#1111] Remove AvailableMonitorsIter type completely

* [#1111] Remove doc references to AvailableMonitorsIter
2019-10-05 10:49:24 -04:00
Alex Butler
4f6ca8792c Remove derivative dependency (#1201)
* Remove derivative dependency

* Update CHANGELOG.md
2019-10-03 16:19:10 -04:00
Kirill Chibisov
5ced36e319 Wayland support for set_cursor_icon (#1204) 2019-10-03 06:02:59 -07:00
andersrein
237e7ee2e6 Wayland support for set_cursor_grab and set_cursor_visible (#1180)
* Fixed relative_pointer not being set up when the "zwp_relative_pointer_manager_v1" callback comes after the "wl_seat" callback

* Ran cargo fmt

* Updated changelog

* Added wayland support for set_grab_cursor and set_cursor_visible

* Updated changelog

* Ran cargo fmt

* Fixed set_cursor_visible and set_cursor_grab so they can be called from any thread.

* Ran cargo_fmt

* Improved CHANGELOG

* Added workaround so that when cursor is hidden it takes effect before the cursor enters the surface. Making the cursor visible again still only happens once the cursor re-enters the surface

* Switched to using Rc<RefCell> instead of Arc<Mutex> since all accesses to the relative_pointer_manager_proxy will happen on the same thread.

* Forgot to run cargo fmt

* Switched to using Rc and RefCell instead of Arc and Mutex where applicable.

* Improved comments and documentation relating to changing a hidden cursor back to visible on wayland.

* Wayland: Fixed cursor not appearing immendiately when setting the cursor to visible.

* Forgot to run cargo fmt

* Switched to only storing the pointers in CursorManager as AutoPointer.

* Fixed typo and removed println

* Update CHANGELOG.md

Co-Authored-By: Kirill Chibisov <wchibisovkirill@gmail.com>
2019-10-01 18:25:59 -07:00
Ryan Goldstein
02f281569d Update cargo fmt 2019-09-30 16:33:41 -04:00
Ryan Goldstein
ab4d971c5e Fix imports in the multithreaded example 2019-09-30 15:55:27 -04:00
Ryan Goldstein
e21df5831e Merge branch 'master' into merge-master-to-web 2019-09-30 11:19:12 -04:00
msiglreith
18a0119b06 Update raw-window-handle to 0.2 (#1191) 2019-09-30 11:17:01 -04:00
Osspial
34348435fd Update to 1.38.0 formatting (#1188) 2019-09-27 17:35:16 -04:00
Ryan Goldstein
cf3b0f3b70 Attach the raw handle data attribute 2019-09-27 17:06:14 -04:00
Ryan Goldstein
a336e9e959 Fix the formatting in the changelog 2019-09-27 16:57:25 -04:00
Ryan Goldstein
ea93a0130d Switch to the released version 2019-09-27 16:17:40 -04:00
Ryan Goldstein
dcd9ddde50 Fix the examples 2019-09-24 19:44:43 -04:00
Ryan Goldstein
86bafdc104 Merge branch 'web' into merge-master-to-web 2019-09-24 19:41:59 -04:00
Ryan Goldstein
6732fa731d Fix compilation errors 2019-09-24 19:39:13 -04:00
Ryan G
8cea3e262b Update the documentation to reflect web support (#1183)
* Update the documentation to reflect web support

Indicate which methods have platform-specific web behavior

* cargo fmt
2019-09-24 19:33:32 -04:00
Ryan Goldstein
3e8669ea7f Merge branch 'master' into merge-master-to-web 2019-09-24 14:21:18 -04:00
andersrein
7df040f451 Wayland: Switched to using a reference to relative_pointer_manager_proxy when creating SeatData (#1179)
* Fixed relative_pointer not being set up when the "zwp_relative_pointer_manager_v1" callback comes after the "wl_seat" callback

* Ran cargo fmt

* Updated changelog

* Improved CHANGELOG

* Switched to using Rc<RefCell> instead of Arc<Mutex> since all accesses to the relative_pointer_manager_proxy will happen on the same thread.

* Forgot to run cargo fmt
2019-09-23 14:50:06 -04:00
Murarth
472eddcc1b X11: Fix panic when no monitors are available (#1158)
* X11: Fix panic when no monitors are available

* Set dummy monitor's dimensions to `(1, 1)`

* X11: Avoid panicking when there are no monitors in Window::new
2019-09-23 14:45:29 -04:00
Michael Palmos
c0a7900341 Allow using multiple XWindowTypes on X11 (#1140) (#1147)
* Allow using multiple `XWindowType`s on X11 (#1140)

* Update documentation to make combining window types clearer

* Update build flags because X11 runs on more than just Linux

* Revert "Update build flags because X11 runs on more than just Linux"

This reverts commit 882b910046.

* Revert "Update documentation to make combining window types clearer"

This reverts commit da00ad391a.

* Revert "Allow using multiple `XWindowType`s on X11 (#1140)"

This reverts commit a230333456.

* Allow using multiple `XWindowType`s on X11 (slice variant) (#1140)

* Multiple `XWindowType`s, with non-static lifetime.

* Multiple `XWindowType`s (#1140) (`Vec` variant)

* Append change to changelog.

* Fix formatting.
2019-09-23 07:10:33 -07:00
Ryan G
28a50817af Fix web redraw requested (#1181)
* Keep track of what windows have requested redraw

Instead of using request_animation_frame and sending redraw request
events, just keep track of all windows that have asked for a redraw.
This doesn't handle dispatching the events

* Issue redraw events to windows that request it

* Cargo fmt
2019-09-23 09:14:26 -04:00
Ryan G
2c47c43f47 Implement WindowID on the web platform (#1177)
* Use actual numeric IDs to differentiate Windows

This is generally important to identifying which window should
recieve which event, but is also specifically crucial for fixing
RedrawRequested on web.

* Cargo fmt
2019-09-19 18:40:18 -04:00
Osspial
2ef39651eb Fix fullscreen window shrinking upon getting restored to a normal window (#1172) 2019-09-19 11:48:20 -04:00
Murarth
eb20612d77 Prevent stealing focus on new windows (#1176) 2019-09-19 11:47:51 -04:00
Aleksi Juvani
695547f4ca Fix events not being emitted during modal loops on macOS (#1173) 2019-09-17 22:49:29 -04:00
Osspial
d35ee0d580 Fix hovering the mouse over the active window creating an endless stream of CursorMoved events (#1170)
* Fix hovering the mouse over the active window creating an endless stream of CursorMoved events

* Format
2019-09-17 11:34:48 -04:00
Aleksi Juvani
95581ab92f Fix null window on initial HiDpiFactorChanged event on iOS (#1167) 2019-09-16 14:27:46 -04:00
Osspial
3716f13d8e Flush high surrogate if not followed by low surrogate (#1166)
* Flush high surrogate if not followed by low surrogate

* Remove transmute from WM_CHAR handler

* Fix window_state being locked while dispatching ReceivedCharacter for surrogate codepoints.

* Format
2019-09-16 14:26:56 -04:00
Osspial
c03ef852a4 Fix surrogate pair handling on Windows (#1165)
* Fix surrogate pair handling on Windows

* Change high_surrogate to Option<u16>

* Format
2019-09-15 22:09:08 -04:00
Aleksi Juvani
28a5feef28 Fix freeze upon exiting exclusive fullscreen on macOS 10.15 (#1127) 2019-09-15 19:59:37 -04:00
Osspial
57a53bda74 Officially remove the Emscripten backend (#1159) 2019-09-13 19:09:45 -04:00
hafiz
36f4eccb5c fix: distinguish grab and grabbing cursors (#1154)
On macOS, there is a difference between a "grab" cursor and a "grabbing"
cursor, where "grab" is an open-hand cursor used during a hover, and
"grabbing" is a closed-hand cursor used on a click. These, and other
native MacOS cursors, can be seen at the [NSCursor documentation](https://developer.apple.com/documentation/appkit/nscursor?language=objc).

See https://github.com/hecrj/iced/issues/9 for the motivation for this
PR.
2019-09-12 16:38:44 -07:00
Victor Berger
b6de19e92e Changelog entry for #1153 (#1157) 2019-09-11 15:22:41 -07:00
Ryan G
e87bc3db20 Send a LoopDestroyed event when the browser is closed (#1155)
* Add the plumbing for handling browser closes

* Implement the business logic for handling closes
2019-09-11 11:47:03 -04:00
Victor Berger
a3739d6bad wayland: instantly wake up if events are pending (#1153)
Just before starting to poll/wait on calloop(mio), check if there
are already events pending in the internal buffer of our wayland
event queue. If so, dispatch them and force an instant wakeup from
the polling, in order to behave as if we were instantly woken up by
incoming wayland events.

When using OpenGL, mesa shares our wayland socket, and also reads
from it, especially if vsync is enabled as it'll do blocking reads.
When doing so, it may enqueue events in the internal buffer of our
event queue.

As the socket has been read, mio will thus not notify it to calloop
as read, and thus calloop will not know it needs to dispatch. In some
cases this can lead to some events being delivered much later than
they should. Combined with key repetition this can actually cause some
flooding of the event queue making this effect event worse.

Fixes #1148
2019-09-11 08:28:21 +02:00
dam4rus
068d114740 Add touch pressure information for touch events on Windows (#1134)
* Add touch pressure information for touch events on Windows

* Modified CHANGELOG.md and FEATURES.md to reflect changes

* Updated documentation of struct Touch to reflect changes

* Replaced mem::uninitalized() with mem::MaybeUninit
Fixed warnings in platform_impl/windows/dpi.rs
2019-09-09 14:15:49 -04:00
Hal Gentz
3273c14dea Hide ModifiersChanged from the docs. (#1152)
We wouldn't want users stumbling on it until all platforms implement it.
2019-09-08 12:57:43 -07:00
Murarth
206c3c246c Implement WindowEvent ModifiersChanged for X11 and Wayland (#1132)
* Implement WindowEvent ModifiersChanged for X11 and Wayland

* Fix modifier key state desync on X11

* Run cargo fmt
2019-09-08 11:43:28 -07:00
mtak-
bfcd85ab15 [ios] Groundwork for new Redraw API, refactoring AppState, and bugfixes (#1133)
* fix #1087. the CFRunLoopTimer was never started if the user never changed the controlflow.

* RedrawRequested ordering matches the new redraw api
consistent asserts
lots of appstate refactoring to rely less on unsafe, and hopefully make it easier to maintain

* ios: dpi bugfix. inputs to setContentScaleFactor are not to be trusted as iOS uses 0.0 as a sentinel value for "default device dpi".

the fix is to always go through the getter.

* move touch handling onto uiview

* update changelog

* rustfmt weirdness

* fix use option around nullable function pointers in ffi

* Document why gl and metal views don't use setNeedsDisplay

* change main events cleared observer priority to 0 instead of magic number
log when processing non-redraw events when we expect to only be processing redraw events
2019-09-04 14:23:11 -07:00
Murarth
c99bba1655 Remove unused unix dlopen module (#1138) 2019-08-31 01:58:45 -07:00
Osspial
1e7376847b Move changelog entries into proper position (#1131) 2019-08-27 19:20:24 -04:00
Osspial
b03e589987 On Windows, Unset maximized when transforming window (#1014)
* Unset maximized when functionally transforming window

* Add docs

* Fix compile issues
2019-08-26 22:07:15 -04:00
Murarth
0b497b62d8 X11: Improve performance of Window::set_cursor_icon (#1116)
* X11: Fix performance issue with rapidly resetting cursor icon

* When setting cursor icon, if the new icon value is the same as the
  current value, no messages are sent the X server.

* X11: Cache cursor objects in XConnection

* Add changelog entry
2019-08-26 22:06:59 -04:00
Osspial
f085b7349c Remove outdated noop comment (#1126) 2019-08-26 22:06:40 -04:00
Aleksi Juvani
dd99b3bd73 Fix inverted parameter in set_prefers_home_indicator_hidden on iOS (#1123) 2019-08-26 19:56:10 -04:00
mtak-
f53683f01f iOS os version checking around certain APIs (#1094)
* iOS os version checking

* iOS, fix some incorrect msg_send return types

* address nits, and fix OS version check for unsupported os versions

* source for 60fps guarantee
2019-08-26 18:47:23 -04:00
Kirill Chibisov
7b707e7d75 macos: Implement run_return (#1108)
* macos: Implement run_return

* Update comments

* Fix CHANGELOG.md
2019-08-23 02:30:53 -07:00
Osspial
31110be396 Release alpha 3 (#1106) 2019-08-14 11:09:47 -04:00
Osspial
604016d69d Implement raw_window_handle::HasRawWindowHandle for Window type (#1105)
* Implement raw_window_handle::HasRawWindowHandle for Window type

* Format

* Address compilation issues

* Fix Linux build hopefully

* Fix iOS build
2019-08-14 07:57:16 -04:00
Zakarum
7ee9d5639b Disable web module on other targets than wasm32 (#1102) 2019-08-14 01:20:05 -07:00
mtak-
1aab328e2a macos: fix an incorrect type signature on NSView drawRect (#1104) 2019-08-14 00:01:22 -04:00
Aleksi Juvani
1366dc326a Add touch pressure information for touch events on iOS (#1090)
* Add touch pressure information for touch events on iOS

* Add a variant for calibrated touch pressure
2019-08-13 18:12:13 -04:00
satrix321
8e73287646 Change 'proxy.rs' into 'custom_events.rs" (#1101)
* changed i32 to CustomEvent enum
* added a match case for custom event
* minor cleanup
* fixes #953
2019-08-13 18:09:34 -04:00
Emmanouil Katefidis
7eed52a97a Update parking_lot to 0.9 (#1097) (#1099) 2019-08-11 13:31:30 -07:00
YVT
31ada5a052 macOS/iOS: Fix auto trait impls of EventLoopProxy (#1084)
* macOS/iOS: Fix auto trait impls of `EventLoopProxy`

`EventLoopProxy<T>` allows sending `T` from an arbitrary thread that
owns the proxy object. Thus, if `T` is `!Send`, `EventLoopProxy<T>` must
not be allowed to leave the main thread.

`EventLoopProxy<T>` uses `std::sync::mpsc::Sender` under the hood,
meaning the `!Sync` restriction of it also applies to
`EventLoopProxy<T>`. That is, even if `T` is thread-safe, a single
`EventLoopProxy` object cannot be shared between threads.

* Update `CHANGELOG.md`
2019-08-08 17:13:13 -07:00
Aleksi Juvani
30b4f8dc9f Replace set_decorations with set_prefers_status_bar_hidden on iOS (#1092) 2019-08-08 16:10:54 -07:00
Héctor Ramón
dbdde3d781 Stop appending canvas to document in web platform (#1089)
* Stop appending canvas to document in web platform

* Remove `tabindex` TODO in web backend

* Return `OsError` instead of panicking on web canvas creation
2019-08-08 14:51:41 -07:00
Hal Gentz
cf0b8babbd Add new EventLoopWindowTargetExtUnix trait. (#1026)
* Add new `EventLoopWindowTargetExtUnix` trait.

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>

* Slide.

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>

* Travis, damn you.

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>

* Update CHANGELOG.md
2019-08-08 14:50:22 -07:00
Aleksi Juvani
c0c22c8ff1 Disable overscan compensation for external displays on iOS (#1088) 2019-08-06 16:47:00 -04:00
YVT
73cf10e4f3 Do not require T: Clone for EventLoopProxy<T>: Clone (#1086)
* Do not require `T: Clone` for `EventLoopProxy<T>: Clone`

* Update `CHANGELOG.md`

* Remove the conflicting `Clone` impl

* Fix match statement
2019-08-05 16:51:42 -04:00
mtak-
8a1c5277eb iOS: update feature table (#1085) 2019-08-01 15:07:22 -06:00
Aleksi Juvani
1e4c176506 Fix armv7-apple-ios compile target (#1083) 2019-08-01 01:30:05 -06:00
mtak-
3c27e7d88f iOS: add support for controlling the home indicator, and Exclusive video mode (#1078)
* iOS: platform specific edge home indicator control

* iOS: exclusive video mode support

* address nits, and linkify all the ios documentation
2019-07-31 00:57:31 -06:00
Aleksi Juvani
5bc3cf18d9 Add exclusive fullscreen mode (#925)
* Add exclusive fullscreen mode

* Add `WindowExtMacOS::set_fullscreen_presentation_options`

* Capture display for exclusive fullscreen on macOS

* Fix applying video mode on macOS after a fullscreen cycle

* Fix compilation on iOS

* Set monitor appropriately for fullscreen on macOS

* Fix exclusive to borderless fullscreen transitions on macOS

* Fix borderless to exclusive fullscreen transition on macOS

* Sort video modes on Windows

* Fix fullscreen issues on Windows

* Fix video mode changes during exclusive fullscreen on Windows

* Add video mode sorting for macOS and iOS

* Fix monitor `ns_screen` returning `None` after video mode change

* Fix "multithreaded" example on macOS

* Restore video mode upon closing an exclusive fullscreen window

* Fix "multithreaded" example closing multiple windows at once

* Fix compilation on Linux

* Update FEATURES.md

* Don't care about logical monitor groups on X11

* Add exclusive fullscreen for X11

* Update FEATURES.md

* Fix transitions between exclusive and borderless fullscreen on X11

* Update CHANGELOG.md

* Document that Wayland doesn't support exclusive fullscreen

* Replace core-graphics display mode bindings on macOS

* Use `panic!()` instead of `unreachable!()` in "fullscreen" example

* Fix fullscreen "always on top" flag on Windows

* Track current monitor for fullscreen in "multithreaded" example

* Fix exclusive fullscreen sometimes not positioning window properly

* Format

* More formatting and fix CI issues

* Fix formatting

* Fix changelog formatting
2019-07-29 14:16:14 -04:00
Brian Kabiro
131e67ddc1 Rename new_user_event method to with_user_event (#1057) (#1068)
Finishes #1057
2019-07-29 08:58:16 -06:00
dam4rus
e5ba79db04 Process WM_SYSCOMMAND to forbid screen savers in fullscreen mode (#1065)
* Process WM_SYSCOMMAND to forbid screen savers in fullscreen mode

Fixes #1047

* Update CHANGELOG.md and documentation to reflect changes from issue #1065

* Updated documentation of window.Window.set_fullscreen to match the documentation of window.WindowBuilder.with_fullscreen.
2019-07-29 03:18:23 -06:00
t嘎
f4e9bf51db add macos with_disallow_hidpi (#1073)
* add macos with_disallow_hidpi

add CHANGELOG

* Always use stable rustfmt for CI. (#1074)

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>

* add macos with_disallow_hidpi

add CHANGELOG
2019-07-29 02:07:36 -06:00
Hal Gentz
03f9e8fce0 Always use stable rustfmt for CI. (#1074)
Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-07-28 03:09:31 -06:00
Felix Rabe
4ae9900363 PULL_REQUEST_TEMPLATE.md: Add entry on warnings (#1017) 2019-07-26 01:14:48 -06:00
dam4rus
454d4190b7 Use himetric values in WM_POINTER events (#1053)
* Use himetric location for WM_POINTER events

* Ran rustfmt
2019-07-26 01:12:06 -06:00
Austin Lasher
a28b60578d Fix run_return example build error on non-desktop platforms (#1067) 2019-07-25 14:56:24 -04:00
Tilman Schmidt
5a206de620 macOS: Drop the closure on exit. (Fixes #1058) (#1063) 2019-07-23 14:44:06 -06:00
Simon Sapin
b547531499 Update the percent-encoding crate to 2.0 (#1066) 2019-07-23 13:38:45 -04:00
Murarth
39e668ffb0 Fix CHANGELOG.md (#1061) 2019-07-22 19:26:52 -06:00
Murarth
bd1ac6cb1e X11: Fix events not being reported promptly (#1048)
* X11: Fix events not being reported promptly

* Add an entry to the changelog
2019-07-17 14:09:02 -04:00
dam4rus
8567758156 Touch events emit screen coordinates instead of client coordinates on Windows (#1042)
* Touch events emit screen coordinates instead of client coordinates on Windows

Fixes #1002

* Don't lose precision of WM_TOUCH events when converting from screen space to client space

* Updated CHANGELOG.md to reflect changes from issue: #1042
2019-07-17 12:25:35 -04:00
Ryan G
e897d70733 Bump the stdweb version to 0.4.18 (#1049)
This removes the need to patch to a git version
2019-07-16 21:56:58 -06:00
Ryan Goldstein
fe12996382 Update the web backend todo list 2019-07-16 20:00:03 -07:00
Murarth
e8e4d4ce66 X11: Fix request_redraw deadlock while handling RedrawRequested (#1046) 2019-07-16 16:53:41 -06:00
Felix Rabe
44af4f4f52 Minor doc changes (#1024)
* Minor doc changes

* More typos
2019-07-12 19:05:07 -04:00
Murarth
7daf146801 Replace std::mem::uninitialized with MaybeUninit (#1027)
* Replace `std::mem::uninitialized` with `MaybeUninit`

* Avoid undefined behavior when using `MaybeUninit`

* Restore unused `PointerState` fields as internally public

* Zero-initialize some struct values in Xlib FFI calls

* Reform usage of `MaybeUninit` in Xlib FFI

* Prefer safe zero-initialization using `Default`, when possible
* Zero-initialize integers and floats using `0` or `0.0`
* Use `MaybeUninit::uninit` for large byte buffers and union types
* Use `MaybeUninit::uninit` when the resulting value is ignored
2019-07-11 10:34:32 -06:00
Héctor Ramón
7b23d190b1 Fix web errors (#1040)
* Fix old `use` declarations

* Fix hidden lifetime parameter

* Fix missing methods in `web::Monitor`.

Originally fixed by @ryanisaacg in 94387c4bf5.

* Disable some tests and examples on `wasm32`
2019-07-10 18:54:54 -04:00
Osspial
17b8310517 Update Windows Multitouch in FEATURES.md (#1039)
It seems we were already implementing multitouch on Windows, and the question mark was inaccurate.
2019-07-10 18:54:34 -04:00
Osspial
5ca828d445 Merge pull request #1031 from rikusalminen/event_loop_test
Add NewEvents(Init) callback to x11
2019-07-10 11:29:21 -04:00
Riku Salminen
1ea29b4de0 x11: NewEvents(StartCause::Init) callback at start
Before starting the event loop, invoke callback with
NewEvents(StartCause::Init).
2019-07-10 15:27:57 +03:00
Osspial
b00cdadb5b Merge pull request #991 from dam4rus/master
Handle WM_POINTER* events in favor of WM_TOUCH
2019-07-10 02:30:09 -04:00
Osspial
53e646dabc Merge pull request #1038 from ZeGentzy/web
Webmerge4
2019-07-10 02:17:21 -04:00
Hal Gentz
613fafdfdf Merge branch 'web' into webmerge2 2019-07-09 22:50:51 -06:00
Osspial
5d0bc5f607 Correct 0.20.0 Alpha 2 release date 2019-07-09 19:19:00 -04:00
Kalmár Róbert
026b331ba5 Handle WM_POINTER* events in favor of WM_TOUCH
Fixes #975
2019-07-08 10:13:02 +02:00
Kalmar Robert
93c36ccf78 Handle WM_POINTER* events in favor of WM_TOUCH
Fixes #975.
2019-07-08 10:12:47 +02:00
Héctor Ramón Jiménez
76645f3b5a Bump version 2019-07-07 05:51:06 +02:00
Héctor Ramón Jiménez
5cc84f32db Improve feature names to enable web backends 2019-07-01 20:43:54 +02:00
Héctor Ramón Jiménez
e89674d337 Add dyn keyword where necessary 2019-07-01 20:23:42 +02:00
Héctor Ramón Jiménez
de120280e3 Fix mouse release/press events 2019-06-29 17:48:22 +02:00
Héctor Ramón Jiménez
7f2ba0ee3e Fix set_cursor_icon 2019-06-29 17:48:01 +02:00
Héctor Ramón Jiménez
ea73dac753 Fix feature names 2019-06-27 01:18:46 +02:00
Héctor Ramón Jiménez
2a35646520 Use latest stdweb revision 2019-06-27 00:23:58 +02:00
Héctor Ramón Jiménez
bb285984da Implement stdweb backend for web platform 2019-06-27 00:02:46 +02:00
Héctor Ramón Jiménez
1596cc5d9e Avoid leaking implementation details in Canvas API 2019-06-25 21:36:24 +02:00
Héctor Ramón Jiménez
8f66d96915 Support ReceivedCharacter event 2019-06-25 21:18:11 +02:00
Héctor Ramón Jiménez
8ad078b964 Implement keyboard and blur/focus events 2019-06-25 21:01:13 +02:00
Héctor Ramón Jiménez
d5368d7979 Implement Canvas::request_redraw 2019-06-25 18:39:41 +02:00
Héctor Ramón Jiménez
9c5657b86c Remove Canvas when dropped 2019-06-25 18:19:22 +02:00
Héctor Ramón Jiménez
b79089ea57 Implement web_sys::Canvas event listeners 2019-06-25 18:07:47 +02:00
Héctor Ramón Jiménez
c5703eb00a Draft web platform structure 2019-06-25 03:15:34 +02:00
Héctor Ramón Jiménez
eea9530f38 Merge remote-tracking branch 'blm/web-sys' into stdweb-eventloop-2 2019-06-24 18:31:56 +02:00
Ryan Goldstein
a0f280e71f Update how timeouts are cleared to avoid possible double-clearing 2019-06-23 14:38:35 -07:00
Ryan Goldstein
cf28751ae3 Remove unnecessary set-to-wait in example 2019-06-23 14:38:16 -07:00
Ryan Goldstein
b571362bf1 Fix a panic due to double-borrow 2019-06-20 21:46:01 -07:00
Ben Merritt
5d31f73302 Clean up Cargo.toml after incorrect rebase 2019-06-17 22:56:37 -07:00
Ben Merritt
7de1261555 Fix some warnings 2019-06-17 22:54:07 -07:00
Ben Merritt
91a511ba8c Replace JS snippet with throw_str 2019-06-17 22:54:07 -07:00
Ben Merritt
94f6294c0a Prevent callbacks from being destroyed too early 2019-06-17 22:54:07 -07:00
Ben Merritt
77cd3adb01 TEMPORARY: add testing example 2019-06-17 22:54:07 -07:00
Ben Merritt
7dabad4d71 Fix throwToEscapeEventLoop function 2019-06-17 22:51:23 -07:00
Ben Merritt
54b4074369 Port remaining modules to web_sys 2019-06-17 22:51:23 -07:00
Ben Merritt
e4d8e22846 Start implementing web-sys backend 2019-06-17 22:51:23 -07:00
Ryan Goldstein
182beb4f8b Indicate that I will be maintaing the stdweb backend 2019-06-16 21:34:54 -07:00
Ryan Goldstein
2690306f4a Implement Poll and WaitUntil in the stdweb backend 2019-06-16 21:30:05 -07:00
Ryan Goldstein
b59e3c670b WIP 2019-06-14 21:15:43 -07:00
Ryan Goldstein
f2b6ef2edd Merge master into stdweb-eventloop-2
Update the internal APIs to match the new API changes
2019-06-01 13:21:50 -07:00
Ryan Goldstein
1409f83fb9 Add support for mouse wheel 2019-05-31 21:50:34 -07:00
Ryan Goldstein
37dadab745 Add access to the canvas in the Window 2019-05-31 21:50:25 -07:00
Ryan Goldstein
70c7382a09 Fix the request_animation_frame lifetimes 2019-05-01 21:20:54 -04:00
Ryan Goldstein
9f801cf79e Only send the request-redraw on the next animation frame 2019-04-29 15:39:43 -04:00
Ryan Goldstein
fe5e300062 Clean up and document the core of stdweb event handling 2019-04-25 00:02:13 -04:00
Ryan Goldstein
9e25561edf Fix compile failures and add canvas positioning 2019-04-02 22:31:30 -04:00
Ryan Goldstein
7c6bdcc459 Handle ControlFlow::Exit and dealing with events-in-events 2019-03-22 22:15:49 -04:00
Ryan Goldstein
b09629f1d4 Handle ControlFlow::Exit 2019-03-18 22:13:30 -04:00
Ryan Goldstein
85446d81f3 Fix warnings 2019-03-16 18:51:11 -04:00
Ryan Goldstein
96786bbb87 Implement focus event 2019-03-16 18:44:13 -04:00
Ryan Goldstein
a5166baba2 Implement request_redraw 2019-03-16 18:40:35 -04:00
Ryan Goldstein
d1deba8620 Rename modules 2019-03-11 22:22:21 -04:00
Ryan Goldstein
aaee72422a Rearchitect to allow API compliance 2019-03-11 22:18:58 -04:00
Ryan Goldstein
3dd0e31cc4 Merge eventloop-2.0 into stdweb-eventloop-2 2019-03-11 15:52:04 -04:00
Ryan Goldstein
283a8dec37 Refactor out the stdweb functionality into different modules 2019-03-09 22:23:39 -05:00
Ryan Goldstein
37d354cf7f Get to a state where a canvas is spawned 2019-03-09 21:54:29 -05:00
Ryan Goldstein
f698d451df Add key and mouse event support that typechecks 2019-03-02 12:31:16 -05:00
Ryan Goldstein
c088f8bd03 Create the outline of event input and handler calls 2019-02-26 13:36:48 -05:00
Ryan Goldstein
f44e98ddc9 Implemented a few easy methods 2019-02-19 20:08:18 -05:00
Ryan Goldstein
fd4db4000c Create the type layout
Everything typechecks, but nothing is implemented
2019-02-12 20:47:31 -05:00
100 changed files with 7909 additions and 3836 deletions

View File

@@ -1,4 +1,5 @@
- [ ] Tested on all platforms changed
- [ ] Compilation warnings were addressed
- [ ] `cargo fmt` has been run on this branch
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior

4
.gitignore vendored
View File

@@ -2,5 +2,9 @@ Cargo.lock
target/
rls/
.vscode/
util/
*~
*.wasm
*.ts
*.js
#*#

View File

@@ -34,7 +34,7 @@ matrix:
os: osx
rust: stable
# iOS
# iOS x86_64
- env: TARGET=x86_64-apple-ios
os: osx
rust: nightly
@@ -42,18 +42,58 @@ matrix:
os: osx
rust: stable
# iOS armv7
- env: TARGET=armv7-apple-ios
os: osx
rust: nightly
- env: TARGET=armv7-apple-ios
os: osx
rust: stable
# iOS arm64
- env: TARGET=aarch64-apple-ios
os: osx
rust: nightly
- env: TARGET=aarch64-apple-ios
os: osx
rust: stable
# wasm stdweb
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
os: linux
rust: stable
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
os: linux
rust: nightly
# wasm web-sys
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
os: linux
rust: stable
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
os: linux
rust: nightly
install:
- rustup self update
- rustup target add $TARGET; true
- rustup component add rustfmt
- rustup toolchain install stable
- rustup component add rustfmt --toolchain stable
script:
- cargo fmt --all -- --check
- cargo build --target $TARGET --verbose
- cargo build --target $TARGET --features serde --verbose
# Running iOS apps on OSX requires the simulator so we skip that for now
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
- cargo +stable fmt --all -- --check
# Install cargo-web to build stdweb
- if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi
# Build without serde then with serde
- if [[ -z "$FEATURES" ]]; then
cargo $WEB build --target $TARGET --verbose;
else
cargo $WEB build --target $TARGET --features $FEATURES --verbose;
fi
- cargo $WEB build --target $TARGET --features serde,$FEATURES --verbose
# Running iOS apps on macOS requires the Simulator so we skip that for now
# The web targets also don't support running tests
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --verbose; fi
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --features serde --verbose; fi
after_success:
- |

View File

@@ -1,6 +1,80 @@
# Unreleased
# 0.20.0 Alpha 2 (2019-06-28)
# 0.20.0 Alpha 4 (2019-10-18)
- Add web support via the 'stdweb' or 'web-sys' features
- On Windows, implemented function to get HINSTANCE
- On macOS, implement `run_return`.
- On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`.
- On X11, performance is improved when rapidly calling `Window::set_cursor_icon`.
- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized.
- On Windows, unset `maximized` when manually changing the window's position or size.
- On Windows, add touch pressure information for touch events.
- On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`.
- On Wayland, fix event processing sometimes stalling when using OpenGL with vsync.
- Officially remove the Emscripten backend.
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
- On iOS, fix panic upon closing the app.
- On X11, allow setting mulitple `XWindowType`s.
- On iOS, fix null window on initial `HiDpiFactorChanged` event.
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized
by the user.
- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events.
- Always dispatch a `RedrawRequested` event after creating a new window.
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
- On X11, prevent stealing input focus when creating a new window.
Only steal input focus when entering fullscreen mode.
- On Wayland, add support for set_cursor_visible and set_cursor_grab.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced.
- Removed `derivative` crate dependency.
- On Wayland, add support for set_cursor_icon.
- Use `impl Iterator<Item = MonitorHandle>` instead of `AvailableMonitorsIter` consistently.
- On macOS, fix fullscreen state being updated after entering fullscreen instead of before,
resulting in `Window::fullscreen` returning the old state in `Resized` events instead of
reflecting the new fullscreen state
- On X11, fix use-after-free during window creation
- On Windows, disable monitor change keyboard shortcut while in exclusive fullscreen.
- On Windows, ensure that changing a borderless fullscreen window's monitor via keyboard shortcuts keeps the window fullscreen on the new monitor.
- Prevent `EventLoop::new` and `EventLoop::with_user_event` from getting called outside the main thread.
- This is because some platforms cannot run the event loop outside the main thread. Preventing this
reduces the potential for cross-platform compatibility gotchyas.
- On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread.
# 0.20.0 Alpha 3 (2019-08-14)
- On macOS, drop the run closure on exit.
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
- On X11, fix delayed events after window redraw.
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
- On Windows, screen saver won't start if the window is in fullscreen mode.
- Change all occurrences of the `new_user_event` method to `with_user_event`.
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode.
- On iOS, add support for hiding the home indicator.
- On iOS, add support for deferring system gestures.
- On iOS, fix a crash that occurred while acquiring a monitor's name.
- On iOS, fix armv7-apple-ios compile target.
- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy<T>`.
- On iOS, disable overscan compensation for external displays (removes black
bars surrounding the image).
- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait.
- On iOS, add `set_prefers_status_bar_hidden` extension function instead of
hijacking `set_decorations` for this purpose.
- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`.
- On iOS, add touch pressure information for touch events.
- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms.
- On macOS, fix the signature of `-[NSView drawRect:]`.
- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application.
- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number.
- On iOS, RedrawRequested now works for gl/metal backed views.
- On iOS, RedrawRequested is generally ordered after EventsCleared.
# 0.20.0 Alpha 2 (2019-07-09)
- On X11, non-resizable windows now have maximize explicitly disabled.
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.20.0-alpha2"
version = "0.20.0-alpha4"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -14,12 +14,17 @@ categories = ["gui"]
[package.metadata.docs.rs]
features = ["serde"]
[features]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
stdweb = ["std_web", "instant/stdweb"]
[dependencies]
instant = "0.1"
lazy_static = "1"
libc = "0.2"
libc = "0.2.64"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
derivative = "1.0.2"
raw-window-handle = "0.3"
[dev-dependencies]
image = "0.21"
@@ -32,7 +37,7 @@ version = "0.2"
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.18.4"
cocoa = "0.19.1"
core-foundation = "0.6"
core-graphics = "0.17.3"
dispatch = "0.1.4"
@@ -43,7 +48,7 @@ version = "0.1.3"
default_features = false
features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies]
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
@@ -75,7 +80,40 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "
calloop = "0.4.2"
smithay-client-toolkit = "0.6"
x11-dl = "2.18.3"
percent-encoding = "1.0"
percent-encoding = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.8"
version = "0.9"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.22"
optional = true
features = [
'console',
'BeforeUnloadEvent',
'Document',
'DomRect',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'KeyboardEvent',
'MouseEvent',
'Node',
'PointerEvent',
'Window',
'WheelEvent'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
package = "stdweb"
version = "=0.4.20"
optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]

View File

@@ -12,7 +12,6 @@ be used to create both games and applications. It supports the main graphical pl
- iOS
- Android
- Web
- via Emscripten
- via WASM
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
@@ -84,6 +83,9 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows, and if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
@@ -100,6 +102,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
@@ -129,6 +132,21 @@ If your PR makes notable changes to Winit's features, please update this section
* GTK Theme Variant
* Base window size
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIWindow` object pointer
* Get the `UIViewController` object pointer
* Get the `UIView` object pointer
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility
* Deferrring system gestures
* Support for custom `UIView` derived class
* Getting the device idiom
* Getting the preferred video mode
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@@ -143,55 +161,57 @@ Legend:
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] | |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ ||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** |
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**| |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ ||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows | | | | |❌ |❌ |❌ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** | |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**|
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**|
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A** |
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ |
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**| |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|Multitouch | |❌ |✔️ |✔️ | | | |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ | |✔️ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**| |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|Touch pressure |✔️ |❌ | | | |✔️ |✔️ |
|Multitouch |✔️ | |✔️ |✔️ |❓ |✔️ |✔️ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ | |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ | |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ | |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ | |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ | |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ | |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Event Loop 2.0 ([#459]) |✔️ | |❌ |✔️ |❌ | | |
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ | |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ | |
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
[#165]: https://github.com/rust-windowing/winit/issues/165

View File

@@ -7,7 +7,7 @@
```toml
[dependencies]
winit = "0.20.0-alpha2"
winit = "0.20.0-alpha4"
```
## [Documentation](https://docs.rs/winit)
@@ -66,7 +66,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
### Platform-specific usage
#### Emscripten and WebAssembly
#### WebAssembly
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:

View File

@@ -1,13 +1,24 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
matrix:
allow_failures:
- CHANNEL: nightly
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%

47
examples/custom_events.rs Normal file
View File

@@ -0,0 +1,47 @@
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[derive(Debug, Clone, Copy)]
enum CustomEvent {
Timer,
}
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
// loop from any thread.
let event_loop_proxy = event_loop.create_proxy();
std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
event_loop_proxy.send_event(CustomEvent::Timer).ok();
}
});
event_loop.run(move |event, _, control_flow| match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
}
#[cfg(target_arch = "wasm32")]
fn main() {
panic!("This example is not supported on web.");
}

View File

@@ -1,56 +1,35 @@
use std::io::{self, Write};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
monitor::MonitorHandle,
window::WindowBuilder,
};
use std::io::{stdin, stdout, Write};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::monitor::{MonitorHandle, VideoMode};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
let event_loop = EventLoop::new();
#[cfg(target_os = "macos")]
let mut macos_use_simple_fullscreen = false;
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
stdout().flush().unwrap();
let monitor = {
// On macOS there are two fullscreen modes "native" and "simple"
#[cfg(target_os = "macos")]
{
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
io::stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
match num {
2 => macos_use_simple_fullscreen = true,
_ => {}
}
let fullscreen = Some(match num {
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
_ => panic!("Please enter a valid number"),
});
// Prompt for monitor when using native fullscreen
if !macos_use_simple_fullscreen {
Some(prompt_for_monitor(&event_loop))
} else {
None
}
}
#[cfg(not(target_os = "macos"))]
Some(prompt_for_monitor(&event_loop))
};
let mut is_fullscreen = monitor.is_some();
let mut is_maximized = false;
let mut decorations = true;
let window = WindowBuilder::new()
.with_title("Hello world!")
.with_fullscreen(monitor)
.with_fullscreen(fullscreen.clone())
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait;
match event {
@@ -67,35 +46,14 @@ fn main() {
} => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => {
#[cfg(target_os = "macos")]
{
if macos_use_simple_fullscreen {
use winit::platform::macos::WindowExtMacOS;
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
is_fullscreen = !is_fullscreen;
}
return;
}
}
is_fullscreen = !is_fullscreen;
if !is_fullscreen {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(window.current_monitor()));
window.set_fullscreen(fullscreen.clone());
}
}
(VirtualKeyCode::S, ElementState::Pressed) => {
println!("window.fullscreen {:?}", window.fullscreen());
#[cfg(target_os = "macos")]
{
use winit::platform::macos::WindowExtMacOS;
println!(
"window.simple_fullscreen {:?}",
WindowExtMacOS::simple_fullscreen(&window)
);
}
}
(VirtualKeyCode::M, ElementState::Pressed) => {
is_maximized = !is_maximized;
@@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
}
print!("Please write the number of the monitor to use: ");
io::stdout().flush().unwrap();
stdout().flush().unwrap();
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = event_loop
.available_monitors()
@@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
monitor
}
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
for (i, video_mode) in monitor.video_modes().enumerate() {
println!("Video mode #{}: {}", i, video_mode);
}
print!("Please write the number of the video mode to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let video_mode = monitor
.video_modes()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {}", video_mode);
video_mode
}

View File

@@ -4,6 +4,6 @@ fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
dbg!(window.available_monitors());
dbg!(window.available_monitors().collect::<Vec<_>>());
dbg!(window.primary_monitor());
}

View File

@@ -1,16 +1,18 @@
extern crate env_logger;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: (u32, u32) = (600, 400);
#[cfg(not(target_arch = "wasm32"))]
fn main() {
extern crate env_logger;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, Fullscreen, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: (u32, u32) = (600, 400);
env_logger::init();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
@@ -19,11 +21,34 @@ fn main() {
.with_inner_size(WINDOW_SIZE.into())
.build(&event_loop)
.unwrap();
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx);
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
WindowEvent::Moved { .. } => {
// We need to update our chosen video mode if the window
// was moved to an another monitor, so that the window
// appears on this monitor instead when we go fullscreen
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
video_modes = window.current_monitor().video_modes().collect();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.iter().nth(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
// completely different video mode, so notify the user
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
@@ -36,7 +61,7 @@ fn main() {
} => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift;
use self::VirtualKeyCode::*;
use VirtualKeyCode::*;
match key {
A => window.set_always_on_top(state),
C => window.set_cursor_icon(match state {
@@ -44,9 +69,26 @@ fn main() {
false => CursorIcon::Default,
}),
D => window.set_decorations(!state),
F => window.set_fullscreen(match state {
true => Some(window.current_monitor()),
false => None,
// Cycle through video modes
Right | Left => {
video_mode_id = match key {
Left => video_mode_id.saturating_sub(1),
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!(
"Picking video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
F => window.set_fullscreen(match (state, modifiers.alt) {
(true, false) => {
Some(Fullscreen::Borderless(window.current_monitor()))
}
(true, true) => Some(Fullscreen::Exclusive(
video_modes.iter().nth(video_mode_id).unwrap().clone(),
)),
(false, _) => None,
}),
G => window.set_cursor_grab(state).unwrap(),
H => window.set_cursor_visible(!state),
@@ -56,6 +98,7 @@ fn main() {
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE.into()),
@@ -108,6 +151,7 @@ fn main() {
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
@@ -125,3 +169,8 @@ fn main() {
}
})
}
#[cfg(target_arch = "wasm32")]
fn main() {
panic!("Example not supported on Wasm");
}

View File

@@ -1,37 +0,0 @@
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop: EventLoop<i32> = EventLoop::new_user_event();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let proxy = event_loop.create_proxy();
std::thread::spawn(move || {
let mut counter = 0;
// Wake up the `event_loop` once every second.
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
proxy.send_event(counter).unwrap();
counter += 1;
}
});
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

@@ -1,4 +1,5 @@
use std::time::{Duration, Instant};
use instant::Instant;
use std::time::Duration;
use winit::{
event::{Event, WindowEvent},

View File

@@ -1,4 +1,5 @@
use std::time::{Duration, Instant};
use instant::Instant;
use std::time::Duration;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},

View File

@@ -7,6 +7,6 @@ fn main() {
println!("Listing available video modes:");
for mode in monitor.video_modes() {
println!("{:?}", mode);
println!("{}", mode);
}
}

View File

@@ -1,11 +1,20 @@
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
window::WindowBuilder,
};
// Limit this example to only compatible platforms.
#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
fn main() {
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
window::WindowBuilder,
};
let mut event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -39,3 +48,8 @@ fn main() {
println!("Okay we're done now for real.");
}
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
fn main() {
println!("This platform doesn't support run_return.");
}

View File

@@ -55,6 +55,7 @@
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
//! - **Web:** DPI factors are handled by the browser and will always be 1.0 for your application.
//!
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a

View File

@@ -4,7 +4,8 @@
//! processed and used to modify the program state. For more details, see the root-level documentation.
//!
//! [event_loop_run]: ../event_loop/struct.EventLoop.html#method.run
use std::{path::PathBuf, time::Instant};
use instant::Instant;
use std::path::PathBuf;
use crate::{
dpi::{LogicalPosition, LogicalSize},
@@ -61,7 +62,7 @@ impl<T> Event<T> {
}
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StartCause {
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
/// moment the timeout was requested and the requested resume time. The actual resume time is
@@ -133,6 +134,10 @@ pub enum WindowEvent {
input: KeyboardInput,
},
/// Keyboard modifiers have changed
#[doc(hidden)]
ModifiersChanged { modifiers: ModifiersState },
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
@@ -303,30 +308,94 @@ pub enum TouchPhase {
Cancelled,
}
/// Represents touch event
/// Represents a touch event
///
/// Every time user touches screen new Start event with some finger id is generated.
/// When the finger is removed from the screen End event with same id is generated.
/// Every time the user touches the screen, a new `Start` event with an unique
/// identifier for the finger is generated. When the finger is lifted, an `End`
/// event is generated with the same finger id.
///
/// For every id there will be at least 2 events with phases Start and End (or Cancelled).
/// There may be 0 or more Move events.
/// After a `Start` event has been emitted, there may be zero or more `Move`
/// events when the finger is moved or the touch pressure changes.
///
/// The finger id may be reused by the system after an `End` event. The user
/// should assume that a new `Start` event received with the same id has nothing
/// to do with the old finger and is a new finger.
///
/// Depending on platform implementation id may or may not be reused by system after End event.
///
/// Gesture regonizer using this event should assume that Start event received with same id
/// as previously received End event is a new finger and has nothing to do with an old one.
///
/// Touch may be cancelled if for example window lost focus.
/// A `Cancelled` event is emitted when the system has canceled tracking this
/// touch, such as when the window loses focus, or on iOS if the user moves the
/// device against their face.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: LogicalPosition,
/// unique identifier of a finger.
/// Describes how hard the screen was pressed. May be `None` if the platform
/// does not support pressure sensitivity.
///
/// ## Platform-specific
///
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
}
/// Describes the force of a touch event
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Force {
/// 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
/// device.
Calibrated {
/// The force of the touch, where a value of 1.0 represents the force of
/// an average touch (predetermined by the system, not user-specific).
///
/// 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
/// calculate this value using the `altitude_angle` value.
force: f64,
/// The maximum possible force for a touch.
///
/// The value of this field is sufficiently high to provide a wide
/// dynamic range for values of the `force` field.
max_possible_force: f64,
/// The altitude (in radians) of the stylus.
///
/// A value of 0 radians indicates that the stylus is parallel to the
/// surface. The value of this property is Pi/2 when the stylus is
/// perpendicular to the surface.
altitude_angle: Option<f64>,
},
/// If the platform reports the force as normalized, we have no way of
/// knowing how much pressure 1.0 corresponds to we know it's the maximum
/// amount of force, but as to how much force, you might either have to
/// press really really hard, or not hard at all, depending on the device.
Normalized(f64),
}
impl Force {
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
/// Instead of normalizing the force, you should prefer to handle
/// `Force::Calibrated` so that the amount of force the user has to apply is
/// consistent across devices.
pub fn normalized(&self) -> f64 {
match self {
Force::Calibrated {
force,
max_possible_force,
altitude_angle,
} => {
let force = match altitude_angle {
Some(altitude_angle) => force / altitude_angle.sin(),
None => *force,
};
force / max_possible_force
}
Force::Normalized(force) => *force,
}
}
}
/// Hardware-dependent keyboard scan code.
pub type ScanCode = u32;

View File

@@ -9,13 +9,11 @@
//! [create_proxy]: ./struct.EventLoop.html#method.create_proxy
//! [event_loop_proxy]: ./struct.EventLoopProxy.html
//! [send_event]: ./struct.EventLoopProxy.html#method.send_event
use std::{error, fmt, ops::Deref, time::Instant};
use instant::Instant;
use std::ops::Deref;
use std::{error, fmt};
use crate::{
event::Event,
monitor::{AvailableMonitorsIter, MonitorHandle},
platform_impl,
};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
@@ -30,6 +28,7 @@ use crate::{
/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
///
pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>,
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
@@ -37,9 +36,10 @@ pub struct EventLoop<T: 'static> {
/// Target that associates windows with an `EventLoop`.
///
/// This type exists to allow you to create new windows while Winit executes your callback.
/// `EventLoop` will coerce into this type, so functions that take this as a parameter can also
/// take `&EventLoop`.
/// This type exists to allow you to create new windows while Winit executes
/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
/// EventLoop<T>`), so functions that take this as a parameter can also take
/// `&EventLoop`.
pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
@@ -69,7 +69,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// the control flow to `Poll`.
///
/// [events_cleared]: ../event/enum.Event.html#variant.EventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
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.
@@ -95,16 +95,12 @@ impl Default for ControlFlow {
impl EventLoop<()> {
/// Builds a new event loop with a `()` as the user event type.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new() -> EventLoop<()> {
EventLoop::<()>::new_user_event()
}
}
impl<T> EventLoop<T> {
/// Builds a new event loop.
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
/// Attempting to create the event loop on a different 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. `EventLoopExt::new_any_thread` functions are exposed
/// in the relevant `platform` module if the target platform supports creating an event loop on
/// any thread.
///
/// Usage will result in display backend initialisation, this can be controlled on linux
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
@@ -114,7 +110,20 @@ impl<T> EventLoop<T> {
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new_user_event() -> EventLoop<T> {
pub fn new() -> EventLoop<()> {
EventLoop::<()>::with_user_event()
}
}
impl<T> EventLoop<T> {
/// Builds a new event loop.
///
/// All caveats documented in [`EventLoop::new`] apply to this function.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn with_user_event() -> EventLoop<T> {
EventLoop {
event_loop: platform_impl::EventLoop::new(),
_marker: ::std::marker::PhantomData,
@@ -149,10 +158,10 @@ impl<T> EventLoop<T> {
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
let data = self.event_loop.available_monitors();
AvailableMonitorsIter {
data: data.into_iter(),
}
self.event_loop
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
@@ -172,11 +181,18 @@ impl<T> Deref for EventLoop<T> {
}
/// Used to send custom events to `EventLoop`.
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
event_loop_proxy: self.event_loop_proxy.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this

View File

@@ -50,7 +50,7 @@
//! match event {
//! Event::EventsCleared => {
//! // Application update code.
//!
//!
//! // Queue a RedrawRequested event.
//! window.request_redraw();
//! },
@@ -121,14 +121,13 @@ extern crate log;
#[macro_use]
extern crate serde;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate derivative;
#[macro_use]
#[cfg(target_os = "windows")]
#[cfg(any(target_os = "ios", target_os = "windows"))]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(feature = "std_web")]
extern crate std_web as stdweb;
pub mod dpi;
#[macro_use]

View File

@@ -1,68 +1,65 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_id]
//! type. This is retreived from an [`AvailableMonitorsIter`][monitor_iter], which can be acquired
//! with:
//! type. This is retreived from one of the following methods, which return an iterator of
//! [`MonitorHandle`][monitor_id]:
//! - [`EventLoop::available_monitors`][loop_get]
//! - [`Window::available_monitors`][window_get].
//!
//! [monitor_id]: ./struct.MonitorHandle.html
//! [monitor_iter]: ./struct.AvailableMonitorsIter.html
//! [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
//! [window_get]: ../window/struct.Window.html#method.available_monitors
use std::collections::vec_deque::IntoIter as VecDequeIter;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
};
/// An iterator over all available monitors.
///
/// Can be acquired with:
/// - [`EventLoop::available_monitors`][loop_get]
/// - [`Window::available_monitors`][window_get].
///
/// [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
/// [window_get]: ../window/struct.Window.html#method.available_monitors
// Implementation note: we retrieve the list once, then serve each element by one by one.
// This may change in the future.
#[derive(Debug)]
pub struct AvailableMonitorsIter {
pub(crate) data: VecDequeIter<platform_impl::MonitorHandle>,
}
impl Iterator for AvailableMonitorsIter {
type Item = MonitorHandle;
#[inline]
fn next(&mut self) -> Option<MonitorHandle> {
self.data.next().map(|id| MonitorHandle { inner: id })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.data.size_hint()
}
}
/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with:
/// - [`MonitorHandle::video_modes`][monitor_get].
///
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) video_mode: platform_impl::VideoMode,
}
impl std::fmt::Debug for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f)
}
}
impl PartialOrd for VideoMode {
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoMode {
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
// to `u32` there
let size: (u32, u32) = self.size().into();
let other_size: (u32, u32) = other.size().into();
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
.then(
self.refresh_rate()
.cmp(&other.refresh_rate())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
}
impl VideoMode {
/// Returns the resolution of this video mode.
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
self.video_mode.size()
}
/// Returns the bit depth of this video mode, as in how many bits you have
@@ -73,15 +70,37 @@ impl VideoMode {
///
/// - **Wayland:** Always returns 32.
/// - **iOS:** Always returns 32.
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode. **Note**: the returned
/// refresh rate is an integer approximation, and you shouldn't rely on this
/// value to be exact.
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
self.video_mode.refresh_rate()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
self.video_mode.monitor()
}
}
impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} @ {} Hz ({} bpp)",
self.size().width,
self.size().height,
self.refresh_rate(),
self.bit_depth()
)
}
}
@@ -90,7 +109,7 @@ impl VideoMode {
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
///
/// [`Window`]: ../window/struct.Window.html
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle,
}
@@ -99,12 +118,20 @@ impl MonitorHandle {
/// Returns a human-readable name of the monitor.
///
/// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// - **Web:** Always returns None
#[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
}
/// Returns the monitor's resolution.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn size(&self) -> PhysicalSize {
self.inner.size()
@@ -112,6 +139,10 @@ impl MonitorHandle {
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn position(&self) -> PhysicalPosition {
self.inner.position()
@@ -125,12 +156,17 @@ impl MonitorHandle {
///
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns 1.0
#[inline]
pub fn hidpi_factor(&self) -> f64 {
self.inner.hidpi_factor()
}
/// Returns all fullscreen video modes supported by this monitor.
///
/// ## Platform-specific
///
/// - **Web:** Always returns an empty iterator
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner.video_modes()

View File

@@ -4,13 +4,13 @@ use std::os::raw::c_void;
use crate::{
event_loop::EventLoop,
monitor::MonitorHandle,
monitor::{MonitorHandle, VideoMode},
window::{Window, WindowBuilder},
};
/// Additional methods on `EventLoop` that are specific to iOS.
/// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the idiom (phone/tablet/tv/etc) for the current device.
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom;
}
@@ -20,32 +20,80 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
}
}
/// Additional methods on `Window` that are specific to iOS.
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Returns a pointer to the `UIWindow` that is used by this window.
/// Returns a pointer to the [`UIWindow`] that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the `UIViewController` that is used by this window.
/// Returns a pointer to the [`UIViewController`] that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window.
/// Returns a pointer to the [`UIView`] that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void;
/// Sets the HiDpi factor used by this window.
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
///
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`.
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for screens showing this `Window`.
/// Sets the valid orientations for the [`Window`].
///
/// On iPhones and iPods upside down portrait is never enabled.
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
///
/// This changes the value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
/// and then calls
/// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
/// The default is to prefer showing the home indicator.
///
/// This changes the value returned by
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn set_prefers_home_indicator_hidden(&self, hidden: bool);
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
///
/// This changes the value returned by
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
/// Sets whether the [`Window`] prefers the status bar hidden.
///
/// The default is to prefer showing the status bar.
///
/// This changes the value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
fn set_prefers_status_bar_hidden(&self, hidden: bool);
}
impl WindowExtIOS for Window {
@@ -73,23 +121,79 @@ impl WindowExtIOS for Window {
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations)
}
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.set_prefers_home_indicator_hidden(hidden)
}
#[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window
.set_preferred_screen_edges_deferring_system_gestures(edges)
}
#[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window.set_prefers_status_bar_hidden(hidden)
}
}
/// Additional methods on `WindowBuilder` that are specific to iOS.
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowBuilderExtIOS {
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided.
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
/// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`.
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to `MonitorHandle::hidpi_factor()`.
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the `Window`.
/// Sets the valid orientations for the [`Window`].
///
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
///
/// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
/// The default is to prefer showing the home indicator.
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_preferred_screen_edges_deferring_system_gestures(
self,
edges: ScreenEdge,
) -> WindowBuilder;
/// Sets whether the [`Window`] prefers the status bar hidden.
///
/// The default is to prefer showing the status bar.
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
}
impl WindowBuilderExtIOS for WindowBuilder {
@@ -110,12 +214,41 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.valid_orientations = valid_orientations;
self
}
#[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
self.platform_specific.prefers_home_indicator_hidden = hidden;
self
}
#[inline]
fn with_preferred_screen_edges_deferring_system_gestures(
mut self,
edges: ScreenEdge,
) -> WindowBuilder {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges;
self
}
#[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to iOS.
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
pub trait MonitorHandleExtIOS {
/// Returns a pointer to the `UIScreen` that is used by this monitor.
/// Returns a pointer to the [`UIScreen`] that is used by this monitor.
///
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoMode`] for this monitor.
///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoMode;
}
impl MonitorHandleExtIOS for MonitorHandle {
@@ -123,9 +256,14 @@ impl MonitorHandleExtIOS for MonitorHandle {
fn ui_screen(&self) -> *mut c_void {
self.inner.ui_screen() as _
}
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
self.inner.preferred_video_mode()
}
}
/// Valid orientations for a particular `Window`.
/// Valid orientations for a particular [`Window`].
#[derive(Clone, Copy, Debug)]
pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone
@@ -161,3 +299,19 @@ pub enum Idiom {
TV,
CarPlay,
}
bitflags! {
/// The [edges] of a screen.
///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
#[derive(Default)]
pub struct ScreenEdge: u8 {
const NONE = 0;
const TOP = 1 << 0;
const LEFT = 1 << 1;
const BOTTOM = 1 << 2;
const RIGHT = 1 << 3;
const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
| ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
}
}

View File

@@ -129,6 +129,7 @@ pub trait WindowBuilderExtMacOS {
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
}
impl WindowBuilderExtMacOS for WindowBuilder {
@@ -182,6 +183,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to MacOS.

View File

@@ -7,6 +7,7 @@
//! - `macos`
//! - `unix`
//! - `windows`
//! - `web`
//!
//! And the following platform-specific module:
//!
@@ -21,3 +22,4 @@ pub mod unix;
pub mod windows;
pub mod desktop;
pub mod web;

View File

@@ -6,14 +6,15 @@ use smithay_client_toolkit::window::{ButtonState, Theme};
use crate::{
dpi::LogicalSize,
event_loop::EventLoop,
event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::platform_impl::{
x11::{ffi::XVisualInfo, XConnection},
EventLoop as LinuxEventLoop, Window as LinuxWindow,
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
// TODO: stupid hack so that glutin can do its work
@@ -90,83 +91,146 @@ impl Theme for WaylandThemeObject {
}
}
/// Additional methods on `EventLoop` that are specific to Unix.
pub trait EventLoopExtUnix {
/// Builds a new `EventLoops` that is forced to use X11.
fn new_x11() -> Result<Self, XNotSupported>
where
Self: Sized;
/// Builds a new `EventLoop` that is forced to use Wayland.
fn new_wayland() -> Self
where
Self: Sized;
/// True if the `EventLoop` uses Wayland.
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland.
fn is_wayland(&self) -> bool;
/// True if the `EventLoop` uses X11.
///
/// True if the `EventLoopWindowTarget` uses X11.
fn is_x11(&self) -> bool;
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`.
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// `EventLoopWindowTarget`.
///
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `EventLoop` is destroyed.
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopExtUnix for EventLoop<T> {
#[inline]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11().map(|ev| EventLoop {
event_loop: ev,
_marker: ::std::marker::PhantomData,
})
}
#[inline]
fn new_wayland() -> Self {
EventLoop {
event_loop: match LinuxEventLoop::new_wayland() {
Ok(e) => e,
Err(_) => panic!(), // TODO: propagate
},
_marker: ::std::marker::PhantomData,
}
}
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
self.p.is_wayland()
}
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
!self.p.is_wayland()
}
#[inline]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.event_loop {
LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()),
match self.p {
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.event_loop {
LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _),
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
}
_ => None,
}
}
}
/// Additional methods on `EventLoop` that are specific to Unix.
pub trait EventLoopExtUnix {
/// Builds a new `EventLoop` that is forced to use X11.
///
/// # Panics
///
/// If called outside the main thread. To initialize an X11 event loop outside
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
fn new_x11() -> Result<Self, XNotSupported>
where
Self: Sized;
/// Builds a new `EventLoop` that is forced to use Wayland.
///
/// # Panics
///
/// If called outside the main thread. To initialize a Wayland event loop outside
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
fn new_wayland() -> Self
where
Self: Sized;
/// Builds a new `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_any_thread() -> Self
where
Self: Sized;
/// Builds a new X11 `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_x11_any_thread() -> Result<Self, XNotSupported>
where
Self: Sized;
/// Builds a new Wayland `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_wayland_any_thread() -> Self
where
Self: Sized;
}
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
EventLoop {
event_loop,
_marker: std::marker::PhantomData,
}
}
impl<T> EventLoopExtUnix for EventLoop<T> {
#[inline]
fn new_any_thread() -> Self {
wrap_ev(LinuxEventLoop::new_any_thread())
}
#[inline]
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
}
#[inline]
fn new_wayland_any_thread() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland_any_thread()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
}
#[inline]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11().map(wrap_ev)
}
#[inline]
fn new_wayland() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
}
}
/// Additional methods on `Window` that are specific to Unix.
pub trait WindowExtUnix {
/// Returns the ID of the `Window` xlib object that is used by this window.
@@ -258,17 +322,17 @@ impl WindowExtUnix for Window {
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
_ => None,
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
}
}
#[inline]
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
_ => None,
}
}
@@ -304,82 +368,82 @@ impl WindowExtUnix for Window {
/// Additional methods on `WindowBuilder` that are specific to Unix.
pub trait WindowBuilderExtUnix {
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
fn with_class(self, class: String, instance: String) -> WindowBuilder;
fn with_class(self, class: String, instance: String) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
fn with_resize_increments(self, increments: LogicalSize) -> Self;
/// Build window with base size hint. Only implemented on X11.
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
fn with_base_size(self, base_size: LogicalSize) -> Self;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland.
///
/// 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_app_id(self, app_id: String) -> WindowBuilder;
fn with_app_id(self, app_id: String) -> Self;
}
impl WindowBuilderExtUnix for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> WindowBuilder {
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
self
}
#[inline]
fn with_x11_screen(mut self, screen_id: i32) -> WindowBuilder {
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_class(mut self, instance: String, class: String) -> WindowBuilder {
fn with_class(mut self, instance: String, class: String) -> Self {
self.platform_specific.class = Some((instance, class));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
self.platform_specific.x11_window_type = x11_window_type;
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
fn with_resize_increments(mut self, increments: LogicalSize) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_size: LogicalSize) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> Self {
self.platform_specific.app_id = Some(app_id);
self
}

22
src/platform/web.rs Normal file
View File

@@ -0,0 +1,22 @@
#![cfg(target_arch = "wasm32")]
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
//! Window.
#[cfg(feature = "stdweb")]
use stdweb::web::html_element::CanvasElement;
#[cfg(feature = "stdweb")]
pub trait WindowExtStdweb {
fn canvas(&self) -> CanvasElement;
}
#[cfg(feature = "web-sys")]
use web_sys::HtmlCanvasElement;
#[cfg(feature = "web-sys")]
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
}

View File

@@ -15,18 +15,52 @@ use crate::{
/// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopExtWindows {
/// Creates an event loop off of the main thread.
///
/// # `Window` caveats
///
/// Note that any `Window` created on the new thread will be destroyed when the thread
/// terminates. Attempting to use a `Window` after its parent thread terminates has
/// unspecified, although explicitly not undefined, behavior.
fn new_any_thread() -> Self
where
Self: Sized;
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
/// undesirable, you can create an `EventLoop` using this function instead.
fn new_dpi_unaware() -> Self
where
Self: Sized;
/// Creates a DPI-unaware event loop off of the main thread.
///
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
fn new_dpi_unaware_any_thread() -> Self
where
Self: Sized;
}
impl<T> EventLoopExtWindows for EventLoop<T> {
#[inline]
fn new_any_thread() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_any_thread(),
_marker: ::std::marker::PhantomData,
}
}
#[inline]
fn new_dpi_unaware() -> Self {
EventLoop {
event_loop: WindowsEventLoop::with_dpi_awareness(false),
event_loop: WindowsEventLoop::new_dpi_unaware(),
_marker: ::std::marker::PhantomData,
}
}
#[inline]
fn new_dpi_unaware_any_thread() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
_marker: ::std::marker::PhantomData,
}
}
@@ -34,6 +68,8 @@ impl<T> EventLoopExtWindows for EventLoop<T> {
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
fn hinstance(&self) -> *mut libc::c_void;
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
@@ -44,6 +80,11 @@ pub trait WindowExtWindows {
}
impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> *mut libc::c_void {
self.window.hinstance() as *mut _
}
#[inline]
fn hwnd(&self) -> *mut libc::c_void {
self.window.hwnd() as *mut _

View File

@@ -19,6 +19,7 @@ use crate::{
CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize,
WindowAttributes, WindowEvent, WindowId as RootWindowId,
};
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
use CreationError::OsError;
pub type OsError = std::io::Error;
@@ -75,6 +76,7 @@ impl EventLoop {
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
},
location,
force: None, // TODO
id: motion.pointer_id as u64,
device_id: DEVICE_ID,
}),
@@ -420,6 +422,15 @@ impl Window {
pub fn id(&self) -> WindowId {
WindowId
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = AndroidHandle {
a_native_window: self.native_window,
..WindowsHandle::empty()
};
RawWindowHandle::Android(handle)
}
}
unsafe impl Send for Window {}

View File

@@ -1,363 +0,0 @@
#![allow(dead_code, non_camel_case_types, non_snake_case)]
#[cfg(test)]
use std::mem;
use std::os::raw::{c_char, c_double, c_int, c_long, c_ulong, c_ushort, c_void};
pub type EM_BOOL = c_int;
pub type EM_UTF8 = c_char;
pub type EMSCRIPTEN_RESULT = c_int;
pub const EM_TRUE: EM_BOOL = 1;
pub const EM_FALSE: EM_BOOL = 0;
// values for EMSCRIPTEN_RESULT
pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1;
pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1;
pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2;
pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3;
pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4;
pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5;
pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6;
pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7;
// values for EMSCRIPTEN EVENT
pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1;
pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2;
pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3;
pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4;
pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5;
pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6;
pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7;
pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8;
pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9;
pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10;
pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11;
pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12;
pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13;
pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14;
pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15;
pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16;
pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17;
pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18;
pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19;
pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20;
pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21;
pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22;
pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23;
pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24;
pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25;
pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26;
pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27;
pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28;
pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29;
pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30;
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31;
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32;
pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33;
pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34;
pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35;
pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36;
pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37;
pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38;
pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32;
pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00;
pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01;
pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02;
pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03;
pub type em_callback_func = Option<unsafe extern "C" fn()>;
pub type em_key_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
keyEvent: *const EmscriptenKeyboardEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_mouse_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
mouseEvent: *const EmscriptenMouseEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_pointerlockchange_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_fullscreenchange_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_touch_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
touchEvent: *const EmscriptenTouchEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
#[repr(C)]
pub struct EmscriptenFullscreenChangeEvent {
pub isFullscreen: c_int,
pub fullscreenEnabled: c_int,
pub nodeName: [c_char; 128usize],
pub id: [c_char; 128usize],
pub elementWidth: c_int,
pub elementHeight: c_int,
pub screenWidth: c_int,
pub screenHeight: c_int,
}
#[test]
fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() {
assert_eq!(mem::size_of::<EmscriptenFullscreenChangeEvent>(), 280usize);
assert_eq!(mem::align_of::<EmscriptenFullscreenChangeEvent>(), 4usize);
}
#[repr(C)]
#[derive(Debug, Copy)]
pub struct EmscriptenKeyboardEvent {
pub key: [c_char; 32usize],
pub code: [c_char; 32usize],
pub location: c_ulong,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub repeat: c_int,
pub locale: [c_char; 32usize],
pub charValue: [c_char; 32usize],
pub charCode: c_ulong,
pub keyCode: c_ulong,
pub which: c_ulong,
}
#[test]
fn bindgen_test_layout_EmscriptenKeyboardEvent() {
assert_eq!(mem::size_of::<EmscriptenKeyboardEvent>(), 184usize);
assert_eq!(mem::align_of::<EmscriptenKeyboardEvent>(), 8usize);
}
impl Clone for EmscriptenKeyboardEvent {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenMouseEvent {
pub timestamp: f64,
pub screenX: c_long,
pub screenY: c_long,
pub clientX: c_long,
pub clientY: c_long,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub button: c_ushort,
pub buttons: c_ushort,
pub movementX: c_long,
pub movementY: c_long,
pub targetX: c_long,
pub targetY: c_long,
pub canvasX: c_long,
pub canvasY: c_long,
pub padding: c_long,
}
#[test]
fn bindgen_test_layout_EmscriptenMouseEvent() {
assert_eq!(mem::size_of::<EmscriptenMouseEvent>(), 120usize);
assert_eq!(mem::align_of::<EmscriptenMouseEvent>(), 8usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenTouchPoint {
pub identifier: c_long,
pub screenX: c_long,
pub screenY: c_long,
pub clientX: c_long,
pub clientY: c_long,
pub pageX: c_long,
pub pageY: c_long,
pub isChanged: c_int,
pub onTarget: c_int,
pub targetX: c_long,
pub targetY: c_long,
pub canvasX: c_long,
pub canvasY: c_long,
}
#[test]
fn bindgen_test_layout_EmscriptenTouchPoint() {
assert_eq!(mem::size_of::<EmscriptenTouchPoint>(), 96usize);
assert_eq!(mem::align_of::<EmscriptenTouchPoint>(), 8usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenTouchEvent {
pub numTouches: c_int,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub touches: [EmscriptenTouchPoint; 32usize],
}
#[test]
fn bindgen_test_layout_EmscriptenTouchEvent() {
assert_eq!(mem::size_of::<EmscriptenTouchEvent>(), 3096usize);
assert_eq!(mem::align_of::<EmscriptenTouchEvent>(), 8usize);
}
#[repr(C)]
pub struct EmscriptenPointerlockChangeEvent {
pub isActive: c_int,
pub nodeName: [c_char; 128usize],
pub id: [c_char; 128usize],
}
#[test]
fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() {
assert_eq!(mem::size_of::<EmscriptenPointerlockChangeEvent>(), 260usize);
assert_eq!(mem::align_of::<EmscriptenPointerlockChangeEvent>(), 4usize);
}
extern "C" {
pub fn emscripten_set_canvas_size(width: c_int, height: c_int) -> EMSCRIPTEN_RESULT;
pub fn emscripten_get_canvas_size(
width: *mut c_int,
height: *mut c_int,
is_fullscreen: *mut c_int,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_element_css_size(
target: *const c_char,
width: c_double,
height: c_double,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_get_element_css_size(
target: *const c_char,
width: *mut c_double,
height: *mut c_double,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_request_pointerlock(
target: *const c_char,
deferUntilInEventHandler: EM_BOOL,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT;
pub fn emscripten_request_fullscreen(
target: *const c_char,
deferUntilInEventHandler: EM_BOOL,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_keydown_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_key_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_keyup_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_key_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mousemove_callback(
target: *const c_char,
user_data: *mut c_void,
use_capture: EM_BOOL,
callback: em_mouse_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mousedown_callback(
target: *const c_char,
user_data: *mut c_void,
use_capture: EM_BOOL,
callback: em_mouse_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mouseup_callback(
target: *const c_char,
user_data: *mut c_void,
use_capture: EM_BOOL,
callback: em_mouse_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_hide_mouse();
pub fn emscripten_get_device_pixel_ratio() -> f64;
pub fn emscripten_set_pointerlockchange_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_pointerlockchange_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_fullscreenchange_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_fullscreenchange_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_asm_const(code: *const c_char);
pub fn emscripten_set_main_loop(
func: em_callback_func,
fps: c_int,
simulate_infinite_loop: EM_BOOL,
);
pub fn emscripten_cancel_main_loop();
pub fn emscripten_set_touchstart_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchend_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchmove_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchcancel_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,15 +16,14 @@ use crate::{
};
use crate::platform_impl::platform::{
app_state::AppState,
app_state,
ffi::{
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSOperatingSystemVersion, NSString,
UIApplicationMain, UIUserInterfaceIdiom,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
},
monitor, view, MonitorHandle,
};
@@ -32,13 +31,6 @@ use crate::platform_impl::platform::{
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
capabilities: Capabilities,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
}
pub struct EventLoop<T: 'static> {
@@ -64,18 +56,11 @@ impl<T: 'static> EventLoop<T> {
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
msg_send![process_info, operatingSystemVersion]
};
let capabilities = version.into();
EventLoop {
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
receiver,
sender_to_clone,
capabilities,
},
_marker: PhantomData,
},
@@ -95,7 +80,7 @@ impl<T: 'static> EventLoop<T> {
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
);
AppState::will_launch(Box::new(EventLoopHandler {
app_state::will_launch(Box::new(EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
@@ -142,8 +127,7 @@ pub struct EventLoopProxy<T> {
source: CFRunLoopSourceRef,
}
unsafe impl<T> Send for EventLoopProxy<T> {}
unsafe impl<T> Sync for EventLoopProxy<T> {}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
@@ -163,7 +147,7 @@ impl<T> Drop for EventLoopProxy<T> {
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wakeup the eventloop
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
@@ -171,7 +155,7 @@ impl<T> EventLoopProxy<T> {
let rl = CFRunLoopGetMain();
// we want all the members of context to be zero/null, except one
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = event_loop_proxy_handler;
context.perform = Some(event_loop_proxy_handler);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
@@ -204,15 +188,40 @@ fn setup_control_flow_observers() {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(),
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed will get sent after EventsCleared
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
@@ -221,7 +230,7 @@ fn setup_control_flow_observers() {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(),
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
@@ -229,6 +238,7 @@ fn setup_control_flow_observers() {
}
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
@@ -238,6 +248,17 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
@@ -297,20 +318,3 @@ pub unsafe fn get_idiom() -> Idiom {
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into()
}
pub struct Capabilities {
pub supports_safe_area: bool,
}
impl From<NSOperatingSystemVersion> for Capabilities {
fn from(os_version: NSOperatingSystemVersion) -> Capabilities {
assert!(
os_version.major >= 8,
"`winit` current requires iOS version 8 or greater"
);
let supports_safe_area = os_version.major >= 11;
Capabilities { supports_safe_area }
}
}

View File

@@ -1,10 +1,10 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::{ffi::CString, ops::BitOr, os::raw::*};
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use objc::{runtime::Object, Encode, Encoding};
use crate::platform::ios::{Idiom, ValidOrientations};
use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
pub type id = *mut Object;
pub const nil: id = 0 as id;
@@ -70,6 +70,24 @@ pub enum UITouchPhase {
Cancelled,
}
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
@@ -173,6 +191,51 @@ impl UIInterfaceOrientationMask {
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
unsafe impl Encode for UIRectEdge {
fn encode() -> Encoding {
NSUInteger::encode()
}
}
impl From<ScreenEdge> for UIRectEdge {
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
assert_eq!(
screen_edge.bits() & !ScreenEdge::ALL.bits(),
0,
"invalid `ScreenEdge`"
);
UIRectEdge(screen_edge.bits().into())
}
}
impl Into<ScreenEdge> for UIRectEdge {
fn into(self) -> ScreenEdge {
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
fn encode() -> Encoding {
NSInteger::encode()
}
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
}
#[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
@@ -268,14 +331,14 @@ pub enum CFRunLoopTimerContext {}
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub perform: extern "C" fn(*mut c_void),
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
pub release: Option<extern "C" fn(*const c_void)>,
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub perform: Option<extern "C" fn(*mut c_void)>,
}
pub trait NSString: Sized {

View File

@@ -79,7 +79,7 @@ use std::fmt;
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle,
monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};

View File

@@ -1,18 +1,98 @@
use std::{
collections::{HashSet, VecDeque},
collections::{BTreeSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
app_state,
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
},
};
use crate::platform_impl::platform::ffi::{
id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger,
};
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) screen_mode: id,
pub(crate) monitor: MonitorHandle,
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
monitor: self.monitor.clone(),
}
}
}
impl Drop for VideoMode {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
let () = msg_send![self.screen_mode, release];
}
}
}
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let os_capabilities = app_state::os_capabilities();
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
};
let size: CGSize = msg_send![screen_mode, size];
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
screen_mode: msg_send![screen_mode, retain],
monitor: MonitorHandle::retained_new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Inner {
uiscreen: id,
}
@@ -25,6 +105,7 @@ impl Drop for Inner {
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle {
inner: Inner,
}
@@ -106,9 +187,10 @@ impl MonitorHandle {
impl Inner {
pub fn name(&self) -> Option<String> {
unsafe {
if self.uiscreen == main_uiscreen().uiscreen {
let main = main_uiscreen();
if self.uiscreen == main.uiscreen {
Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen().uiscreen {
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
Some("Mirrored".to_string())
} else {
uiscreens()
@@ -140,22 +222,18 @@ impl Inner {
}
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let mut modes = BTreeSet::new();
unsafe {
let available_modes: id = msg_send![self.uiscreen, availableModes];
let available_mode_count: NSUInteger = msg_send![available_modes, count];
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
let mut modes = HashSet::with_capacity(available_mode_count);
for i in 0..available_mode_count {
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
let size: CGSize = unsafe { msg_send![mode, size] };
modes.insert(VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
});
for i in 0..available_mode_count {
let mode: id = msg_send![available_modes, objectAtIndex: i];
modes.insert(RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
});
}
}
modes.into_iter()
@@ -167,6 +245,15 @@ impl Inner {
pub fn ui_screen(&self) -> id {
self.uiscreen
}
pub fn preferred_video_mode(&self) -> RootVideoMode {
unsafe {
let mode: id = msg_send![self.uiscreen, preferredMode];
RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
}
}
}
}
// requires being run on main thread
@@ -176,8 +263,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle {
}
// requires being run on main thread
unsafe fn mirrored_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen];
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
MonitorHandle::retained_new(uiscreen)
}

View File

@@ -6,18 +6,78 @@ use objc::{
};
use crate::{
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS,
window::{WindowAttributes, WindowId as RootWindowId},
platform_impl::platform::{
app_state::{self, OSCapabilities},
event_loop,
ffi::{
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
UIRectEdge, UITouchPhase, UITouchType,
},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId,
},
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
};
use crate::platform_impl::platform::{
app_state::AppState,
event_loop,
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId,
};
macro_rules! add_property {
(
$decl:ident,
$name:ident: $t:ty,
$setter_name:ident: |$object:ident| $after_set:expr,
$getter_name:ident,
) => {
add_property!(
$decl,
$name: $t,
$setter_name: true, |_, _|{}; |$object| $after_set,
$getter_name,
)
};
(
$decl:ident,
$name:ident: $t:ty,
$setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr,
$getter_name:ident,
) => {
{
const VAR_NAME: &'static str = concat!("_", stringify!($name));
$decl.add_ivar::<$t>(VAR_NAME);
let setter = if $capability {
#[allow(non_snake_case)]
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
unsafe {
$object.set_ivar::<$t>(VAR_NAME, value);
}
$after_set
}
$setter_name
} else {
#[allow(non_snake_case)]
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
unsafe {
$object.set_ivar::<$t>(VAR_NAME, value);
}
$err(&app_state::os_capabilities(), "ignoring")
}
$setter_name
};
#[allow(non_snake_case)]
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
}
$decl.add_method(
sel!($setter_name:),
setter as extern "C" fn(&mut Object, Sel, $t),
);
$decl.add_method(
sel!($getter_name),
$getter_name as extern "C" fn(&Object, Sel) -> $t,
);
}
};
}
// requires main thread
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
@@ -41,7 +101,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
unsafe {
let window: id = msg_send![object, window];
AppState::handle_nonuser_event(Event::WindowEvent {
assert!(!window.is_null());
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
@@ -52,22 +113,143 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
extern "C" fn layout_subviews(object: &Object, _: Sel) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews];
let window: id = msg_send![object, window];
assert!(!window.is_null());
let bounds: CGRect = msg_send![window, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
AppState::handle_nonuser_event(Event::WindowEvent {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
});
}
}
extern "C" fn set_content_scale_factor(
object: &mut Object,
_: Sel,
untrusted_hidpi_factor: CGFloat,
) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews];
let () = msg_send![
super(object, superclass),
setContentScaleFactor: untrusted_hidpi_factor
];
let window: id = msg_send![object, window];
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
// makeKeyAndVisible]` at window creation time (either manually or internally by
// UIKit when the `UIView` is first created), in which case we send no events here
if window.is_null() {
return;
}
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter
let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor];
assert!(
!hidpi_factor.is_nan()
&& hidpi_factor.is_finite()
&& hidpi_factor.is_sign_positive()
&& hidpi_factor > 0.0,
"invalid hidpi_factor set on UIView",
);
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
}
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
unsafe {
let window: id = msg_send![object, window];
assert!(!window.is_null());
let uiscreen: id = msg_send![window, screen];
let touches_enum: id = msg_send![touches, objectEnumerator];
let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force {
let trait_collection: id = msg_send![object, traitCollection];
let touch_capability: UIForceTouchCapability =
msg_send![trait_collection, forceTouchCapability];
// Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available {
let force: CGFloat = msg_send![touch, force];
let max_possible_force: CGFloat =
msg_send![touch, maximumPossibleForce];
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
let angle: CGFloat = msg_send![touch, altitudeAngle];
Some(angle as _)
} else {
None
};
Some(Force::Calibrated {
force: force as _,
max_possible_force: max_possible_force as _,
altitude_angle,
})
} else {
None
}
} else {
None
};
let touch_id = touch as u64;
let phase: UITouchPhase = msg_send![touch, phase];
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
force,
phase,
}),
});
}
app_state::handle_nonuser_events(touch_events);
}
}
@@ -82,184 +264,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
sel!(layoutSubviews),
layout_subviews as extern "C" fn(&Object, Sel),
);
decl.register()
})
}
// requires main thread
unsafe fn get_view_controller_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiviewcontroller_class = class!(UIViewController);
extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
unsafe {
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
}
extern "C" fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
unsafe { *object.get_ivar::<BOOL>("_prefers_status_bar_hidden") }
}
extern "C" fn set_supported_orientations(
object: &mut Object,
_: Sel,
orientations: UIInterfaceOrientationMask,
) {
unsafe {
object.set_ivar::<UIInterfaceOrientationMask>(
"_supported_orientations",
orientations,
);
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
}
extern "C" fn supported_orientations(
object: &Object,
_: Sel,
) -> UIInterfaceOrientationMask {
unsafe { *object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations") }
}
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES
}
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`");
decl.add_ivar::<BOOL>("_prefers_status_bar_hidden");
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
decl.add_method(
sel!(setPrefersStatusBarHidden:),
set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL),
);
decl.add_method(
sel!(prefersStatusBarHidden),
prefers_status_bar_hidden as extern "C" fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(setSupportedInterfaceOrientations:),
set_supported_orientations
as extern "C" fn(&mut Object, Sel, UIInterfaceOrientationMask),
);
decl.add_method(
sel!(supportedInterfaceOrientations),
supported_orientations as extern "C" fn(&Object, Sel) -> UIInterfaceOrientationMask,
);
decl.add_method(
sel!(shouldAutorotate),
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
);
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
unsafe fn get_window_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiwindow_class = class!(UIWindow);
extern "C" fn become_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern "C" fn resign_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
unsafe {
let uiscreen = msg_send![object, screen];
let touches_enum: id = msg_send![touches, objectEnumerator];
let mut touch_events = Vec::new();
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let touch_id = touch as u64;
let phase: UITouchPhase = msg_send![touch, phase];
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
phase,
}),
});
}
AppState::handle_nonuser_events(touch_events);
}
}
extern "C" fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) {
unsafe {
let () = msg_send![
super(object, class!(UIWindow)),
setContentScaleFactor: hidpi_factor
];
let view_controller: id = msg_send![object, rootViewController];
let view: id = msg_send![view_controller, view];
let () = msg_send![view, setContentScaleFactor: hidpi_factor];
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![object, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
};
AppState::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Resized(size),
})),
);
}
}
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
.expect("Failed to declare class `WinitUIWindow`");
decl.add_method(
sel!(becomeKeyWindow),
become_key_window as extern "C" fn(&Object, Sel),
);
decl.add_method(
sel!(resignKeyWindow),
resign_key_window as extern "C" fn(&Object, Sel),
sel!(setContentScaleFactor:),
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
);
decl.add_method(
@@ -279,9 +286,114 @@ unsafe fn get_window_class() -> &'static Class {
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
);
decl.register()
})
}
// requires main thread
unsafe fn get_view_controller_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let os_capabilities = app_state::os_capabilities();
let uiviewcontroller_class = class!(UIViewController);
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES
}
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`");
decl.add_method(
sel!(setContentScaleFactor:),
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
sel!(shouldAutorotate),
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
);
add_property! {
decl,
prefers_status_bar_hidden: BOOL,
setPrefersStatusBarHidden: |object| {
unsafe {
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
},
prefersStatusBarHidden,
}
add_property! {
decl,
prefers_home_indicator_auto_hidden: BOOL,
setPrefersHomeIndicatorAutoHidden:
os_capabilities.home_indicator_hidden,
OSCapabilities::home_indicator_hidden_err_msg;
|object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
}
},
prefersHomeIndicatorAutoHidden,
}
add_property! {
decl,
supported_orientations: UIInterfaceOrientationMask,
setSupportedInterfaceOrientations: |object| {
unsafe {
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
},
supportedInterfaceOrientations,
}
add_property! {
decl,
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
setPreferredScreenEdgesDeferringSystemGestures:
os_capabilities.defer_system_gestures,
OSCapabilities::defer_system_gestures_err_msg;
|object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
},
preferredScreenEdgesDeferringSystemGestures,
}
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
unsafe fn get_window_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiwindow_class = class!(UIWindow);
extern "C" fn become_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern "C" fn resign_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
.expect("Failed to declare class `WinitUIWindow`");
decl.add_method(
sel!(becomeKeyWindow),
become_key_window as extern "C" fn(&Object, Sel),
);
decl.add_method(
sel!(resignKeyWindow),
resign_key_window as extern "C" fn(&Object, Sel),
);
CLASS = Some(decl.register());
@@ -302,13 +414,16 @@ pub unsafe fn create_view(
let view: id = msg_send![view, initWithFrame: frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let () = msg_send![view, setMultipleTouchEnabled: YES];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat];
}
view
}
// requires main thread
pub unsafe fn create_view_controller(
window_attributes: &WindowAttributes,
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id,
) -> id {
@@ -324,16 +439,24 @@ pub unsafe fn create_view_controller(
!view_controller.is_null(),
"Failed to initialize `UIViewController` instance"
);
let status_bar_hidden = if window_attributes.decorations {
NO
} else {
let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
YES
} else {
NO
};
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
platform_attributes.valid_orientations,
idiom,
);
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
YES
} else {
NO
};
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();
let () = msg_send![
view_controller,
setPrefersStatusBarHidden: status_bar_hidden
@@ -342,6 +465,14 @@ pub unsafe fn create_view_controller(
view_controller,
setSupportedInterfaceOrientations: supported_orientations
];
let () = msg_send![
view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
let () = msg_send![
view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
let () = msg_send![view_controller, setView: view];
view_controller
}
@@ -349,7 +480,7 @@ pub unsafe fn create_view_controller(
// requires main thread
pub unsafe fn create_window(
window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: id,
) -> id {
@@ -363,11 +494,14 @@ pub unsafe fn create_window(
"Failed to initialize `UIWindow` instance"
);
let () = msg_send![window, setRootViewController: view_controller];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
}
if let &Some(ref monitor) = &window_attributes.fullscreen {
let () = msg_send![window, setScreen:monitor.ui_screen()];
match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let uiscreen = video_mode.monitor().ui_screen() as id;
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()],
None => (),
}
window
@@ -376,17 +510,17 @@ pub unsafe fn create_window(
pub fn create_delegate_class() {
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
unsafe {
AppState::did_finish_launching();
app_state::did_finish_launching();
}
YES
}
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
unsafe { AppState::handle_nonuser_event(Event::Resumed) }
unsafe { app_state::handle_nonuser_event(Event::Resumed) }
}
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
unsafe { AppState::handle_nonuser_event(Event::Suspended) }
unsafe { app_state::handle_nonuser_event(Event::Suspended) }
}
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
@@ -411,8 +545,8 @@ pub fn create_delegate_class() {
});
}
}
AppState::handle_nonuser_events(events);
AppState::terminated();
app_state::handle_nonuser_events(events);
app_state::terminated();
}
}

View File

@@ -1,30 +1,34 @@
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use objc::runtime::{Class, Object, NO, YES};
use objc::runtime::{Class, Object, BOOL, NO, YES};
use crate::{
dpi::{self, LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ValidOrientations},
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state::AppState,
event_loop,
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
app_state, event_loop,
ffi::{
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge, UIScreenOverscanCompensation,
},
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{CursorIcon, WindowAttributes},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
};
pub struct Inner {
pub window: id,
pub view_controller: id,
pub view: id,
supports_safe_area: bool,
gl_or_metal_backed: bool,
}
impl Drop for Inner {
@@ -55,7 +59,19 @@ impl Inner {
pub fn request_redraw(&self) {
unsafe {
let () = msg_send![self.view, setNeedsDisplay];
if self.gl_or_metal_backed {
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window);
} else {
let () = msg_send![self.view, setNeedsDisplay];
}
}
}
@@ -63,8 +79,8 @@ impl Inner {
unsafe {
let safe_area = self.safe_area_screen_space();
Ok(LogicalPosition {
x: safe_area.origin.x,
y: safe_area.origin.y,
x: safe_area.origin.x as _,
y: safe_area.origin.y as _,
})
}
}
@@ -73,8 +89,8 @@ impl Inner {
unsafe {
let screen_frame = self.screen_frame();
Ok(LogicalPosition {
x: screen_frame.origin.x,
y: screen_frame.origin.y,
x: screen_frame.origin.x as _,
y: screen_frame.origin.y as _,
})
}
}
@@ -98,8 +114,8 @@ impl Inner {
unsafe {
let safe_area = self.safe_area_screen_space();
LogicalSize {
width: safe_area.size.width,
height: safe_area.size.height,
width: safe_area.size.width as _,
height: safe_area.size.height as _,
}
}
}
@@ -108,8 +124,8 @@ impl Inner {
unsafe {
let screen_frame = self.screen_frame();
LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
}
}
}
@@ -157,26 +173,41 @@ impl Inner {
warn!("`Window::set_maximized` is ignored on iOS")
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe {
match monitor {
Some(monitor) => {
let uiscreen = monitor.ui_screen() as id;
let current: id = msg_send![self.window, screen];
let bounds: CGRect = msg_send![uiscreen, bounds];
// this is pretty slow on iOS, so avoid doing it if we can
if uiscreen != current {
let () = msg_send![self.window, setScreen: uiscreen];
}
let () = msg_send![self.window, setFrame: bounds];
let uiscreen = match monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
uiscreen
}
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"),
Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
// this is pretty slow on iOS, so avoid doing it if we can
let current: id = msg_send![self.window, screen];
if uiscreen != current {
let () = msg_send![self.window, setScreen: uiscreen];
}
let bounds: CGRect = msg_send![uiscreen, bounds];
let () = msg_send![self.window, setFrame: bounds];
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
let () = msg_send![
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
}
}
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor();
let uiscreen = monitor.inner.ui_screen();
@@ -189,21 +220,15 @@ impl Inner {
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(monitor)
Some(Fullscreen::Borderless(monitor))
} else {
None
}
}
}
pub fn set_decorations(&self, decorations: bool) {
unsafe {
let status_bar_hidden = if decorations { NO } else { YES };
let () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
}
pub fn set_decorations(&self, _decorations: bool) {
warn!("`Window::set_decorations` is ignored on iOS")
}
pub fn set_always_on_top(&self, _always_on_top: bool) {
@@ -238,6 +263,16 @@ impl Inner {
pub fn id(&self) -> WindowId {
self.window.into()
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = IOSHandle {
ui_window: self.window as _,
ui_view: self.view as _,
ui_view_controller: self.view_controller as _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(handle)
}
}
pub struct Window {
@@ -277,7 +312,7 @@ impl DerefMut for Window {
impl Window {
pub fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
_event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
@@ -293,25 +328,38 @@ impl Window {
// TODO: transparency, visible
unsafe {
let screen = window_attributes
.fullscreen
.as_ref()
.map(|screen| screen.ui_screen() as _)
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen());
let screen = match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
video_mode.video_mode.monitor.ui_screen() as id
}
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
None => monitor::main_uiscreen().ui_screen(),
};
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size {
Some(dim) => CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: dim.width,
height: dim.height,
width: dim.width as _,
height: dim.height as _,
},
},
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
let gl_or_metal_backed = {
let view_class: id = msg_send![view, class];
let layer_class: id = msg_send![view_class, layerClass];
let is_metal: BOOL =
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal == YES || is_gl == YES
};
let view_controller =
view::create_view_controller(&window_attributes, &platform_attributes, view);
let window = view::create_window(
@@ -321,17 +369,41 @@ impl Window {
view_controller,
);
let supports_safe_area = event_loop.capabilities().supports_safe_area;
let result = Window {
inner: Inner {
window,
view_controller,
view,
supports_safe_area,
gl_or_metal_backed,
},
};
AppState::set_key_window(window);
app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor];
if hidpi_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![view, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
Ok(result)
}
}
@@ -370,6 +442,36 @@ impl Inner {
msg_send![
self.view_controller,
setSupportedInterfaceOrientations: supported_orientations
]
}
}
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe {
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
let () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
}
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
unsafe {
let () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
}
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
unsafe {
let status_bar_hidden = if hidden { YES } else { NO };
let () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
}
}
@@ -406,7 +508,7 @@ impl Inner {
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds: CGRect = msg_send![self.window, bounds];
if self.supports_safe_area {
if app_state::os_capabilities().safe_area {
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
let safe_bounds = CGRect {
origin: CGPoint {
@@ -494,6 +596,9 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
@@ -502,6 +607,9 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
hidpi_factor: None,
valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,
preferred_screen_edges_deferring_system_gestures: Default::default(),
}
}
}

View File

@@ -1,15 +0,0 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![allow(dead_code)]
use std::os::raw::{c_char, c_int, c_void};
pub const RTLD_LAZY: c_int = 0x001;
pub const RTLD_NOW: c_int = 0x002;
#[link(name = "dl")]
extern "C" {
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
pub fn dlerror() -> *mut c_char;
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
pub fn dlclose(handle: *mut c_void) -> c_int;
}

View File

@@ -1,23 +1,25 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem, os::raw::*, sync::Arc};
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
use parking_lot::Mutex;
use raw_window_handle::RawWindowHandle;
use smithay_client_toolkit::reexports::client::ConnectError;
pub use self::x11::XNotSupported;
use self::x11::{ffi::XVisualInfo, XConnection, XError};
use self::x11::{
ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError,
};
use crate::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode},
window::{CursorIcon, WindowAttributes},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
mod dlopen;
pub mod wayland;
pub mod x11;
@@ -30,7 +32,7 @@ pub mod x11;
/// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub visual_infos: Option<XVisualInfo>,
pub screen_id: Option<i32>,
@@ -38,11 +40,27 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>,
pub override_redirect: bool,
pub x11_window_type: x11::util::WindowType,
pub x11_window_types: Vec<XWindowType>,
pub gtk_theme_variant: Option<String>,
pub app_id: Option<String>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
visual_infos: None,
screen_id: None,
resize_increments: None,
base_size: None,
class: None,
override_redirect: false,
x11_window_types: vec![XWindowType::Normal],
gtk_theme_variant: None,
app_id: None,
}
}
}
lazy_static! {
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
@@ -92,7 +110,7 @@ impl DeviceId {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle {
X(x11::MonitorHandle),
Wayland(wayland::MonitorHandle),
@@ -140,7 +158,7 @@ impl MonitorHandle {
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
match self {
MonitorHandle::X(m) => Box::new(m.video_modes()),
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
@@ -148,6 +166,46 @@ impl MonitorHandle {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoMode {
X(x11::VideoMode),
Wayland(wayland::VideoMode),
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
match self {
&VideoMode::X(ref m) => m.size(),
&VideoMode::Wayland(ref m) => m.size(),
}
}
#[inline]
pub fn bit_depth(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.bit_depth(),
&VideoMode::Wayland(ref m) => m.bit_depth(),
}
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.refresh_rate(),
&VideoMode::Wayland(ref m) => m.refresh_rate(),
}
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
match self {
&VideoMode::X(ref m) => m.monitor(),
&VideoMode::Wayland(ref m) => m.monitor(),
}
}
}
impl Window {
#[inline]
pub fn new<T>(
@@ -310,17 +368,15 @@ impl Window {
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
match self {
&Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle {
inner: MonitorHandle::Wayland(monitor_id),
}),
&Window::Wayland(ref w) => w.fullscreen(),
}
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
match self {
&Window::X(ref w) => w.set_fullscreen(monitor),
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
@@ -402,6 +458,13 @@ impl Window {
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
}
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
match self {
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
}
}
unsafe extern "C" fn x_error_callback(
@@ -410,14 +473,16 @@ unsafe extern "C" fn x_error_callback(
) -> c_int {
let xconn_lock = X11_BACKEND.lock();
if let Ok(ref xconn) = *xconn_lock {
let mut buf: [c_char; 1024] = mem::uninitialized();
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr(),
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
);
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
let error = XError {
description: description.into_owned(),
@@ -439,22 +504,38 @@ pub enum EventLoop<T: 'static> {
X(x11::EventLoop<T>),
}
#[derive(Clone)]
pub enum EventLoopProxy<T: 'static> {
X(x11::EventLoopProxy<T>),
Wayland(wayland::EventLoopProxy<T>),
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
match self {
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
}
}
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> {
assert_is_main_thread("new_any_thread");
EventLoop::new_any_thread()
}
pub fn new_any_thread() -> EventLoop<T> {
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
match env_var.as_str() {
"x11" => {
// TODO: propagate
return EventLoop::new_x11().expect("Failed to initialize X11 backend");
return EventLoop::new_x11_any_thread()
.expect("Failed to initialize X11 backend");
}
"wayland" => {
return EventLoop::new_wayland().expect("Failed to initialize Wayland backend");
return EventLoop::new_wayland_any_thread()
.expect("Failed to initialize Wayland backend");
}
_ => panic!(
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
@@ -463,12 +544,12 @@ impl<T: 'static> EventLoop<T> {
}
}
let wayland_err = match EventLoop::new_wayland() {
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
let x11_err = match EventLoop::new_x11() {
let x11_err = match EventLoop::new_x11_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
@@ -481,10 +562,22 @@ impl<T: 'static> EventLoop<T> {
}
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
assert_is_main_thread("new_wayland_any_thread");
EventLoop::new_wayland_any_thread()
}
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
wayland::EventLoop::new().map(EventLoop::Wayland)
}
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
assert_is_main_thread("new_x11_any_thread");
EventLoop::new_x11_any_thread()
}
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
X11_BACKEND
.lock()
.as_ref()
@@ -502,7 +595,7 @@ impl<T: 'static> EventLoop<T> {
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
EventLoop::X(ref evlp) => evlp
EventLoop::X(ref evlp) => get_xtarget(&evlp.target)
.x_connection()
.available_monitors()
.into_iter()
@@ -515,7 +608,9 @@ impl<T: 'static> EventLoop<T> {
pub fn primary_monitor(&self) -> MonitorHandle {
match *self {
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
EventLoop::X(ref evlp) => {
MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor())
}
}
}
@@ -546,14 +641,6 @@ impl<T: 'static> EventLoop<T> {
}
}
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
EventLoop::Wayland(_) => true,
EventLoop::X(_) => false,
}
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
match *self {
EventLoop::Wayland(ref evl) => evl.window_target(),
@@ -576,6 +663,16 @@ pub enum EventLoopWindowTarget<T> {
X(x11::EventLoopWindowTarget<T>),
}
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
EventLoopWindowTarget::Wayland(_) => true,
EventLoopWindowTarget::X(_) => false,
}
}
}
fn sticky_exit_callback<T, F>(
evt: Event<T>,
target: &RootELW<T>,
@@ -595,3 +692,35 @@ fn sticky_exit_callback<T, F>(
// user callback
callback(evt, target, cf)
}
fn assert_is_main_thread(suggested_method: &str) {
if !is_main_thread() {
panic!(
"Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you really, absolutely need to create an \
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
suggested_method
);
}
}
#[cfg(target_os = "linux")]
fn is_main_thread() -> bool {
use libc::{c_long, getpid, syscall, SYS_gettid};
unsafe { syscall(SYS_gettid) == getpid() as c_long }
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
fn is_main_thread() -> bool {
use libc::pthread_main_np;
unsafe { pthread_main_np() == 1 }
}
#[cfg(target_os = "netbsd")]
fn is_main_thread() -> bool {
use libc::_lwp_self;
unsafe { _lwp_self() == 1 }
}

View File

@@ -7,17 +7,29 @@ use std::{
time::Instant,
};
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
};
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
zwp_relative_pointer_v1::ZwpRelativePointerV1,
};
use smithay_client_toolkit::pointer::{AutoPointer, AutoThemer};
use smithay_client_toolkit::reexports::client::protocol::{
wl_compositor::WlCompositor, wl_shm::WlShm, wl_surface::WlSurface,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event::ModifiersState,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
monitor::VideoMode,
platform_impl::platform::sticky_exit_callback,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
VideoMode as PlatformVideoMode,
},
window::CursorIcon,
};
use super::{window::WindowStore, DeviceId, WindowId};
@@ -66,16 +78,166 @@ impl<T> WindowEventsSink<T> {
}
}
pub struct CursorManager {
pointer_constraints_proxy: Arc<Mutex<Option<ZwpPointerConstraintsV1>>>,
auto_themer: Option<AutoThemer>,
pointers: Vec<AutoPointer>,
locked_pointers: Vec<ZwpLockedPointerV1>,
cursor_visible: bool,
current_cursor: CursorIcon,
}
impl CursorManager {
fn new(constraints: Arc<Mutex<Option<ZwpPointerConstraintsV1>>>) -> CursorManager {
CursorManager {
pointer_constraints_proxy: constraints,
auto_themer: None,
pointers: Vec::new(),
locked_pointers: Vec::new(),
cursor_visible: true,
current_cursor: CursorIcon::default(),
}
}
fn register_pointer(&mut self, pointer: wl_pointer::WlPointer) {
let auto_themer = self
.auto_themer
.as_ref()
.expect("AutoThemer not initialized. Server did not advertise shm or compositor?");
self.pointers.push(auto_themer.theme_pointer(pointer));
}
fn set_auto_themer(&mut self, auto_themer: AutoThemer) {
self.auto_themer = Some(auto_themer);
}
pub fn set_cursor_visible(&mut self, visible: bool) {
if !visible {
for pointer in self.pointers.iter() {
(**pointer).set_cursor(0, None, 0, 0);
}
} else {
self.set_cursor_icon_impl(self.current_cursor);
}
self.cursor_visible = visible;
}
/// A helper function to restore cursor styles on PtrEvent::Enter.
pub fn reload_cursor_style(&mut self) {
if !self.cursor_visible {
self.set_cursor_visible(false);
} else {
self.set_cursor_icon_impl(self.current_cursor);
}
}
pub fn set_cursor_icon(&mut self, cursor: CursorIcon) {
if cursor != self.current_cursor {
self.current_cursor = cursor;
if self.cursor_visible {
self.set_cursor_icon_impl(cursor);
}
}
}
fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) {
let cursor = match cursor {
CursorIcon::Alias => "link",
CursorIcon::Arrow => "arrow",
CursorIcon::Cell => "plus",
CursorIcon::Copy => "copy",
CursorIcon::Crosshair => "crosshair",
CursorIcon::Default => "left_ptr",
CursorIcon::Hand => "hand",
CursorIcon::Help => "question_arrow",
CursorIcon::Move => "move",
CursorIcon::Grab => "grab",
CursorIcon::Grabbing => "grabbing",
CursorIcon::Progress => "progress",
CursorIcon::AllScroll => "all-scroll",
CursorIcon::ContextMenu => "context-menu",
CursorIcon::NoDrop => "no-drop",
CursorIcon::NotAllowed => "crossed_circle",
// Resize cursors
CursorIcon::EResize => "right_side",
CursorIcon::NResize => "top_side",
CursorIcon::NeResize => "top_right_corner",
CursorIcon::NwResize => "top_left_corner",
CursorIcon::SResize => "bottom_side",
CursorIcon::SeResize => "bottom_right_corner",
CursorIcon::SwResize => "bottom_left_corner",
CursorIcon::WResize => "left_side",
CursorIcon::EwResize => "h_double_arrow",
CursorIcon::NsResize => "v_double_arrow",
CursorIcon::NwseResize => "bd_double_arrow",
CursorIcon::NeswResize => "fd_double_arrow",
CursorIcon::ColResize => "h_double_arrow",
CursorIcon::RowResize => "v_double_arrow",
CursorIcon::Text => "text",
CursorIcon::VerticalText => "vertical-text",
CursorIcon::Wait => "watch",
CursorIcon::ZoomIn => "zoom-in",
CursorIcon::ZoomOut => "zoom-out",
};
for pointer in self.pointers.iter() {
// Ignore erros, since we don't want to fail hard in case we can't find a proper cursor
// in a given theme.
let _ = pointer.set_cursor(cursor, None);
}
}
// This function can only be called from a thread on which `pointer_constraints_proxy` event
// queue is located, so calling it directly from a Window doesn't work well, in case
// you've sent your window to another thread, so we need to pass cursor grab updates to
// the event loop and call this function from there.
fn grab_pointer(&mut self, surface: Option<&WlSurface>) {
for locked_pointer in self.locked_pointers.drain(..) {
locked_pointer.destroy();
}
if let Some(surface) = surface {
for pointer in self.pointers.iter() {
let locked_pointer = self
.pointer_constraints_proxy
.try_lock()
.unwrap()
.as_ref()
.and_then(|pointer_constraints| {
super::pointer::implement_locked_pointer(
surface,
&**pointer,
pointer_constraints,
)
.ok()
});
if let Some(locked_pointer) = locked_pointer {
self.locked_pointers.push(locked_pointer);
}
}
}
}
}
pub struct EventLoop<T: 'static> {
// The loop
inner_loop: ::calloop::EventLoop<()>,
// The wayland display
pub display: Arc<Display>,
// the output manager
// The output manager
pub outputs: OutputMgr,
// our sink, shared with some handlers, buffering the events
// Our sink, shared with some handlers, buffering the events
sink: Arc<Mutex<WindowEventsSink<T>>>,
pending_user_events: Rc<RefCell<VecDeque<T>>>,
// The cursor manager
cursor_manager: Arc<Mutex<CursorManager>>,
// Utility for grabbing the cursor and changing visibility
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
user_sender: ::calloop::channel::Sender<T>,
_kbd_source: ::calloop::Source<
@@ -87,19 +249,20 @@ pub struct EventLoop<T: 'static> {
// A handle that can be sent across threads and used to wake up the `EventLoop`.
//
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> {
user_sender: ::calloop::channel::Sender<T>,
user_sender: calloop::channel::Sender<T>,
}
pub struct EventLoopWindowTarget<T> {
// the event queue
// The event queue
pub evq: RefCell<::calloop::Source<EventQueue>>,
// The window store
pub store: Arc<Mutex<WindowStore>>,
// the env
// The cursor manager
pub cursor_manager: Arc<Mutex<CursorManager>>,
// The env
pub env: Environment,
// a cleanup switch to prune dead windows
// A cleanup switch to prune dead windows
pub cleanup_needed: Arc<Mutex<bool>>,
// The wayland display
pub display: Arc<Display>,
@@ -108,6 +271,14 @@ pub struct EventLoopWindowTarget<T> {
_marker: ::std::marker::PhantomData<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_sender: self.user_sender.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed)
@@ -136,14 +307,24 @@ impl<T: 'static> EventLoop<T> {
})
.unwrap();
let pointer_constraints_proxy = Arc::new(Mutex::new(None));
let mut seat_manager = SeatManager {
sink: sink.clone(),
relative_pointer_manager_proxy: None,
relative_pointer_manager_proxy: Rc::new(RefCell::new(None)),
pointer_constraints_proxy: pointer_constraints_proxy.clone(),
store: store.clone(),
seats: seats.clone(),
kbd_sender,
cursor_manager: Arc::new(Mutex::new(CursorManager::new(pointer_constraints_proxy))),
};
let cursor_manager = seat_manager.cursor_manager.clone();
let cursor_manager_clone = cursor_manager.clone();
let shm_cell = Rc::new(RefCell::new(None));
let compositor_cell = Rc::new(RefCell::new(None));
let env = Environment::from_display_with_cb(
&display,
&mut event_queue,
@@ -154,14 +335,53 @@ impl<T: 'static> EventLoop<T> {
version,
} => {
if interface == "zwp_relative_pointer_manager_v1" {
seat_manager.relative_pointer_manager_proxy = Some(
registry
.bind(version, id, move |pointer_manager| {
pointer_manager.implement_closure(|_, _| (), ())
})
.unwrap(),
)
let relative_pointer_manager_proxy = registry
.bind(version, id, move |pointer_manager| {
pointer_manager.implement_closure(|_, _| (), ())
})
.unwrap();
*seat_manager
.relative_pointer_manager_proxy
.try_borrow_mut()
.unwrap() = Some(relative_pointer_manager_proxy);
}
if interface == "zwp_pointer_constraints_v1" {
let pointer_constraints_proxy = registry
.bind(version, id, move |pointer_constraints| {
pointer_constraints.implement_closure(|_, _| (), ())
})
.unwrap();
*seat_manager.pointer_constraints_proxy.lock().unwrap() =
Some(pointer_constraints_proxy);
}
if interface == "wl_shm" {
let shm: WlShm = registry
.bind(version, id, move |shm| shm.implement_closure(|_, _| (), ()))
.unwrap();
(*shm_cell.borrow_mut()) = Some(shm);
}
if interface == "wl_compositor" {
let compositor: WlCompositor = registry
.bind(version, id, move |compositor| {
compositor.implement_closure(|_, _| (), ())
})
.unwrap();
(*compositor_cell.borrow_mut()) = Some(compositor);
}
if compositor_cell.borrow().is_some() && shm_cell.borrow().is_some() {
let compositor = compositor_cell.borrow_mut().take().unwrap();
let shm = shm_cell.borrow_mut().take().unwrap();
let auto_themer = AutoThemer::init(None, compositor, &shm);
cursor_manager_clone
.lock()
.unwrap()
.set_auto_themer(auto_themer);
}
if interface == "wl_seat" {
seat_manager.add_seat(id, version, registry)
}
@@ -194,6 +414,7 @@ impl<T: 'static> EventLoop<T> {
})
.unwrap();
let cursor_manager_clone = cursor_manager.clone();
Ok(EventLoop {
inner_loop,
sink,
@@ -202,12 +423,14 @@ impl<T: 'static> EventLoop<T> {
outputs: env.outputs.clone(),
_user_source: user_source,
user_sender,
cursor_manager,
_kbd_source: kbd_source,
window_target: RootELW {
p: crate::platform_impl::EventLoopWindowTarget::Wayland(EventLoopWindowTarget {
evq: RefCell::new(source),
store,
env,
cursor_manager: cursor_manager_clone,
cleanup_needed: Arc::new(Mutex::new(false)),
seats,
display,
@@ -229,7 +452,7 @@ impl<T: 'static> EventLoop<T> {
F: 'static + FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
self.run_return(callback);
::std::process::exit(0);
std::process::exit(0);
}
pub fn run_return<F>(&mut self, mut callback: F)
@@ -304,6 +527,25 @@ impl<T: 'static> EventLoop<T> {
// send pending events to the server
self.display.flush().expect("Wayland connection lost.");
// During the run of the user callback, some other code monitoring and reading the
// wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the wayland socket, to avoid getting stuck.
let instant_wakeup = {
let window_target = match self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
_ => unreachable!(),
};
let dispatched = window_target
.evq
.borrow_mut()
.dispatch_pending()
.expect("Wayland connection lost.");
dispatched > 0
};
match control_flow {
ControlFlow::Exit => break,
ControlFlow::Poll => {
@@ -318,7 +560,12 @@ impl<T: 'static> EventLoop<T> {
);
}
ControlFlow::Wait => {
self.inner_loop.dispatch(None, &mut ()).unwrap();
let timeout = if instant_wakeup {
Some(::std::time::Duration::from_millis(0))
} else {
None
};
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
callback(
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
start: Instant::now(),
@@ -331,7 +578,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// compute the blocking duration
let duration = if deadline > start {
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
::std::time::Duration::from_millis(0)
@@ -380,15 +627,17 @@ impl<T: 'static> EventLoop<T> {
available_monitors(&self.outputs)
}
pub fn display(&self) -> &Display {
&*self.display
}
pub fn window_target(&self) -> &RootELW<T> {
&self.window_target
}
}
impl<T> EventLoopWindowTarget<T> {
pub fn display(&self) -> &Display {
&*self.display
}
}
/*
* Private EventLoop Internals
*/
@@ -413,7 +662,16 @@ impl<T> EventLoop<T> {
}
// process pending resize/refresh
window_target.store.lock().unwrap().for_each(
|newsize, size, new_dpi, refresh, frame_refresh, closed, wid, frame| {
|newsize,
size,
new_dpi,
refresh,
frame_refresh,
closed,
grab_cursor,
surface,
wid,
frame| {
if let Some(frame) = frame {
if let Some((w, h)) = newsize {
frame.resize(w, h);
@@ -443,6 +701,11 @@ impl<T> EventLoop<T> {
if closed {
sink.send_window_event(crate::event::WindowEvent::CloseRequested, wid);
}
if let Some(grab_cursor) = grab_cursor {
let surface = if grab_cursor { Some(surface) } else { None };
self.cursor_manager.lock().unwrap().grab_pointer(surface);
}
},
)
}
@@ -457,7 +720,9 @@ struct SeatManager<T: 'static> {
store: Arc<Mutex<WindowStore>>,
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
pointer_constraints_proxy: Arc<Mutex<Option<ZwpPointerConstraintsV1>>>,
cursor_manager: Arc<Mutex<CursorManager>>,
}
impl<T: 'static> SeatManager<T> {
@@ -469,11 +734,12 @@ impl<T: 'static> SeatManager<T> {
store: self.store.clone(),
pointer: None,
relative_pointer: None,
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.as_ref().cloned(),
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(),
keyboard: None,
touch: None,
kbd_sender: self.kbd_sender.clone(),
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
cursor_manager: self.cursor_manager.clone(),
};
let seat = registry
.bind(min(version, 5), id, move |seat| {
@@ -501,10 +767,11 @@ struct SeatData<T> {
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
pointer: Option<wl_pointer::WlPointer>,
relative_pointer: Option<ZwpRelativePointerV1>,
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
keyboard: Option<wl_keyboard::WlKeyboard>,
touch: Option<wl_touch::WlTouch>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
cursor_manager: Arc<Mutex<CursorManager>>,
}
impl<T: 'static> SeatData<T> {
@@ -519,19 +786,27 @@ impl<T: 'static> SeatData<T> {
self.sink.clone(),
self.store.clone(),
self.modifiers_tracker.clone(),
self.cursor_manager.clone(),
));
self.relative_pointer =
self.relative_pointer_manager_proxy
.as_ref()
.and_then(|manager| {
super::pointer::implement_relative_pointer(
self.sink.clone(),
self.pointer.as_ref().unwrap(),
manager,
)
.ok()
})
self.cursor_manager
.lock()
.unwrap()
.register_pointer(self.pointer.as_ref().unwrap().clone());
self.relative_pointer = self
.relative_pointer_manager_proxy
.try_borrow()
.unwrap()
.as_ref()
.and_then(|manager| {
super::pointer::implement_relative_pointer(
self.sink.clone(),
self.pointer.as_ref().unwrap(),
manager,
)
.ok()
})
}
// destroy pointer if applicable
if !capabilities.contains(wl_seat::Capability::Pointer) {
@@ -603,17 +878,67 @@ impl<T> Drop for SeatData<T> {
* Monitor stuff
*/
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
}
}
}
#[derive(Clone)]
pub struct MonitorHandle {
pub(crate) proxy: wl_output::WlOutput,
pub(crate) mgr: OutputMgr,
}
impl Clone for MonitorHandle {
fn clone(&self) -> MonitorHandle {
MonitorHandle {
proxy: self.proxy.clone(),
mgr: self.mgr.clone(),
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.native_identifier() == other.native_identifier()
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.native_identifier().cmp(&other.native_identifier())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.native_identifier().hash(state);
}
}
@@ -680,15 +1005,20 @@ impl MonitorHandle {
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let monitor = self.clone();
self.mgr
.with_info(&self.proxy, |_, info| info.modes.clone())
.unwrap_or(vec![])
.into_iter()
.map(|x| VideoMode {
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: 32,
.map(move |x| RootVideoMode {
video_mode: PlatformVideoMode::Wayland(VideoMode {
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: 32,
monitor: monitor.clone(),
}),
})
}
}

View File

@@ -83,7 +83,17 @@ pub fn init_keyboard(
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers {
modifiers: event_modifiers,
} => *modifiers_tracker.lock().unwrap() = event_modifiers.into(),
} => {
let modifiers = event_modifiers.into();
*modifiers_tracker.lock().unwrap() = modifiers;
if let Some(wid) = *target.lock().unwrap() {
my_sink
.send((WindowEvent::ModifiersChanged { modifiers }, wid))
.unwrap();
}
}
}
},
move |repeat_event: KeyRepeatEvent, _| {

View File

@@ -3,7 +3,8 @@
pub use self::{
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink,
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
WindowEventsSink,
},
window::Window,
};

View File

@@ -5,7 +5,11 @@ use crate::event::{
WindowEvent,
};
use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId};
use super::{
event_loop::{CursorManager, WindowEventsSink},
window::WindowStore,
DeviceId,
};
use smithay_client_toolkit::reexports::client::protocol::{
wl_pointer::{self, Event as PtrEvent, WlPointer},
@@ -17,11 +21,19 @@ use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1
zwp_relative_pointer_v1::ZwpRelativePointerV1,
};
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime,
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
};
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
pub fn implement_pointer<T: 'static>(
seat: &wl_seat::WlSeat,
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
cursor_manager: Arc<Mutex<CursorManager>>,
) -> WlPointer {
seat.get_pointer(|pointer| {
let mut mouse_focus = None;
@@ -33,6 +45,7 @@ pub fn implement_pointer<T: 'static>(
move |evt, pointer| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
let mut cursor_manager = cursor_manager.lock().unwrap();
match evt {
PtrEvent::Enter {
surface,
@@ -62,6 +75,8 @@ pub fn implement_pointer<T: 'static>(
wid,
);
}
cursor_manager.reload_cursor_style();
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
@@ -241,3 +256,13 @@ pub fn implement_relative_pointer<T: 'static>(
)
})
}
pub fn implement_locked_pointer(
surface: &WlSurface,
pointer: &WlPointer,
constraints: &ZwpPointerConstraintsV1,
) -> Result<ZwpLockedPointerV1, ()> {
constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| {
c.implement_closure(|_, _| (), ())
})
}

View File

@@ -39,6 +39,7 @@ pub(crate) fn implement_touch<T: 'static>(
),
phase: TouchPhase::Started,
location: (x, y).into(),
force: None, // TODO
id: id as u64,
}),
wid,
@@ -61,6 +62,7 @@ pub(crate) fn implement_touch<T: 'static>(
),
phase: TouchPhase::Ended,
location: pt.location.into(),
force: None, // TODO
id: id as u64,
}),
pt.wid,
@@ -78,6 +80,7 @@ pub(crate) fn implement_touch<T: 'static>(
),
phase: TouchPhase::Moved,
location: (x, y).into(),
force: None, // TODO
id: id as u64,
}),
pt.wid,
@@ -94,6 +97,7 @@ pub(crate) fn implement_touch<T: 'static>(
),
phase: TouchPhase::Cancelled,
location: pt.location.into(),
force: None, // TODO
id: pt.id as u64,
}),
pt.wid,

View File

@@ -1,5 +1,7 @@
use raw_window_handle::unix::WaylandHandle;
use std::{
collections::VecDeque,
mem::replace,
sync::{Arc, Mutex, Weak},
};
@@ -8,10 +10,11 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::{
platform::wayland::event_loop::{available_monitors, primary_monitor},
MonitorHandle as PlatformMonitorHandle,
PlatformSpecificWindowBuilderAttributes as PlAttributes,
},
window::{CursorIcon, WindowAttributes},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
use smithay_client_toolkit::{
@@ -24,12 +27,12 @@ use smithay_client_toolkit::{
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
};
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
pub struct Window {
surface: wl_surface::WlSurface,
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
cursor_manager: Arc<Mutex<CursorManager>>,
outputs: OutputMgr, // Access to info for all monitors
size: Arc<Mutex<(u32, u32)>>,
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
@@ -37,6 +40,7 @@ pub struct Window {
need_frame_refresh: Arc<Mutex<bool>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
}
impl Window {
@@ -51,6 +55,7 @@ impl Window {
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let cursor_manager = evlp.cursor_manager.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
window_store.lock().unwrap().dpi_change(&surface, dpi);
surface.set_buffer_scale(dpi);
@@ -108,13 +113,19 @@ impl Window {
}
// Check for fullscreen requirements
if let Some(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
}) = attributes.fullscreen
{
frame.set_fullscreen(Some(&monitor_id.proxy));
} else if attributes.maximized {
frame.set_maximized();
match attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => {
panic!("Wayland doesn't support exclusive fullscreen")
}
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => {
if attributes.maximized {
frame.set_maximized();
}
}
}
frame.set_resizable(attributes.resizable);
@@ -133,6 +144,7 @@ impl Window {
let need_frame_refresh = Arc::new(Mutex::new(true));
let frame = Arc::new(Mutex::new(frame));
let need_refresh = Arc::new(Mutex::new(true));
let cursor_grab_changed = Arc::new(Mutex::new(None));
evlp.store.lock().unwrap().windows.push(InternalWindow {
closed: false,
@@ -140,6 +152,7 @@ impl Window {
size: size.clone(),
need_refresh: need_refresh.clone(),
fullscreen: fullscreen.clone(),
cursor_grab_changed: cursor_grab_changed.clone(),
need_frame_refresh: need_frame_refresh.clone(),
surface: surface.clone(),
kill_switch: kill_switch.clone(),
@@ -158,7 +171,9 @@ impl Window {
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
need_frame_refresh,
need_refresh,
cursor_manager,
fullscreen,
cursor_grab_changed,
})
}
@@ -252,25 +267,31 @@ impl Window {
}
}
pub fn fullscreen(&self) -> Option<MonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
if *(self.fullscreen.lock().unwrap()) {
Some(self.current_monitor())
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
}))
} else {
None
}
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
if let Some(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
}) = monitor
{
self.frame
.lock()
.unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
} else {
self.frame.lock().unwrap().unset_fullscreen();
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
match fullscreen {
Some(Fullscreen::Exclusive(_)) => {
panic!("Wayland doesn't support exclusive fullscreen")
}
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
})) => {
self.frame
.lock()
.unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
}
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => self.frame.lock().unwrap().unset_fullscreen(),
}
}
@@ -279,18 +300,21 @@ impl Window {
}
#[inline]
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
// TODO
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let mut cursor_manager = self.cursor_manager.lock().unwrap();
cursor_manager.set_cursor_icon(cursor);
}
#[inline]
pub fn set_cursor_visible(&self, _visible: bool) {
// TODO: This isn't possible on Wayland yet
pub fn set_cursor_visible(&self, visible: bool) {
let mut cursor_manager = self.cursor_manager.lock().unwrap();
cursor_manager.set_cursor_visible(visible);
}
#[inline]
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
*self.cursor_grab_changed.lock().unwrap() = Some(grab);
Ok(())
}
#[inline]
@@ -321,6 +345,14 @@ impl Window {
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor(&self.outputs)
}
pub fn raw_window_handle(&self) -> WaylandHandle {
WaylandHandle {
surface: self.surface().as_ref().c_ptr() as *mut _,
display: self.display().as_ref().c_ptr() as *mut _,
..WaylandHandle::empty()
}
}
}
impl Drop for Window {
@@ -341,6 +373,7 @@ struct InternalWindow {
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
need_frame_refresh: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
@@ -408,6 +441,8 @@ impl WindowStore {
bool,
bool,
bool,
Option<bool>,
&wl_surface::WlSurface,
WindowId,
Option<&mut SWindow<ConceptFrame>>,
),
@@ -419,9 +454,11 @@ impl WindowStore {
window.newsize.take(),
&mut *(window.size.lock().unwrap()),
window.new_dpi,
::std::mem::replace(&mut *window.need_refresh.lock().unwrap(), false),
::std::mem::replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
window.closed,
window.cursor_grab_changed.lock().unwrap().take(),
&window.surface,
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);

View File

@@ -8,6 +8,8 @@ use super::{
XExtension,
};
use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
@@ -21,6 +23,9 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension,
pub(super) target: Rc<RootELW<T>>,
pub(super) mod_keymap: ModifierKeymap,
pub(super) device_mod_state: ModifierKeyState,
pub(super) window_mod_state: ModifierKeyState,
}
impl<T: 'static> EventProcessor<T> {
@@ -112,12 +117,22 @@ impl<T: 'static> EventProcessor<T> {
let event_type = xev.get_type();
match event_type {
ffi::MappingNotify => {
unsafe {
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
let mapping: &ffi::XMappingEvent = xev.as_ref();
if mapping.request == ffi::MappingModifier
|| mapping.request == ffi::MappingKeyboard
{
unsafe {
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
}
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");
self.mod_keymap.reset_from_x_connection(&wt.xconn);
self.device_mod_state.update(&self.mod_keymap);
self.window_mod_state.update(&self.mod_keymap);
}
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");
}
ffi::ClientMessage => {
@@ -374,7 +389,11 @@ impl<T: 'static> EventProcessor<T> {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
let new_hidpi_factor = monitor.hidpi_factor;
shared_state_lock.last_monitor = Some(monitor.clone());
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
shared_state_lock.last_monitor = Some(monitor.clone());
}
new_hidpi_factor
};
if last_hidpi_factor != new_hidpi_factor {
@@ -514,13 +533,6 @@ impl<T: 'static> EventProcessor<T> {
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
// a keycode of 0.
if xkev.keycode != 0 {
let modifiers = ModifiersState {
alt: xkev.state & ffi::Mod1Mask != 0,
shift: xkev.state & ffi::ShiftMask != 0,
ctrl: xkev.state & ffi::ControlMask != 0,
logo: xkev.state & ffi::Mod4Mask != 0,
};
let keysym = unsafe {
let mut keysym = 0;
(wt.xconn.xlib.XLookupString)(
@@ -535,6 +547,8 @@ impl<T: 'static> EventProcessor<T> {
};
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.window_mod_state.modifiers();
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
@@ -547,6 +561,27 @@ impl<T: 'static> EventProcessor<T> {
},
},
});
if let Some(modifier) =
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
{
self.window_mod_state.key_event(
state,
xkev.keycode as ffi::KeyCode,
modifier,
);
let new_modifiers = self.window_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged {
modifiers: new_modifiers,
},
});
}
}
}
if state == Pressed {
@@ -859,6 +894,21 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true),
});
// When focus is gained, send any existing modifiers
// to the window in a ModifiersChanged event. This is
// done to compensate for modifier keys that may be
// changed while a window is out of focus.
if !self.device_mod_state.is_empty() {
self.window_mod_state = self.device_mod_state.clone();
let modifiers = self.window_mod_state.modifiers();
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged { modifiers },
});
}
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
@@ -890,6 +940,22 @@ impl<T: 'static> EventProcessor<T> {
.borrow_mut()
.unfocus(xev.event)
.expect("Failed to unfocus input context");
// When focus is lost, send a ModifiersChanged event
// containing no modifiers set. This is done to compensate
// for modifier keys that may be changed while a window
// is out of focus.
if !self.window_mod_state.is_empty() {
self.window_mod_state.clear();
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: WindowEvent::ModifiersChanged {
modifiers: ModifiersState::default(),
},
});
}
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: Focused(false),
@@ -918,6 +984,7 @@ impl<T: 'static> EventProcessor<T> {
device_id: mkdid(xev.deviceid),
phase,
location,
force: None, // TODO
id: xev.detail as u64,
}),
})
@@ -1021,18 +1088,25 @@ impl<T: 'static> EventProcessor<T> {
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
if let Some(modifier) =
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
{
self.device_mod_state.key_event(
state,
keycode as ffi::KeyCode,
modifier,
);
}
let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent {
device_id: mkdid(device_id),
event: DeviceEvent::Key(KeyboardInput {
scancode,
virtual_keycode,
state,
// So, in an ideal world we can use libxkbcommon to get modifiers.
// However, libxkbcommon-x11 isn't as commonly installed as one
// would hope. We can still use the Xkb extension to get
// comprehensive keyboard state updates, but interpreting that
// info manually is going to be involved.
modifiers: ModifiersState::default(),
modifiers,
}),
});
}

View File

@@ -11,7 +11,7 @@ mod window;
mod xdisplay;
pub use self::{
monitor::MonitorHandle,
monitor::{MonitorHandle, VideoMode},
window::UnownedWindow,
xdisplay::{XConnection, XError, XNotSupported},
};
@@ -20,7 +20,7 @@ use std::{
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
ffi::CStr,
mem,
mem::{self, MaybeUninit},
ops::Deref,
os::raw::*,
rc::Rc,
@@ -34,6 +34,7 @@ use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
util::modifiers::ModifierKeymap,
};
use crate::{
error::OsError as RootOsError,
@@ -60,16 +61,24 @@ pub struct EventLoop<T: 'static> {
_x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>,
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
pending_user_events: Rc<RefCell<VecDeque<T>>>,
event_processor: Rc<RefCell<EventProcessor<T>>>,
user_sender: ::calloop::channel::Sender<T>,
pending_events: Rc<RefCell<VecDeque<Event<T>>>>,
target: Rc<RootELW<T>>,
pub(crate) target: Rc<RootELW<T>>,
}
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> {
user_sender: ::calloop::channel::Sender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_sender: self.user_sender.clone(),
}
}
}
impl<T: 'static> EventLoop<T> {
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
@@ -100,22 +109,21 @@ impl<T: 'static> EventLoop<T> {
.expect("Failed to query XRandR extension");
let xi2ext = unsafe {
let mut result = XExtension {
opcode: mem::uninitialized(),
first_event_id: mem::uninitialized(),
first_error_id: mem::uninitialized(),
};
let mut ext = XExtension::default();
let res = (xconn.xlib.XQueryExtension)(
xconn.display,
b"XInputExtension\0".as_ptr() as *const c_char,
&mut result.opcode as *mut c_int,
&mut result.first_event_id as *mut c_int,
&mut result.first_error_id as *mut c_int,
&mut ext.opcode,
&mut ext.first_event_id,
&mut ext.first_error_id,
);
if res == ffi::False {
panic!("X server missing XInput extension");
}
result
ext
};
unsafe {
@@ -136,6 +144,9 @@ impl<T: 'static> EventLoop<T> {
xconn.update_cached_wm_info(root);
let mut mod_keymap = ModifierKeymap::new();
mod_keymap.reset_from_x_connection(&xconn);
let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime,
@@ -172,13 +183,16 @@ impl<T: 'static> EventLoop<T> {
// Handle X11 events
let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default();
let mut processor = EventProcessor {
let processor = EventProcessor {
target: target.clone(),
dnd,
devices: Default::default(),
randr_event_offset,
ime_receiver,
xi2ext,
mod_keymap,
device_mod_state: Default::default(),
window_mod_state: Default::default(),
};
// Register for device hotplug events
@@ -190,6 +204,9 @@ impl<T: 'static> EventLoop<T> {
processor.init_device(ffi::XIAllDevices);
let processor = Rc::new(RefCell::new(processor));
let event_processor = processor.clone();
// Setup the X11 event source
let mut x11_events =
::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd);
@@ -198,16 +215,11 @@ impl<T: 'static> EventLoop<T> {
.handle()
.insert_source(x11_events, {
let pending_events = pending_events.clone();
let mut callback = move |event| {
pending_events.borrow_mut().push_back(event);
};
move |evt, &mut ()| {
if evt.readiness.is_readable() {
// process all pending events
let mut xev = unsafe { mem::uninitialized() };
while unsafe { processor.poll_one_event(&mut xev) } {
processor.process_event(&mut xev, &mut callback);
}
let mut processor = processor.borrow_mut();
let mut pending_events = pending_events.borrow_mut();
drain_events(&mut processor, &mut pending_events);
}
}
})
@@ -220,18 +232,13 @@ impl<T: 'static> EventLoop<T> {
_user_source,
user_sender,
pending_user_events,
event_processor,
target,
};
result
}
/// Returns the `XConnection` of this events loop.
#[inline]
pub fn x_connection(&self) -> &Arc<XConnection> {
&get_xtarget(&self.target).xconn
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_sender: self.user_sender.clone(),
@@ -249,6 +256,12 @@ impl<T: 'static> EventLoop<T> {
let mut control_flow = ControlFlow::default();
let wt = get_xtarget(&self.target);
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Init),
&self.target,
&mut control_flow,
);
loop {
// Empty the event buffer
{
@@ -272,8 +285,10 @@ impl<T: 'static> EventLoop<T> {
}
// Empty the redraw requests
{
let mut guard = wt.pending_redraws.lock().unwrap();
for wid in guard.drain() {
// Release the lock to prevent deadlock
let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
for wid in windows {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(super::WindowId::X(wid)),
@@ -354,6 +369,10 @@ impl<T: 'static> EventLoop<T> {
}
}
}
// If the user callback had any interaction with the X server,
// it may have received and buffered some user input events.
self.drain_events();
}
callback(
@@ -370,13 +389,43 @@ impl<T: 'static> EventLoop<T> {
self.run_return(callback);
::std::process::exit(0);
}
fn drain_events(&self) {
let mut processor = self.event_processor.borrow_mut();
let mut pending_events = self.pending_events.borrow_mut();
drain_events(&mut processor, &mut pending_events);
}
}
fn get_xtarget<T>(rt: &RootELW<T>) -> &EventLoopWindowTarget<T> {
if let super::EventLoopWindowTarget::X(ref target) = rt.p {
target
} else {
unreachable!();
fn drain_events<T: 'static>(
processor: &mut EventProcessor<T>,
pending_events: &mut VecDeque<Event<T>>,
) {
let mut callback = |event| {
pending_events.push_back(event);
};
// process all pending events
let mut xev = MaybeUninit::uninit();
while unsafe { processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
processor.process_event(&mut xev, &mut callback);
}
}
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
match target.p {
super::EventLoopWindowTarget::X(ref target) => target,
_ => unreachable!(),
}
}
impl<T> EventLoopWindowTarget<T> {
/// Returns the `XConnection` of this events loop.
#[inline]
pub fn x_connection(&self) -> &Arc<XConnection> {
&self.xconn
}
}
@@ -395,19 +444,19 @@ struct DeviceInfo<'a> {
impl<'a> DeviceInfo<'a> {
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
unsafe {
let mut count = mem::uninitialized();
let mut count = 0;
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
xconn.check_errors().ok().and_then(|_| {
if info.is_null() || count == 0 {
None
} else {
Some(DeviceInfo {
xconn,
info,
count: count as usize,
})
}
})
xconn.check_errors().ok()?;
if info.is_null() || count == 0 {
None
} else {
Some(DeviceInfo {
xconn,
info,
count: count as usize,
})
}
}
}
}
@@ -512,7 +561,7 @@ impl<'a> Drop for GenericEventCookie<'a> {
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Default, Copy, Clone)]
struct XExtension {
opcode: c_int,
first_event_id: c_int,

View File

@@ -4,47 +4,66 @@ use parking_lot::Mutex;
use super::{
ffi::{
RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window,
XRRScreenResources,
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
},
util, XConnection, XError,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
};
// Used to test XRandR < 1.5 code path. This should always be committed as false.
const FORCE_RANDR_COMPAT: bool = false;
// Also used for testing. This should always be committed as false.
// Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
lazy_static! {
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
}
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
if avail_major == major {
avail_minor >= minor
} else {
avail_major > major
}
} else {
unreachable!();
}
}
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
// We update this lazily.
(*MONITORS.lock()).take()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) native_mode: RRMode,
pub(crate) monitor: Option<MonitorHandle>,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
}
}
}
#[derive(Debug, Clone)]
pub struct MonitorHandle {
/// The actual id
id: u32,
pub(crate) id: RRCrtc,
/// The name of the monitor
pub(crate) name: String,
/// The size of the monitor
@@ -61,16 +80,43 @@ pub struct MonitorHandle {
video_modes: Vec<VideoMode>,
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id.cmp(&other.id)
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl MonitorHandle {
fn from_repr(
fn new(
xconn: &XConnection,
resources: *mut XRRScreenResources,
id: u32,
repr: util::MonitorRepr,
id: RRCrtc,
crtc: *mut XRRCrtcInfo,
primary: bool,
) -> Option<Self> {
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? };
let (dimensions, position) = unsafe { (repr.size(), repr.position()) };
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle {
id,
@@ -84,6 +130,19 @@ impl MonitorHandle {
})
}
fn dummy() -> Self {
MonitorHandle {
id: 0,
name: "<dummy monitor>".into(),
hidpi_factor: 1.0,
dimensions: (1, 1),
position: (0, 0),
primary: true,
rect: util::AaRect::new((0, 0), (1, 1)),
video_modes: Vec::new(),
}
}
pub fn name(&self) -> Option<String> {
Some(self.name.clone())
}
@@ -107,17 +166,27 @@ impl MonitorHandle {
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes.clone().into_iter()
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let monitor = self.clone();
self.video_modes.clone().into_iter().map(move |mut x| {
x.monitor = Some(monitor.clone());
RootVideoMode {
video_mode: PlatformVideoMode::X(x),
}
})
}
}
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
let monitors = self.available_monitors();
let default = monitors
.get(0)
.expect("[winit] Failed to find any monitors using XRandR.");
if monitors.is_empty() {
// Return a dummy monitor to avoid panicking
return MonitorHandle::dummy();
}
let default = monitors.get(0).unwrap();
let window_rect = match window_rect {
Some(rect) => rect,
@@ -139,8 +208,12 @@ impl XConnection {
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if version_is_at_least(1, 3) {
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
@@ -155,48 +228,19 @@ impl XConnection {
let mut available;
let mut has_primary = false;
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
// videowalls.
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
let mut monitor_count = 0;
let monitors =
(xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
assert!(monitor_count >= 0);
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let is_primary = *(*crtc).outputs.offset(0) == primary;
has_primary |= is_primary;
MonitorHandle::from_repr(
self,
resources,
monitor_index as u32,
monitor.into(),
is_primary,
)
.map(|monitor_id| available.push(monitor_id));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
MonitorHandle::from_repr(self, resources, crtc_id as u32, crtc, is_primary)
.map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
.map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}
// If no monitors were detected as being primary, we just pick one ourselves!
@@ -232,23 +276,19 @@ impl XConnection {
self.available_monitors()
.into_iter()
.find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.")
.unwrap_or_else(MonitorHandle::dummy)
}
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
{
let mut version_lock = XRANDR_VERSION.lock();
if version_lock.is_none() {
let mut major = 0;
let mut minor = 0;
let has_extension =
unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) };
if has_extension != True {
panic!("[winit] XRandR extension not available.");
}
*version_lock = Some((major, minor));
}
}
let has_xrandr = unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
};
assert!(
has_xrandr == True,
"[winit] XRandR extension not available."
);
let mut event_offset = 0;
let mut error_offset = 0;

View File

@@ -30,13 +30,17 @@ impl XConnection {
event_mask: Option<c_long>,
data: ClientMsgPayload,
) -> Flusher<'_> {
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
event.type_ = ffi::ClientMessage;
event.display = self.display;
event.window = window;
event.message_type = message_type;
event.format = c_long::FORMAT as c_int;
event.data = unsafe { mem::transmute(data) };
let event = ffi::XClientMessageEvent {
type_: ffi::ClientMessage,
display: self.display,
window,
message_type,
format: c_long::FORMAT as c_int,
data: unsafe { mem::transmute(data) },
// These fields are ignored by `XSendEvent`
serial: 0,
send_event: 0,
};
self.send_event(target_window, event_mask, event)
}
@@ -54,12 +58,17 @@ impl XConnection {
let format = T::FORMAT;
let size_of_t = mem::size_of::<T>();
debug_assert_eq!(size_of_t, format.get_actual_size());
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
event.type_ = ffi::ClientMessage;
event.display = self.display;
event.window = window;
event.message_type = message_type;
event.format = format as c_int;
let mut event = ffi::XClientMessageEvent {
type_: ffi::ClientMessage,
display: self.display,
window,
message_type,
format: format as c_int,
data: ffi::ClientMessageData::new(),
// These fields are ignored by `XSendEvent`
serial: 0,
send_event: 0,
};
let t_per_payload = format.get_payload_size() / size_of_t;
assert!(t_per_payload > 0);

View File

@@ -0,0 +1,129 @@
use crate::window::CursorIcon;
use super::*;
impl XConnection {
pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option<CursorIcon>) {
let cursor = *self
.cursor_cache
.lock()
.entry(cursor)
.or_insert_with(|| self.get_cursor(cursor));
self.update_cursor(window, cursor);
}
fn create_empty_cursor(&self) -> ffi::Cursor {
let data = 0;
let pixmap = unsafe {
let screen = (self.xlib.XDefaultScreen)(self.display);
let window = (self.xlib.XRootWindow)(self.display, screen);
(self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1)
};
if pixmap == 0 {
panic!("failed to allocate pixmap for cursor");
}
unsafe {
// We don't care about this color, since it only fills bytes
// in the pixmap which are not 0 in the mask.
let mut dummy_color = MaybeUninit::uninit();
let cursor = (self.xlib.XCreatePixmapCursor)(
self.display,
pixmap,
pixmap,
dummy_color.as_mut_ptr(),
dummy_color.as_mut_ptr(),
0,
0,
);
(self.xlib.XFreePixmap)(self.display, pixmap);
cursor
}
}
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
}
}
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
for name in names.iter() {
let xcursor = self.load_cursor(name);
if xcursor != 0 {
return xcursor;
}
}
0
}
fn get_cursor(&self, cursor: Option<CursorIcon>) -> ffi::Cursor {
let cursor = match cursor {
Some(cursor) => cursor,
None => return self.create_empty_cursor(),
};
let load = |name: &[u8]| self.load_cursor(name);
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
// Try multiple names in some cases where the name
// differs on the desktop environments or themes.
//
// Try the better looking (or more suiting) names first.
match cursor {
CursorIcon::Alias => load(b"link\0"),
CursorIcon::Arrow => load(b"arrow\0"),
CursorIcon::Cell => load(b"plus\0"),
CursorIcon::Copy => load(b"copy\0"),
CursorIcon::Crosshair => load(b"crosshair\0"),
CursorIcon::Default => load(b"left_ptr\0"),
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
CursorIcon::Help => load(b"question_arrow\0"),
CursorIcon::Move => load(b"move\0"),
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
CursorIcon::Progress => load(b"left_ptr_watch\0"),
CursorIcon::AllScroll => load(b"all-scroll\0"),
CursorIcon::ContextMenu => load(b"context-menu\0"),
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
// Resize cursors
CursorIcon::EResize => load(b"right_side\0"),
CursorIcon::NResize => load(b"top_side\0"),
CursorIcon::NeResize => load(b"top_right_corner\0"),
CursorIcon::NwResize => load(b"top_left_corner\0"),
CursorIcon::SResize => load(b"bottom_side\0"),
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
CursorIcon::VerticalText => load(b"vertical-text\0"),
CursorIcon::Wait => load(b"watch\0"),
CursorIcon::ZoomIn => load(b"zoom-in\0"),
CursorIcon::ZoomOut => load(b"zoom-out\0"),
}
}
fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) {
unsafe {
(self.xlib.XDefineCursor)(self.display, window, cursor);
self.flush_requests().expect("Failed to set the cursor");
}
}
}

View File

@@ -41,14 +41,14 @@ impl AaRect {
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct TranslatedCoords {
pub x_rel_root: c_int,
pub y_rel_root: c_int,
pub child: ffi::Window,
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct Geometry {
pub root: ffi::Window,
// If you want positions relative to the root window, use translate_coords.
@@ -183,7 +183,8 @@ impl XConnection {
window: ffi::Window,
root: ffi::Window,
) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
let mut coords = TranslatedCoords::default();
unsafe {
(self.xlib.XTranslateCoordinates)(
self.display,
@@ -191,18 +192,20 @@ impl XConnection {
root,
0,
0,
&mut translated_coords.x_rel_root,
&mut translated_coords.y_rel_root,
&mut translated_coords.child,
&mut coords.x_rel_root,
&mut coords.y_rel_root,
&mut coords.child,
);
}
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
self.check_errors().map(|_| translated_coords)
self.check_errors()?;
Ok(coords)
}
// This is adequate for inner_size
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
let mut geometry: Geometry = unsafe { mem::uninitialized() };
let mut geometry = Geometry::default();
let _status = unsafe {
(self.xlib.XGetGeometry)(
self.display,
@@ -216,8 +219,9 @@ impl XConnection {
&mut geometry.depth,
)
};
//println!("XGetGeometry geo:{:?}", geometry);
self.check_errors().map(|_| geometry)
self.check_errors()?;
Ok(geometry)
}
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
@@ -264,10 +268,10 @@ impl XConnection {
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
let parent = unsafe {
let mut root: ffi::Window = mem::uninitialized();
let mut parent: ffi::Window = mem::uninitialized();
let mut root = 0;
let mut parent = 0;
let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: c_uint = mem::uninitialized();
let mut nchildren = 0;
// What's filled into `parent` if `window` is the root window?
let _status = (self.xlib.XQueryTree)(

View File

@@ -72,21 +72,21 @@ impl Default for WindowType {
impl WindowType {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
use self::WindowType::*;
let atom_name: &[u8] = match self {
&Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
&Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
&Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
&Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
&Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
&Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
&Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
&DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
&PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
&Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
&Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
&Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
&Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
&Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
let atom_name: &[u8] = match *self {
Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
};
unsafe { xconn.get_atom_unchecked(atom_name) }
}
@@ -317,13 +317,13 @@ impl XConnection {
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
let size_hints = self.alloc_size_hints();
let mut supplied_by_user: c_long = unsafe { mem::uninitialized() };
let mut supplied_by_user = MaybeUninit::uninit();
unsafe {
(self.xlib.XGetWMNormalHints)(
self.display,
window,
size_hints.ptr,
&mut supplied_by_user,
supplied_by_user.as_mut_ptr(),
);
}
self.check_errors().map(|_| NormalHints { size_hints })

View File

@@ -1,4 +1,4 @@
use std::str;
use std::{slice, str};
use super::*;
use crate::event::ModifiersState;
@@ -23,18 +23,19 @@ impl From<ffi::XIModifierState> for ModifiersState {
}
}
// NOTE: Some of these fields are not used, but may be of use in the future.
pub struct PointerState<'a> {
xconn: &'a XConnection,
root: ffi::Window,
child: ffi::Window,
pub root: ffi::Window,
pub child: ffi::Window,
pub root_x: c_double,
pub root_y: c_double,
win_x: c_double,
win_y: c_double,
pub win_x: c_double,
pub win_y: c_double,
buttons: ffi::XIButtonState,
modifiers: ffi::XIModifierState,
group: ffi::XIGroupState,
relative_to_window: bool,
pub group: ffi::XIGroupState,
pub relative_to_window: bool,
}
impl<'a> PointerState<'a> {
@@ -93,29 +94,46 @@ impl XConnection {
device_id: c_int,
) -> Result<PointerState<'_>, XError> {
unsafe {
let mut pointer_state: PointerState<'_> = mem::uninitialized();
pointer_state.xconn = self;
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)(
let mut root = 0;
let mut child = 0;
let mut root_x = 0.0;
let mut root_y = 0.0;
let mut win_x = 0.0;
let mut win_y = 0.0;
let mut buttons = Default::default();
let mut modifiers = Default::default();
let mut group = Default::default();
let relative_to_window = (self.xinput2.XIQueryPointer)(
self.display,
device_id,
window,
&mut pointer_state.root,
&mut pointer_state.child,
&mut pointer_state.root_x,
&mut pointer_state.root_y,
&mut pointer_state.win_x,
&mut pointer_state.win_y,
&mut pointer_state.buttons,
&mut pointer_state.modifiers,
&mut pointer_state.group,
&mut root,
&mut child,
&mut root_x,
&mut root_y,
&mut win_x,
&mut win_y,
&mut buttons,
&mut modifiers,
&mut group,
) == ffi::True;
if let Err(err) = self.check_errors() {
// Running the destrutor would be bad news for us...
mem::forget(pointer_state);
Err(err)
} else {
Ok(pointer_state)
}
self.check_errors()?;
Ok(PointerState {
xconn: self,
root,
child,
root_x,
root_y,
win_x,
win_y,
buttons,
modifiers,
group,
relative_to_window,
})
}
}
@@ -123,7 +141,8 @@ impl XConnection {
&self,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
buffer: &mut [u8],
buffer: *mut u8,
size: usize,
) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
@@ -131,8 +150,8 @@ impl XConnection {
(self.xlib.Xutf8LookupString)(
ic,
key_event,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
buffer as *mut c_char,
size as c_int,
&mut keysym,
&mut status,
)
@@ -141,21 +160,28 @@ impl XConnection {
}
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() };
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
// The buffer overflowed, so we'll make a new one on the heap.
if status == ffi::XBufferOverflow {
let mut buffer = Vec::with_capacity(count as usize);
unsafe { buffer.set_len(count as usize) };
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
unsafe { MaybeUninit::uninit().assume_init() };
// If the buffer overflows, we'll make a new one on the heap.
let mut vec;
let (_, status, count) =
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
let bytes = if status == ffi::XBufferOverflow {
vec = Vec::with_capacity(count as usize);
let (_, _, new_count) =
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
debug_assert_eq!(count, new_count);
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
unsafe { vec.set_len(count as usize) };
&vec[..count as usize]
} else {
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
}
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
};
str::from_utf8(bytes).unwrap_or("").to_string()
}
}

View File

@@ -3,12 +3,14 @@
mod atom;
mod client_msg;
mod cursor;
mod format;
mod geometry;
mod hint;
mod icon;
mod input;
mod memory;
pub mod modifiers;
mod randr;
mod window_property;
mod wm;
@@ -18,7 +20,12 @@ pub use self::{
randr::*, window_property::*, wm::*,
};
use std::{mem, ops::BitAnd, os::raw::*, ptr};
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
ptr,
};
use super::{ffi, XConnection, XError};

View File

@@ -0,0 +1,149 @@
use std::{collections::HashMap, slice};
use super::*;
use crate::event::{ElementState, ModifiersState};
// Offsets within XModifierKeymap to each set of keycodes.
// We are only interested in Shift, Control, Alt, and Logo.
//
// There are 8 sets total. The order of keycode sets is:
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
//
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
const SHIFT_OFFSET: usize = 0;
const CONTROL_OFFSET: usize = 2;
const ALT_OFFSET: usize = 3;
const LOGO_OFFSET: usize = 6;
const NUM_MODS: usize = 8;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Modifier {
Alt,
Ctrl,
Shift,
Logo,
}
#[derive(Debug, Default)]
pub struct ModifierKeymap {
// Maps keycodes to modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
}
#[derive(Clone, Debug, Default)]
pub struct ModifierKeyState {
// Contains currently pressed modifier keys and their corresponding modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
}
impl ModifierKeymap {
pub fn new() -> ModifierKeymap {
ModifierKeymap::default()
}
pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option<Modifier> {
self.keys.get(&keycode).cloned()
}
pub fn reset_from_x_connection(&mut self, xconn: &XConnection) {
unsafe {
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
if keymap.is_null() {
panic!("failed to allocate XModifierKeymap");
}
self.reset_from_x_keymap(&*keymap);
(xconn.xlib.XFreeModifiermap)(keymap);
}
}
pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) {
let keys_per_mod = keymap.max_keypermod as usize;
let keys = unsafe {
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
};
self.keys.clear();
self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift);
self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl);
self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt);
self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo);
}
fn read_x_keys(
&mut self,
keys: &[ffi::KeyCode],
offset: usize,
keys_per_mod: usize,
modifier: Modifier,
) {
let start = offset * keys_per_mod;
let end = start + keys_per_mod;
for &keycode in &keys[start..end] {
if keycode != 0 {
self.keys.insert(keycode, modifier);
}
}
}
}
impl ModifierKeyState {
pub fn clear(&mut self) {
self.keys.clear();
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn update(&mut self, mods: &ModifierKeymap) {
self.keys.retain(|k, v| {
if let Some(m) = mods.get_modifier(*k) {
*v = m;
true
} else {
false
}
});
}
pub fn modifiers(&self) -> ModifiersState {
let mut state = ModifiersState::default();
for &m in self.keys.values() {
set_modifier(&mut state, m);
}
state
}
pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) {
match state {
ElementState::Pressed => self.key_press(keycode, modifier),
ElementState::Released => self.key_release(keycode),
}
}
pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) {
self.keys.insert(keycode, modifier);
}
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
self.keys.remove(&keycode);
}
}
fn set_modifier(state: &mut ModifiersState, modifier: Modifier) {
match modifier {
Modifier::Alt => state.alt = true,
Modifier::Ctrl => state.ctrl = true,
Modifier::Shift => state.shift = true,
Modifier::Logo => state.logo = true,
}
}

View File

@@ -1,7 +1,10 @@
use std::{env, slice, str::FromStr};
use super::*;
use crate::{dpi::validate_hidpi_factor, monitor::VideoMode};
use super::{
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32),
@@ -34,47 +37,6 @@ pub fn calc_dpi_factor(
dpi_factor
}
pub enum MonitorRepr {
Monitor(*mut ffi::XRRMonitorInfo),
Crtc(*mut ffi::XRRCrtcInfo),
}
impl MonitorRepr {
pub unsafe fn get_output(&self) -> ffi::RROutput {
match *self {
// Same member names, but different locations within the struct...
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
}
}
pub unsafe fn size(&self) -> (u32, u32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
}
}
pub unsafe fn position(&self) -> (i32, i32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
}
}
}
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
MonitorRepr::Monitor(monitor)
}
}
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
MonitorRepr::Crtc(crtc)
}
}
impl XConnection {
// Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
@@ -96,11 +58,11 @@ impl XConnection {
}
pub unsafe fn get_output_info(
&self,
resources: *mut ffi::XRRScreenResources,
repr: &MonitorRepr,
resources: *mut XRRScreenResources,
crtc: *mut XRRCrtcInfo,
) -> Option<(String, f64, Vec<VideoMode>)> {
let output_info =
(self.xrandr.XRRGetOutputInfo)(self.display, resources, repr.get_output());
(self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
if output_info.is_null() {
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
// it's possible for it to return null.
@@ -132,8 +94,13 @@ impl XConnection {
size: (x.width, x.height),
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: bit_depth as u16,
native_mode: x.id,
// This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user
monitor: None,
}
});
})
.collect();
let name_slice = slice::from_raw_parts(
(*output_info).name as *mut u8,
@@ -144,7 +111,7 @@ impl XConnection {
dpi / 96.
} else {
calc_dpi_factor(
repr.size(),
((*crtc).width as u32, (*crtc).height as u32),
(
(*output_info).mm_width as u64,
(*output_info).mm_height as u64,
@@ -153,6 +120,63 @@ impl XConnection {
};
(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, hidpi_factor, modes.collect()))
Some((name, hidpi_factor, modes))
}
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let status = (self.xrandr.XRRSetCrtcConfig)(
self.display,
resources,
crtc_id,
CurrentTime,
(*crtc).x,
(*crtc).y,
mode_id,
(*crtc).rotation,
(*crtc).outputs.offset(0),
1,
);
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
if status == Success as i32 {
Ok(())
} else {
Err(())
}
}
}
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let mode = (*crtc).mode;
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
mode
}
}
}

View File

@@ -43,13 +43,14 @@ impl XConnection {
let mut offset = 0;
let mut done = false;
let mut actual_type = 0;
let mut actual_format = 0;
let mut quantity_returned = 0;
let mut bytes_after = 0;
let mut buf: *mut c_uchar = ptr::null_mut();
while !done {
unsafe {
let mut actual_type: ffi::Atom = mem::uninitialized();
let mut actual_format: c_int = mem::uninitialized();
let mut quantity_returned: c_ulong = mem::uninitialized();
let mut bytes_after: c_ulong = mem::uninitialized();
let mut buf: *mut c_uchar = ptr::null_mut();
(self.xlib.XGetWindowProperty)(
self.display,
window,

View File

@@ -1,4 +1,15 @@
use std::{cmp, collections::HashSet, env, ffi::CString, mem, os::raw::*, path::Path, sync::Arc};
use raw_window_handle::unix::XlibHandle;
use std::{
cmp,
collections::HashSet,
env,
ffi::CString,
mem::{self, replace, MaybeUninit},
os::raw::*,
path::Path,
ptr, slice,
sync::Arc,
};
use libc;
use parking_lot::Mutex;
@@ -6,12 +17,13 @@ use parking_lot::Mutex;
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
},
window::{CursorIcon, Icon, WindowAttributes},
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
};
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
@@ -36,18 +48,22 @@ pub struct SharedState {
pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorHandle>,
pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<RootMonitorHandle>,
// Used to restore position after exiting fullscreen.
pub fullscreen: Option<Fullscreen>,
// Used to restore position after exiting fullscreen
pub restore_position: Option<(i32, i32)>,
// Used to restore video mode after exiting fullscreen
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>,
pub is_visible: bool,
}
impl SharedState {
fn new(dpi_factor: f64) -> Mutex<Self> {
fn new(dpi_factor: f64, is_visible: bool) -> Mutex<Self> {
let mut shared_state = SharedState::default();
shared_state.guessed_dpi = Some(dpi_factor);
shared_state.is_visible = is_visible;
Mutex::new(shared_state)
}
}
@@ -103,7 +119,7 @@ impl UnownedWindow {
.unwrap_or(1.0)
})
} else {
return Err(os_error!(OsError::XMisc("No monitors were detected.")));
1.0
};
info!("Guessed window DPI factor: {}", dpi_factor);
@@ -216,7 +232,7 @@ impl UnownedWindow {
cursor_grabbed: Mutex::new(false),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(dpi_factor),
shared_state: SharedState::new(dpi_factor, window_attrs.visible),
pending_redraws: event_loop.pending_redraws.clone(),
};
@@ -282,9 +298,7 @@ impl UnownedWindow {
window.set_pid().map(|flusher| flusher.queue());
if pl_attribs.x11_window_type != Default::default() {
window.set_window_type(pl_attribs.x11_window_type).queue();
}
window.set_window_types(pl_attribs.x11_window_types).queue();
if let Some(variant) = pl_attribs.gtk_theme_variant {
window.set_gtk_theme_variant(variant).queue();
@@ -341,6 +355,8 @@ impl UnownedWindow {
unsafe {
(xconn.xlib.XMapRaised)(xconn.display, window.xwindow);
} //.queue();
window.wait_for_visibility_notify();
}
// Attempt to make keyboard input repeat detectable
@@ -398,6 +414,7 @@ impl UnownedWindow {
if window_attrs.fullscreen.is_some() {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.unwrap()
.queue();
}
if window_attrs.always_on_top {
@@ -405,27 +422,6 @@ impl UnownedWindow {
.set_always_on_top_inner(window_attrs.always_on_top)
.queue();
}
if window_attrs.visible {
unsafe {
// XSetInputFocus generates an error if the window is not visible, so we wait
// until we receive VisibilityNotify.
let mut event = mem::uninitialized();
(xconn.xlib.XIfEvent)(
// This will flush the request buffer IF it blocks.
xconn.display,
&mut event as *mut ffi::XEvent,
Some(visibility_predicate),
window.xwindow as _,
);
(xconn.xlib.XSetInputFocus)(
xconn.display,
window.xwindow,
ffi::RevertToParent,
ffi::CurrentTime,
);
}
}
}
// We never want to give the user a broken window, since by then, it's too late to handle.
@@ -449,19 +445,22 @@ impl UnownedWindow {
let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") };
let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") };
unsafe {
let (hostname, hostname_length) = {
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
// the limit defined by OpenBSD.
const MAXHOSTNAMELEN: usize = 256;
let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized();
let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len());
if status != 0 {
return None;
}
hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety
let hostname_length = libc::strlen(hostname.as_ptr());
(hostname, hostname_length as usize)
};
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
// the limit defined by OpenBSD.
const MAXHOSTNAMELEN: usize = 256;
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buffer: [MaybeUninit<c_char>; MAXHOSTNAMELEN] =
MaybeUninit::uninit().assume_init();
let status = libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len());
if status != 0 {
return None;
}
ptr::write(buffer[MAXHOSTNAMELEN - 1].as_mut_ptr() as *mut u8, b'\0'); // a little extra safety
let hostname_length = libc::strlen(buffer.as_ptr() as *const c_char);
let hostname = slice::from_raw_parts(buffer.as_ptr() as *const c_char, hostname_length);
self.xconn
.change_property(
self.xwindow,
@@ -482,15 +481,19 @@ impl UnownedWindow {
}
}
fn set_window_type(&self, window_type: util::WindowType) -> util::Flusher<'_> {
fn set_window_types(&self, window_types: Vec<util::WindowType>) -> util::Flusher<'_> {
let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") };
let window_type_atom = window_type.as_atom(&self.xconn);
let atoms: Vec<_> = window_types
.iter()
.map(|t| t.as_atom(&self.xconn))
.collect();
self.xconn.change_property(
self.xwindow,
hint_atom,
ffi::XA_ATOM,
util::PropMode::Replace,
&[window_type_atom],
&atoms,
)
}
@@ -548,44 +551,151 @@ impl UnownedWindow {
fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> {
let fullscreen_atom =
unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") };
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0));
if fullscreen {
// Ensure that the fullscreen window receives input focus to prevent
// locking up the user's display.
unsafe {
(self.xconn.xlib.XSetInputFocus)(
self.xconn.display,
self.xwindow,
ffi::RevertToParent,
ffi::CurrentTime,
);
}
}
flusher
}
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorHandle>) -> util::Flusher<'_> {
match monitor {
fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
let mut shared_state_lock = self.shared_state.lock();
if !shared_state_lock.is_visible {
// Setting fullscreen on a window that is not visible will generate an error.
return None;
}
let old_fullscreen = shared_state_lock.fullscreen.clone();
if old_fullscreen == fullscreen {
return None;
}
shared_state_lock.fullscreen = fullscreen.clone();
match (&old_fullscreen, &fullscreen) {
// Store the desktop video mode before entering exclusive
// fullscreen, so we can restore it upon exit, as XRandR does not
// provide a mechanism to set this per app-session or restore this
// to the desktop video mode as macOS and Windows do
(
&None,
&Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
})),
)
| (
&Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
})),
) => {
let monitor = video_mode.monitor.as_ref().unwrap();
shared_state_lock.desktop_video_mode =
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)));
}
// Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
self.xconn
.set_crtc_config(monitor_id, mode_id)
.expect("failed to restore desktop video mode");
}
_ => (),
}
drop(shared_state_lock);
match fullscreen {
None => {
let flusher = self.set_fullscreen_hint(false);
if let Some(position) = self.shared_state.lock().restore_position.take() {
let mut shared_state_lock = self.shared_state.lock();
if let Some(position) = shared_state_lock.restore_position.take() {
self.set_position_inner(position.0, position.1).queue();
}
flusher
Some(flusher)
}
Some(RootMonitorHandle {
inner: PlatformMonitorHandle::X(monitor),
}) => {
Some(fullscreen) => {
let (video_mode, monitor) = match fullscreen {
Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
}) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()),
Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::X(ref monitor),
}) => (None, monitor),
_ => unreachable!(),
};
// Don't set fullscreen on an invalid dummy monitor handle
if monitor.id == 0 {
return None;
}
if let Some(video_mode) = video_mode {
// FIXME: this is actually not correct if we're setting the
// video mode to a resolution higher than the current
// desktop resolution, because XRandR does not automatically
// reposition the monitors to the right and below this
// monitor.
//
// What ends up happening is we will get the fullscreen
// window showing up on those monitors as well, because
// their virtual position now overlaps with the monitor that
// we just made larger..
//
// It'd be quite a bit of work to handle this correctly (and
// nobody else seems to bother doing this correctly either),
// so we're just leaving this broken. Fixing this would
// involve storing all CRTCs upon entering fullscreen,
// restoring them upon exit, and after entering fullscreen,
// repositioning displays to the right and below this
// display. I think there would still be edge cases that are
// difficult or impossible to handle correctly, e.g. what if
// a new monitor was plugged in while in fullscreen?
//
// I think we might just want to disallow setting the video
// mode higher than the current desktop video mode (I'm sure
// this will make someone unhappy, but it's very unusual for
// games to want to do this anyway).
self.xconn
.set_crtc_config(monitor.id, video_mode.native_mode)
.expect("failed to set video mode");
}
let window_position = self.outer_position_physical();
self.shared_state.lock().restore_position = Some(window_position);
let monitor_origin: (i32, i32) = monitor.position().into();
self.set_position_inner(monitor_origin.0, monitor_origin.1)
.queue();
self.set_fullscreen_hint(true)
Some(self.set_fullscreen_hint(true))
}
_ => unreachable!(),
}
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
self.shared_state.lock().fullscreen.clone()
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
self.shared_state.lock().fullscreen = monitor.clone();
self.set_fullscreen_inner(monitor)
.flush()
.expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents();
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
flusher
.sync()
.expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents();
}
}
fn get_rect(&self) -> util::AaRect {
@@ -599,11 +709,11 @@ impl UnownedWindow {
pub fn current_monitor(&self) -> X11MonitorHandle {
let monitor = self.shared_state.lock().last_monitor.as_ref().cloned();
monitor.unwrap_or_else(|| {
let monitor = self
.xconn
.get_monitor_for_window(Some(self.get_rect()))
.to_owned();
self.shared_state.lock().last_monitor = Some(monitor.clone());
let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect()));
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
self.shared_state.lock().last_monitor = Some(monitor.clone());
}
monitor
})
}
@@ -738,12 +848,22 @@ impl UnownedWindow {
#[inline]
pub fn set_visible(&self, visible: bool) {
let is_visible = self.shared_state.lock().is_visible;
if visible == is_visible {
return;
}
match visible {
true => unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn
.flush_requests()
.expect("Failed to call XMapRaised");
// Some X requests may generate an error if the window is not
// visible, so we must wait until the window becomes visible.
self.wait_for_visibility_notify();
},
false => unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
@@ -752,6 +872,21 @@ impl UnownedWindow {
.expect("Failed to call XUnmapWindow");
},
}
self.shared_state.lock().is_visible = visible;
}
fn wait_for_visibility_notify(&self) {
unsafe {
let mut event = MaybeUninit::uninit();
(self.xconn.xlib.XIfEvent)(
self.xconn.display,
event.as_mut_ptr(),
Some(visibility_predicate),
self.xwindow as _,
);
}
}
fn update_cached_frame_extents(&self) {
@@ -1025,131 +1160,14 @@ impl UnownedWindow {
unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ }
}
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
unsafe {
(self.xconn.xcursor.XcursorLibraryLoadCursor)(
self.xconn.display,
name.as_ptr() as *const c_char,
)
}
}
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
for name in names.iter() {
let xcursor = self.load_cursor(name);
if xcursor != 0 {
return xcursor;
}
}
0
}
fn get_cursor(&self, cursor: CursorIcon) -> ffi::Cursor {
let load = |name: &[u8]| self.load_cursor(name);
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
// Try multiple names in some cases where the name
// differs on the desktop environments or themes.
//
// Try the better looking (or more suiting) names first.
match cursor {
CursorIcon::Alias => load(b"link\0"),
CursorIcon::Arrow => load(b"arrow\0"),
CursorIcon::Cell => load(b"plus\0"),
CursorIcon::Copy => load(b"copy\0"),
CursorIcon::Crosshair => load(b"crosshair\0"),
CursorIcon::Default => load(b"left_ptr\0"),
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
CursorIcon::Help => load(b"question_arrow\0"),
CursorIcon::Move => load(b"move\0"),
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
CursorIcon::Progress => load(b"left_ptr_watch\0"),
CursorIcon::AllScroll => load(b"all-scroll\0"),
CursorIcon::ContextMenu => load(b"context-menu\0"),
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
// Resize cursors
CursorIcon::EResize => load(b"right_side\0"),
CursorIcon::NResize => load(b"top_side\0"),
CursorIcon::NeResize => load(b"top_right_corner\0"),
CursorIcon::NwResize => load(b"top_left_corner\0"),
CursorIcon::SResize => load(b"bottom_side\0"),
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
CursorIcon::VerticalText => load(b"vertical-text\0"),
CursorIcon::Wait => load(b"watch\0"),
CursorIcon::ZoomIn => load(b"zoom-in\0"),
CursorIcon::ZoomOut => load(b"zoom-out\0"),
}
}
fn update_cursor(&self, cursor: ffi::Cursor) {
unsafe {
(self.xconn.xlib.XDefineCursor)(self.xconn.display, self.xwindow, cursor);
if cursor != 0 {
(self.xconn.xlib.XFreeCursor)(self.xconn.display, cursor);
}
self.xconn
.flush_requests()
.expect("Failed to set or free the cursor");
}
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
*self.cursor.lock() = cursor;
if *self.cursor_visible.lock() {
self.update_cursor(self.get_cursor(cursor));
let old_cursor = replace(&mut *self.cursor.lock(), cursor);
if cursor != old_cursor && *self.cursor_visible.lock() {
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
}
}
// TODO: This could maybe be cached. I don't think it's worth
// the complexity, since cursor changes are not so common,
// and this is just allocating a 1x1 pixmap...
fn create_empty_cursor(&self) -> Option<ffi::Cursor> {
let data = 0;
let pixmap = unsafe {
(self.xconn.xlib.XCreateBitmapFromData)(self.xconn.display, self.xwindow, &data, 1, 1)
};
if pixmap == 0 {
// Failed to allocate
return None;
}
let cursor = unsafe {
// We don't care about this color, since it only fills bytes
// in the pixmap which are not 0 in the mask.
let dummy_color: ffi::XColor = mem::uninitialized();
let cursor = (self.xconn.xlib.XCreatePixmapCursor)(
self.xconn.display,
pixmap,
pixmap,
&dummy_color as *const _ as *mut _,
&dummy_color as *const _ as *mut _,
0,
0,
);
(self.xconn.xlib.XFreePixmap)(self.xconn.display, pixmap);
cursor
};
Some(cursor)
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed.lock();
@@ -1219,14 +1237,13 @@ impl UnownedWindow {
return;
}
let cursor = if visible {
self.get_cursor(*self.cursor.lock())
Some(*self.cursor.lock())
} else {
self.create_empty_cursor()
.expect("Failed to create empty cursor")
None
};
*visible_lock = visible;
drop(visible_lock);
self.update_cursor(cursor);
self.xconn.set_cursor_icon(self.xwindow, cursor);
}
#[inline]
@@ -1277,4 +1294,13 @@ impl UnownedWindow {
.unwrap()
.insert(WindowId(self.xwindow));
}
#[inline]
pub fn raw_window_handle(&self) -> XlibHandle {
XlibHandle {
window: self.xwindow,
display: self.xconn.display as _,
..XlibHandle::empty()
}
}
}

View File

@@ -1,8 +1,10 @@
use std::{error::Error, fmt, os::raw::c_int, ptr};
use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr};
use libc;
use parking_lot::Mutex;
use crate::window::CursorIcon;
use super::ffi;
/// A connection to an X server.
@@ -19,6 +21,7 @@ pub struct XConnection {
pub display: *mut ffi::Display,
pub x11_fd: c_int,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
}
unsafe impl Send for XConnection {}
@@ -64,6 +67,7 @@ impl XConnection {
display,
x11_fd: fd,
latest_error: Mutex::new(None),
cursor_cache: Default::default(),
})
}

View File

@@ -3,6 +3,7 @@ use std::{
fmt::{self, Debug},
hint::unreachable_unchecked,
mem,
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Mutex, MutexGuard,
@@ -37,13 +38,13 @@ pub trait EventHandler: Debug {
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<F, T: 'static> {
callback: F,
struct EventLoopHandler<T: 'static> {
callback: Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
will_exit: bool,
window_target: RootWindowTarget<T>,
window_target: Rc<RootWindowTarget<T>>,
}
impl<F, T> Debug for EventLoopHandler<F, T> {
impl<T> Debug for EventLoopHandler<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("EventLoopHandler")
@@ -52,11 +53,7 @@ impl<F, T> Debug for EventLoopHandler<F, T> {
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.callback)(event.userify(), &self.window_target, control_flow);
self.will_exit |= *control_flow == ControlFlow::Exit;
@@ -87,7 +84,6 @@ struct Handler {
start_time: Mutex<Option<Instant>>,
callback: Mutex<Option<Box<dyn EventHandler>>>,
pending_events: Mutex<VecDeque<Event<Never>>>,
deferred_events: Mutex<VecDeque<Event<Never>>>,
pending_redraw: Mutex<Vec<WindowId>>,
waker: Mutex<EventLoopWaker>,
}
@@ -100,10 +96,6 @@ impl Handler {
self.pending_events.lock().unwrap()
}
fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
self.deferred_events.lock().unwrap()
}
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
self.pending_redraw.lock().unwrap()
}
@@ -148,10 +140,6 @@ impl Handler {
mem::replace(&mut *self.events(), Default::default())
}
fn take_deferred(&self) -> VecDeque<Event<Never>> {
mem::replace(&mut *self.deferred(), Default::default())
}
fn should_redraw(&self) -> Vec<WindowId> {
mem::replace(&mut *self.redraw(), Default::default())
}
@@ -180,13 +168,20 @@ impl Handler {
pub enum AppState {}
impl AppState {
pub fn set_callback<F, T>(callback: F, window_target: RootWindowTarget<T>)
// This function extends lifetime of `callback` to 'static as its side effect
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
T: 'static,
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback,
// This transmute is always safe, in case it was reached through `run`, since our
// lifetime will be already 'static. In other cases caller should ensure that all data
// they passed to callback will actually outlive it, some apps just can't move
// everything to event loop, so this is something that they should care about.
callback: mem::transmute::<
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
>(Box::new(callback)),
will_exit: false,
window_target,
}));
@@ -196,6 +191,7 @@ impl AppState {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(Event::LoopDestroyed);
HANDLER.set_in_callback(false);
HANDLER.callback.lock().unwrap().take();
}
pub fn launched() {
@@ -259,20 +255,6 @@ impl AppState {
HANDLER.events().append(&mut events);
}
pub fn send_event_immediately(event: Event<Never>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Event sent from different thread: {:#?}", event);
}
HANDLER.deferred().push_back(event);
if !HANDLER.get_in_callback() {
HANDLER.set_in_callback(true);
for event in HANDLER.take_deferred() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.set_in_callback(false);
}
}
pub fn cleared() {
if !HANDLER.is_ready() {
return;
@@ -298,7 +280,7 @@ impl AppState {
}
HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() {
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(),
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
(old, new) if old == new => (),
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),

View File

@@ -1,5 +1,6 @@
use std::{
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, sync::mpsc,
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc,
sync::mpsc,
};
use cocoa::{
@@ -34,7 +35,7 @@ impl<T> Default for EventLoopWindowTarget<T> {
}
pub struct EventLoop<T: 'static> {
window_target: RootWindowTarget<T>,
window_target: Rc<RootWindowTarget<T>>,
_delegate: IdRef,
}
@@ -59,10 +60,10 @@ impl<T> EventLoop<T> {
};
setup_control_flow_observers();
EventLoop {
window_target: RootWindowTarget {
window_target: Rc::new(RootWindowTarget {
p: Default::default(),
_marker: PhantomData,
},
}),
_delegate: delegate,
}
}
@@ -81,46 +82,50 @@ impl<T> EventLoop<T> {
&self.window_target
}
pub fn run<F>(self, callback: F) -> !
pub fn run<F>(mut self, callback: F) -> !
where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
self.run_return(callback);
process::exit(0);
}
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let _pool = NSAutoreleasePool::new(nil);
let app = NSApp();
assert_ne!(app, nil);
AppState::set_callback(callback, self.window_target);
AppState::set_callback(callback, Rc::clone(&self.window_target));
let _: () = msg_send![app, run];
AppState::exit();
process::exit(0)
}
}
pub fn run_return<F>(&mut self, _callback: F)
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unimplemented!();
}
pub fn create_proxy(&self) -> Proxy<T> {
Proxy::new(self.window_target.p.sender.clone())
}
}
#[derive(Clone)]
pub struct Proxy<T> {
sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl<T> Send for Proxy<T> {}
unsafe impl<T> Sync for Proxy<T> {}
unsafe impl<T: Send> Send for Proxy<T> {}
impl<T> Clone for Proxy<T> {
fn clone(&self) -> Self {
Proxy::new(self.sender.clone())
}
}
impl<T> Proxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self {
unsafe {
// just wakeup the eventloop
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and

View File

@@ -6,6 +6,13 @@ use cocoa::{
base::id,
foundation::{NSInteger, NSUInteger},
};
use core_foundation::{
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
};
use core_graphics::{
base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef},
};
use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value();
@@ -108,3 +115,95 @@ pub enum NSWindowLevel {
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
}
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP";
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut libc::c_void;
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
}

View File

@@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc};
pub use self::{
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
monitor::MonitorHandle,
monitor::{MonitorHandle, VideoMode},
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
};
use crate::{

View File

@@ -1,25 +1,148 @@
use std::{collections::VecDeque, fmt};
use super::ffi;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::util::IdRef,
};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::{NSString, NSUInteger},
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode};
use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
base::{CFRelease, TCFType},
string::CFString,
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use core_video_sys::{
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode,
platform_impl::platform::util::IdRef,
};
#[derive(Clone)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode,
}
#[derive(Clone, PartialEq)]
impl PartialEq for VideoMode {
fn eq(&self, other: &Self) -> bool {
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate == other.refresh_rate
&& self.monitor == other.monitor
}
}
impl Eq for VideoMode {}
impl std::hash::Hash for VideoMode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate.hash(state);
self.monitor.hash(state);
}
}
impl std::fmt::Debug for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoMode")
.field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate", &self.refresh_rate)
.field("monitor", &self.monitor)
.finish()
}
}
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID);
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
@@ -101,7 +224,7 @@ impl MonitorHandle {
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let cv_refresh_rate = unsafe {
let mut display_link = std::ptr::null_mut();
assert_eq!(
@@ -117,11 +240,27 @@ impl MonitorHandle {
time.timeScale as i64 / time.timeValue
};
CGDisplayMode::all_display_modes(self.0, std::ptr::null())
.expect("failed to obtain list of display modes")
.into_iter()
.map(move |mode| {
let cg_refresh_rate = mode.refresh_rate().round() as i64;
let monitor = self.clone();
unsafe {
let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.into_iter()
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
@@ -131,34 +270,55 @@ impl MonitorHandle {
cv_refresh_rate
};
VideoMode {
size: (mode.width() as u32, mode.height() as u32),
let pixel_encoding =
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode))
.to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
16
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
30
} else {
unimplemented!()
};
let video_mode = VideoMode {
size: (
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
),
refresh_rate: refresh_rate as u16,
bit_depth: mode.bit_depth() as u16,
}
bit_depth,
monitor: monitor.clone(),
native_mode: NativeDisplayMode(mode),
};
RootVideoMode { video_mode }
})
}
}
pub(crate) fn ns_screen(&self) -> Option<id> {
unsafe {
let native_id = self.native_identifier();
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let mut matching_screen: Option<id> = None;
for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen);
let value: id = msg_send![device_description, objectForKey:*key];
if value != nil {
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
if screen_number as u32 == native_id {
matching_screen = Some(screen);
break;
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
let other_uuid =
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
if uuid == other_uuid {
return Some(screen);
}
}
}
matching_screen
None
}
}
}

View File

@@ -1,10 +1,9 @@
use std::{self, os::raw::*, ptr, time::Instant};
use crate::platform_impl::platform::app_state::AppState;
use crate::platform_impl::platform::{app_state::AppState, ffi};
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
@@ -13,7 +12,7 @@ extern "C" {
pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
repeats: ffi::Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
@@ -51,11 +50,6 @@ extern "C" {
pub fn CFRelease(cftype: *const c_void);
}
pub type Boolean = u8;
#[allow(dead_code)]
const FALSE: Boolean = 0;
const TRUE: Boolean = 1;
pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
@@ -102,7 +96,7 @@ pub struct CFRunLoopSourceContext {
pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
@@ -162,12 +156,12 @@ impl RunLoop {
let observer = CFRunLoopObserverCreate(
ptr::null_mut(),
flags,
TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode);
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
}
}
@@ -204,9 +198,9 @@ impl Default for EventLoopWaker {
fn default() -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// create a timer with a 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 immediatley in did_finish_launching
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,

View File

@@ -206,7 +206,10 @@ extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
}
}
}
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
context.ns_window.setLevel_(0);
context.ns_window.toggleFullScreen_(nil);
}
Box::from_raw(context_ptr);
@@ -228,6 +231,21 @@ pub unsafe fn toggle_full_screen_async(
);
}
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
unsafe {
let screen = Box::from_raw(screen as *mut u32);
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
}
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
dispatch_async_f(
dispatch_get_main_queue(),
Box::into_raw(Box::new(ns_screen)) as *mut _,
restore_display_mode_callback,
);
}
struct SetMaximizedData {
ns_window: id,
is_zoomed: bool,

View File

@@ -15,10 +15,12 @@ pub enum Cursor {
impl From<CursorIcon> for Cursor {
fn from(cursor: CursorIcon) -> Self {
// See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc.
match cursor {
CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"),
CursorIcon::Hand => Cursor::Native("pointingHandCursor"),
CursorIcon::Grabbing | CursorIcon::Grab => Cursor::Native("closedHandCursor"),
CursorIcon::Grab => Cursor::Native("openHandCursor"),
CursorIcon::Grabbing => Cursor::Native("closedHandCursor"),
CursorIcon::Text => Cursor::Native("IBeamCursor"),
CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
CursorIcon::Copy => Cursor::Native("dragCopyCursor"),

View File

@@ -104,7 +104,7 @@ lazy_static! {
);
decl.add_method(
sel!(drawRect:),
draw_rect as extern "C" fn(&Object, Sel, id),
draw_rect as extern "C" fn(&Object, Sel, NSRect),
);
decl.add_method(
sel!(acceptsFirstResponder),
@@ -280,7 +280,7 @@ extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) {
trace!("Completed `viewDidMoveToWindow`");
}
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: id) {
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);

View File

@@ -1,3 +1,4 @@
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
use std::{
collections::VecDeque,
f64,
@@ -8,6 +9,23 @@ use std::{
},
};
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
ffi,
monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef},
view::{self, new_view},
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
};
use cocoa::{
appkit::{
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
@@ -17,30 +35,12 @@ use cocoa::{
base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
};
use core_graphics::display::CGDisplay;
use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel, BOOL, NO, YES},
};
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
ffi,
monitor::{self, MonitorHandle},
util::{self, IdRef},
view::{self, new_view},
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, WindowAttributes, WindowId as RootWindowId},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub usize);
@@ -66,6 +66,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub titlebar_buttons_hidden: bool,
pub fullsize_content_view: bool,
pub resize_increments: Option<LogicalSize>,
pub disallow_hidpi: bool,
}
fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
@@ -86,10 +87,15 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
}
}
unsafe fn create_view(ns_window: id) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
unsafe fn create_view(
ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
let (ns_view, cursor) = new_view(ns_window);
ns_view.non_nil().map(|ns_view| {
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
if !pl_attribs.disallow_hidpi {
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
}
// On Mojave, views automatically become layer-backed shortly after being added to
// a window. Changing the layer-backedness of a view breaks the association between
@@ -113,11 +119,14 @@ fn create_window(
unsafe {
let pool = NSAutoreleasePool::new(nil);
let screen = match attrs.fullscreen {
Some(ref monitor_id) => {
let monitor_screen = monitor_id.inner.ns_screen();
Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
| Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: VideoMode { ref monitor, .. },
})) => {
let monitor_screen = monitor.ns_screen();
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
}
_ => None,
None => None,
};
let frame = match screen {
Some(screen) => appkit::NSScreen::frame(screen),
@@ -233,12 +242,15 @@ lazy_static! {
#[derive(Default)]
pub struct SharedState {
pub resizable: bool,
pub fullscreen: Option<RootMonitorHandle>,
pub fullscreen: Option<Fullscreen>,
pub maximized: bool,
pub standard_frame: Option<NSRect>,
is_simple_fullscreen: bool,
pub saved_style: Option<NSWindowStyleMask>,
/// Presentation options saved before entering `set_simple_fullscreen`, and
/// restored upon exiting it
save_presentation_opts: Option<NSApplicationPresentationOptions>,
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
}
impl SharedState {
@@ -301,10 +313,11 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
})?;
let (ns_view, cursor) = unsafe { create_view(*ns_window) }.ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSView`"))
})?;
let (ns_view, cursor) =
unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSView`"))
})?;
let input_context = unsafe { util::create_input_context(*ns_view) };
@@ -355,16 +368,7 @@ impl UnownedWindow {
let delegate = new_delegate(&window, fullscreen.is_some());
// Set fullscreen mode after we setup everything
if let Some(monitor) = fullscreen {
if monitor.inner != window.current_monitor().inner {
// To do this with native fullscreen, we probably need to
// warp the window... while we could use
// `enterFullScreenMode`, they're idiomatically different
// fullscreen modes, so we'd have to support both anyway.
unimplemented!();
}
window.set_fullscreen(Some(monitor));
}
window.set_fullscreen(fullscreen);
// Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size
@@ -594,19 +598,22 @@ impl UnownedWindow {
}
}
/// This is called when the window is exiting fullscreen, whether by the
/// user clicking on the green fullscreen button or programmatically by
/// `toggleFullScreen:`
pub(crate) fn restore_state_from_fullscreen(&self) {
let maximized = {
trace!("Locked shared state in `restore_state_from_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
trace!("Locked shared state in `restore_state_from_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = None;
shared_state_lock.fullscreen = None;
let mask = self.saved_style(&mut *shared_state_lock);
let maximized = shared_state_lock.maximized;
let mask = self.saved_style(&mut *shared_state_lock);
self.set_style_mask_async(mask);
shared_state_lock.maximized
};
drop(shared_state_lock);
trace!("Unocked shared state in `restore_state_from_fullscreen`");
self.set_style_mask_async(mask);
self.set_maximized(maximized);
}
@@ -627,44 +634,168 @@ impl UnownedWindow {
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
let shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen.clone()
}
#[inline]
/// TODO: Right now set_fullscreen do not work on switching monitors
/// in fullscreen mode
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
trace!("Locked shared state in `set_fullscreen`");
let shared_state_lock = self.shared_state.lock().unwrap();
if shared_state_lock.is_simple_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");
return;
}
let not_fullscreen = {
trace!("Locked shared state in `set_fullscreen`");
let current = &shared_state_lock.fullscreen;
match (current, monitor) {
(&Some(ref a), Some(ref b)) if a.inner != b.inner => {
// Our best bet is probably to move to the origin of the
// target monitor.
unimplemented!()
}
(&None, None) | (&Some(_), Some(_)) => return,
_ => (),
}
let old_fullscreen = shared_state_lock.fullscreen.clone();
if fullscreen == old_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");
current.is_none()
};
return;
}
trace!("Unlocked shared state in `set_fullscreen`");
drop(shared_state_lock);
unsafe {
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
not_fullscreen,
Arc::downgrade(&self.shared_state),
)
};
// If the fullscreen is on a different monitor, we must move the window
// to that monitor before we toggle fullscreen (as `toggleFullScreen`
// does not take a screen parameter, but uses the current screen)
if let Some(ref fullscreen) = fullscreen {
let new_screen = match fullscreen {
Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor,
Fullscreen::Exclusive(RootVideoMode {
video_mode: VideoMode { ref monitor, .. },
}) => monitor,
}
.ns_screen()
.unwrap();
unsafe {
let old_screen = NSWindow::screen(*self.ns_window);
if old_screen != new_screen {
let mut screen_frame: NSRect = msg_send![new_screen, frame];
// The coordinate system here has its origin at bottom-left
// and Y goes up
screen_frame.origin.y += screen_frame.size.height;
util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin);
}
}
}
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
// same thing as we're doing here (captures the display, sets the
// video mode, and hides the menu bar and dock), with the exception
// of that I couldn't figure out how to set the display mode with
// it. I think `enterFullScreenMode:withOptions:` is still using the
// older display mode API where display modes were of the type
// `CFDictionary`, but this has changed, so we can't obtain the
// correct parameter for this any longer. Apple's code samples for
// this function seem to just pass in "YES" for the display mode
// parameter, which is not consistent with the docs saying that it
// takes a `NSDictionary`..
let display_id = video_mode.monitor().inner.native_identifier();
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
unsafe {
// Fade to black (and wait for the fade to complete) to hide the
// flicker from capturing the display and switching display mode
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
== ffi::kCGErrorSuccess
{
ffi::CGDisplayFade(
fade_token,
0.3,
ffi::kCGDisplayBlendNormal,
ffi::kCGDisplayBlendSolidColor,
0.0,
0.0,
0.0,
ffi::TRUE,
);
}
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
}
unsafe {
let result = ffi::CGDisplaySetDisplayMode(
display_id,
video_mode.video_mode.native_mode.0,
std::ptr::null(),
);
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
// After the display has been configured, fade back in
// asynchronously
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
ffi::CGDisplayFade(
fade_token,
0.6,
ffi::kCGDisplayBlendSolidColor,
ffi::kCGDisplayBlendNormal,
0.0,
0.0,
0.0,
ffi::FALSE,
);
ffi::CGReleaseDisplayFadeReservation(fade_token);
}
}
}
trace!("Locked shared state in `set_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = fullscreen.clone();
trace!("Unlocked shared state in `set_fullscreen`");
match (&old_fullscreen, &fullscreen) {
(&None, &Some(_)) => unsafe {
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Borderless(_)), &None) => unsafe {
// State is restored by `window_did_exit_fullscreen`
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe {
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
// Rest of the state is restored by `window_did_exit_fullscreen`
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe {
// If we're already in fullscreen mode, calling
// `CGDisplayCapture` will place the shielding window on top of
// our window, which results in a black display and is not what
// we want. So, we must place our window on top of the shielding
// window. Unfortunately, this also makes our window be on top
// of the menu bar, and this looks broken, so we must make sure
// that the menu bar is disabled. This is done in the window
// delegate in `window:willUseFullScreenPresentationOptions:`.
let () = msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1];
},
(
&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })),
&Some(Fullscreen::Borderless(_)),
) => unsafe {
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
},
_ => (),
}
}
#[inline]
@@ -764,6 +895,16 @@ impl UnownedWindow {
pub fn primary_monitor(&self) -> MonitorHandle {
monitor::primary_monitor()
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = MacOSHandle {
ns_window: *self.ns_window as *mut _,
ns_view: *self.ns_view as *mut _,
..MacOSHandle::empty()
};
RawWindowHandle::MacOS(handle)
}
}
impl WindowExtMacOS for UnownedWindow {

View File

@@ -5,9 +5,9 @@ use std::{
};
use cocoa::{
appkit::{self, NSView, NSWindow},
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
base::{id, nil},
foundation::NSAutoreleasePool,
foundation::{NSAutoreleasePool, NSUInteger},
};
use objc::{
declare::ClassDecl,
@@ -22,7 +22,7 @@ use crate::{
util::{self, IdRef},
window::{get_window_id, UnownedWindow},
},
window::WindowId,
window::{Fullscreen, WindowId},
};
pub struct WindowDelegateState {
@@ -84,11 +84,7 @@ impl WindowDelegateState {
pub fn emit_resize_event(&mut self) {
let rect = unsafe { NSView::frame(*self.ns_view) };
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*self.ns_window)),
event: WindowEvent::Resized(size),
};
AppState::send_event_immediately(event);
self.emit_event(WindowEvent::Resized(size));
}
fn emit_move_event(&mut self) {
@@ -182,6 +178,11 @@ lazy_static! {
dragging_exited as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(window:willUseFullScreenPresentationOptions:),
window_will_use_fullscreen_presentation_options
as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
);
decl.add_method(
sel!(windowDidEnterFullScreen:),
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
@@ -401,23 +402,54 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_will_enter_fullscreen`");
window.shared_state.lock().unwrap().maximized = window.is_zoomed();
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.maximized = window.is_zoomed();
match shared_state.fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (),
// `window_will_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state
Some(Fullscreen::Borderless(_)) => (),
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => {
shared_state.fullscreen = Some(Fullscreen::Borderless(window.current_monitor()))
}
}
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
})
});
trace!("Completed `windowWillEnterFullscreen:`");
}
extern "C" fn window_will_use_fullscreen_presentation_options(
_this: &Object,
_: Sel,
_: id,
_proposed_options: NSUInteger,
) -> NSUInteger {
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
// placed on top of the menu bar in exclusive fullscreen mode. This looks
// broken so we always disable the menu bar in exclusive fullscreen. We may
// still want to make this configurable for borderless fullscreen. Right now
// we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen.
(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
.bits()
}
/// Invoked when entered fullscreen
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidEnterFullscreen:`");
with_state(this, |state| {
state.with_window(|window| {
let monitor = window.current_monitor();
trace!("Locked shared state in `window_did_enter_fullscreen`");
window.shared_state.lock().unwrap().fullscreen = Some(monitor);
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
});
state.initial_fullscreen = false;
});
trace!("Completed `windowDidEnterFullscreen:`");

View File

@@ -21,8 +21,8 @@ mod platform;
#[cfg(target_os = "ios")]
#[path = "ios/mod.rs"]
mod platform;
#[cfg(target_os = "emscripten")]
#[path = "emscripten/mod.rs"]
#[cfg(target_arch = "wasm32")]
#[path = "web/mod.rs"]
mod platform;
#[cfg(all(
@@ -35,6 +35,6 @@ mod platform;
not(target_os = "freebsd"),
not(target_os = "netbsd"),
not(target_os = "openbsd"),
not(target_os = "emscripten")
not(target_arch = "wasm32"),
))]
compile_error!("The platform you're compiling for is not supported by winit");

View File

@@ -0,0 +1,8 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub i32);
impl Id {
pub unsafe fn dummy() -> Self {
Id(0)
}
}

View File

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,67 @@
mod proxy;
mod runner;
mod state;
mod window_target;
pub use self::proxy::Proxy;
pub use self::window_target::WindowTarget;
use super::{backend, device, monitor, window};
use crate::event::Event;
use crate::event_loop as root;
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
use std::marker::PhantomData;
pub struct EventLoop<T: 'static> {
elw: root::EventLoopWindowTarget<T>,
}
impl<T> EventLoop<T> {
pub fn new() -> Self {
EventLoop {
elw: root::EventLoopWindowTarget {
p: WindowTarget::new(),
_marker: PhantomData,
},
}
}
pub fn available_monitors(&self) -> VecDequeIter<monitor::Handle> {
VecDeque::new().into_iter()
}
pub fn primary_monitor(&self) -> monitor::Handle {
monitor::Handle
}
pub fn run<F>(self, mut event_handler: F) -> !
where
F: 'static + FnMut(Event<T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
{
let target = root::EventLoopWindowTarget {
p: self.elw.p.clone(),
_marker: PhantomData,
};
self.elw.p.run(Box::new(move |event, flow| {
event_handler(event, &target, flow)
}));
// Throw an exception to break out of Rust exceution 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 create_proxy(&self) -> Proxy<T> {
self.elw.p.proxy()
}
pub fn window_target(&self) -> &root::EventLoopWindowTarget<T> {
&self.elw
}
}

View File

@@ -0,0 +1,26 @@
use super::runner;
use crate::event::Event;
use crate::event_loop::EventLoopClosed;
pub struct Proxy<T: 'static> {
runner: runner::Shared<T>,
}
impl<T: 'static> Proxy<T> {
pub fn new(runner: runner::Shared<T>) -> Self {
Proxy { runner }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.runner.send_event(Event::UserEvent(event));
Ok(())
}
}
impl<T: 'static> Clone for Proxy<T> {
fn clone(&self) -> Self {
Proxy {
runner: self.runner.clone(),
}
}
}

View File

@@ -0,0 +1,254 @@
use super::{backend, state::State};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop as root;
use crate::window::WindowId;
use instant::{Duration, Instant};
use std::{
cell::RefCell,
clone::Clone,
collections::{HashSet, VecDeque},
rc::Rc,
};
pub struct Shared<T>(Rc<Execution<T>>);
impl<T> Clone for Shared<T> {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}
pub struct Execution<T> {
runner: RefCell<Option<Runner<T>>>,
events: RefCell<VecDeque<Event<T>>>,
id: RefCell<u32>,
redraw_pending: RefCell<HashSet<WindowId>>,
}
struct Runner<T> {
state: State,
is_busy: bool,
event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>,
}
impl<T: 'static> Runner<T> {
pub fn new(event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>) -> Self {
Runner {
state: State::Init,
is_busy: false,
event_handler,
}
}
}
impl<T: 'static> Shared<T> {
pub fn new() -> Self {
Shared(Rc::new(Execution {
runner: RefCell::new(None),
events: RefCell::new(VecDeque::new()),
id: RefCell::new(0),
redraw_pending: RefCell::new(HashSet::new()),
}))
}
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootEventLoopWindowTarget reference
pub fn set_listener(&self, event_handler: Box<dyn FnMut(Event<T>, &mut root::ControlFlow)>) {
self.0.runner.replace(Some(Runner::new(event_handler)));
self.send_event(Event::NewEvents(StartCause::Init));
let close_instance = self.clone();
backend::on_unload(move || close_instance.handle_unload());
}
// Generate a strictly increasing ID
// This is used to differentiate windows when handling events
pub fn generate_id(&self) -> u32 {
let mut id = self.0.id.borrow_mut();
*id += 1;
*id
}
pub fn request_redraw(&self, id: WindowId) {
self.0.redraw_pending.borrow_mut().insert(id);
}
// Add an event to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub fn send_event(&self, event: Event<T>) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
}
// Determine if event handling is in process, and then release the borrow on the runner
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
Some(ref runner) if !runner.is_busy => {
if let Event::NewEvents(cause) = event {
(cause, true)
} else {
(
match runner.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => {
return;
}
},
false,
)
}
}
_ => {
// Events are currently being handled, so queue this one and don't try to
// double-process the event queue
self.0.events.borrow_mut().push_back(event);
return;
}
};
let mut control = self.current_control_flow();
// Handle starting a new batch of events
//
// The user is informed via Event::NewEvents that there is a batch of events to process
// However, there is only one of these per batch of events
self.handle_event(Event::NewEvents(start_cause), &mut control);
if !event_is_start {
self.handle_event(event, &mut control);
}
// Collect all of the redraw events to avoid double-locking the RefCell
let redraw_events: Vec<WindowId> = self.0.redraw_pending.borrow_mut().drain().collect();
for window_id in redraw_events {
self.handle_event(
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
},
&mut control,
);
}
self.handle_event(Event::EventsCleared, &mut control);
self.apply_control_flow(control);
// If the event loop is closed, it has been closed this iteration and now the closing
// event should be emitted
if self.is_closed() {
self.handle_event(Event::LoopDestroyed, &mut control);
}
}
fn handle_unload(&self) {
self.apply_control_flow(root::ControlFlow::Exit);
let mut control = self.current_control_flow();
self.handle_event(Event::LoopDestroyed, &mut control);
}
// handle_event takes in events and either queues them or applies a callback
//
// It should only ever be called from send_event
fn handle_event(&self, event: Event<T>, control: &mut root::ControlFlow) {
let is_closed = self.is_closed();
match *self.0.runner.borrow_mut() {
Some(ref mut runner) => {
// An event is being processed, so the runner should be marked busy
runner.is_busy = true;
(runner.event_handler)(event, control);
// Maintain closed state, even if the callback changes it
if is_closed {
*control = root::ControlFlow::Exit;
}
// An event is no longer being processed
runner.is_busy = false;
}
// If an event is being handled without a runner somehow, add it to the event queue so
// it will eventually be processed
_ => self.0.events.borrow_mut().push_back(event),
}
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
// If the runner doesn't exist and this method recurses, it will recurse infinitely
if !is_closed && self.0.runner.borrow().is_some() {
// Take an event out of the queue and handle it
if let Some(event) = self.0.events.borrow_mut().pop_front() {
self.handle_event(event, control);
}
}
}
// Apply the new ControlFlow that has been selected by the user
// Start any necessary timeouts etc
fn apply_control_flow(&self, control_flow: root::ControlFlow) {
let new_state = match control_flow {
root::ControlFlow::Poll => {
let cloned = self.clone();
State::Poll {
timeout: backend::Timeout::new(
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
Duration::from_millis(0),
),
}
}
root::ControlFlow::Wait => State::Wait {
start: Instant::now(),
},
root::ControlFlow::WaitUntil(end) => {
let start = Instant::now();
let delay = if end <= start {
Duration::from_millis(0)
} else {
end - start
};
let cloned = self.clone();
State::WaitUntil {
start,
end,
timeout: backend::Timeout::new(
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
delay,
),
}
}
root::ControlFlow::Exit => State::Exit,
};
match *self.0.runner.borrow_mut() {
Some(ref mut runner) => {
runner.state = new_state;
}
None => (),
}
}
// Check if the event loop is currently closed
fn is_closed(&self) -> bool {
match *self.0.runner.borrow() {
Some(ref runner) => runner.state.is_exit(),
None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed
}
}
// Get the current control flow state
fn current_control_flow(&self) -> root::ControlFlow {
match *self.0.runner.borrow() {
Some(ref runner) => runner.state.control_flow(),
None => root::ControlFlow::Poll,
}
}
}

View File

@@ -0,0 +1,40 @@
use super::backend;
use crate::event_loop::ControlFlow;
use instant::Instant;
#[derive(Debug)]
pub enum State {
Init,
WaitUntil {
timeout: backend::Timeout,
start: Instant,
end: Instant,
},
Wait {
start: Instant,
},
Poll {
timeout: backend::Timeout,
},
Exit,
}
impl State {
pub fn is_exit(&self) -> bool {
match self {
State::Exit => true,
_ => false,
}
}
pub fn control_flow(&self) -> ControlFlow {
match self {
State::Init => ControlFlow::Poll,
State::WaitUntil { end, .. } => ControlFlow::WaitUntil(*end),
State::Wait { .. } => ControlFlow::Wait,
State::Poll { .. } => ControlFlow::Poll,
State::Exit => ControlFlow::Exit,
}
}
}

View File

@@ -0,0 +1,197 @@
use super::{backend, device, proxy::Proxy, runner, window};
use crate::dpi::LogicalSize;
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
use std::clone::Clone;
pub struct WindowTarget<T: 'static> {
pub(crate) runner: runner::Shared<T>,
}
impl<T> Clone for WindowTarget<T> {
fn clone(&self) -> Self {
WindowTarget {
runner: self.runner.clone(),
}
}
}
impl<T> WindowTarget<T> {
pub fn new() -> Self {
WindowTarget {
runner: runner::Shared::new(),
}
}
pub fn proxy(&self) -> Proxy<T> {
Proxy::new(self.runner.clone())
}
pub fn run(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
self.runner.set_listener(event_handler);
}
pub fn generate_id(&self) -> window::Id {
window::Id(self.runner.generate_id())
}
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
let runner = self.runner.clone();
canvas.set_attribute("data-raw-handle", &id.0.to_string());
canvas.on_blur(move || {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::Focused(false),
});
});
let runner = self.runner.clone();
canvas.on_focus(move || {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::Focused(true),
});
});
let runner = self.runner.clone();
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
device_id: DeviceId(unsafe { device::Id::dummy() }),
input: KeyboardInput {
scancode,
state: ElementState::Pressed,
virtual_keycode,
modifiers,
},
},
});
});
let runner = self.runner.clone();
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
device_id: DeviceId(unsafe { device::Id::dummy() }),
input: KeyboardInput {
scancode,
state: ElementState::Released,
virtual_keycode,
modifiers,
},
},
});
});
let runner = self.runner.clone();
canvas.on_received_character(move |char_code| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::ReceivedCharacter(char_code),
});
});
let runner = self.runner.clone();
canvas.on_cursor_leave(move |pointer_id| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorLeft {
device_id: DeviceId(device::Id(pointer_id)),
},
});
});
let runner = self.runner.clone();
canvas.on_cursor_enter(move |pointer_id| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorEntered {
device_id: DeviceId(device::Id(pointer_id)),
},
});
});
let runner = self.runner.clone();
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorMoved {
device_id: DeviceId(device::Id(pointer_id)),
position,
modifiers,
},
});
});
let runner = self.runner.clone();
canvas.on_mouse_press(move |pointer_id, button, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseInput {
device_id: DeviceId(device::Id(pointer_id)),
state: ElementState::Pressed,
button,
modifiers,
},
});
});
let runner = self.runner.clone();
canvas.on_mouse_release(move |pointer_id, button, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseInput {
device_id: DeviceId(device::Id(pointer_id)),
state: ElementState::Released,
button,
modifiers,
},
});
});
let runner = self.runner.clone();
canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseWheel {
device_id: DeviceId(device::Id(pointer_id)),
delta,
phase: TouchPhase::Moved,
modifiers,
},
});
});
let runner = self.runner.clone();
let raw = canvas.raw().clone();
let mut intended_size = LogicalSize {
width: raw.width() as f64,
height: raw.height() as f64,
};
canvas.on_fullscreen_change(move || {
// If the canvas is marked as fullscreen, it is moving *into* fullscreen
// If it is not, it is moving *out of* fullscreen
let new_size = if backend::is_fullscreen(&raw) {
intended_size = LogicalSize {
width: raw.width() as f64,
height: raw.height() as f64,
};
backend::window_size()
} else {
intended_size
};
raw.set_width(new_size.width as u32);
raw.set_height(new_size.height as u32);
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::Resized(new_size),
});
runner.request_redraw(WindowId(id));
});
}
}

View File

@@ -0,0 +1,28 @@
// TODO: close events (port from old stdweb branch)
// TODO: pointer locking (stdweb PR required)
// TODO: fullscreen API (stdweb PR required)
mod device;
mod error;
mod event_loop;
mod monitor;
mod window;
#[cfg(feature = "web-sys")]
#[path = "web_sys/mod.rs"]
mod backend;
#[cfg(feature = "stdweb")]
#[path = "stdweb/mod.rs"]
mod backend;
pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub use self::event_loop::{
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
};
pub use self::monitor::{Handle as MonitorHandle, Mode as VideoMode};
pub use self::window::{
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
Window,
};

View File

@@ -0,0 +1,51 @@
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandle, VideoMode};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Handle;
impl Handle {
pub fn hidpi_factor(&self) -> f64 {
1.0
}
pub fn position(&self) -> PhysicalPosition {
PhysicalPosition { x: 0.0, y: 0.0 }
}
pub fn name(&self) -> Option<String> {
None
}
pub fn size(&self) -> PhysicalSize {
PhysicalSize {
width: 0.0,
height: 0.0,
}
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
std::iter::empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Mode;
impl Mode {
pub fn size(&self) -> PhysicalSize {
unimplemented!();
}
pub fn bit_depth(&self) -> u16 {
unimplemented!();
}
pub fn refresh_rate(&self) -> u16 {
32
}
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle { inner: Handle }
}
}

View File

@@ -0,0 +1,282 @@
use super::event;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use std::cell::RefCell;
use std::rc::Rc;
use stdweb::traits::IPointerEvent;
use stdweb::unstable::TryInto;
use stdweb::web::event::{
BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, KeyDownEvent, KeyPressEvent,
KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, PointerOutEvent,
PointerOverEvent, PointerUpEvent,
};
use stdweb::web::html_element::CanvasElement;
use stdweb::web::{
document, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement,
};
pub struct Canvas {
raw: CanvasElement,
on_focus: Option<EventListenerHandle>,
on_blur: Option<EventListenerHandle>,
on_keyboard_release: Option<EventListenerHandle>,
on_keyboard_press: Option<EventListenerHandle>,
on_received_character: Option<EventListenerHandle>,
on_cursor_leave: Option<EventListenerHandle>,
on_cursor_enter: Option<EventListenerHandle>,
on_cursor_move: Option<EventListenerHandle>,
on_mouse_press: Option<EventListenerHandle>,
on_mouse_release: Option<EventListenerHandle>,
on_mouse_wheel: Option<EventListenerHandle>,
on_fullscreen_change: Option<EventListenerHandle>,
wants_fullscreen: Rc<RefCell<bool>>,
}
impl Drop for Canvas {
fn drop(&mut self) {
self.raw.remove();
}
}
impl Canvas {
pub fn create() -> Result<Self, RootOE> {
let canvas: CanvasElement = document()
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.try_into()
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
// sequential keyboard navigation, but its order is defined by the
// document's source order.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
canvas
.set_attribute("tabindex", "0")
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
Ok(Canvas {
raw: canvas,
on_blur: None,
on_focus: None,
on_keyboard_release: None,
on_keyboard_press: None,
on_received_character: None,
on_cursor_leave: None,
on_cursor_enter: None,
on_cursor_move: None,
on_mouse_release: None,
on_mouse_press: None,
on_mouse_wheel: None,
on_fullscreen_change: None,
wants_fullscreen: Rc::new(RefCell::new(false)),
})
}
pub fn set_attribute(&self, attribute: &str, value: &str) {
self.raw
.set_attribute(attribute, value)
.expect(&format!("Set attribute: {}", attribute));
}
pub fn position(&self) -> (f64, f64) {
let bounds = self.raw.get_bounding_client_rect();
(bounds.get_x(), bounds.get_y())
}
pub fn width(&self) -> f64 {
self.raw.width() as f64
}
pub fn height(&self) -> f64 {
self.raw.height() as f64
}
pub fn set_size(&self, size: LogicalSize) {
self.raw.set_width(size.width as u32);
self.raw.set_height(size.height as u32);
}
pub fn raw(&self) -> &CanvasElement {
&self.raw
}
pub fn on_blur<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_blur = Some(self.add_event(move |_: BlurEvent| {
handler();
}));
}
pub fn on_focus<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_focus = Some(self.add_event(move |_: FocusEvent| {
handler();
}));
}
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
pub fn on_received_character<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(char),
{
// TODO: Use `beforeinput`.
//
// The `keypress` event is deprecated, but there does not seem to be a
// viable/compatible alternative as of now. `beforeinput` is still widely
// unsupported.
self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| {
handler(event::codepoint(&event));
}));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
handler(event.pointer_id());
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
handler(event.pointer_id());
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
{
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
}));
}
pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler()));
}
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
where
E: ConcreteEvent,
F: 'static + FnMut(E),
{
self.raw.add_event_listener(move |event: E| {
event.stop_propagation();
event.cancel_bubble();
handler(event);
})
}
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
fn add_user_event<E, F>(&self, mut handler: F) -> EventListenerHandle
where
E: ConcreteEvent,
F: 'static + FnMut(E),
{
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
self.add_event(move |event: E| {
handler(event);
if *wants_fullscreen.borrow() {
canvas.request_fullscreen();
*wants_fullscreen.borrow_mut() = false;
}
})
}
pub fn request_fullscreen(&self) {
*self.wants_fullscreen.borrow_mut() = true;
}
pub fn is_fullscreen(&self) -> bool {
super::is_fullscreen(&self.raw)
}
}

View File

@@ -0,0 +1,229 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
use stdweb::{js, unstable::TryInto, JsSerialize};
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
match event.button() {
stdweb::web::event::MouseButton::Left => MouseButton::Left,
stdweb::web::event::MouseButton::Right => MouseButton::Right,
stdweb::web::event::MouseButton::Wheel => MouseButton::Middle,
stdweb::web::event::MouseButton::Button4 => MouseButton::Other(0),
stdweb::web::event::MouseButton::Button5 => MouseButton::Other(1),
}
}
pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
}
pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
LogicalPosition {
x: event.offset_x() as f64,
y: event.offset_y() as f64,
}
}
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
let x = event.delta_x();
let y = event.delta_y();
match event.delta_mode() {
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
MouseWheelDeltaMode::Page => None,
}
}
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
let key_code = js! ( return @{event}.keyCode; );
key_code
.try_into()
.expect("The which value should be a number")
}
pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
Some(match &event.code()[..] {
"Digit1" => VirtualKeyCode::Key1,
"Digit2" => VirtualKeyCode::Key2,
"Digit3" => VirtualKeyCode::Key3,
"Digit4" => VirtualKeyCode::Key4,
"Digit5" => VirtualKeyCode::Key5,
"Digit6" => VirtualKeyCode::Key6,
"Digit7" => VirtualKeyCode::Key7,
"Digit8" => VirtualKeyCode::Key8,
"Digit9" => VirtualKeyCode::Key9,
"Digit0" => VirtualKeyCode::Key0,
"KeyA" => VirtualKeyCode::A,
"KeyB" => VirtualKeyCode::B,
"KeyC" => VirtualKeyCode::C,
"KeyD" => VirtualKeyCode::D,
"KeyE" => VirtualKeyCode::E,
"KeyF" => VirtualKeyCode::F,
"KeyG" => VirtualKeyCode::G,
"KeyH" => VirtualKeyCode::H,
"KeyI" => VirtualKeyCode::I,
"KeyJ" => VirtualKeyCode::J,
"KeyK" => VirtualKeyCode::K,
"KeyL" => VirtualKeyCode::L,
"KeyM" => VirtualKeyCode::M,
"KeyN" => VirtualKeyCode::N,
"KeyO" => VirtualKeyCode::O,
"KeyP" => VirtualKeyCode::P,
"KeyQ" => VirtualKeyCode::Q,
"KeyR" => VirtualKeyCode::R,
"KeyS" => VirtualKeyCode::S,
"KeyT" => VirtualKeyCode::T,
"KeyU" => VirtualKeyCode::U,
"KeyV" => VirtualKeyCode::V,
"KeyW" => VirtualKeyCode::W,
"KeyX" => VirtualKeyCode::X,
"KeyY" => VirtualKeyCode::Y,
"KeyZ" => VirtualKeyCode::Z,
"Escape" => VirtualKeyCode::Escape,
"F1" => VirtualKeyCode::F1,
"F2" => VirtualKeyCode::F2,
"F3" => VirtualKeyCode::F3,
"F4" => VirtualKeyCode::F4,
"F5" => VirtualKeyCode::F5,
"F6" => VirtualKeyCode::F6,
"F7" => VirtualKeyCode::F7,
"F8" => VirtualKeyCode::F8,
"F9" => VirtualKeyCode::F9,
"F10" => VirtualKeyCode::F10,
"F11" => VirtualKeyCode::F11,
"F12" => VirtualKeyCode::F12,
"F13" => VirtualKeyCode::F13,
"F14" => VirtualKeyCode::F14,
"F15" => VirtualKeyCode::F15,
"F16" => VirtualKeyCode::F16,
"F17" => VirtualKeyCode::F17,
"F18" => VirtualKeyCode::F18,
"F19" => VirtualKeyCode::F19,
"F20" => VirtualKeyCode::F20,
"F21" => VirtualKeyCode::F21,
"F22" => VirtualKeyCode::F22,
"F23" => VirtualKeyCode::F23,
"F24" => VirtualKeyCode::F24,
"PrintScreen" => VirtualKeyCode::Snapshot,
"ScrollLock" => VirtualKeyCode::Scroll,
"Pause" => VirtualKeyCode::Pause,
"Insert" => VirtualKeyCode::Insert,
"Home" => VirtualKeyCode::Home,
"Delete" => VirtualKeyCode::Delete,
"End" => VirtualKeyCode::End,
"PageDown" => VirtualKeyCode::PageDown,
"PageUp" => VirtualKeyCode::PageUp,
"ArrowLeft" => VirtualKeyCode::Left,
"ArrowUp" => VirtualKeyCode::Up,
"ArrowRight" => VirtualKeyCode::Right,
"ArrowDown" => VirtualKeyCode::Down,
"Backspace" => VirtualKeyCode::Back,
"Enter" => VirtualKeyCode::Return,
"Space" => VirtualKeyCode::Space,
"Compose" => VirtualKeyCode::Compose,
"Caret" => VirtualKeyCode::Caret,
"NumLock" => VirtualKeyCode::Numlock,
"Numpad0" => VirtualKeyCode::Numpad0,
"Numpad1" => VirtualKeyCode::Numpad1,
"Numpad2" => VirtualKeyCode::Numpad2,
"Numpad3" => VirtualKeyCode::Numpad3,
"Numpad4" => VirtualKeyCode::Numpad4,
"Numpad5" => VirtualKeyCode::Numpad5,
"Numpad6" => VirtualKeyCode::Numpad6,
"Numpad7" => VirtualKeyCode::Numpad7,
"Numpad8" => VirtualKeyCode::Numpad8,
"Numpad9" => VirtualKeyCode::Numpad9,
"AbntC1" => VirtualKeyCode::AbntC1,
"AbntC2" => VirtualKeyCode::AbntC2,
"NumpadAdd" => VirtualKeyCode::Add,
"Quote" => VirtualKeyCode::Apostrophe,
"Apps" => VirtualKeyCode::Apps,
"At" => VirtualKeyCode::At,
"Ax" => VirtualKeyCode::Ax,
"Backslash" => VirtualKeyCode::Backslash,
"Calculator" => VirtualKeyCode::Calculator,
"Capital" => VirtualKeyCode::Capital,
"Semicolon" => VirtualKeyCode::Semicolon,
"Comma" => VirtualKeyCode::Comma,
"Convert" => VirtualKeyCode::Convert,
"NumpadDecimal" => VirtualKeyCode::Decimal,
"NumpadDivide" => VirtualKeyCode::Divide,
"Equal" => VirtualKeyCode::Equals,
"Backquote" => VirtualKeyCode::Grave,
"Kana" => VirtualKeyCode::Kana,
"Kanji" => VirtualKeyCode::Kanji,
"AltLeft" => VirtualKeyCode::LAlt,
"BracketLeft" => VirtualKeyCode::LBracket,
"ControlLeft" => VirtualKeyCode::LControl,
"ShiftLeft" => VirtualKeyCode::LShift,
"MetaLeft" => VirtualKeyCode::LWin,
"Mail" => VirtualKeyCode::Mail,
"MediaSelect" => VirtualKeyCode::MediaSelect,
"MediaStop" => VirtualKeyCode::MediaStop,
"Minus" => VirtualKeyCode::Minus,
"NumpadMultiply" => VirtualKeyCode::Multiply,
"Mute" => VirtualKeyCode::Mute,
"LaunchMyComputer" => VirtualKeyCode::MyComputer,
"NavigateForward" => VirtualKeyCode::NavigateForward,
"NavigateBackward" => VirtualKeyCode::NavigateBackward,
"NextTrack" => VirtualKeyCode::NextTrack,
"NoConvert" => VirtualKeyCode::NoConvert,
"NumpadComma" => VirtualKeyCode::NumpadComma,
"NumpadEnter" => VirtualKeyCode::NumpadEnter,
"NumpadEquals" => VirtualKeyCode::NumpadEquals,
"OEM102" => VirtualKeyCode::OEM102,
"Period" => VirtualKeyCode::Period,
"PlayPause" => VirtualKeyCode::PlayPause,
"Power" => VirtualKeyCode::Power,
"PrevTrack" => VirtualKeyCode::PrevTrack,
"AltRight" => VirtualKeyCode::RAlt,
"BracketRight" => VirtualKeyCode::RBracket,
"ControlRight" => VirtualKeyCode::RControl,
"ShiftRight" => VirtualKeyCode::RShift,
"MetaRight" => VirtualKeyCode::RWin,
"Slash" => VirtualKeyCode::Slash,
"Sleep" => VirtualKeyCode::Sleep,
"Stop" => VirtualKeyCode::Stop,
"NumpadSubtract" => VirtualKeyCode::Subtract,
"Sysrq" => VirtualKeyCode::Sysrq,
"Tab" => VirtualKeyCode::Tab,
"Underline" => VirtualKeyCode::Underline,
"Unlabeled" => VirtualKeyCode::Unlabeled,
"AudioVolumeDown" => VirtualKeyCode::VolumeDown,
"AudioVolumeUp" => VirtualKeyCode::VolumeUp,
"Wake" => VirtualKeyCode::Wake,
"WebBack" => VirtualKeyCode::WebBack,
"WebFavorites" => VirtualKeyCode::WebFavorites,
"WebForward" => VirtualKeyCode::WebForward,
"WebHome" => VirtualKeyCode::WebHome,
"WebRefresh" => VirtualKeyCode::WebRefresh,
"WebSearch" => VirtualKeyCode::WebSearch,
"WebStop" => VirtualKeyCode::WebStop,
"Yen" => VirtualKeyCode::Yen,
_ => return None,
})
}
pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
}
pub fn codepoint(event: &impl IKeyboardEvent) -> char {
// `event.key()` always returns a non-empty `String`. Therefore, this should
// never panic.
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}

View File

@@ -0,0 +1,52 @@
mod canvas;
mod event;
mod timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
use crate::dpi::LogicalSize;
use crate::platform::web::WindowExtStdweb;
use crate::window::Window;
use stdweb::js;
use stdweb::web::event::BeforeUnloadEvent;
use stdweb::web::window;
use stdweb::web::IEventTarget;
use stdweb::web::{document, html_element::CanvasElement, Element};
pub fn throw(msg: &str) {
js! { throw @{msg} }
}
pub fn exit_fullscreen() {
document().exit_fullscreen();
}
pub fn on_unload(mut handler: impl FnMut() + 'static) {
window().add_event_listener(move |_: BeforeUnloadEvent| handler());
}
impl WindowExtStdweb for Window {
fn canvas(&self) -> CanvasElement {
self.window.canvas().raw().clone()
}
}
pub fn window_size() -> LogicalSize {
let window = window();
let width = window.inner_width() as f64;
let height = window.inner_height() as f64;
LogicalSize { width, height }
}
pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
match document().fullscreen_element() {
Some(elem) => {
let raw: Element = canvas.clone().into();
raw == elem
}
None => false,
}
}

View File

@@ -0,0 +1,25 @@
use std::time::Duration;
use stdweb::web::{window, IWindowOrWorker, TimeoutHandle};
#[derive(Debug)]
pub struct Timeout {
handle: TimeoutHandle,
}
impl Timeout {
pub fn new<F>(f: F, duration: Duration) -> Timeout
where
F: 'static + FnMut(),
{
Timeout {
handle: window().set_clearable_timeout(f, duration.as_millis() as u32),
}
}
}
impl Drop for Timeout {
fn drop(&mut self) {
let handle = std::mem::replace(&mut self.handle, unsafe { std::mem::uninitialized() });
handle.clear();
}
}

View File

@@ -0,0 +1,303 @@
use super::event;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
pub struct Canvas {
raw: HtmlCanvasElement,
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
on_keyboard_release: Option<Closure<dyn FnMut(KeyboardEvent)>>,
on_keyboard_press: Option<Closure<dyn FnMut(KeyboardEvent)>>,
on_received_character: Option<Closure<dyn FnMut(KeyboardEvent)>>,
on_cursor_leave: Option<Closure<dyn FnMut(PointerEvent)>>,
on_cursor_enter: Option<Closure<dyn FnMut(PointerEvent)>>,
on_cursor_move: Option<Closure<dyn FnMut(PointerEvent)>>,
on_mouse_press: Option<Closure<dyn FnMut(PointerEvent)>>,
on_mouse_release: Option<Closure<dyn FnMut(PointerEvent)>>,
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
wants_fullscreen: Rc<RefCell<bool>>,
}
impl Drop for Canvas {
fn drop(&mut self) {
self.raw.remove();
}
}
impl Canvas {
pub fn create() -> Result<Self, RootOE> {
let window =
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
let document = window
.document()
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.unchecked_into();
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
// sequential keyboard navigation, but its order is defined by the
// document's source order.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
canvas
.set_attribute("tabindex", "0")
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
Ok(Canvas {
raw: canvas,
on_blur: None,
on_focus: None,
on_keyboard_release: None,
on_keyboard_press: None,
on_received_character: None,
on_cursor_leave: None,
on_cursor_enter: None,
on_cursor_move: None,
on_mouse_release: None,
on_mouse_press: None,
on_mouse_wheel: None,
on_fullscreen_change: None,
wants_fullscreen: Rc::new(RefCell::new(false)),
})
}
pub fn set_attribute(&self, attribute: &str, value: &str) {
self.raw
.set_attribute(attribute, value)
.expect(&format!("Set attribute: {}", attribute));
}
pub fn position(&self) -> (f64, f64) {
let bounds = self.raw.get_bounding_client_rect();
(bounds.x(), bounds.y())
}
pub fn width(&self) -> f64 {
self.raw.width() as f64
}
pub fn height(&self) -> f64 {
self.raw.height() as f64
}
pub fn set_size(&self, size: LogicalSize) {
self.raw.set_width(size.width as u32);
self.raw.set_height(size.height as u32);
}
pub fn raw(&self) -> &HtmlCanvasElement {
&self.raw
}
pub fn on_blur<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| {
handler();
}));
}
pub fn on_focus<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| {
handler();
}));
}
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_release =
Some(self.add_user_event("keyup", move |event: KeyboardEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press =
Some(self.add_user_event("keydown", move |event: KeyboardEvent| {
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
pub fn on_received_character<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(char),
{
// TODO: Use `beforeinput`.
//
// The `keypress` event is deprecated, but there does not seem to be a
// viable/compatible alternative as of now. `beforeinput` is still widely
// unsupported.
self.on_received_character = Some(self.add_user_event(
"keypress",
move |event: KeyboardEvent| {
handler(event::codepoint(&event));
},
));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| {
handler(event.pointer_id());
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| {
handler(event.pointer_id());
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_release = Some(self.add_user_event(
"pointerup",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
},
));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_press = Some(self.add_user_event(
"pointerdown",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
},
));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
{
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| {
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
}));
}
pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_fullscreen_change =
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
}
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
{
let closure = Closure::wrap(Box::new(move |event: E| {
{
let event_ref = event.as_ref();
event_ref.stop_propagation();
event_ref.cancel_bubble();
}
handler(event);
}) as Box<dyn FnMut(E)>);
self.raw
.add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref())
.expect("Failed to add event listener with callback");
closure
}
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
fn add_user_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
F: 'static + FnMut(E),
{
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
self.add_event(event_name, move |event: E| {
handler(event);
if *wants_fullscreen.borrow() {
canvas
.request_fullscreen()
.expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
}
})
}
pub fn request_fullscreen(&self) {
*self.wants_fullscreen.borrow_mut() = true;
}
pub fn is_fullscreen(&self) -> bool {
super::is_fullscreen(&self.raw)
}
}

View File

@@ -0,0 +1,227 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use std::convert::TryInto;
use web_sys::{KeyboardEvent, MouseEvent, WheelEvent};
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
match event.button() {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
i => MouseButton::Other((i - 3).try_into().expect("very large mouse button value")),
}
}
pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
}
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition {
LogicalPosition {
x: event.offset_x() as f64,
y: event.offset_y() as f64,
}
}
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
let x = event.delta_x();
let y = event.delta_y();
match event.delta_mode() {
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
_ => None,
}
}
pub fn scan_code(event: &KeyboardEvent) -> ScanCode {
match event.key_code() {
0 => event.char_code(),
i => i,
}
}
pub fn virtual_key_code(event: &KeyboardEvent) -> Option<VirtualKeyCode> {
Some(match &event.code()[..] {
"Digit1" => VirtualKeyCode::Key1,
"Digit2" => VirtualKeyCode::Key2,
"Digit3" => VirtualKeyCode::Key3,
"Digit4" => VirtualKeyCode::Key4,
"Digit5" => VirtualKeyCode::Key5,
"Digit6" => VirtualKeyCode::Key6,
"Digit7" => VirtualKeyCode::Key7,
"Digit8" => VirtualKeyCode::Key8,
"Digit9" => VirtualKeyCode::Key9,
"Digit0" => VirtualKeyCode::Key0,
"KeyA" => VirtualKeyCode::A,
"KeyB" => VirtualKeyCode::B,
"KeyC" => VirtualKeyCode::C,
"KeyD" => VirtualKeyCode::D,
"KeyE" => VirtualKeyCode::E,
"KeyF" => VirtualKeyCode::F,
"KeyG" => VirtualKeyCode::G,
"KeyH" => VirtualKeyCode::H,
"KeyI" => VirtualKeyCode::I,
"KeyJ" => VirtualKeyCode::J,
"KeyK" => VirtualKeyCode::K,
"KeyL" => VirtualKeyCode::L,
"KeyM" => VirtualKeyCode::M,
"KeyN" => VirtualKeyCode::N,
"KeyO" => VirtualKeyCode::O,
"KeyP" => VirtualKeyCode::P,
"KeyQ" => VirtualKeyCode::Q,
"KeyR" => VirtualKeyCode::R,
"KeyS" => VirtualKeyCode::S,
"KeyT" => VirtualKeyCode::T,
"KeyU" => VirtualKeyCode::U,
"KeyV" => VirtualKeyCode::V,
"KeyW" => VirtualKeyCode::W,
"KeyX" => VirtualKeyCode::X,
"KeyY" => VirtualKeyCode::Y,
"KeyZ" => VirtualKeyCode::Z,
"Escape" => VirtualKeyCode::Escape,
"F1" => VirtualKeyCode::F1,
"F2" => VirtualKeyCode::F2,
"F3" => VirtualKeyCode::F3,
"F4" => VirtualKeyCode::F4,
"F5" => VirtualKeyCode::F5,
"F6" => VirtualKeyCode::F6,
"F7" => VirtualKeyCode::F7,
"F8" => VirtualKeyCode::F8,
"F9" => VirtualKeyCode::F9,
"F10" => VirtualKeyCode::F10,
"F11" => VirtualKeyCode::F11,
"F12" => VirtualKeyCode::F12,
"F13" => VirtualKeyCode::F13,
"F14" => VirtualKeyCode::F14,
"F15" => VirtualKeyCode::F15,
"F16" => VirtualKeyCode::F16,
"F17" => VirtualKeyCode::F17,
"F18" => VirtualKeyCode::F18,
"F19" => VirtualKeyCode::F19,
"F20" => VirtualKeyCode::F20,
"F21" => VirtualKeyCode::F21,
"F22" => VirtualKeyCode::F22,
"F23" => VirtualKeyCode::F23,
"F24" => VirtualKeyCode::F24,
"PrintScreen" => VirtualKeyCode::Snapshot,
"ScrollLock" => VirtualKeyCode::Scroll,
"Pause" => VirtualKeyCode::Pause,
"Insert" => VirtualKeyCode::Insert,
"Home" => VirtualKeyCode::Home,
"Delete" => VirtualKeyCode::Delete,
"End" => VirtualKeyCode::End,
"PageDown" => VirtualKeyCode::PageDown,
"PageUp" => VirtualKeyCode::PageUp,
"ArrowLeft" => VirtualKeyCode::Left,
"ArrowUp" => VirtualKeyCode::Up,
"ArrowRight" => VirtualKeyCode::Right,
"ArrowDown" => VirtualKeyCode::Down,
"Backspace" => VirtualKeyCode::Back,
"Enter" => VirtualKeyCode::Return,
"Space" => VirtualKeyCode::Space,
"Compose" => VirtualKeyCode::Compose,
"Caret" => VirtualKeyCode::Caret,
"NumLock" => VirtualKeyCode::Numlock,
"Numpad0" => VirtualKeyCode::Numpad0,
"Numpad1" => VirtualKeyCode::Numpad1,
"Numpad2" => VirtualKeyCode::Numpad2,
"Numpad3" => VirtualKeyCode::Numpad3,
"Numpad4" => VirtualKeyCode::Numpad4,
"Numpad5" => VirtualKeyCode::Numpad5,
"Numpad6" => VirtualKeyCode::Numpad6,
"Numpad7" => VirtualKeyCode::Numpad7,
"Numpad8" => VirtualKeyCode::Numpad8,
"Numpad9" => VirtualKeyCode::Numpad9,
"AbntC1" => VirtualKeyCode::AbntC1,
"AbntC2" => VirtualKeyCode::AbntC2,
"NumpadAdd" => VirtualKeyCode::Add,
"Quote" => VirtualKeyCode::Apostrophe,
"Apps" => VirtualKeyCode::Apps,
"At" => VirtualKeyCode::At,
"Ax" => VirtualKeyCode::Ax,
"Backslash" => VirtualKeyCode::Backslash,
"Calculator" => VirtualKeyCode::Calculator,
"Capital" => VirtualKeyCode::Capital,
"Semicolon" => VirtualKeyCode::Semicolon,
"Comma" => VirtualKeyCode::Comma,
"Convert" => VirtualKeyCode::Convert,
"NumpadDecimal" => VirtualKeyCode::Decimal,
"NumpadDivide" => VirtualKeyCode::Divide,
"Equal" => VirtualKeyCode::Equals,
"Backquote" => VirtualKeyCode::Grave,
"Kana" => VirtualKeyCode::Kana,
"Kanji" => VirtualKeyCode::Kanji,
"AltLeft" => VirtualKeyCode::LAlt,
"BracketLeft" => VirtualKeyCode::LBracket,
"ControlLeft" => VirtualKeyCode::LControl,
"ShiftLeft" => VirtualKeyCode::LShift,
"MetaLeft" => VirtualKeyCode::LWin,
"Mail" => VirtualKeyCode::Mail,
"MediaSelect" => VirtualKeyCode::MediaSelect,
"MediaStop" => VirtualKeyCode::MediaStop,
"Minus" => VirtualKeyCode::Minus,
"NumpadMultiply" => VirtualKeyCode::Multiply,
"Mute" => VirtualKeyCode::Mute,
"LaunchMyComputer" => VirtualKeyCode::MyComputer,
"NavigateForward" => VirtualKeyCode::NavigateForward,
"NavigateBackward" => VirtualKeyCode::NavigateBackward,
"NextTrack" => VirtualKeyCode::NextTrack,
"NoConvert" => VirtualKeyCode::NoConvert,
"NumpadComma" => VirtualKeyCode::NumpadComma,
"NumpadEnter" => VirtualKeyCode::NumpadEnter,
"NumpadEquals" => VirtualKeyCode::NumpadEquals,
"OEM102" => VirtualKeyCode::OEM102,
"Period" => VirtualKeyCode::Period,
"PlayPause" => VirtualKeyCode::PlayPause,
"Power" => VirtualKeyCode::Power,
"PrevTrack" => VirtualKeyCode::PrevTrack,
"AltRight" => VirtualKeyCode::RAlt,
"BracketRight" => VirtualKeyCode::RBracket,
"ControlRight" => VirtualKeyCode::RControl,
"ShiftRight" => VirtualKeyCode::RShift,
"MetaRight" => VirtualKeyCode::RWin,
"Slash" => VirtualKeyCode::Slash,
"Sleep" => VirtualKeyCode::Sleep,
"Stop" => VirtualKeyCode::Stop,
"NumpadSubtract" => VirtualKeyCode::Subtract,
"Sysrq" => VirtualKeyCode::Sysrq,
"Tab" => VirtualKeyCode::Tab,
"Underline" => VirtualKeyCode::Underline,
"Unlabeled" => VirtualKeyCode::Unlabeled,
"AudioVolumeDown" => VirtualKeyCode::VolumeDown,
"AudioVolumeUp" => VirtualKeyCode::VolumeUp,
"Wake" => VirtualKeyCode::Wake,
"WebBack" => VirtualKeyCode::WebBack,
"WebFavorites" => VirtualKeyCode::WebFavorites,
"WebForward" => VirtualKeyCode::WebForward,
"WebHome" => VirtualKeyCode::WebHome,
"WebRefresh" => VirtualKeyCode::WebRefresh,
"WebSearch" => VirtualKeyCode::WebSearch,
"WebStop" => VirtualKeyCode::WebStop,
"Yen" => VirtualKeyCode::Yen,
_ => return None,
})
}
pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
}
pub fn codepoint(event: &KeyboardEvent) -> char {
// `event.key()` always returns a non-empty `String`. Therefore, this should
// never panic.
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}

View File

@@ -0,0 +1,70 @@
mod canvas;
mod event;
mod timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
use crate::dpi::LogicalSize;
use crate::platform::web::WindowExtWebSys;
use crate::window::Window;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement};
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
}
pub fn exit_fullscreen() {
let window = web_sys::window().expect("Failed to obtain window");
let document = window.document().expect("Failed to obtain document");
document.exit_fullscreen();
}
pub fn on_unload(mut handler: impl FnMut() + 'static) {
let window = web_sys::window().expect("Failed to obtain window");
let closure = Closure::wrap(
Box::new(move |_: BeforeUnloadEvent| handler()) as Box<dyn FnMut(BeforeUnloadEvent)>
);
window
.add_event_listener_with_callback("beforeunload", &closure.as_ref().unchecked_ref())
.expect("Failed to add close listener");
}
impl WindowExtWebSys for Window {
fn canvas(&self) -> HtmlCanvasElement {
self.window.canvas().raw().clone()
}
}
pub fn window_size() -> LogicalSize {
let window = web_sys::window().expect("Failed to obtain window");
let width = window
.inner_width()
.expect("Failed to get width")
.as_f64()
.expect("Failed to get width as f64");
let height = window
.inner_height()
.expect("Failed to get height")
.as_f64()
.expect("Failed to get height as f64");
LogicalSize { width, height }
}
pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
let window = window().expect("Failed to obtain window");
let document = window.document().expect("Failed to obtain document");
match document.fullscreen_element() {
Some(elem) => {
let raw: Element = canvas.clone().into();
raw == elem
}
None => false,
}
}

View File

@@ -0,0 +1,40 @@
use std::time::Duration;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
#[derive(Debug)]
pub struct Timeout {
handle: i32,
_closure: Closure<dyn FnMut()>,
}
impl Timeout {
pub fn new<F>(f: F, duration: Duration) -> Timeout
where
F: 'static + FnMut(),
{
let window = web_sys::window().expect("Failed to obtain window");
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
let handle = window
.set_timeout_with_callback_and_timeout_and_arguments_0(
&closure.as_ref().unchecked_ref(),
duration.as_millis() as i32,
)
.expect("Failed to set timeout");
Timeout {
handle,
_closure: closure,
}
}
}
impl Drop for Timeout {
fn drop(&mut self) {
let window = web_sys::window().expect("Failed to obtain window");
window.clear_timeout_with_handle(self.handle);
}
}

View File

@@ -0,0 +1,288 @@
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::icon::Icon;
use crate::monitor::MonitorHandle as RootMH;
use crate::window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWI};
use raw_window_handle::web::WebHandle;
use super::{backend, monitor, EventLoopWindowTarget};
use std::cell::RefCell;
use std::collections::vec_deque::IntoIter as VecDequeIter;
use std::collections::VecDeque;
pub struct Window {
canvas: backend::Canvas,
previous_pointer: RefCell<&'static str>,
position: RefCell<LogicalPosition>,
id: Id,
register_redraw_request: Box<dyn Fn()>,
}
impl Window {
pub fn new<T>(
target: &EventLoopWindowTarget<T>,
attr: WindowAttributes,
_: PlatformSpecificBuilderAttributes,
) -> Result<Self, RootOE> {
let runner = target.runner.clone();
let id = target.generate_id();
let mut canvas = backend::Canvas::create()?;
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
target.register(&mut canvas, id);
let window = Window {
canvas,
previous_pointer: RefCell::new("auto"),
position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }),
id,
register_redraw_request,
};
window.set_inner_size(attr.inner_size.unwrap_or(LogicalSize {
width: 1024.0,
height: 768.0,
}));
window.set_title(&attr.title);
window.set_maximized(attr.maximized);
window.set_visible(attr.visible);
window.set_window_icon(attr.window_icon);
Ok(window)
}
pub fn canvas(&self) -> &backend::Canvas {
&self.canvas
}
pub fn set_title(&self, title: &str) {
self.canvas.set_attribute("alt", title);
}
pub fn set_visible(&self, _visible: bool) {
// Intentionally a no-op
}
pub fn request_redraw(&self) {
(self.register_redraw_request)();
}
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let (x, y) = self.canvas.position();
Ok(LogicalPosition { x, y })
}
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Ok(*self.position.borrow())
}
pub fn set_outer_position(&self, position: LogicalPosition) {
*self.position.borrow_mut() = position;
self.canvas.set_attribute("position", "fixed");
self.canvas.set_attribute("left", &position.x.to_string());
self.canvas.set_attribute("top", &position.y.to_string());
}
#[inline]
pub fn inner_size(&self) -> LogicalSize {
LogicalSize {
width: self.canvas.width() as f64,
height: self.canvas.height() as f64,
}
}
#[inline]
pub fn outer_size(&self) -> LogicalSize {
LogicalSize {
width: self.canvas.width() as f64,
height: self.canvas.height() as f64,
}
}
#[inline]
pub fn set_inner_size(&self, size: LogicalSize) {
self.canvas.set_size(size);
}
#[inline]
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
// Intentionally a no-op: users can't resize canvas elements
}
#[inline]
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
// Intentionally a no-op: users can't resize canvas elements
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// Intentionally a no-op: users can't resize canvas elements
}
#[inline]
pub fn hidpi_factor(&self) -> f64 {
1.0
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let text = match cursor {
CursorIcon::Default => "auto",
CursorIcon::Crosshair => "crosshair",
CursorIcon::Hand => "pointer",
CursorIcon::Arrow => "default",
CursorIcon::Move => "move",
CursorIcon::Text => "text",
CursorIcon::Wait => "wait",
CursorIcon::Help => "help",
CursorIcon::Progress => "progress",
CursorIcon::NotAllowed => "not-allowed",
CursorIcon::ContextMenu => "context-menu",
CursorIcon::Cell => "cell",
CursorIcon::VerticalText => "vertical-text",
CursorIcon::Alias => "alias",
CursorIcon::Copy => "copy",
CursorIcon::NoDrop => "no-drop",
CursorIcon::Grab => "grab",
CursorIcon::Grabbing => "grabbing",
CursorIcon::AllScroll => "all-scroll",
CursorIcon::ZoomIn => "zoom-in",
CursorIcon::ZoomOut => "zoom-out",
CursorIcon::EResize => "e-resize",
CursorIcon::NResize => "n-resize",
CursorIcon::NeResize => "ne-resize",
CursorIcon::NwResize => "nw-resize",
CursorIcon::SResize => "s-resize",
CursorIcon::SeResize => "se-resize",
CursorIcon::SwResize => "sw-resize",
CursorIcon::WResize => "w-resize",
CursorIcon::EwResize => "ew-resize",
CursorIcon::NsResize => "ns-resize",
CursorIcon::NeswResize => "nesw-resize",
CursorIcon::NwseResize => "nwse-resize",
CursorIcon::ColResize => "col-resize",
CursorIcon::RowResize => "row-resize",
};
*self.previous_pointer.borrow_mut() = text;
self.canvas
.set_attribute("style", &format!("cursor: {}", text));
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
// Intentionally a no-op, as the web does not support setting cursor positions
Ok(())
}
#[inline]
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
// Intentionally a no-op, as the web does not (properly) support grabbing the cursor
Ok(())
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
if !visible {
self.canvas.set_attribute("cursor", "none");
} else {
self.canvas
.set_attribute("cursor", *self.previous_pointer.borrow());
}
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// Intentionally a no-op, as canvases cannot be 'maximized'
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
if self.canvas.is_fullscreen() {
Some(Fullscreen::Borderless(self.current_monitor()))
} else {
None
}
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
if monitor.is_some() {
self.canvas.request_fullscreen();
} else if self.canvas.is_fullscreen() {
backend::exit_fullscreen();
}
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {
// Intentionally a no-op, no canvas decorations
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// Intentionally a no-op, no window ordering
}
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
// Currently an intentional no-op
}
#[inline]
pub fn set_ime_position(&self, _position: LogicalPosition) {
// Currently a no-op as it does not seem there is good support for this on web
}
#[inline]
pub fn current_monitor(&self) -> RootMH {
RootMH {
inner: monitor::Handle,
}
}
#[inline]
pub fn available_monitors(&self) -> VecDequeIter<monitor::Handle> {
VecDeque::new().into_iter()
}
#[inline]
pub fn primary_monitor(&self) -> monitor::Handle {
monitor::Handle
}
#[inline]
pub fn id(&self) -> Id {
return self.id;
}
#[inline]
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let handle = WebHandle {
id: self.id.0,
..WebHandle::empty()
};
raw_window_handle::RawWindowHandle::Web(handle)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub(crate) u32);
impl Id {
pub unsafe fn dummy() -> Id {
Id(0)
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PlatformSpecificBuilderAttributes;

View File

@@ -1,6 +1,6 @@
#![allow(non_snake_case, unused_unsafe)]
use std::{mem, os::raw::c_void, sync::Once};
use std::sync::Once;
use winapi::{
shared::{
@@ -9,13 +9,12 @@ use winapi::{
winerror::S_OK,
},
um::{
libloaderapi::{GetProcAddress, LoadLibraryA},
shellscalingapi::{
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
PROCESS_PER_MONITOR_DPI_AWARE,
},
wingdi::{GetDeviceCaps, LOGPIXELSX},
winnt::{HRESULT, LPCSTR},
winnt::HRESULT,
winuser::{self, MONITOR_DEFAULTTONEAREST},
},
};
@@ -35,33 +34,6 @@ type GetDpiForMonitor = unsafe extern "system" fn(
) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
};
}
lazy_static! {
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
get_function!("user32.dll", GetDpiForWindow);
@@ -71,10 +43,7 @@ lazy_static! {
get_function!("user32.dll", EnableNonClientDpiScaling);
}
pub fn become_dpi_aware(enable: bool) {
if !enable {
return;
}
pub fn become_dpi_aware() {
static ENABLE_DPI_AWARENESS: Once = Once::new();
ENABLE_DPI_AWARENESS.call_once(|| {
unsafe {

View File

@@ -1,3 +1,4 @@
#![allow(non_snake_case)]
//! An events loop on Win32 is a background thread.
//!
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
@@ -38,14 +39,14 @@ use winapi::{
},
um::{
commctrl, libloaderapi, ole2, processthreadsapi, winbase,
winnt::{LONG, LPCSTR, SHORT},
winnt::{HANDLE, LONG, LPCSTR, SHORT},
winuser,
},
};
use crate::{
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
event::{DeviceEvent, Event, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::platform::{
dpi::{
@@ -53,15 +54,49 @@ use crate::{
},
drop_handler::FileDropHandler,
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
monitor,
raw_input::{get_raw_input_data, get_raw_mouse_button_state},
util,
window::adjust_size,
window_state::{CursorFlags, WindowFlags, WindowState},
wrap_device_id, WindowId, DEVICE_ID,
},
window::WindowId as RootWindowId,
window::{Fullscreen, WindowId as RootWindowId},
};
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
pointerId: UINT,
entriesCount: *mut UINT,
pointerCount: *mut UINT,
pointerInfo: *mut winuser::POINTER_INFO,
) -> BOOL;
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: UINT) -> BOOL;
type GetPointerDeviceRects = unsafe extern "system" fn(
device: HANDLE,
pointerDeviceRect: *mut RECT,
displayRect: *mut RECT,
) -> BOOL;
type GetPointerTouchInfo =
unsafe extern "system" fn(pointerId: UINT, touchInfo: *mut winuser::POINTER_TOUCH_INFO) -> BOOL;
type GetPointerPenInfo =
unsafe extern "system" fn(pointId: UINT, penInfo: *mut winuser::POINTER_PEN_INFO) -> BOOL;
lazy_static! {
static ref GET_POINTER_FRAME_INFO_HISTORY: Option<GetPointerFrameInfoHistory> =
get_function!("user32.dll", GetPointerFrameInfoHistory);
static ref SKIP_POINTER_FRAME_MESSAGES: Option<SkipPointerFrameMessages> =
get_function!("user32.dll", SkipPointerFrameMessages);
static ref GET_POINTER_DEVICE_RECTS: Option<GetPointerDeviceRects> =
get_function!("user32.dll", GetPointerDeviceRects);
static ref GET_POINTER_TOUCH_INFO: Option<GetPointerTouchInfo> =
get_function!("user32.dll", GetPointerTouchInfo);
static ref GET_POINTER_PEN_INFO: Option<GetPointerPenInfo> =
get_function!("user32.dll", GetPointerPenInfo);
}
pub(crate) struct SubclassInput<T> {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: EventLoopRunnerShared<T>,
@@ -97,18 +132,40 @@ pub struct EventLoopWindowTarget<T> {
pub(crate) runner_shared: EventLoopRunnerShared<T>,
}
macro_rules! main_thread_check {
($fn_name:literal) => {{
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
if thread_id != main_thread_id() {
panic!(concat!(
"Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you really, absolutely need to create an \
EventLoop on a different thread, please use the `EventLoopExtWindows::",
$fn_name,
"` function."
));
}
}};
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> {
Self::with_dpi_awareness(true)
main_thread_check!("new_any_thread");
Self::new_any_thread()
}
pub fn window_target(&self) -> &RootELW<T> {
&self.window_target
pub fn new_any_thread() -> EventLoop<T> {
become_dpi_aware();
Self::new_dpi_unaware_any_thread()
}
pub fn with_dpi_awareness(dpi_aware: bool) -> EventLoop<T> {
become_dpi_aware(dpi_aware);
pub fn new_dpi_unaware() -> EventLoop<T> {
main_thread_check!("new_dpi_unaware_any_thread");
Self::new_dpi_unaware_any_thread()
}
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
let runner_shared = Rc::new(ELRShared {
runner: RefCell::new(None),
@@ -131,6 +188,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn window_target(&self) -> &RootELW<T> {
&self.window_target
}
pub fn run<F>(mut self, event_handler: F) -> !
where
F: 'static + FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
@@ -143,10 +204,6 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
unsafe {
winuser::IsGUIThread(1);
}
let event_loop_windows_ref = &self.window_target;
let mut runner = unsafe {
@@ -245,6 +302,21 @@ impl<T> EventLoopWindowTarget<T> {
}
}
fn main_thread_id() -> DWORD {
static mut MAIN_THREAD_ID: DWORD = 0;
#[used]
#[allow(non_upper_case_globals)]
#[link_section = ".CRT$XCU"]
static INIT_MAIN_THREAD_ID: unsafe fn() = {
unsafe fn initer() {
MAIN_THREAD_ID = processthreadsapi::GetCurrentThreadId();
}
initer
};
unsafe { MAIN_THREAD_ID }
}
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
pub(crate) struct ELRShared<T> {
runner: RefCell<Option<EventLoopRunner<T>>>,
@@ -654,13 +726,21 @@ impl EventLoopThreadExecutor {
type ThreadExecFn = Box<Box<dyn FnMut()>>;
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> {
target_window: HWND,
event_send: Sender<T>,
}
unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
target_window: self.target_window,
event_send: self.event_send.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
unsafe {
@@ -820,6 +900,13 @@ pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>)
assert_eq!(subclass_result, 1);
}
fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
match pressure {
1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)),
_ => None,
}
}
/// Any window whose callback is configured to this function will have its events propagated
/// through the events loop of the thread the window was created in.
//
@@ -933,6 +1020,51 @@ unsafe extern "system" fn public_window_callback<T>(
commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
winuser::WM_WINDOWPOSCHANGING => {
let mut window_state = subclass_input.window_state.lock();
if let Some(ref mut fullscreen) = window_state.fullscreen {
let window_pos = &mut *(lparam as *mut winuser::WINDOWPOS);
let new_rect = RECT {
left: window_pos.x,
top: window_pos.y,
right: window_pos.x + window_pos.cx,
bottom: window_pos.y + window_pos.cy,
};
let new_monitor =
winuser::MonitorFromRect(&new_rect, winuser::MONITOR_DEFAULTTONULL);
match fullscreen {
Fullscreen::Borderless(ref mut fullscreen_monitor) => {
if new_monitor != fullscreen_monitor.inner.hmonitor()
&& new_monitor != ptr::null_mut()
{
if let Ok(new_monitor_info) = monitor::get_monitor_info(new_monitor) {
let new_monitor_rect = new_monitor_info.rcMonitor;
window_pos.x = new_monitor_rect.left;
window_pos.y = new_monitor_rect.top;
window_pos.cx = new_monitor_rect.right - new_monitor_rect.left;
window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top;
}
*fullscreen_monitor = crate::monitor::MonitorHandle {
inner: monitor::MonitorHandle::new(new_monitor),
};
}
}
Fullscreen::Exclusive(ref video_mode) => {
let old_monitor = video_mode.video_mode.monitor.hmonitor();
if let Ok(old_monitor_info) = monitor::get_monitor_info(old_monitor) {
let old_monitor_rect = old_monitor_info.rcMonitor;
window_pos.x = old_monitor_rect.left;
window_pos.y = old_monitor_rect.top;
window_pos.cx = old_monitor_rect.right - old_monitor_rect.left;
window_pos.cy = old_monitor_rect.bottom - old_monitor_rect.top;
}
}
}
}
0
}
// WM_MOVE supplies client area positions, so we send Moved here instead.
winuser::WM_WINDOWPOSCHANGED => {
use crate::event::WindowEvent::Moved;
@@ -982,11 +1114,34 @@ unsafe extern "system" fn public_window_callback<T>(
winuser::WM_CHAR => {
use crate::event::WindowEvent::ReceivedCharacter;
let chr: char = mem::transmute(wparam as u32);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ReceivedCharacter(chr),
});
use std::char;
let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF;
let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF;
if is_high_surrogate {
subclass_input.window_state.lock().high_surrogate = Some(wparam as u16);
} else if is_low_surrogate {
let high_surrogate = subclass_input.window_state.lock().high_surrogate.take();
if let Some(high_surrogate) = high_surrogate {
let pair = [high_surrogate, wparam as u16];
if let Some(Ok(chr)) = char::decode_utf16(pair.iter().copied()).next() {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ReceivedCharacter(chr),
});
}
}
} else {
subclass_input.window_state.lock().high_surrogate = None;
if let Some(chr) = char::from_u32(wparam as u32) {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ReceivedCharacter(chr),
});
}
}
0
}
@@ -996,6 +1151,17 @@ unsafe extern "system" fn public_window_callback<T>(
// other unwanted default hotkeys as well.
winuser::WM_SYSCHAR => 0,
winuser::WM_SYSCOMMAND => {
if wparam == winuser::SC_SCREENSAVE {
let window_state = subclass_input.window_state.lock();
if window_state.fullscreen.is_some() {
return 0;
}
}
winuser::DefWindowProcW(window, msg, wparam, lparam)
}
winuser::WM_MOUSEMOVE => {
use crate::event::WindowEvent::{CursorEntered, CursorMoved};
let mouse_was_outside_window = {
@@ -1431,8 +1597,17 @@ unsafe extern "system" fn public_window_callback<T>(
{
let dpi_factor = hwnd_scale_factor(window);
for input in &inputs {
let x = (input.x as f64) / 100f64;
let y = (input.y as f64) / 100f64;
let mut location = POINT {
x: input.x / 100,
y: input.y / 100,
};
if winuser::ScreenToClient(window, &mut location as *mut _) == 0 {
continue;
}
let x = location.x as f64 + (input.x % 100) as f64 / 100f64;
let y = location.y as f64 + (input.y % 100) as f64 / 100f64;
let location = LogicalPosition::from_physical((x, y), dpi_factor);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
@@ -1447,6 +1622,7 @@ unsafe extern "system" fn public_window_callback<T>(
continue;
},
location,
force: None, // WM_TOUCH doesn't support pressure information
id: input.dwID as u64,
device_id: DEVICE_ID,
}),
@@ -1457,6 +1633,147 @@ unsafe extern "system" fn public_window_callback<T>(
0
}
winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => {
if let (
Some(GetPointerFrameInfoHistory),
Some(SkipPointerFrameMessages),
Some(GetPointerDeviceRects),
) = (
*GET_POINTER_FRAME_INFO_HISTORY,
*SKIP_POINTER_FRAME_MESSAGES,
*GET_POINTER_DEVICE_RECTS,
) {
let pointer_id = LOWORD(wparam as DWORD) as UINT;
let mut entries_count = 0 as UINT;
let mut pointers_count = 0 as UINT;
if GetPointerFrameInfoHistory(
pointer_id,
&mut entries_count as *mut _,
&mut pointers_count as *mut _,
std::ptr::null_mut(),
) == 0
{
return 0;
}
let pointer_info_count = (entries_count * pointers_count) as usize;
let mut pointer_infos = Vec::with_capacity(pointer_info_count);
pointer_infos.set_len(pointer_info_count);
if GetPointerFrameInfoHistory(
pointer_id,
&mut entries_count as *mut _,
&mut pointers_count as *mut _,
pointer_infos.as_mut_ptr(),
) == 0
{
return 0;
}
let dpi_factor = hwnd_scale_factor(window);
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory
// The information retrieved appears in reverse chronological order, with the most recent entry in the first
// row of the returned array
for pointer_info in pointer_infos.iter().rev() {
let mut device_rect = mem::MaybeUninit::uninit();
let mut display_rect = mem::MaybeUninit::uninit();
if (GetPointerDeviceRects(
pointer_info.sourceDevice,
device_rect.as_mut_ptr(),
display_rect.as_mut_ptr(),
)) == 0
{
continue;
}
let device_rect = device_rect.assume_init();
let display_rect = display_rect.assume_init();
// For the most precise himetric to pixel conversion we calculate the ratio between the resolution
// of the display device (pixel) and the touch device (himetric).
let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64
/ (device_rect.right - device_rect.left) as f64;
let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64
/ (device_rect.bottom - device_rect.top) as f64;
// ptHimetricLocation's origin is 0,0 even on multi-monitor setups.
// On multi-monitor setups we need to translate the himetric location to the rect of the
// display device it's attached to.
let x = display_rect.left as f64
+ pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x;
let y = display_rect.top as f64
+ pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y;
let mut location = POINT {
x: x.floor() as i32,
y: y.floor() as i32,
};
if winuser::ScreenToClient(window, &mut location as *mut _) == 0 {
continue;
}
let force = match pointer_info.pointerType {
winuser::PT_TOUCH => {
let mut touch_info = mem::MaybeUninit::uninit();
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
match GetPointerTouchInfo(
pointer_info.pointerId,
touch_info.as_mut_ptr(),
) {
0 => None,
_ => normalize_pointer_pressure(
touch_info.assume_init().pressure,
),
}
})
}
winuser::PT_PEN => {
let mut pen_info = mem::MaybeUninit::uninit();
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
match GetPointerPenInfo(
pointer_info.pointerId,
pen_info.as_mut_ptr(),
) {
0 => None,
_ => {
normalize_pointer_pressure(pen_info.assume_init().pressure)
}
}
})
}
_ => None,
};
let x = location.x as f64 + x.fract();
let y = location.y as f64 + y.fract();
let location = LogicalPosition::from_physical((x, y), dpi_factor);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::Touch(Touch {
phase: if pointer_info.pointerFlags & winuser::POINTER_FLAG_DOWN != 0 {
TouchPhase::Started
} else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UP != 0 {
TouchPhase::Ended
} else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UPDATE != 0
{
TouchPhase::Moved
} else {
continue;
},
location,
force,
id: pointer_info.pointerId as u64,
device_id: DEVICE_ID,
}),
});
}
SkipPointerFrameMessages(pointer_id);
}
0
}
winuser::WM_SETFOCUS => {
use crate::event::WindowEvent::Focused;
subclass_input.send_event(Event::WindowEvent {

View File

@@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND};
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle,
monitor::{MonitorHandle, VideoMode},
window::Window,
};
@@ -67,6 +67,8 @@ impl WindowId {
}
}
#[macro_use]
mod util;
mod dpi;
mod drop_handler;
mod event;
@@ -74,6 +76,5 @@ mod event_loop;
mod icon;
mod monitor;
mod raw_input;
mod util;
mod window;
mod window_state;

View File

@@ -3,54 +3,93 @@ use winapi::{
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
},
um::{wingdi, winnt::LONG, winuser},
um::{wingdi, winuser},
};
use std::{
collections::{HashSet, VecDeque},
collections::{BTreeSet, VecDeque},
io, mem, ptr,
};
use super::{util, EventLoop};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
dpi::{dpi_to_scale_factor, get_monitor_dpi},
window::Window,
},
};
/// Win32 implementation of the main `MonitorHandle` object.
#[derive(Derivative)]
#[derivative(Debug, Clone)]
pub struct MonitorHandle {
/// Monitor handle.
hmonitor: HMonitor,
#[derivative(Debug = "ignore")]
monitor_info: winuser::MONITORINFOEXW,
/// The system name of the monitor.
monitor_name: String,
/// True if this is the primary monitor.
primary: bool,
/// The position of the monitor in pixels on the desktop.
///
/// A window that is positioned at these coordinates will overlap the monitor.
position: (i32, i32),
/// The current resolution in pixels on the monitor.
dimensions: (u32, u32),
/// DPI scale factor.
hidpi_factor: f64,
#[derive(Clone)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
pub(crate) native_video_mode: wingdi::DEVMODEW,
}
impl PartialEq for VideoMode {
fn eq(&self, other: &Self) -> bool {
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate == other.refresh_rate
&& self.monitor == other.monitor
}
}
impl Eq for VideoMode {}
impl std::hash::Hash for VideoMode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate.hash(state);
self.monitor.hash(state);
}
}
impl std::fmt::Debug for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoMode")
.field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate", &self.refresh_rate)
.field("monitor", &self.monitor)
.finish()
}
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle(HMONITOR);
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
// For more info see:
// https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396
#[derive(Debug, Clone)]
struct HMonitor(HMONITOR);
unsafe impl Send for HMonitor {}
unsafe impl Send for MonitorHandle {}
unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR,
@@ -59,7 +98,7 @@ unsafe extern "system" fn monitor_enum_proc(
data: LPARAM,
) -> BOOL {
let monitors = data as *mut VecDeque<MonitorHandle>;
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor));
(*monitors).push_back(MonitorHandle::new(hmonitor));
TRUE // continue enumeration
}
@@ -79,12 +118,12 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
pub fn primary_monitor() -> MonitorHandle {
const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
MonitorHandle::from_hmonitor(hmonitor)
MonitorHandle::new(hmonitor)
}
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
MonitorHandle::from_hmonitor(hmonitor)
MonitorHandle::new(hmonitor)
}
impl<T> EventLoop<T> {
@@ -125,73 +164,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
}
impl MonitorHandle {
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
let place = monitor_info.rcMonitor;
let dimensions = (
(place.right - place.left) as u32,
(place.bottom - place.top) as u32,
);
MonitorHandle {
hmonitor: HMonitor(hmonitor),
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
position: (place.left as i32, place.top as i32),
dimensions,
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
monitor_info,
}
pub(crate) fn new(hmonitor: HMONITOR) -> Self {
MonitorHandle(hmonitor)
}
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
let left = self.position.0 as LONG;
let right = left + self.dimensions.0 as LONG;
let top = self.position.1 as LONG;
let bottom = top + self.dimensions.1 as LONG;
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
let monitor_info = get_monitor_info(self.0).unwrap();
point.x >= monitor_info.rcMonitor.left
&& point.x <= monitor_info.rcMonitor.right
&& point.y >= monitor_info.rcMonitor.top
&& point.y <= monitor_info.rcMonitor.bottom
}
#[inline]
pub fn name(&self) -> Option<String> {
Some(self.monitor_name.clone())
let monitor_info = get_monitor_info(self.0).unwrap();
Some(util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()))
}
#[inline]
pub fn native_identifier(&self) -> String {
self.monitor_name.clone()
self.name().unwrap()
}
#[inline]
pub fn hmonitor(&self) -> HMONITOR {
self.hmonitor.0
self.0
}
#[inline]
pub fn size(&self) -> PhysicalSize {
self.dimensions.into()
let monitor_info = get_monitor_info(self.0).unwrap();
PhysicalSize {
width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as f64,
height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64,
}
}
#[inline]
pub fn position(&self) -> PhysicalPosition {
self.position.into()
let monitor_info = get_monitor_info(self.0).unwrap();
PhysicalPosition {
x: monitor_info.rcMonitor.left as f64,
y: monitor_info.rcMonitor.top as f64,
}
}
#[inline]
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
// EnumDisplaySettingsExW can return duplicate values (or some of the
// fields are probably changing, but we aren't looking at those fields
// anyway), so we're using a HashSet deduplicate
let mut modes = HashSet::new();
// anyway), so we're using a BTreeSet deduplicate
let mut modes = BTreeSet::new();
let mut i = 0;
loop {
unsafe {
let device_name = self.monitor_info.szDevice.as_ptr();
let monitor_info = get_monitor_info(self.0).unwrap();
let device_name = monitor_info.szDevice.as_ptr();
let mut mode: wingdi::DEVMODEW = mem::zeroed();
mode.dmSize = mem::size_of_val(&mode) as WORD;
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
@@ -205,10 +240,14 @@ impl MonitorHandle {
| wingdi::DM_DISPLAYFREQUENCY;
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
modes.insert(VideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight),
bit_depth: mode.dmBitsPerPel as u16,
refresh_rate: mode.dmDisplayFrequency as u16,
modes.insert(RootVideoMode {
video_mode: VideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight),
bit_depth: mode.dmBitsPerPel as u16,
refresh_rate: mode.dmDisplayFrequency as u16,
monitor: self.clone(),
native_video_mode: mode,
},
});
}
}

View File

@@ -1,6 +1,7 @@
use std::{
io, mem,
ops::BitAnd,
os::raw::c_void,
ptr, slice,
sync::atomic::{AtomicBool, Ordering},
};
@@ -12,9 +13,44 @@ use winapi::{
minwindef::{BOOL, DWORD},
windef::{HWND, POINT, RECT},
},
um::{winbase::lstrlenW, winuser},
um::{
libloaderapi::{GetProcAddress, LoadLibraryA},
winbase::lstrlenW,
winnt::LPCSTR,
winuser,
},
};
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
crate::platform_impl::platform::util::get_function_impl(
concat!($lib, '\0'),
concat!(stringify!($func), '\0'),
)
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
};
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
@@ -105,6 +141,16 @@ pub fn set_cursor_hidden(hidden: bool) {
}
}
pub fn get_cursor_clip() -> Result<RECT, io::Error> {
unsafe {
let mut rect: RECT = mem::zeroed();
win_to_err(|| winuser::GetClipCursor(&mut rect)).map(|_| rect)
}
}
/// Sets the cursor's clip rect.
///
/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event.
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
unsafe {
let rect_ptr = rect
@@ -115,6 +161,19 @@ pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
}
}
pub fn get_desktop_rect() -> RECT {
unsafe {
let left = winuser::GetSystemMetrics(winuser::SM_XVIRTUALSCREEN);
let top = winuser::GetSystemMetrics(winuser::SM_YVIRTUALSCREEN);
RECT {
left,
top,
right: left + winuser::GetSystemMetrics(winuser::SM_CXVIRTUALSCREEN),
bottom: top + winuser::GetSystemMetrics(winuser::SM_CYVIRTUALSCREEN),
}
}
}
pub fn is_focused(window: HWND) -> bool {
window == unsafe { winuser::GetActiveWindow() }
}

View File

@@ -1,6 +1,7 @@
#![cfg(target_os = "windows")]
use parking_lot::Mutex;
use raw_window_handle::{windows::WindowsHandle, RawWindowHandle};
use std::{
cell::Cell,
ffi::OsStr,
@@ -13,7 +14,7 @@ use std::{
use winapi::{
ctypes::c_int,
shared::{
minwindef::{DWORD, LPARAM, UINT, WORD, WPARAM},
minwindef::{DWORD, HINSTANCE, LPARAM, UINT, WORD, WPARAM},
windef::{HWND, POINT, RECT},
},
um::{
@@ -46,7 +47,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorIcon, Icon, WindowAttributes},
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
};
/// The Win32 implementation of the main `Window` object.
@@ -211,6 +212,15 @@ impl Window {
pub fn set_outer_position(&self, logical_position: LogicalPosition) {
let dpi_factor = self.hidpi_factor();
let (x, y) = logical_position.to_physical(dpi_factor).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
self.set_position_physical(x, y);
}
@@ -286,6 +296,15 @@ impl Window {
pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
self.set_inner_size_physical(width, height);
}
@@ -327,7 +346,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::RESIZABLE, resizable)
});
});
@@ -339,6 +358,21 @@ impl Window {
self.window.0
}
#[inline]
pub fn hinstance(&self) -> HINSTANCE {
unsafe { winuser::GetWindowLongW(self.hwnd(), winuser::GWL_HINSTANCE) as *mut _ }
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = WindowsHandle {
hwnd: self.window.0 as *mut _,
hinstance: self.hinstance() as *mut _,
..WindowsHandle::empty()
};
RawWindowHandle::Windows(handle)
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state.lock().mouse.cursor = cursor;
@@ -421,80 +455,178 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, maximized)
});
});
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
let window_state = self.window_state.lock();
window_state.fullscreen.clone()
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
unsafe {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
match &monitor {
&Some(RootMonitorHandle { ref inner }) => {
let (x, y): (i32, i32) = inner.position().into();
let (width, height): (u32, u32) = inner.size().into();
let mut window_state_lock = window_state.lock();
let old_fullscreen = window_state_lock.fullscreen.clone();
if window_state_lock.fullscreen == fullscreen {
return;
}
window_state_lock.fullscreen = fullscreen.clone();
drop(window_state_lock);
let mut monitor = monitor.clone();
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
let client_rect =
util::get_client_rect(window.0).expect("get client rect failed!");
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor,
});
window_state_lock.fullscreen = monitor.take();
WindowState::refresh_window_state(
window_state_lock,
window.0,
Some(RECT {
left: x,
top: y,
right: x + width as c_int,
bottom: y + height as c_int,
}),
);
mark_fullscreen(window.0, true);
// Save window bounds before entering fullscreen
match (&old_fullscreen, &fullscreen) {
(&None, &Some(_)) => {
let client_rect = util::get_client_rect(window.0).unwrap();
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor,
});
}
&None => {
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
window_state_lock.fullscreen = None;
_ => (),
}
if let Some(SavedWindow {
client_rect,
dpi_factor,
}) = window_state_lock.saved_window
{
window_state_lock.dpi_factor = dpi_factor;
window_state_lock.saved_window = None;
// Change video mode if we're transitioning to or from exclusive
// fullscreen
match (&old_fullscreen, &fullscreen) {
(&None, &Some(Fullscreen::Exclusive(ref video_mode)))
| (
&Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(ref video_mode)),
)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
{
let monitor = video_mode.monitor();
WindowState::refresh_window_state(
window_state_lock,
let mut display_name = OsStr::new(&monitor.inner.native_identifier())
.encode_wide()
.collect::<Vec<_>>();
// `encode_wide` does not add a null-terminator but
// `ChangeDisplaySettingsExW` requires a null-terminated
// string, so add it
display_name.push(0);
let mut native_video_mode = video_mode.video_mode.native_video_mode.clone();
let res = unsafe {
winuser::ChangeDisplaySettingsExW(
display_name.as_ptr(),
&mut native_video_mode,
std::ptr::null_mut(),
winuser::CDS_FULLSCREEN,
std::ptr::null_mut(),
)
};
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
}
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let res = unsafe {
winuser::ChangeDisplaySettingsExW(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
winuser::CDS_FULLSCREEN,
std::ptr::null_mut(),
)
};
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
}
_ => (),
}
unsafe {
// There are some scenarios where calling `ChangeDisplaySettingsExW` takes long
// enough to execute that the DWM thinks our program has frozen and takes over
// our program's window. When that happens, the `SetWindowPos` call below gets
// eaten and the window doesn't get set to the proper fullscreen position.
//
// Calling `PeekMessageW` here notifies Windows that our process is still running
// fine, taking control back from the DWM and ensuring that the `SetWindowPos` call
// below goes through.
let mut msg = mem::zeroed();
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0);
}
// Update window style
WindowState::set_window_flags(window_state_lock, window.0, |f| {
f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some())
});
// Update window bounds
match &fullscreen {
Some(fullscreen) => {
let monitor = match fullscreen {
Fullscreen::Exclusive(ref video_mode) => video_mode.monitor(),
Fullscreen::Borderless(ref monitor) => monitor.clone(),
};
let position: (i32, i32) = monitor.position().into();
let size: (u32, u32) = monitor.size().into();
unsafe {
winuser::SetWindowPos(
window.0,
ptr::null_mut(),
position.0,
position.1,
size.0 as i32,
size.1 as i32,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
}
}
None => {
let mut window_state_lock = window_state.lock();
if let Some(SavedWindow {
client_rect,
dpi_factor,
}) = window_state_lock.saved_window.take()
{
window_state_lock.dpi_factor = dpi_factor;
drop(window_state_lock);
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
unsafe {
winuser::SetWindowPos(
window.0,
Some(client_rect),
ptr::null_mut(),
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
}
mark_fullscreen(window.0, false);
});
}
}
}
}
unsafe {
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
}
});
}
#[inline]
@@ -503,8 +635,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::DECORATIONS, decorations)
});
});
@@ -516,7 +647,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
});
});
@@ -769,9 +900,7 @@ unsafe fn init<T: 'static>(
let window_state = {
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| {
*f = window_flags
});
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
window_state
};
@@ -865,7 +994,7 @@ pub fn com_initialized() {
// is activated. If the window is not fullscreen, the Shell falls back to
// heuristics to determine how the window should be treated, which means
// that it could still consider the window as fullscreen. :(
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) {
com_initialized();
TASKBAR_LIST.with(|task_bar_list_ptr| {

View File

@@ -1,8 +1,7 @@
use crate::{
dpi::LogicalSize,
monitor::MonitorHandle,
platform_impl::platform::{event_loop, icon::WinIcon, util},
window::{CursorIcon, WindowAttributes},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
use parking_lot::MutexGuard;
use std::{io, ptr};
@@ -29,10 +28,11 @@ pub struct WindowState {
pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64,
pub fullscreen: Option<MonitorHandle>,
pub fullscreen: Option<Fullscreen>,
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
/// times in `EventsCleared`.
pub queued_out_of_band_redraw: bool,
pub high_surrogate: Option<u16>,
window_flags: WindowFlags,
}
@@ -84,6 +84,7 @@ bitflags! {
WindowFlags::RESIZABLE.bits |
WindowFlags::MAXIMIZED.bits
);
const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
}
@@ -114,6 +115,7 @@ impl WindowState {
fullscreen: None,
queued_out_of_band_redraw: false,
high_surrogate: None,
window_flags: WindowFlags::empty(),
}
}
@@ -122,32 +124,16 @@ impl WindowState {
self.window_flags
}
pub fn set_window_flags<F>(
mut this: MutexGuard<'_, Self>,
window: HWND,
set_client_rect: Option<RECT>,
f: F,
) where
pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
where
F: FnOnce(&mut WindowFlags),
{
let old_flags = this.window_flags;
f(&mut this.window_flags);
let is_fullscreen = this.fullscreen.is_some();
this.window_flags
.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
let new_flags = this.window_flags;
drop(this);
old_flags.apply_diff(window, new_flags, set_client_rect);
}
pub fn refresh_window_state(
this: MutexGuard<'_, Self>,
window: HWND,
set_client_rect: Option<RECT>,
) {
Self::set_window_flags(this, window, set_client_rect, |_| ());
old_flags.apply_diff(window, new_flags);
}
pub fn set_window_flags_in_place<F>(&mut self, f: F)
@@ -185,6 +171,7 @@ impl WindowFlags {
fn mask(mut self) -> WindowFlags {
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK;
self |= WindowFlags::FULLSCREEN_OR_MASK;
}
if !self.contains(WindowFlags::VISIBLE) {
self &= WindowFlags::INVISIBLE_AND_MASK;
@@ -236,7 +223,7 @@ impl WindowFlags {
}
/// Adjust the window client rectangle to the return value, if present.
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
fn apply_diff(mut self, window: HWND, mut new: WindowFlags) {
self = self.mask();
new = new.mask();
@@ -295,45 +282,20 @@ impl WindowFlags {
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
match set_client_rect
.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r))
{
Some(client_rect) => {
let (x, y, w, h) = (
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
);
winuser::SetWindowPos(
window,
ptr::null_mut(),
x,
y,
w,
h,
winuser::SWP_NOZORDER
| winuser::SWP_FRAMECHANGED
| winuser::SWP_NOACTIVATE,
);
}
None => {
// Refresh the window frame.
winuser::SetWindowPos(
window,
ptr::null_mut(),
0,
0,
0,
0,
winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_FRAMECHANGED
| winuser::SWP_NOACTIVATE,
);
}
let mut flags = winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_FRAMECHANGED;
// We generally don't want style changes here to affect window
// focus, but for fullscreen windows they must be activated
// (i.e. focused) so that they appear on top of the taskbar
if !new.contains(WindowFlags::MARKER_FULLSCREEN) {
flags |= winuser::SWP_NOACTIVATE;
}
// Refresh the window frame
winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
}
}
@@ -345,10 +307,25 @@ impl CursorFlags {
let client_rect = util::get_client_rect(window)?;
if util::is_focused(window) {
if self.contains(CursorFlags::GRABBED) {
util::set_cursor_clip(Some(client_rect))?;
} else {
util::set_cursor_clip(None)?;
let cursor_clip = match self.contains(CursorFlags::GRABBED) {
true => Some(client_rect),
false => None,
};
let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?);
let desktop_rect = rect_to_tuple(util::get_desktop_rect());
let active_cursor_clip = match desktop_rect == active_cursor_clip {
true => None,
false => Some(active_cursor_clip),
};
// We do this check because calling `set_cursor_clip` incessantly will flood the event
// loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags`
// which at times gets called once every iteration of the eventloop.
if active_cursor_clip != cursor_clip.map(rect_to_tuple) {
util::set_cursor_clip(cursor_clip)?;
}
}

View File

@@ -5,7 +5,7 @@ use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError},
event_loop::EventLoopWindowTarget,
monitor::{AvailableMonitorsIter, MonitorHandle},
monitor::{MonitorHandle, VideoMode},
platform_impl,
};
@@ -45,6 +45,18 @@ impl fmt::Debug for Window {
}
}
impl Drop for Window {
fn drop(&mut self) {
// If the window is in exclusive fullscreen, we must restore the desktop
// video mode (generally this would be done on application exit, but
// closing the window doesn't necessarily always mean application exit,
// such as when there are multiple windows)
if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() {
self.set_fullscreen(None);
}
}
}
/// Identifier of a window. Unique for each window.
///
/// Can be obtained with `window.id()`.
@@ -110,7 +122,7 @@ pub struct WindowAttributes {
/// Whether the window should be set as fullscreen upon creation.
///
/// The default is `None`.
pub fullscreen: Option<MonitorHandle>,
pub fullscreen: Option<Fullscreen>,
/// The title of the window in the title bar.
///
@@ -168,10 +180,11 @@ impl Default for WindowAttributes {
}
}
}
impl WindowBuilder {
/// Initializes a new `WindowBuilder` with default values.
#[inline]
pub fn new() -> WindowBuilder {
pub fn new() -> Self {
WindowBuilder {
window: Default::default(),
platform_specific: Default::default(),
@@ -179,106 +192,129 @@ impl WindowBuilder {
}
/// Requests the window to be of specific dimensions.
///
/// See [`Window::set_inner_size`] for details.
///
/// [`Window::set_inner_size`]: struct.Window.html#method.set_inner_size
#[inline]
pub fn with_inner_size(mut self, size: LogicalSize) -> WindowBuilder {
pub fn with_inner_size(mut self, size: LogicalSize) -> Self {
self.window.inner_size = Some(size);
self
}
/// Sets a minimum dimension size for the window
/// Sets a minimum dimension size for the window.
///
/// See [`Window::set_min_inner_size`] for details.
///
/// [`Window::set_min_inner_size`]: struct.Window.html#method.set_min_inner_size
#[inline]
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> WindowBuilder {
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> Self {
self.window.min_inner_size = Some(min_size);
self
}
/// Sets a maximum dimension size for the window
/// Sets a maximum dimension size for the window.
///
/// See [`Window::set_max_inner_size`] for details.
///
/// [`Window::set_max_inner_size`]: struct.Window.html#method.set_max_inner_size
#[inline]
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> WindowBuilder {
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> Self {
self.window.max_inner_size = Some(max_size);
self
}
/// Sets whether the window is resizable or not
/// Sets whether the window is resizable or not.
///
/// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be
/// triggered by DPI scaling, entering fullscreen mode, etc.
/// See [`Window::set_resizable`] for details.
///
/// ## Platform-specific
///
/// This only has an effect on desktop platforms.
///
/// Due to a bug in XFCE, this has no effect on Xfwm.
/// [`Window::set_resizable`]: struct.Window.html#method.set_resizable
#[inline]
pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder {
pub fn with_resizable(mut self, resizable: bool) -> Self {
self.window.resizable = resizable;
self
}
/// Requests a specific title for the window.
///
/// See [`Window::set_title`] for details.
///
/// [`Window::set_title`]: struct.Window.html#method.set_title
#[inline]
pub fn with_title<T: Into<String>>(mut self, title: T) -> WindowBuilder {
pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
self.window.title = title.into();
self
}
/// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle)
/// means a fullscreen window on that specific monitor
/// Sets the window fullscreen state.
///
/// See [`Window::set_fullscreen`] for details.
///
/// [`Window::set_fullscreen`]: struct.Window.html#method.set_fullscreen
#[inline]
pub fn with_fullscreen(mut self, monitor: Option<MonitorHandle>) -> WindowBuilder {
pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> Self {
self.window.fullscreen = monitor;
self
}
/// Requests maximized mode.
///
/// See [`Window::set_maximized`] for details.
///
/// [`Window::set_maximized`]: struct.Window.html#method.set_maximized
#[inline]
pub fn with_maximized(mut self, maximized: bool) -> WindowBuilder {
pub fn with_maximized(mut self, maximized: bool) -> Self {
self.window.maximized = maximized;
self
}
/// Sets whether the window will be initially hidden or visible.
///
/// See [`Window::set_visible`] for details.
///
/// [`Window::set_visible`]: struct.Window.html#method.set_visible
#[inline]
pub fn with_visible(mut self, visible: bool) -> WindowBuilder {
pub fn with_visible(mut self, visible: bool) -> Self {
self.window.visible = visible;
self
}
/// Sets whether the background of the window should be transparent.
#[inline]
pub fn with_transparent(mut self, transparent: bool) -> WindowBuilder {
pub fn with_transparent(mut self, transparent: bool) -> Self {
self.window.transparent = transparent;
self
}
/// Sets whether the window should have a border, a title bar, etc.
///
/// See [`Window::set_decorations`] for details.
///
/// [`Window::set_decorations`]: struct.Window.html#method.set_decorations
#[inline]
pub fn with_decorations(mut self, decorations: bool) -> WindowBuilder {
pub fn with_decorations(mut self, decorations: bool) -> Self {
self.window.decorations = decorations;
self
}
/// Sets whether or not the window will always be on top of other windows.
///
/// See [`Window::set_always_on_top`] for details.
///
/// [`Window::set_always_on_top`]: struct.Window.html#method.set_always_on_top
#[inline]
pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder {
pub fn with_always_on_top(mut self, always_on_top: bool) -> Self {
self.window.always_on_top = always_on_top;
self
}
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
/// Sets the window icon.
///
/// ## Platform-specific
/// See [`Window::set_window_icon`] for details.
///
/// This only has an effect on Windows and X11.
///
/// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
///
/// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That
/// said, it's usually in the same ballpark as on Windows.
/// [`Window::set_window_icon`]: struct.Window.html#method.set_window_icon
#[inline]
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> WindowBuilder {
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> Self {
self.window.window_icon = window_icon;
self
}
@@ -286,14 +322,21 @@ impl WindowBuilder {
/// Builds the window.
///
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
///
/// Platform-specific behavior:
/// - **Web**: The window is created but not inserted into the web page automatically. Please
/// see the web platform module for more information.
#[inline]
pub fn build<T: 'static>(
self,
window_target: &EventLoopWindowTarget<T>,
) -> Result<Window, OsError> {
// building
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
.map(|window| Window { window })
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific).map(
|window| {
window.request_redraw();
Window { window }
},
)
}
}
@@ -301,10 +344,16 @@ impl WindowBuilder {
impl Window {
/// Creates a new Window for platforms where this is appropriate.
///
/// This function is equivalent to `WindowBuilder::new().build(event_loop)`.
/// This function is equivalent to [`WindowBuilder::new().build(event_loop)`].
///
/// Error should be very rare and only occur in case of permission denied, incompatible system,
/// out of memory, etc.
///
/// Platform-specific behavior:
/// - **Web**: The window is created but not inserted into the web page automatically. Please
/// see the web platform module for more information.
///
/// [`WindowBuilder::new().build(event_loop)`]: struct.WindowBuilder.html#method.build
#[inline]
pub fn new<T: 'static>(event_loop: &EventLoopWindowTarget<T>) -> Result<Window, OsError> {
let builder = WindowBuilder::new();
@@ -398,9 +447,8 @@ impl Window {
/// Modifies the position of the window.
///
/// See `outer_position` for more information about the coordinates.
///
/// This is a no-op if the window has already been closed.
/// See `outer_position` for more information about the coordinates. This automatically un-maximizes the
/// window if it's maximized.
///
/// ## Platform-specific
///
@@ -430,7 +478,8 @@ impl Window {
/// Modifies the inner size of the window.
///
/// See `inner_size` for more information about the values.
/// See `inner_size` for more information about the values. This automatically un-maximizes the
/// window if it's maximized.
///
/// ## Platform-specific
///
@@ -460,6 +509,7 @@ impl Window {
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Web:** Has no effect.
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
self.window.set_min_inner_size(dimensions)
@@ -470,6 +520,7 @@ impl Window {
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Web:** Has no effect.
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
self.window.set_max_inner_size(dimensions)
@@ -495,6 +546,7 @@ impl Window {
///
/// - **Android:** Has no effect.
/// - **iOS:** Can only be called on the main thread.
/// - **Web:** Has no effect.
#[inline]
pub fn set_visible(&self, visible: bool) {
self.window.set_visible(visible)
@@ -514,6 +566,7 @@ impl Window {
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Web:** Has no effect.
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.window.set_resizable(resizable)
@@ -524,6 +577,7 @@ impl Window {
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Web:** Has no effect.
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.window.set_maximized(maximized)
@@ -533,10 +587,27 @@ impl Window {
///
/// ## Platform-specific
///
/// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a
/// video mode change. *Caveat!* macOS doesn't provide task switching (or
/// spaces!) while in exclusive fullscreen mode. This mode should be used
/// when a video mode change is desired, but for a better user experience,
/// borderless fullscreen might be preferred.
///
/// `Fullscreen::Borderless` provides a borderless fullscreen window on a
/// separate space. This is the idiomatic way for fullscreen games to work
/// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if
/// separate spaces are not preferred.
///
/// The dock and the menu bar are always disabled in fullscreen mode.
/// - **iOS:** Can only be called on the main thread.
/// - **Wayland:** Does not support exclusive fullscreen mode.
/// - **Windows:** Screen saver is disabled in fullscreen mode.
///
/// [simple]:
/// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen
#[inline]
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
self.window.set_fullscreen(monitor)
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.window.set_fullscreen(fullscreen)
}
/// Gets the window's current fullscreen state.
@@ -545,16 +616,16 @@ impl Window {
///
/// - **iOS:** Can only be called on the main thread.
#[inline]
pub fn fullscreen(&self) -> Option<MonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
self.window.fullscreen()
}
/// Turn window decorations on or off.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden
/// via [`setPrefersStatusBarHidden`].
/// - **Web:** Has no effect.
///
/// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc
#[inline]
@@ -567,6 +638,7 @@ impl Window {
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Web:** Has no effect.
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
self.window.set_always_on_top(always_on_top)
@@ -575,11 +647,15 @@ impl Window {
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
///
/// For more usage notes, see `WindowBuilder::with_window_icon`.
///
/// ## Platform-specific
///
/// This only has an effect on Windows and X11.
///
/// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
///
/// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That
/// said, it's usually in the same ballpark as on Windows.
#[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
self.window.set_window_icon(window_icon)
@@ -590,6 +666,7 @@ impl Window {
/// ## Platform-specific
///
/// **iOS:** Has no effect.
/// - **Web:** Has no effect.
#[inline]
pub fn set_ime_position(&self, position: LogicalPosition) {
self.window.set_ime_position(position)
@@ -614,6 +691,7 @@ impl Window {
/// ## Platform-specific
///
/// - **iOS:** Always returns an `Err`.
/// - **Web:** Has no effect.
#[inline]
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> {
self.window.set_cursor_position(position)
@@ -625,8 +703,11 @@ impl Window {
///
/// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually
/// awkward.
/// - **Wayland:** This presently merely locks the cursor in a fixed location, which looks visually
/// awkward.
/// - **Android:** Has no effect.
/// - **iOS:** Always returns an Err.
/// - **Web:** Has no effect.
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
self.window.set_cursor_grab(grab)
@@ -640,6 +721,7 @@ impl Window {
///
/// - **Windows:** The cursor is only hidden within the confines of the window.
/// - **X11:** The cursor is only hidden within the confines of the window.
/// - **Wayland:** The cursor is only hidden within the confines of the window.
/// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
/// outside of the window.
/// - **iOS:** Has no effect.
@@ -670,11 +752,11 @@ impl Window {
///
/// **iOS:** Can only be called on the main thread.
#[inline]
pub fn available_monitors(&self) -> AvailableMonitorsIter {
let data = self.window.available_monitors();
AvailableMonitorsIter {
data: data.into_iter(),
}
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.window
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
@@ -692,6 +774,12 @@ impl Window {
}
}
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
self.window.raw_window_handle()
}
}
/// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -725,7 +813,9 @@ pub enum CursorIcon {
Alias,
Copy,
NoDrop,
/// Indicates something can be grabbed.
Grab,
/// Indicates something is grabbed.
Grabbing,
AllScroll,
ZoomIn,
@@ -754,3 +844,9 @@ impl Default for CursorIcon {
CursorIcon::Default
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Fullscreen {
Exclusive(VideoMode),
Borderless(MonitorHandle),
}

View File

@@ -1,6 +1,7 @@
#[allow(dead_code)]
fn needs_send<T: Send>() {}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn event_loop_proxy_send() {
#[allow(dead_code)]
@@ -10,6 +11,7 @@ fn event_loop_proxy_send() {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn window_send() {
// ensures that `winit::Window` implements `Send`

View File

@@ -1,6 +1,7 @@
#[allow(dead_code)]
fn needs_sync<T: Sync>() {}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn window_sync() {
// ensures that `winit::Window` implements `Sync`