Compare commits

..

224 Commits

Author SHA1 Message Date
Osspial
c0b46a03b5 Relase alpha 6 (#1338)
* Relase alpha 6

* Update CHANGELOG.md

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2020-01-03 18:17:05 -05:00
Kirill Chibisov
114c18e70d On X11, make WINIT_HIDPI_FACTOR dominate Xft.dpi in some cases (#1354)
* On X11, make `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in some cases

This commit makes `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in general and
adds a special value `0` for `WINIT_HIDPI_FACTOR` to use winit computed
DPI factor with randr over Xft.dpi.

* Use `randr` instead of `0` for auto dpi scaling

* Update CHANGELOG

* blow up on wrong env var

* Allow empty string for env var
2020-01-03 18:15:47 -05:00
hatoo
7367b8be6c On Macos, Hide cursor only inside window (#1348)
* On MacOS, hide cursor by invisible cursor

* Add CHANGELOG

* Fix variable name

* Add comments for `CURSOR_BYTES`
2020-01-03 03:34:14 +03:00
hatoo
dd768fe655 MacOS fix CursorEntered and CursorLeft events fired at old window size. (#1335)
* On macOS, Fix `CursorEntered` and `CursorLeft`

* Add CHANGELOG

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2019-12-30 13:32:37 -07:00
Osspial
d9bda3e985 Implement ModifiersChanged on Windows, and fix bugs discovered in implementation process (#1344)
* Move DeviceEvent handling to the message target window.

Previously, device events seem to have only been sent to one particular
window, and when that window was closed Winit would stop receiving
device events. This also allows users to create windowless event loops
that process device events - an intriguing idea, to say the least.

* Emit LWin and RWin VirtualKeyCodes on Windows

* Implement ModifiersChanged on Windows

* Make ModifiersChanged a tuple variant instead of a struct variant

* Add changelog entries

* Format

* Update changelog entry

* Fix AltGr handling

* Reformat

* Publicly expose ModifiersChanged and deprecate misc. modifiers fields
2019-12-30 14:11:11 -05:00
hatoo
fa7a3025ec [MacOS] Fix memory management (#1342)
* macOS, Reduce memory usage

* macOS, Fix memory leak
2019-12-29 15:16:12 -07:00
Osspial
e4451d6786 Fix Window::set_visible not setting internal flags correctly (#1345) 2019-12-29 10:39:15 -05:00
Michael Tang
468b6b83ec fix: remove deprecated usage of mem::uninitialized. (#1341) 2019-12-28 18:53:41 -05:00
Osspial
8a3a32f286 Properly mark a few changes as breaking 2019-12-28 16:22:51 -05:00
Osspial
20e81695ca Change ModifiersState to a bitflags struct (#1306)
* Change ModifiersState to a bitflags struct

* Make examples work

* Add modifier state methods

* all things considered, only erroring out in one file throughout all of these changes is kinda impressive

* Make expansion plans more clear

* Move changelog entry

* Try to fix macos build

* Revert modifiers println in cursor_grab

* Make serde serialization less bug-prone
2019-12-28 15:36:06 -05:00
Osspial
027c52171d Fix changelog 2019-12-27 16:28:06 -05:00
Osspial
cc206d31b7 Implement windows focus key press/release on Windows (#1307)
* X11: Sync key press/release with window focus

* When a window loses focus, key release events are issued for all pressed keys
* When a window gains focus, key press events are issued for all pressed keys
* Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput`
  to indicate that these events are synthetic.
* Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued
  on all other platforms

* Implement windows focus key press/release on Windows

* Docs

Co-authored-by: Murarth <murarth@gmail.com>
2019-12-27 16:26:23 -05:00
hatoo
5d99316c96 macOS: Don't change fullscreen state during fullscreen transition (#1331)
* Register windowWillExitFullScreen

* On macOS: Do not toggle fullscreen during fullscreen transition

* Add CHANGELOG

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2019-12-24 11:56:56 -07:00
David Hewitt
d59eec4633 Add support for Windows Dark Mode (#1217)
* Add support for Windows Dark Mode

* Add is_dark_mode() getter to WindowExtWindows

* Add WindowEvent::DarkModeChanged

* Add support for dark mode in Windows 10 builds > 18362

* Change strategy for querying windows 10 build version

* Drop window state before sending event

Co-Authored-By: daxpedda <daxpedda@gmail.com>

* Change implementation of windows dark mode support

* Expand supported range of windows 10 versions with dark mode

* Use get_function! macro where possible

* Minor style fixes

* Improve documentation for ThemeChanged

* Use `as` conversion for `BOOL`

* Correct CHANGELOG entry for dark mode

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Osspial <osspial@gmail.com>
2019-12-22 12:04:09 -07:00
Osspial
25e018d1ce Fix extraneous # in 0.20.0 Alpha 5 2019-12-22 11:24:49 -05:00
Osspial
25123bed23 Rebasing moved the RedrawRequested changelog entry to the wrong position
:/
2019-12-22 11:23:27 -05:00
Osspial
a8d6db0fc1 Update alpha version in readme.md 2019-12-22 11:19:17 -05:00
Osspial
8a9a9cd92d Move changelog entry into proper position
Fix window_run_return

Make docs build
2019-12-22 11:17:24 -05:00
simlay
530ff5420b Implement revamped RedrawRequested on iOS. (#1299)
* Implement revamped `RedrawRequested` on iOS

* Added RedrawEventsCleared to events_cleared logic

* Fixed from comments

* Added RedrawEventsCleared to draw_rect handler.

* Fixed out of order `RedrawEventsCleared` events.

* cargo fmt
2019-12-22 11:17:23 -05:00
Héctor Ramón
133b11fa6d Implement revamped RedrawRequested on Web (#1301)
* Implement revamped `RedrawRequested` on Web

* Add `web` example
2019-12-22 11:17:23 -05:00
Héctor Ramón
5b489284e4 Implement revamped RedrawRequested on macOS (#1235) 2019-12-22 11:17:23 -05:00
Heghedus Razvan
cdc32eb817 Implemented revamped RedrawRequested for linux wayland (#1237)
Signed-off-by: Heghedus Razvan <heghedus.razvan@gmail.com>
2019-12-22 11:17:23 -05:00
Osspial
eb38ff453a Run rustfmt 2019-12-22 11:17:23 -05:00
Osspial
8eb7853a1a Implement revamped RedrawRequested on Windows (#1050)
* Move event loop runner to runner module

* Implement new redraw API
2019-12-22 11:17:23 -05:00
Murarth
0c151f9fb3 Implement changes to RedrawRequested event (#1062)
* Implement changes to `RedrawRequested` event

Implements the changes described in #1041 for the X11 platform and for
platform-independent public-facing code.

* Fix `request_redraw` example

* Fix examples in lib docs

* Only issue `RedrawRequested` on final `Expose` event
2019-12-22 11:17:23 -05:00
simlay
c10c820311 Reimplement NativeDisplayMode on iOS for #1310 (#1330)
* Reimplement NativeDisplayMode on iOS for #1310

* Type annotations from code review.

Co-Authored-By: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com>

Co-authored-by: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com>
2019-12-22 01:39:22 -07:00
Justin Miller
82889e2367 Window::set_minimized (#985) (#990)
* Expose set_minimized. Implement for macOS (#985)

* Implement set_minimized for Wayland (#985)

Co-Authored-By: Victor Berger <vberger@users.noreply.github.com>

* Implement set_minimized for Windows (#985)

* Remove debug logs (#985)

* Implement Window::set_minimized for X11

* Remove extra param from set_window_flags call

* Cargo fmt

* Add example of usage

* Update changelog

* Update feature matrix

* Cargo fmt

* Update example to remove unnecessary event var

* Stop setting window styles when minimizing (#985)

* Add stub for WASM (#985)

Co-authored-by: Victor Berger <vberger@users.noreply.github.com>
Co-authored-by: Murarth <murarth@gmail.com>
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Osspial <osspial@gmail.com>
2019-12-22 01:04:11 -05:00
Osspial
92741aa4ec Fix array_into_iter warning on Windows (#1329) 2019-12-21 17:49:44 -07:00
Murarth
2f352ca5cf X11: Fix CursorEntered event for non-winit window (#1320)
* X11: Fix CursorEntered event for non-winit window

* Retry CI

Co-authored-by: Osspial <osspial@gmail.com>
2019-12-21 17:47:29 -07:00
hatoo
38c8cb9f4a FIX Crash on macOS when starting maximized without decorations (#1323)
* FIX #1288

* Fix CHANGELOG.md

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2019-12-19 18:03:41 -07:00
Kirill Chibisov
73248bdced On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode (#1324)
Mutter can reposition window on resize, if it is behind mutter's "bounding box".
So, when you start winit window in maximized mode with CSD, mutter places its CSD
behind the GNOME's status bar initially, and then sends configure with the
exact same size as your current window. If winit decides to optimize calling
frame.resize(..) in this case, we won't call set_geometry(we're calling
it through resize) and GNOME won't reposition your window to be inside the
"bounding box", which is not a desired behavior for the end user.
2019-12-19 17:08:28 -07:00
hatoo
01203b247b Fix run_return in MacOS (#1321)
* Fix run_return in MacOS

* MacOS: Fix the way of getting a window in run_return

* Fix CHANGELOG.md
2019-12-19 12:10:47 +03:00
Kirill Chibisov
3e1d169160 On Wayland, fix cursor icon updates on window borders when using CSD (#1322)
* On Wayland, fix cursor icon updates on window borders when using CSD

* Move changelog entry to a right place
2019-12-18 06:41:44 -07:00
Christian Duerr
c1b93fc3d0 Add ModifiersChanged event for macOS (#1268)
* Add ModifiersChanged event for macOS

This implements the macOS portion of #1124.

* Fix ModifiersChanged event import

* Fix event passing window instead of device id
2019-12-13 00:48:32 +03:00
Murarth
1f81e5c872 X11: Report CursorMoved when touch event occurs (#1297)
* X11: Report `CursorMoved` when touch event occurs

* Only trigger CursorMoved events for the first touch ID

* Fix testing for current touch events

* Fix first touch logic
2019-12-11 17:23:55 -07:00
Manish Goregaokar
e5291c9e28 Release 0.20.0-alpha5 (#1315) 2019-12-09 17:29:50 -07:00
Murarth
35505a3114 X11: Sync key press/release with window focus (#1296)
* X11: Sync key press/release with window focus

* When a window loses focus, key release events are issued for all pressed keys
* When a window gains focus, key press events are issued for all pressed keys
* Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput`
  to indicate that these events are synthetic.
* Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued
  on all other platforms

* Clarify code with comments
2019-12-07 15:51:37 -07:00
zserik
830d47a5f7 Have EventLoopClosed contain the original event (#1294)
* Fix issue #1292

* Remove "optionally" from changelog entry
2019-12-07 10:22:03 -07:00
Murarth
1a514dff38 X11: Fix incorrect DPI factor when waking from suspend (#1303) 2019-12-04 10:18:20 -07:00
Osspial
2888d5c6cf Fix array_into_iter warning on Windows (#1308) 2019-12-04 12:02:33 -05:00
Osspial
400f75a2b3 Make WindowStore::for_each less terrifying to rebase (#1304) 2019-12-04 03:55:49 -05:00
Charles Helmich
07bdd3e218 Fix ReceivedCharacter not working with Alt held on Windows (#1282)
* Fix ReceivedCharacter with Alt held on Windows

* Update CHANGELOG.md
2019-11-29 15:49:43 -05:00
Héctor Ramón
35a11ae24f Stop emitting corporate characters in macOS (#1254) 2019-11-27 12:14:36 +03:00
Ryan G
3d28283a81 Only use 'extern crate stdweb' on web targets (#1291)
The 'extern crate' declaration shouldn't be there even if the stdweb feature is on, unless the crate is being compiled for web.
2019-11-27 00:38:18 -05:00
daxpedda
aec5a9fa09 Stop refocusing window when switching from fullscreen to windowed (#1285)
* Stop refocusing window when switching from fullscreen to windowed

* Update Changelog.
2019-11-26 22:49:14 -05:00
Manish Goregaokar
0f94f62025 Bump parking_lot to 0.10 (#1287) 2019-11-25 18:05:44 -07:00
Murarth
a95ebc5ee6 X11: Fix incorrect modifiers when events are missed (#1279)
* X11: Fix incorrect modifiers when events are missed

* Syncs modifier state with state data in X key/button/motion events.
* Fixes modifier state in XWayland, as xinput2 raw input events will
  not be received when a window does not have focus.
* Removes `impl From<_> for ModifiersState` on X11/Wayland API types,
  replacing them with `pub(crate)` methods.

* Cleanup modifier state update using a macro

* Remove keys from modifier state when updating
2019-11-22 17:11:30 -07:00
Murarth
a70ac1531e X11: Fix window creation hangs when another application is fullscreen (#1248)
* X11: Fix window creation hangs when another application is fullscreen

Previously, the X11 backend would block until a `VisibilityNotify` event
is received when creating a Window that is visible or when calling
`set_visible(true)` on a Window that is not currently visible. This
could cause winit to hang in situations where the WM does not quickly
send this event to the application, such as another window being
fullscreen at the time.

This behavior existed to prevent an X protocol error caused by setting
fullscreen state on an invisible window. This fix instead stores desired
fullscreen state when `set_fullscreen` is called (iff the window is not
visible or not yet visible) and issues X commands to set fullscreen
state when a `VisibilityNotify` event is received through the normal
processing of events in the event loop.

* Add window_debug example to facilitate testing

* Add a CHANGELOG entry

* Call `XUnmapWindow` if `VisibilityNotify` is received on an invisible window
2019-11-22 17:11:04 -07:00
ariesfluctus
b6e8dd0d8a Fixed typo (#1273) 2019-11-14 00:10:31 -05:00
Murarth
af80ce842d Fix cargo doc on nightly builds (#1274) 2019-11-12 16:51:46 -07:00
Osspial
08bae037f0 Use paths for intra-doc links, and verify that links are valid in CI (#1244)
* Use paths to generate intra-doc links

* Add entry to PR checklist
2019-11-11 16:05:59 -07:00
Thom Chiovoloni
cd39327ea2 Fix invalid_value lint triggering on mem::zeroed of CFRunLoopSourceContext (#1271) 2019-11-11 15:50:31 -07:00
Murarth
9828f368d6 X11: Fix misreporting DPI factor at startup (#1252)
* X11: Fix misreporting DPI factor at startup

* Add a CHANGELOG entry
2019-11-10 13:55:29 -07:00
Murarth
1ed15c7ec7 X11: Fix events not being reported when using run_return (#1245)
* X11: Fix events not being reported using `run_return`

* Adapt examples to be more practical

* Add CHANGELOG entry
2019-11-10 11:24:43 -07:00
Murarth
c66784995d X11: Fix modifiers being reported after release (#1262)
* X11: Fix modifiers being reported after release

* Moves `ModifiersChanged` variant from `WindowEvent` to `DeviceEvent`

* Add CHANGELOG entry
2019-11-10 00:16:44 -07:00
Murarth
dba21c06ed Run cargo fmt on Rust stable 1.39.0 (#1264) 2019-11-07 13:48:34 -07:00
Kirill Chibisov
72fc6a74ec on Wayland, drop resize events equal to the current window size (#1249)
* on Wayland, drop resize events equal to the current window size

* Add changelog entry
2019-10-31 20:45:45 -04:00
Michael Tang
f916311744 Add error message when building for web without selecting a feature. (#1253) 2019-10-31 12:45:12 -04:00
David Sinclair
05a1f4280c Miscellaneous small changes (#1238)
* Use a slice instead of a new object

* Remove unnecessary 'into_iter'

* Use 'and_then' instead of 'map_or'
2019-10-23 19:45:25 -04:00
Bogaevsky
6608a0241d macOS: fixes app termination (#1234)
* fixes app termination

* applies fmt

* fmt all

* updates changelog

* keeps formating consistent

* fixes misstype
2019-10-23 17:24:50 +03:00
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
110 changed files with 10601 additions and 4941 deletions

View File

@@ -1,5 +1,7 @@
- [ ] Tested on all platforms changed
- [ ] Compilation warnings were addressed
- [ ] `cargo fmt` has been run on this branch
- [ ] `cargo doc` builds successfully
- [ ] 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
- [ ] Created or updated an example program if it would help users understand this functionality

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,60 @@ 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
# Ensure that the documentation builds properly.
- cargo doc --no-deps
# 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,128 @@
# Unreleased
# 0.20.0 Alpha 2 (2019-06-28)
# 0.20.0 Alpha 6 (2020-01-03)
- On macOS, fix `set_cursor_visible` hides cursor outside of window.
- On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size.
- On macOS, fix error when `set_fullscreen` is called during fullscreen transition.
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
- On X11, fix `CursorEntered` event being generated for non-winit windows.
- On macOS, fix crash when starting maximized without decorations.
- On macOS, fix application not to terminate on `run_return`.
- On Wayland, fix cursor icon updates on window borders when using CSD.
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
- Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows.
- **Breaking**: Changes to the `RedrawRequested` event (#1041):
- `RedrawRequested` has been moved from `WindowEvent` to `Event`.
- `EventsCleared` has been renamed to `MainEventsCleared`.
- `RedrawRequested` is now issued only after `MainEventsCleared`.
- `RedrawEventsCleared` is issued after each set of `RedrawRequested` events.
- Implement synthetic window focus key events on Windows.
- **Breaking**: Change `ModifiersState` to a `bitflags` struct.
- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`.
- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted.
- On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior.
- Add `DeviceEvent::ModifiersChanged`.
- Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`.
- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output.
- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`.
# 0.20.0 Alpha 5 (2019-12-09)
- On macOS, fix application termination on `ControlFlow::Exit`
- On Windows, fix missing `ReceivedCharacter` events when Alt is held.
- On macOS, stop emitting private corporate characters in `ReceivedCharacter` events.
- On X11, fix misreporting DPI factor at startup.
- On X11, fix events not being reported when using `run_return`.
- On X11, fix key modifiers being incorrectly reported.
- On X11, fix window creation hanging when another window is fullscreen.
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
- On X11, fix reporting incorrect DPI factor when waking from suspend.
- Change `EventLoopClosed` to contain the original event.
- **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`,
indicating that the event is generated by winit.
- On X11, generate synthetic key events for keys held when a window gains or loses focus.
- On X11, issue a `CursorMoved` event when a `Touch` event occurs,
as X11 implicitly moves the cursor for such events.
# 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, fixed DeviceEvents for relative mouse movement is not always produced
- 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.
- On Wayland, drop resize events identical to the current window size.
# 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-alpha6"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -14,12 +14,18 @@ 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"
bitflags = "1"
[dev-dependencies]
image = "0.21"
@@ -32,7 +38,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,9 +49,6 @@ version = "0.1.3"
default_features = false
features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies]
bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
features = [
@@ -75,7 +78,43 @@ 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.10"
[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"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.1"

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
@@ -81,9 +80,13 @@ If your PR makes notable changes to Winit's features, please update this section
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows, and if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
@@ -100,6 +103,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.
@@ -113,6 +117,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the taskbar icon
* Setting the parent window
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting
### macOS
* Window activation policy
@@ -129,6 +134,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 +163,58 @@ 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**|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**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-alpha6"
```
## [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%
@@ -21,3 +32,4 @@ build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo doc --no-deps

View File

@@ -1,5 +1,5 @@
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
@@ -12,6 +12,8 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
@@ -22,7 +24,6 @@ fn main() {
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
..
@@ -30,8 +31,8 @@ fn main() {
use winit::event::VirtualKeyCode::*;
match key {
Escape => *control_flow = ControlFlow::Exit,
G => window.set_cursor_grab(!modifiers.shift).unwrap(),
H => window.set_cursor_visible(modifiers.shift),
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
H => window.set_cursor_visible(modifiers.shift()),
_ => (),
}
}
@@ -43,6 +44,7 @@ fn main() {
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
},
DeviceEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
_ => (),

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
}

35
examples/minimize.rs Normal file
View File

@@ -0,0 +1,35 @@
extern crate winit;
use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
// Keyboard input event to handle minimize via a hotkey
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
window_id,
} => {
if window_id == window.id() {
// Pressing the 'M' key will minimize the window
if input.virtual_keycode == Some(VirtualKeyCode::M) {
window.set_minimized(true);
}
}
}
_ => *control_flow = ControlFlow::Wait,
});
}

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 {
@@ -35,8 +60,8 @@ fn main() {
..
} => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift;
use self::VirtualKeyCode::*;
let state = !modifiers.shift();
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},
@@ -19,14 +20,11 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::EventsCleared => {
Event::MainEventsCleared => {
window.request_redraw();
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
Event::RedrawRequested(_) => {
println!("{:?}", event);
}
_ => (),

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);
}
}

72
examples/web.rs Normal file
View File

@@ -0,0 +1,72 @@
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
pub fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
#[cfg(feature = "web-sys")]
{
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
body.append_child(&canvas)
.expect("Append canvas to HTML body");
}
#[cfg(feature = "stdweb")]
{
use std_web::web::INode;
use winit::platform::web::WindowExtStdweb;
let canvas = window.canvas();
let document = std_web::web::document();
let body: std_web::web::Node = document.body().expect("Get HTML body").into();
body.append_child(&canvas);
}
event_loop.run(move |event, _, control_flow| {
#[cfg(feature = "web-sys")]
log::debug!("{:?}", event);
#[cfg(feature = "stdweb")]
std_web::console!(log, "%s", format!("{:?}", event));
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
Event::MainEventsCleared => {
window.request_redraw();
}
_ => *control_flow = ControlFlow::Wait,
}
});
}
#[cfg(feature = "web-sys")]
mod wasm {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug);
super::main();
}
}

View File

@@ -20,7 +20,10 @@ fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
Event::MainEventsCleared => {
window.request_redraw();
}
_ => *control_flow = ControlFlow::Poll,
}
});
}

121
examples/window_debug.rs Normal file
View File

@@ -0,0 +1,121 @@
// This example is used by developers to test various window functions.
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Fullscreen, WindowBuilder},
};
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::from((100, 100)))
.build(&event_loop)
.unwrap();
eprintln!("debugging keys:");
eprintln!(" (E) Enter exclusive fullscreen");
eprintln!(" (F) Toggle borderless fullscreen");
#[cfg(waiting_for_set_minimized)]
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
#[cfg(waiting_for_set_minimized)]
let mut minimized = false;
let mut maximized = false;
let mut visible = true;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
}),
..
} => match key {
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
}
}
VirtualKeyCode::V => {
if !visible {
visible = !visible;
window.set_visible(visible);
}
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => match input {
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize) -> f64 {
size.width * size.height
}
let monitor = window.current_monitor();
if let Some(mode) = monitor.video_modes().max_by(|a, b| {
area(a.size())
.partial_cmp(&area(b.size()))
.expect("NaN in video mode size")
}) {
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
}
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
*control_flow = ControlFlow::Exit;
}
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
maximized = !maximized;
window.set_maximized(maximized);
}
_ => (),
},
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -1,41 +1,58 @@
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 std::{thread::sleep, time::Duration};
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
window::WindowBuilder,
};
let mut event_loop = EventLoop::new();
let window = WindowBuilder::new()
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
println!("Close the window to continue.");
event_loop.run_return(|event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
drop(window);
let mut quit = false;
let _window_2 = WindowBuilder::new()
.with_title("A second, fantasticer window!")
.build(&event_loop)
.unwrap();
while !quit {
event_loop.run_return(|event, _, control_flow| {
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
}
println!("Wa ha ha! You thought that closing the window would finish this?!");
event_loop.run_return(|event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
quit = true;
*control_flow = ControlFlow::Exit;
}
Event::MainEventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => *control_flow = ControlFlow::Wait,
}
});
println!("Okay we're done now for real.");
// Sleep for 1/60 second to simulate rendering
sleep(Duration::from_millis(16));
}
}
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
fn main() {
println!("This platform doesn't support run_return.");
}

View File

@@ -28,18 +28,18 @@
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
//! - You can track the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorHandle::hidpi_factor`](../monitor/struct.MonitorHandle.html#method.hidpi_factor), or the
//! [`MonitorHandle::hidpi_factor`](crate::monitor::MonitorHandle::hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::hidpi_factor`](../window/struct.Window.html#method.hidpi_factor), which is roughly equivalent
//! [`Window::hidpi_factor`](crate::window::Window::hidpi_factor), which is roughly equivalent
//! to `window.current_monitor().hidpi_factor()`.
//!
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged)
//! the most robust way is to monitor the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
@@ -49,30 +49,31 @@
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
//! display to use that 2.0 DPI factor, given the use of the command line.
//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! - **X11:** On X11, we calculate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **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
//! [`Resized`](../event/enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
//! [`Resized`](crate::event::WindowEvent::Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](../event/enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
//! [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) if you're only listening for size.
//!
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! `winit` will send [`Resized`](../enum.WindowEvent.html#variant.Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events
//! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
//! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
/// Checks that the DPI factor is a normal positive `f64`.

View File

@@ -3,13 +3,14 @@
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
//! 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};
//! [event_loop_run]: crate::event_loop::EventLoop::run
use instant::Instant;
use std::path::PathBuf;
use crate::{
dpi::{LogicalPosition, LogicalSize},
platform_impl,
window::WindowId,
window::{Theme, WindowId},
};
/// Describes a generic event.
@@ -25,13 +26,26 @@ pub enum Event<T> {
device_id: DeviceId,
event: DeviceEvent,
},
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](../event_loop/struct.EventLoopProxy.html#method.send_event)
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
UserEvent(T),
/// Emitted when new events arrive from the OS to be processed.
NewEvents(StartCause),
/// Emitted when all of the event loop's events have been processed and control flow is about
/// to be taken away from the program.
EventsCleared,
/// Emitted when all events (except for `RedrawRequested`) have been reported.
///
/// This event is followed by zero or more instances of `RedrawRequested`
/// and, finally, `RedrawEventsCleared`.
MainEventsCleared,
/// The OS or application has requested that a window be redrawn.
///
/// Emitted only after `MainEventsCleared`.
RedrawRequested(WindowId),
/// Emitted after any `RedrawRequested` events.
///
/// If there are no `RedrawRequested` events, it is reported immediately after
/// `MainEventsCleared`.
RedrawEventsCleared,
/// Emitted when the event loop is being shut down. This is irreversable - if this event is
/// emitted, it is guaranteed to be the last event emitted.
@@ -52,7 +66,9 @@ impl<T> Event<T> {
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
EventsCleared => Ok(EventsCleared),
MainEventsCleared => Ok(MainEventsCleared),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
RedrawEventsCleared => Ok(RedrawEventsCleared),
LoopDestroyed => Ok(LoopDestroyed),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
@@ -61,7 +77,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
@@ -131,6 +147,16 @@ pub enum WindowEvent {
KeyboardInput {
device_id: DeviceId,
input: KeyboardInput,
/// If `true`, the event was generated synthetically by winit
/// in one of the following circumstances:
///
/// * Synthetic key press events are generated for all keys pressed
/// when a window gains focus. Likewise, synthetic key release events
/// are generated for all keys pressed when a window goes out of focus.
/// ***Currently, this is only functional on X11 and Windows***
///
/// Otherwise, this value is always `false`.
is_synthetic: bool,
},
/// The cursor has moved on the window.
@@ -141,6 +167,7 @@ pub enum WindowEvent {
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
position: LogicalPosition,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -155,6 +182,7 @@ pub enum WindowEvent {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -163,6 +191,7 @@ pub enum WindowEvent {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -184,9 +213,6 @@ pub enum WindowEvent {
value: f64,
},
/// The OS or application has requested that the window be redrawn.
RedrawRequested,
/// Touch event has been received
Touch(Touch),
@@ -198,8 +224,16 @@ pub enum WindowEvent {
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different DPI factor.
///
/// For more information about DPI in general, see the [`dpi`](../dpi/index.html) module.
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
HiDpiFactorChanged(f64),
/// The system window theme has changed.
///
/// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme.
///
/// At the moment this is only supported on Windows.
ThemeChanged(Theme),
}
/// Identifier of an input device.
@@ -261,7 +295,19 @@ pub enum DeviceEvent {
button: ButtonId,
state: ElementState,
},
Key(KeyboardInput),
/// The keyboard modifiers have changed.
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
///
/// Platform-specific behavior:
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
/// issue, and it should get fixed - but it's the current state of the API.
ModifiersChanged(ModifiersState),
Text {
codepoint: char,
},
@@ -290,6 +336,7 @@ pub struct KeyboardInput {
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
pub modifiers: ModifiersState,
}
@@ -303,30 +350,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;
@@ -572,21 +683,99 @@ pub enum VirtualKeyCode {
Cut,
}
/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct ModifiersState {
/// The "shift" key
pub shift: bool,
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "logo" key
///
/// This is the "windows" key on PC and "command" key on Mac.
pub logo: bool,
impl ModifiersState {
/// Returns `true` if the shift key is pressed.
pub fn shift(&self) -> bool {
self.intersects(Self::SHIFT)
}
/// Returns `true` if the control key is pressed.
pub fn ctrl(&self) -> bool {
self.intersects(Self::CTRL)
}
/// Returns `true` if the alt key is pressed.
pub fn alt(&self) -> bool {
self.intersects(Self::ALT)
}
/// Returns `true` if the logo key is pressed.
pub fn logo(&self) -> bool {
self.intersects(Self::LOGO)
}
}
bitflags! {
/// Represents the current state of the keyboard modifiers
///
/// Each flag represents a modifier and is set if this modifier is active.
#[derive(Default)]
pub struct ModifiersState: u32 {
// left and right modifiers are currently commented out, but we should be able to support
// them in a future release
/// The "shift" key.
const SHIFT = 0b100 << 0;
// const LSHIFT = 0b010 << 0;
// const RSHIFT = 0b001 << 0;
/// The "control" key.
const CTRL = 0b100 << 3;
// const LCTRL = 0b010 << 3;
// const RCTRL = 0b001 << 3;
/// The "alt" key.
const ALT = 0b100 << 6;
// const LALT = 0b010 << 6;
// const RALT = 0b001 << 6;
/// This is the "windows" key on PC and "command" key on Mac.
const LOGO = 0b100 << 9;
// const LLOGO = 0b010 << 9;
// const RLOGO = 0b001 << 9;
}
}
#[cfg(feature = "serde")]
mod modifiers_serde {
use super::ModifiersState;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Default, Serialize, Deserialize)]
#[serde(default)]
#[serde(rename = "ModifiersState")]
pub struct ModifiersStateSerialize {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub logo: bool,
}
impl Serialize for ModifiersState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = ModifiersStateSerialize {
shift: self.shift(),
ctrl: self.ctrl(),
alt: self.alt(),
logo: self.logo(),
};
s.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ModifiersState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let ModifiersStateSerialize {
shift,
ctrl,
alt,
logo,
} = ModifiersStateSerialize::deserialize(deserializer)?;
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, shift);
m.set(ModifiersState::CTRL, ctrl);
m.set(ModifiersState::ALT, alt);
m.set(ModifiersState::LOGO, logo);
Ok(m)
}
}
}

View File

@@ -6,16 +6,14 @@
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
//!
//! [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};
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
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
@@ -59,7 +59,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// Set by the user callback given to the `EventLoop::run` method.
///
/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][events_cleared]
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
/// is emitted. Defaults to `Poll`.
///
/// ## Persistency
@@ -68,8 +68,8 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
/// the control flow to `Poll`.
///
/// [events_cleared]: ../event/enum.Event.html#variant.EventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
#[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,
@@ -130,7 +139,7 @@ impl<T> EventLoop<T> {
///
/// Any values not passed to this function will *not* be dropped.
///
/// [`ControlFlow`]: ./enum.ControlFlow.html
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
where
@@ -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,18 +181,25 @@ 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
/// function.
///
/// Returns an `Err` if the associated `EventLoop` no longer exists.
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.event_loop_proxy.send_event(event)
}
}
@@ -195,17 +211,17 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
}
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
/// no longer exists.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EventLoopClosed;
/// no longer exists. Contains the original event given to `send_event`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl fmt::Display for EventLoopClosed {
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", error::Error::description(self))
}
}
impl error::Error for EventLoopClosed {
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
fn description(&self) -> &str {
"Tried to wake up a closed `EventLoop`"
}

View File

@@ -48,19 +48,16 @@
//!
//! event_loop.run(move |event, _, control_flow| {
//! match event {
//! Event::EventsCleared => {
//! Event::MainEventsCleared => {
//! // Application update code.
//!
//!
//! // Queue a RedrawRequested event.
//! window.request_redraw();
//! },
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! Event::RedrawRequested(_) => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in EventsCleared, since
//! // It's preferrable to render in this event rather than in MainEventsCleared, since
//! // rendering in here allows the program to gracefully handle redraws requested
//! // by the OS.
//! },
@@ -92,25 +89,26 @@
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the [`Window`].
//!
//! [`EventLoop`]: ./event_loop/struct.EventLoop.html
//! [`EventLoop::new()`]: ./event_loop/struct.EventLoop.html#method.new
//! [event_loop_run]: ./event_loop/struct.EventLoop.html#method.run
//! [`ControlFlow`]: ./event_loop/enum.ControlFlow.html
//! [`Exit`]: ./event_loop/enum.ControlFlow.html#variant.Exit
//! [`Window`]: ./window/struct.Window.html
//! [`WindowBuilder`]: ./window/struct.WindowBuilder.html
//! [window_new]: ./window/struct.Window.html#method.new
//! [window_builder_new]: ./window/struct.WindowBuilder.html#method.new
//! [window_builder_build]: ./window/struct.WindowBuilder.html#method.build
//! [window_id_fn]: ./window/struct.Window.html#method.id
//! [`Event`]: ./event/enum.Event.html
//! [`WindowEvent`]: ./event/enum.WindowEvent.html
//! [`DeviceEvent`]: ./event/enum.DeviceEvent.html
//! [`UserEvent`]: ./event/enum.Event.html#variant.UserEvent
//! [`LoopDestroyed`]: ./event/enum.Event.html#variant.LoopDestroyed
//! [`platform`]: ./platform/index.html
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`Window`]: window::Window
//! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build
//! [window_id_fn]: window::Window::id
//! [`Event`]: event::Event
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
#![deny(rust_2018_idioms)]
#![deny(intra_doc_link_resolution_failure)]
#[allow(unused_imports)]
#[macro_use]
@@ -121,14 +119,12 @@ extern crate log;
#[macro_use]
extern crate serde;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate derivative;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(all(target_arch = "wasm32", 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:
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
//! type. This is retreived from one of the following methods, which return an iterator of
//! [`MonitorHandle`][monitor_handle]:
//! - [`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;
//! [monitor_handle]: crate::monitor::MonitorHandle
//! [loop_get]: crate::event_loop::EventLoop::available_monitors
//! [window_get]: crate::window::Window::available_monitors
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)]
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
#[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()
)
}
}
@@ -89,8 +108,8 @@ 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)]
/// [`Window`]: crate::window::Window
#[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()
@@ -119,18 +150,23 @@ impl MonitorHandle {
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](../dpi/index.html) module for more information.
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///
/// - **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.
@@ -41,9 +77,17 @@ pub trait WindowExtWindows {
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Whether the system theme is currently Windows 10's "Dark Mode".
fn is_dark_mode(&self) -> bool;
}
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 _
@@ -53,6 +97,11 @@ impl WindowExtWindows for Window {
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn is_dark_mode(&self) -> bool {
self.window.is_dark_mode()
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.

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,
}),
@@ -155,7 +157,7 @@ impl EventLoop {
}
impl EventLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> {
android_glue::wake_event_loop();
Ok(())
}
@@ -358,6 +360,11 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {
unimplemented!()
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
@@ -420,6 +427,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);
@@ -181,8 +165,10 @@ impl<T> EventLoopProxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
@@ -204,15 +190,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 +232,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 +240,7 @@ fn setup_control_flow_observers() {
}
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
@@ -238,6 +250,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 +320,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,113 @@
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: NativeDisplayMode,
pub(crate) monitor: MonitorHandle,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub id);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.0, release];
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
let _: id = msg_send![self.0, retain];
}
NativeDisplayMode(self.0)
}
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
}
}
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];
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
screen_mode,
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 +120,7 @@ impl Drop for Inner {
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle {
inner: Inner,
}
@@ -106,9 +202,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 +237,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 +260,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 +278,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,10 +101,11 @@ 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 {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
assert!(!window.is_null());
app_state::handle_nonuser_events(
std::iter::once(Event::RedrawRequested(RootWindowId(window.into())))
.chain(std::iter::once(Event::RedrawEventsCleared)),
);
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), drawRect: rect];
}
@@ -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,16 @@ 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.0];
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 +512,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 +547,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 _,
}
}
}
@@ -153,30 +169,49 @@ impl Inner {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS")
}
pub fn set_maximized(&self, _maximized: bool) {
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 +224,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 +267,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 +316,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 +332,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 +373,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 +446,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 +512,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 +600,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 +611,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,23 @@ impl Window {
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn set_minimized(&self, minimized: bool) {
match self {
&Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle {
inner: MonitorHandle::Wayland(monitor_id),
}),
&Window::X(ref w) => w.set_minimized(minimized),
&Window::Wayland(ref w) => w.set_minimized(minimized),
}
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
pub fn fullscreen(&self) -> Option<Fullscreen> {
match self {
&Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen(),
}
}
#[inline]
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 +466,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 +481,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 +512,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 +552,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 +570,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 +603,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 +616,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 +649,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(),
@@ -563,7 +658,7 @@ impl<T: 'static> EventLoop<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
match *self {
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
@@ -576,6 +671,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 +700,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};
@@ -42,6 +54,10 @@ impl<T> WindowEventsSink<T> {
}
}
pub fn send_event(&mut self, evt: crate::event::Event<T>) {
self.buffer.push_back(evt);
}
pub fn send_window_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) {
self.buffer.push_back(crate::event::Event::WindowEvent {
event: evt,
@@ -66,40 +82,189 @@ 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<
::calloop::channel::Channel<(crate::event::WindowEvent, super::WindowId)>,
>,
_kbd_source: ::calloop::Source<::calloop::channel::Channel<crate::event::Event<()>>>,
window_target: RootELW<T>,
}
// 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,9 +273,23 @@ 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)
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
}
}
@@ -125,25 +304,36 @@ impl<T: 'static> EventLoop<T> {
let inner_loop = ::calloop::EventLoop::new().unwrap();
let (kbd_sender, kbd_channel) = ::calloop::channel::channel();
let (kbd_sender, kbd_channel) = ::calloop::channel::channel::<crate::event::Event<()>>();
let kbd_sink = sink.clone();
let kbd_source = inner_loop
.handle()
.insert_source(kbd_channel, move |evt, &mut ()| {
if let ::calloop::channel::Event::Msg((evt, wid)) = evt {
kbd_sink.lock().unwrap().send_window_event(evt, wid);
if let ::calloop::channel::Event::Msg(evt) = evt {
let evt = evt.map_nonuser_event().ok().unwrap();
kbd_sink.lock().unwrap().send_event(evt);
}
})
.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 +344,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 +423,7 @@ impl<T: 'static> EventLoop<T> {
})
.unwrap();
let cursor_manager_clone = cursor_manager.clone();
Ok(EventLoop {
inner_loop,
sink,
@@ -202,12 +432,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 +461,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)
@@ -277,24 +509,34 @@ impl<T: 'static> EventLoop<T> {
);
}
}
// do a second run of post-dispatch-triggers, to handle user-generated "request-redraw"
// in response of resize & friends
self.post_dispatch_triggers();
// send Events cleared
{
let mut guard = sink.lock().unwrap();
guard.empty_with(|evt| {
sticky_exit_callback(
crate::event::Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// handle request-redraw
{
self.redraw_triggers(|wid, window_target| {
sticky_exit_callback(
evt,
&self.window_target,
crate::event::Event::RedrawRequested(crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(wid),
)),
window_target,
&mut control_flow,
&mut callback,
);
});
}
// send Events cleared
// send RedrawEventsCleared
{
sticky_exit_callback(
crate::event::Event::EventsCleared,
crate::event::Event::RedrawEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
@@ -304,6 +546,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 +579,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 +597,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,20 +646,47 @@ 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
*/
impl<T> EventLoop<T> {
fn redraw_triggers<F>(&mut self, mut callback: F)
where
F: FnMut(WindowId, &RootELW<T>),
{
let window_target = match self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
_ => unreachable!(),
};
window_target.store.lock().unwrap().for_each_redraw_trigger(
|refresh, frame_refresh, wid, frame| {
if let Some(frame) = frame {
if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
}
}
if refresh {
callback(wid, &self.window_target);
}
},
)
}
fn post_dispatch_triggers(&mut self) {
let mut sink = self.sink.lock().unwrap();
let window_target = match self.window_target.p {
@@ -412,39 +705,47 @@ 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| {
if let Some(frame) = frame {
if let Some((w, h)) = newsize {
frame.resize(w, h);
frame.refresh();
window_target.store.lock().unwrap().for_each(|window| {
if let Some(frame) = window.frame {
if let Some(newsize) = window.newsize {
let (w, h) = newsize;
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
// which calls `set_geometry` under the hood, for now.
frame.resize(w, h);
frame.refresh();
// Don't send resize event downstream if the new size is identical to the
// current one.
if newsize != *window.size {
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
sink.send_window_event(
crate::event::WindowEvent::Resized(logical_size),
wid,
window.wid,
);
*size = (w, h);
} else if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
*window.size = (w, h);
}
}
if let Some(dpi) = new_dpi {
sink.send_window_event(
crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64),
wid,
);
}
if refresh {
sink.send_window_event(crate::event::WindowEvent::RedrawRequested, wid);
}
if closed {
sink.send_window_event(crate::event::WindowEvent::CloseRequested, wid);
}
},
)
}
if let Some(dpi) = window.new_dpi {
sink.send_window_event(
crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64),
window.wid,
);
}
if window.closed {
sink.send_window_event(crate::event::WindowEvent::CloseRequested, window.wid);
}
if let Some(grab_cursor) = window.grab_cursor {
let surface = if grab_cursor {
Some(window.surface)
} else {
None
};
self.cursor_manager.lock().unwrap().grab_pointer(surface);
}
})
}
}
@@ -456,8 +757,10 @@ struct SeatManager<T: 'static> {
sink: Arc<Mutex<WindowEventsSink<T>>>,
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>,
kbd_sender: ::calloop::channel::Sender<crate::event::Event<()>>,
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 +772,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| {
@@ -498,13 +802,14 @@ impl<T: 'static> SeatManager<T> {
struct SeatData<T> {
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
kbd_sender: ::calloop::channel::Sender<crate::event::Event<()>>,
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 +824,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 +916,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 +1043,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

@@ -8,11 +8,13 @@ use smithay_client_toolkit::{
reexports::client::protocol::{wl_keyboard, wl_seat},
};
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
use crate::event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
sink: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
sink: ::calloop::channel::Sender<crate::event::Event<()>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> wl_keyboard::WlKeyboard {
// { variables to be captured by the closures
@@ -29,12 +31,22 @@ pub fn init_keyboard(
match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
*target.lock().unwrap() = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
*target.lock().unwrap() = None;
}
KbEvent::Key {
@@ -52,20 +64,19 @@ pub fn init_keyboard(
};
let vkcode = key_to_vkey(rawkey, keysym);
my_sink
.send((
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
is_synthetic: false,
},
wid,
))
})
.unwrap();
// send char event only on key press, not release
if let ElementState::Released = state {
@@ -74,7 +85,10 @@ pub fn init_keyboard(
if let Some(txt) = utf8 {
for chr in txt.chars() {
my_sink
.send((WindowEvent::ReceivedCharacter(chr), wid))
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
@@ -83,7 +97,18 @@ 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 = ModifiersState::from_wayland(event_modifiers);
*modifiers_tracker.lock().unwrap() = modifiers;
my_sink
.send(Event::DeviceEvent {
device_id: device_id(),
event: DeviceEvent::ModifiersChanged(modifiers),
})
.unwrap();
}
}
},
move |repeat_event: KeyRepeatEvent, _| {
@@ -91,25 +116,27 @@ pub fn init_keyboard(
let state = ElementState::Pressed;
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
repeat_sink
.send((
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
},
is_synthetic: false,
},
wid,
))
})
.unwrap();
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
repeat_sink
.send((WindowEvent::ReceivedCharacter(chr), wid))
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
@@ -137,12 +164,22 @@ pub fn init_keyboard(
move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
@@ -153,20 +190,19 @@ pub fn init_keyboard(
_ => unreachable!(),
};
my_sink
.send((
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
is_synthetic: false,
},
wid,
))
})
.unwrap();
}
}
@@ -366,13 +402,21 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
}
}
impl From<keyboard::ModifiersState> for ModifiersState {
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
ModifiersState {
shift: mods.shift,
ctrl: mods.ctrl,
alt: mods.alt,
logo: mods.logo,
}
impl ModifiersState {
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, mods.shift);
m.set(ModifiersState::CTRL, mods.ctrl);
m.set(ModifiersState::ALT, mods.alt);
m.set(ModifiersState::LOGO, mods.logo);
m
}
}
fn device_id() -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId))
}
fn mk_root_wid(wid: crate::platform_impl::wayland::WindowId) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid))
}

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,
@@ -43,6 +56,13 @@ pub fn implement_pointer<T: 'static>(
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
// Reload cursor style only when we enter winit's surface. Calling
// this function every time on `PtrEvent::Enter` could interfere with
// SCTK CSD handling, since it changes cursor icons when you hover
// cursor over the window borders.
cursor_manager.reload_cursor_style();
sink.send_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(
@@ -241,3 +261,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,
})
}
@@ -244,6 +259,13 @@ impl Window {
*(self.need_frame_refresh.lock().unwrap()) = true;
}
pub fn set_minimized(&self, minimized: bool) {
// An app cannot un-minimize itself on Wayland
if minimized {
self.frame.lock().unwrap().set_minimized();
}
}
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.frame.lock().unwrap().set_maximized();
@@ -252,25 +274,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 +307,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 +352,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 +380,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>>>,
@@ -352,6 +392,17 @@ pub struct WindowStore {
windows: Vec<InternalWindow>,
}
pub struct WindowStoreForEach<'a> {
pub newsize: Option<(u32, u32)>,
pub size: &'a mut (u32, u32),
pub new_dpi: Option<i32>,
pub closed: bool,
pub grab_cursor: Option<bool>,
pub surface: &'a wl_surface::WlSurface,
pub wid: WindowId,
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
}
impl WindowStore {
pub fn new() -> WindowStore {
WindowStore {
@@ -401,30 +452,21 @@ impl WindowStore {
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(
Option<(u32, u32)>,
&mut (u32, u32),
Option<i32>,
bool,
bool,
bool,
WindowId,
Option<&mut SWindow<ConceptFrame>>,
),
F: FnMut(WindowStoreForEach<'_>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
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),
window.closed,
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
f(WindowStoreForEach {
newsize: window.newsize.take(),
size: &mut *(window.size.lock().unwrap()),
new_dpi: window.new_dpi,
closed: window.closed,
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
surface: &window.surface,
wid: make_wid(&window.surface),
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
});
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
@@ -432,4 +474,20 @@ impl WindowStore {
window.closed = false;
}
}
pub fn for_each_redraw_trigger<F>(&mut self, mut f: F)
where
F: FnMut(bool, bool, WindowId, Option<&mut SWindow<ConceptFrame>>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
}
}
}

View File

@@ -1,4 +1,4 @@
use std::{cell::RefCell, collections::HashMap, ptr, rc::Rc, slice};
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice};
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
@@ -8,9 +8,13 @@ use super::{
XExtension,
};
use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent,
},
event_loop::EventLoopWindowTarget as RootELW,
};
@@ -21,6 +25,11 @@ 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,
// Number of touch events currently in progress
pub(super) num_touch: u32,
pub(super) first_touch: Option<u64>,
}
impl<T: 'static> EventProcessor<T> {
@@ -62,6 +71,13 @@ impl<T: 'static> EventProcessor<T> {
self.with_window(window_id, |_| ()).is_some()
}
pub(super) fn poll(&self) -> bool {
let wt = get_xtarget(&self.target);
let result = unsafe { (wt.xconn.xlib.XPending)(wt.xconn.display) };
result != 0
}
pub(super) unsafe fn poll_one_event(&mut self, event_ptr: *mut ffi::XEvent) -> bool {
let wt = get_xtarget(&self.target);
// This function is used to poll and remove a single event
@@ -109,15 +125,44 @@ impl<T: 'static> EventProcessor<T> {
return;
}
// We can't call a `&mut self` method because of the above borrow,
// so we use this macro for repeated modifier state updates.
macro_rules! update_modifiers {
( $state:expr , $modifier:expr ) => {{
match ($state, $modifier) {
(state, modifier) => {
if let Some(modifiers) =
self.device_mod_state.update_state(&state, modifier)
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(modifiers),
});
}
}
}
}};
}
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_keymap(&self.mod_keymap);
}
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");
}
ffi::ClientMessage => {
@@ -362,20 +407,20 @@ impl<T: 'static> EventProcessor<T> {
let (width, height) = shared_state_lock
.dpi_adjusted
.unwrap_or_else(|| (xev.width as f64, xev.height as f64));
let last_hidpi_factor =
shared_state_lock.guessed_dpi.take().unwrap_or_else(|| {
shared_state_lock
.last_monitor
.as_ref()
.map(|last_monitor| last_monitor.hidpi_factor)
.unwrap_or(1.0)
});
let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor;
let new_hidpi_factor = {
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());
new_hidpi_factor
let new_monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
if new_monitor.is_dummy() {
// Avoid updating monitor using a dummy monitor handle
last_hidpi_factor
} else {
monitor = new_monitor;
shared_state_lock.last_monitor = monitor.clone();
monitor.hidpi_factor
}
};
if last_hidpi_factor != new_hidpi_factor {
events.dpi_changed =
@@ -479,16 +524,24 @@ impl<T: 'static> EventProcessor<T> {
});
}
ffi::VisibilityNotify => {
let xev: &ffi::XVisibilityEvent = xev.as_ref();
let xwindow = xev.window;
self.with_window(xwindow, |window| window.visibility_notify());
}
ffi::Expose => {
let xev: &ffi::XExposeEvent = xev.as_ref();
let window = xev.window;
let window_id = mkwid(window);
// Multiple Expose events may be received for subareas of a window.
// We issue `RedrawRequested` only for the last event of such a series.
if xev.count == 0 {
let window = xev.window;
let window_id = mkwid(window);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
callback(Event::RedrawRequested(window_id));
}
}
ffi::KeyPress | ffi::KeyRelease => {
@@ -510,41 +563,33 @@ impl<T: 'static> EventProcessor<T> {
// value, though this should only be an issue under multiseat configurations.
let device = util::VIRTUAL_CORE_KEYBOARD;
let device_id = mkdid(device);
let keycode = xkev.keycode;
// 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)(
xkev,
ptr::null_mut(),
0,
&mut keysym,
ptr::null_mut(),
);
wt.xconn.check_errors().expect("Failed to lookup keysym");
keysym
};
if keycode != 0 {
let scancode = keycode - 8;
let keysym = wt.xconn.lookup_keysym(xkev);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
update_modifiers!(
ModifiersState::from_x11_mask(xkev.state),
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
);
let modifiers = self.device_mod_state.modifiers();
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id,
input: KeyboardInput {
state,
scancode: xkev.keycode - 8,
scancode,
virtual_keycode,
modifiers,
},
is_synthetic: false,
},
});
}
@@ -581,7 +626,7 @@ impl<T: 'static> EventProcessor<T> {
ElementState::{Pressed, Released},
MouseButton::{Left, Middle, Other, Right},
MouseScrollDelta::LineDelta,
Touch, TouchPhase,
Touch,
WindowEvent::{
AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput,
MouseWheel,
@@ -598,7 +643,8 @@ impl<T: 'static> EventProcessor<T> {
return;
}
let modifiers = ModifiersState::from(xev.mods);
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
let state = if xev.evtype == ffi::XI_ButtonPress {
Pressed
@@ -674,7 +720,8 @@ impl<T: 'static> EventProcessor<T> {
let window_id = mkwid(xev.event);
let new_cursor_pos = (xev.event_x, xev.event_y);
let modifiers = ModifiersState::from(xev.mods);
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
let cursor_moved = self.with_window(xev.event, |window| {
let mut shared_state_lock = window.shared_state.lock();
@@ -788,14 +835,15 @@ impl<T: 'static> EventProcessor<T> {
}
}
}
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
if let Some(dpi_factor) =
self.with_window(xev.event, |window| window.hidpi_factor())
{
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
@@ -859,6 +907,10 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true),
});
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
// 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
@@ -877,9 +929,12 @@ impl<T: 'static> EventProcessor<T> {
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers: ModifiersState::from(xev.mods),
modifiers,
},
});
// Issue key press events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback);
}
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
@@ -890,8 +945,14 @@ impl<T: 'static> EventProcessor<T> {
.borrow_mut()
.unfocus(xev.event)
.expect("Failed to unfocus input context");
let window_id = mkwid(xev.event);
// Issue key release events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
window_id,
event: Focused(false),
})
}
@@ -908,17 +969,35 @@ impl<T: 'static> EventProcessor<T> {
let dpi_factor =
self.with_window(xev.event, |window| window.hidpi_factor());
if let Some(dpi_factor) = dpi_factor {
let id = xev.detail as u64;
let modifiers = self.device_mod_state.modifiers();
let location = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
// Mouse cursor position changes when touch events are received.
// Only the first concurrently active touch ID moves the mouse cursor.
if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase)
{
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
position: location,
modifiers,
},
});
}
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid),
phase,
location,
id: xev.detail as u64,
force: None, // TODO
id,
}),
})
}
@@ -1001,40 +1080,44 @@ impl<T: 'static> EventProcessor<T> {
_ => unreachable!(),
};
let device_id = xev.sourceid;
let device_id = mkdid(xev.sourceid);
let keycode = xev.detail;
if keycode < 8 {
return;
}
let scancode = (keycode - 8) as u32;
let keysym = unsafe {
(wt.xconn.xlib.XKeycodeToKeysym)(
wt.xconn.display,
xev.detail as ffi::KeyCode,
0,
)
};
wt.xconn
.check_errors()
.expect("Failed to lookup raw keysym");
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent {
device_id: mkdid(device_id),
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,
}),
});
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 new_modifiers = self.device_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(new_modifiers),
});
}
}
}
ffi::XI_HierarchyChanged => {
@@ -1113,4 +1196,65 @@ impl<T: 'static> EventProcessor<T> {
Err(_) => (),
}
}
fn handle_pressed_keys<F>(
&self,
window_id: crate::window::WindowId,
state: ElementState,
callback: &mut F,
) where
F: FnMut(Event<T>),
{
let wt = get_xtarget(&self.target);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let modifiers = self.device_mod_state.modifiers();
// Get the set of keys currently pressed and apply Key events to each
let keys = wt.xconn.query_keymap();
for keycode in &keys {
if keycode < 8 {
continue;
}
let scancode = (keycode - 8) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id,
input: KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers,
},
is_synthetic: true,
},
});
}
}
}
fn is_first_touch(first: &mut Option<u64>, num: &mut u32, id: u64, phase: TouchPhase) -> bool {
match phase {
TouchPhase::Started => {
if *num == 0 {
*first = Some(id);
}
*num += 1;
}
TouchPhase::Cancelled | TouchPhase::Ended => {
if *first == Some(id) {
*first = None;
}
*num = num.saturating_sub(1);
}
_ => (),
}
*first == Some(id)
}

View File

@@ -1,4 +1,10 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
mod dnd;
mod event_processor;
@@ -11,7 +17,7 @@ mod window;
mod xdisplay;
pub use self::{
monitor::MonitorHandle,
monitor::{MonitorHandle, VideoMode},
window::UnownedWindow,
xdisplay::{XConnection, XError, XNotSupported},
};
@@ -20,12 +26,13 @@ use std::{
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
ffi::CStr,
mem,
mem::{self, MaybeUninit},
ops::Deref,
os::raw::*,
rc::Rc,
slice,
sync::{mpsc, Arc, Mutex, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
@@ -34,10 +41,11 @@ use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
util::modifiers::ModifierKeymap,
};
use crate::{
error::OsError as RootOsError,
event::{Event, WindowEvent},
event::{Event, StartCause},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
window::WindowAttributes,
@@ -60,16 +68,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 +116,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 +151,11 @@ impl<T: 'static> EventLoop<T> {
xconn.update_cached_wm_info(root);
let pending_redraws: Arc<Mutex<HashSet<WindowId>>> = Default::default();
let mut mod_keymap = ModifierKeymap::new();
mod_keymap.reset_from_x_connection(&xconn);
let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime,
@@ -146,7 +166,7 @@ impl<T: 'static> EventLoop<T> {
xconn,
wm_delete_window,
net_wm_ping,
pending_redraws: Default::default(),
pending_redraws: pending_redraws.clone(),
}),
_marker: ::std::marker::PhantomData,
});
@@ -172,13 +192,17 @@ 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(),
num_touch: 0,
first_touch: None,
};
// Register for device hotplug events
@@ -190,6 +214,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 +225,13 @@ 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();
let mut pending_redraws = pending_redraws.lock().unwrap();
drain_events(&mut processor, &mut pending_events, &mut pending_redraws);
}
}
})
@@ -220,18 +244,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,7 +268,15 @@ 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 {
self.drain_events();
// Empty the event buffer
{
let mut guard = self.pending_events.borrow_mut();
@@ -270,90 +297,91 @@ impl<T: 'static> EventLoop<T> {
);
}
}
// send MainEventsCleared
{
sticky_exit_callback(
crate::event::Event::MainEventsCleared,
&self.target,
&mut control_flow,
&mut callback,
);
}
// 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)),
event: WindowEvent::RedrawRequested,
},
Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))),
&self.target,
&mut control_flow,
&mut callback,
);
}
}
// send Events cleared
// send RedrawEventsCleared
{
sticky_exit_callback(
crate::event::Event::EventsCleared,
crate::event::Event::RedrawEventsCleared,
&self.target,
&mut control_flow,
&mut callback,
);
}
let start = Instant::now();
let (mut cause, deadline, mut timeout);
match control_flow {
ControlFlow::Exit => break,
ControlFlow::Poll => {
// non-blocking dispatch
self.inner_loop
.dispatch(Some(::std::time::Duration::from_millis(0)), &mut ())
.unwrap();
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Poll),
&self.target,
&mut control_flow,
);
cause = StartCause::Poll;
deadline = None;
timeout = Some(Duration::from_millis(0));
}
ControlFlow::Wait => {
self.inner_loop.dispatch(None, &mut ()).unwrap();
callback(
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
start: ::std::time::Instant::now(),
requested_resume: None,
}),
&self.target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = ::std::time::Instant::now();
// compute the blocking duration
let duration = if deadline > start {
deadline - start
} else {
::std::time::Duration::from_millis(0)
cause = StartCause::WaitCancelled {
start,
requested_resume: None,
};
self.inner_loop.dispatch(Some(duration), &mut ()).unwrap();
let now = std::time::Instant::now();
if now < deadline {
callback(
crate::event::Event::NewEvents(
crate::event::StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
},
),
&self.target,
&mut control_flow,
);
deadline = None;
timeout = None;
}
ControlFlow::WaitUntil(wait_deadline) => {
cause = StartCause::ResumeTimeReached {
start,
requested_resume: wait_deadline,
};
timeout = if wait_deadline > start {
Some(wait_deadline - start)
} else {
callback(
crate::event::Event::NewEvents(
crate::event::StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
},
),
&self.target,
&mut control_flow,
);
}
Some(Duration::from_millis(0))
};
deadline = Some(wait_deadline);
}
}
if self.events_waiting() {
timeout = Some(Duration::from_millis(0));
}
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
if let Some(deadline) = deadline {
if deadline > Instant::now() {
cause = StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
};
}
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
}
callback(
@@ -370,19 +398,66 @@ 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();
let wt = get_xtarget(&self.target);
let mut pending_redraws = wt.pending_redraws.lock().unwrap();
drain_events(&mut processor, &mut pending_events, &mut pending_redraws);
}
fn events_waiting(&self) -> bool {
!self.pending_events.borrow().is_empty() || self.event_processor.borrow().poll()
}
}
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>>,
pending_redraws: &mut HashSet<WindowId>,
) {
let mut callback = |event| {
if let Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))) = event {
pending_redraws.insert(wid);
} else {
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
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed)
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
}
}
@@ -395,19 +470,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 +587,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,24 @@ impl MonitorHandle {
})
}
pub 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(crate) fn is_dummy(&self) -> bool {
// Zero is an invalid XID value; no real monitor will have it
self.id == 0
}
pub fn name(&self) -> Option<String> {
Some(self.name.clone())
}
@@ -107,17 +171,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 +213,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 +233,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 +281,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;
@@ -11,35 +11,39 @@ pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
// To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
impl From<ffi::XIModifierState> for ModifiersState {
fn from(mods: ffi::XIModifierState) -> Self {
let state = mods.effective as c_uint;
ModifiersState {
alt: state & ffi::Mod1Mask != 0,
shift: state & ffi::ShiftMask != 0,
ctrl: state & ffi::ControlMask != 0,
logo: state & ffi::Mod4Mask != 0,
}
impl ModifiersState {
pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self {
ModifiersState::from_x11_mask(state.effective as c_uint)
}
pub(crate) fn from_x11_mask(mask: c_uint) -> Self {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, mask & ffi::Mod1Mask != 0);
m.set(ModifiersState::CTRL, mask & ffi::ShiftMask != 0);
m.set(ModifiersState::ALT, mask & ffi::ControlMask != 0);
m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0);
m
}
}
// 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> {
pub fn get_modifier_state(&self) -> ModifiersState {
self.modifiers.into()
ModifiersState::from_x11(&self.modifiers)
}
}
@@ -93,29 +97,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 +144,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 +153,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 +163,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

@@ -0,0 +1,92 @@
use std::{iter::Enumerate, ptr, slice::Iter};
use super::*;
pub struct Keymap {
keys: [u8; 32],
}
pub struct KeymapIter<'a> {
iter: Enumerate<Iter<'a, u8>>,
index: usize,
item: Option<u8>,
}
impl Keymap {
pub fn iter(&self) -> KeymapIter<'_> {
KeymapIter {
iter: self.keys.iter().enumerate(),
index: 0,
item: None,
}
}
}
impl<'a> IntoIterator for &'a Keymap {
type Item = ffi::KeyCode;
type IntoIter = KeymapIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl Iterator for KeymapIter<'_> {
type Item = ffi::KeyCode;
fn next(&mut self) -> Option<ffi::KeyCode> {
if self.item.is_none() {
while let Some((index, &item)) = self.iter.next() {
if item != 0 {
self.index = index;
self.item = Some(item);
break;
}
}
}
self.item.take().map(|item| {
debug_assert!(item != 0);
let bit = first_bit(item);
if item != bit {
// Remove the first bit; save the rest for further iterations
self.item = Some(item ^ bit);
}
let shift = bit.trailing_zeros() + (self.index * 8) as u32;
shift as ffi::KeyCode
})
}
}
impl XConnection {
pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym {
unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) }
}
pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym {
let mut keysym = 0;
unsafe {
(self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut());
}
keysym
}
pub fn query_keymap(&self) -> Keymap {
let mut keys = [0; 32];
unsafe {
(self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char);
}
Keymap { keys }
}
}
fn first_bit(b: u8) -> u8 {
1 << b.trailing_zeros()
}

View File

@@ -3,12 +3,15 @@
mod atom;
mod client_msg;
mod cursor;
mod format;
mod geometry;
mod hint;
mod icon;
mod input;
pub mod keys;
mod memory;
pub mod modifiers;
mod randr;
mod window_property;
mod wm;
@@ -18,7 +21,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,187 @@
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>,
state: ModifiersState,
}
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 update_keymap(&mut self, mods: &ModifierKeymap) {
self.keys.retain(|k, v| {
if let Some(m) = mods.get_modifier(*k) {
*v = m;
true
} else {
false
}
});
self.reset_state();
}
pub fn update_state(
&mut self,
state: &ModifiersState,
except: Option<Modifier>,
) -> Option<ModifiersState> {
let mut new_state = *state;
match except {
Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()),
Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()),
Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()),
Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()),
None => (),
}
if self.state == new_state {
None
} else {
self.keys.retain(|_k, v| get_modifier(&new_state, *v));
self.state = new_state;
Some(new_state)
}
}
pub fn modifiers(&self) -> ModifiersState {
self.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);
set_modifier(&mut self.state, modifier, true);
}
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
if let Some(modifier) = self.keys.remove(&keycode) {
if self.keys.values().find(|&&m| m == modifier).is_none() {
set_modifier(&mut self.state, modifier, false);
}
}
}
fn reset_state(&mut self) {
let mut new_state = ModifiersState::default();
for &m in self.keys.values() {
set_modifier(&mut new_state, m, true);
}
self.state = new_state;
}
}
fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool {
match modifier {
Modifier::Alt => state.alt(),
Modifier::Ctrl => state.ctrl(),
Modifier::Shift => state.shift(),
Modifier::Logo => state.logo(),
}
}
fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) {
match modifier {
Modifier::Alt => state.set(ModifiersState::ALT, value),
Modifier::Ctrl => state.set(ModifiersState::CTRL, value),
Modifier::Shift => state.set(ModifiersState::SHIFT, value),
Modifier::Logo => state.set(ModifiersState::LOGO, value),
}
}

View File

@@ -1,26 +1,22 @@
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};
/// Represents values of `WINIT_HIDPI_FACTOR`.
pub enum EnvVarDPI {
Randr,
Scale(f64),
NotSet,
}
pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32),
(width_mm, height_mm): (u64, u64),
) -> f64 {
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
let dpi_override = env::var("WINIT_HIDPI_FACTOR")
.ok()
.and_then(|var| f64::from_str(&var).ok());
if let Some(dpi_override) = dpi_override {
if !validate_hidpi_factor(dpi_override) {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`",
dpi_override,
);
}
return dpi_override;
}
// See http://xpra.org/trac/ticket/728 for more information.
if width_mm == 0 || height_mm == 0 {
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
@@ -34,47 +30,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 +51,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,27 +87,128 @@ 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,
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
repr.size(),
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
let dpi_env = env::var("WINIT_HIDPI_FACTOR").ok().map_or_else(
|| EnvVarDPI::NotSet,
|var| {
if var.to_lowercase() == "randr" {
EnvVarDPI::Randr
} else if let Ok(dpi) = f64::from_str(&var) {
EnvVarDPI::Scale(dpi)
} else if var.is_empty() {
EnvVarDPI::NotSet
} else {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
var
);
}
},
);
let hidpi_factor = match dpi_env {
EnvVarDPI::Randr => calc_dpi_factor(
((*crtc).width as u32, (*crtc).height as u32),
(
(*output_info).mm_width as u64,
(*output_info).mm_height as u64,
),
)
),
EnvVarDPI::Scale(dpi_override) => {
if !validate_hidpi_factor(dpi_override) {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
dpi_override,
);
}
dpi_override
}
EnvVarDPI::NotSet => {
if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
((*crtc).width as u32, (*crtc).height as u32),
(
(*output_info).mm_width as u64,
(*output_info).mm_height as u64,
),
)
}
}
};
(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,49 +17,73 @@ 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};
unsafe extern "C" fn visibility_predicate(
_display: *mut ffi::Display,
event: *mut ffi::XEvent,
arg: ffi::XPointer, // We populate this with the window ID (by value) when we call XIfEvent
) -> ffi::Bool {
let event: &ffi::XAnyEvent = (*event).as_ref();
let window = arg as ffi::Window;
(event.window == window && event.type_ == ffi::VisibilityNotify) as _
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct SharedState {
pub cursor_pos: Option<(f64, f64)>,
pub size: Option<(u32, u32)>,
pub position: Option<(i32, i32)>,
pub inner_position: Option<(i32, i32)>,
pub inner_position_rel_parent: Option<(i32, i32)>,
pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorHandle>,
pub last_monitor: X11MonitorHandle,
pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<RootMonitorHandle>,
// Used to restore position after exiting fullscreen.
pub fullscreen: Option<Fullscreen>,
// Set when application calls `set_fullscreen` when window is not visible
pub desired_fullscreen: Option<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 visibility: Visibility,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Visibility {
No,
Yes,
// Waiting for VisibilityNotify
YesWait,
}
impl SharedState {
fn new(dpi_factor: f64) -> Mutex<Self> {
let mut shared_state = SharedState::default();
shared_state.guessed_dpi = Some(dpi_factor);
Mutex::new(shared_state)
fn new(last_monitor: X11MonitorHandle, is_visible: bool) -> Mutex<Self> {
let visibility = if is_visible {
Visibility::YesWait
} else {
Visibility::No
};
Mutex::new(SharedState {
last_monitor,
visibility,
cursor_pos: None,
size: None,
position: None,
inner_position: None,
inner_position_rel_parent: None,
dpi_adjusted: None,
fullscreen: None,
desired_fullscreen: None,
restore_position: None,
desktop_video_mode: None,
frame_extents: None,
min_inner_size: None,
max_inner_size: None,
})
}
}
@@ -77,34 +112,27 @@ impl UnownedWindow {
let xconn = &event_loop.xconn;
let root = event_loop.root;
let monitors = xconn.available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].hidpi_factor());
for monitor in &monitors {
if Some(monitor.hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
dpi_factor.unwrap_or_else(|| {
xconn
.query_pointer(root, util::VIRTUAL_CORE_POINTER)
.ok()
.and_then(|pointer_state| {
let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.rect.contains_point(x, y) {
dpi_factor = Some(monitor.hidpi_factor());
break;
}
}
dpi_factor
})
.unwrap_or(1.0)
})
let mut monitors = xconn.available_monitors();
let guessed_monitor = if monitors.is_empty() {
X11MonitorHandle::dummy()
} else {
return Err(os_error!(OsError::XMisc("No monitors were detected.")));
xconn
.query_pointer(root, util::VIRTUAL_CORE_POINTER)
.ok()
.and_then(|pointer_state| {
let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);
for i in 0..monitors.len() {
if monitors[i].rect.contains_point(x, y) {
return Some(monitors.swap_remove(i));
}
}
None
})
.unwrap_or_else(|| monitors.swap_remove(0))
};
let dpi_factor = guessed_monitor.hidpi_factor();
info!("Guessed window DPI factor: {}", dpi_factor);
@@ -216,7 +244,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(guessed_monitor, window_attrs.visible),
pending_redraws: event_loop.pending_redraws.clone(),
};
@@ -282,9 +310,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();
@@ -398,34 +424,13 @@ impl UnownedWindow {
if window_attrs.fullscreen.is_some() {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.queue();
.map(|flusher| flusher.queue());
}
if window_attrs.always_on_top {
window
.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 +454,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 +490,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,64 +560,185 @@ 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();
match shared_state_lock.visibility {
// Setting fullscreen on a window that is not visible will generate an error.
Visibility::No | Visibility::YesWait => {
shared_state_lock.desired_fullscreen = Some(fullscreen);
return None;
}
Visibility::Yes => (),
}
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.is_dummy() {
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> {
self.shared_state.lock().fullscreen.clone()
pub fn fullscreen(&self) -> Option<Fullscreen> {
let shared_state = self.shared_state.lock();
shared_state
.desired_fullscreen
.clone()
.unwrap_or_else(|| shared_state.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 {
// TODO: This might round-trip more times than needed.
let position = self.outer_position_physical();
let size = self.outer_size_physical();
util::AaRect::new(position, size)
// Called by EventProcessor when a VisibilityNotify event is received
pub(crate) fn visibility_notify(&self) {
let mut shared_state = self.shared_state.lock();
match shared_state.visibility {
Visibility::No => unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
},
Visibility::Yes => (),
Visibility::YesWait => {
shared_state.visibility = Visibility::Yes;
if let Some(fullscreen) = shared_state.desired_fullscreen.take() {
drop(shared_state);
self.set_fullscreen(fullscreen);
}
}
}
}
#[inline]
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());
monitor
})
self.shared_state.lock().last_monitor.clone()
}
pub fn available_monitors(&self) -> Vec<X11MonitorHandle> {
@@ -616,6 +749,35 @@ impl UnownedWindow {
self.xconn.primary_monitor()
}
fn set_minimized_inner(&self, minimized: bool) -> util::Flusher<'_> {
unsafe {
if minimized {
let screen = (self.xconn.xlib.XDefaultScreen)(self.xconn.display);
(self.xconn.xlib.XIconifyWindow)(self.xconn.display, self.xwindow, screen);
util::Flusher::new(&self.xconn)
} else {
let atom = self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0");
self.xconn.send_client_msg(
self.xwindow,
self.root,
atom,
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
[1, ffi::CurrentTime as c_long, 0, 0, 0],
)
}
}
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
self.set_minimized_inner(minimized)
.flush()
.expect("Failed to change window minimization");
}
fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> {
let horz_atom = unsafe {
self.xconn
@@ -738,19 +900,31 @@ impl UnownedWindow {
#[inline]
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
let mut shared_state = self.shared_state.lock();
match (visible, shared_state.visibility) {
(true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => {
return
}
_ => (),
}
if visible {
unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn
.flush_requests()
.expect("Failed to call XMapRaised");
},
false => unsafe {
}
self.xconn
.flush_requests()
.expect("Failed to call XMapRaised");
shared_state.visibility = Visibility::YesWait;
} else {
unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
self.xconn
.flush_requests()
.expect("Failed to call XUnmapWindow");
},
}
self.xconn
.flush_requests()
.expect("Failed to call XUnmapWindow");
shared_state.visibility = Visibility::No;
}
}
@@ -847,17 +1021,6 @@ impl UnownedWindow {
self.logicalize_size(self.inner_size_physical())
}
pub(crate) fn outer_size_physical(&self) -> (u32, u32) {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
let (w, h) = self.inner_size_physical();
extents.inner_size_to_outer(w, h)
} else {
self.update_cached_frame_extents();
self.outer_size_physical()
}
}
#[inline]
pub fn outer_size(&self) -> LogicalSize {
let extents = self.shared_state.lock().frame_extents.clone();
@@ -1025,131 +1188,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 +1265,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 +1322,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,
@@ -10,14 +11,19 @@ use std::{
time::Instant,
};
use cocoa::{appkit::NSApp, base::nil};
use cocoa::{
appkit::NSApp,
base::nil,
foundation::{NSAutoreleasePool, NSString},
};
use crate::{
event::{Event, StartCause, WindowEvent},
event::{Event, StartCause},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{observer::EventLoopWaker, util::Never},
window::WindowId,
};
use objc::runtime::Object;
lazy_static! {
static ref HANDLER: Handler = Default::default();
@@ -37,13 +43,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 +58,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 +89,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 +101,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 +145,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 +173,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 +196,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 +260,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;
@@ -283,22 +270,41 @@ impl AppState {
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.handle_nonuser_event(Event::MainEventsCleared);
for window_id in HANDLER.should_redraw() {
HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
HANDLER.handle_nonuser_event(Event::RedrawRequested(window_id));
}
HANDLER.handle_nonuser_event(Event::EventsCleared);
HANDLER.handle_nonuser_event(Event::RedrawEventsCleared);
HANDLER.set_in_callback(false);
}
if HANDLER.should_exit() {
let _: () = unsafe { msg_send![NSApp(), stop: nil] };
return;
unsafe {
let _: () = msg_send![NSApp(), stop: nil];
let pool = NSAutoreleasePool::new(nil);
let windows: *const Object = msg_send![NSApp(), windows];
let window: *const Object = msg_send![windows, objectAtIndex:0];
assert_ne!(window, nil);
let title: *const Object = msg_send![window, title];
assert_ne!(title, nil);
let postfix = NSString::alloc(nil).init_str("*");
let some_unique_title: *const Object =
msg_send![title, stringByAppendingString: postfix];
assert_ne!(some_unique_title, nil);
// To stop event loop immediately, we need to send some UI event here.
let _: () = msg_send![window, setTitle: some_unique_title];
// And restore it.
let _: () = msg_send![window, setTitle: title];
pool.drain();
};
}
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

@@ -208,7 +208,7 @@ pub fn scancode_to_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
// constants for the rest.
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
pub fn check_function_keys(string: &String) -> Option<VirtualKeyCode> {
pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
if let Some(ch) = string.encode_utf16().next() {
return Some(match ch {
0xf718 => VirtualKeyCode::F21,
@@ -224,12 +224,24 @@ pub fn check_function_keys(string: &String) -> Option<VirtualKeyCode> {
pub fn event_mods(event: id) -> ModifiersState {
let flags = unsafe { NSEvent::modifierFlags(event) };
ModifiersState {
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
}
let mut m = ModifiersState::empty();
m.set(
ModifiersState::SHIFT,
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
);
m.set(
ModifiersState::CTRL,
flags.contains(NSEventModifierFlags::NSControlKeyMask),
);
m.set(
ModifiersState::ALT,
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
);
m.set(
ModifiersState::LOGO,
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
);
m
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
@@ -264,6 +276,7 @@ pub unsafe fn modifier_event(
virtual_keycode,
modifiers: event_mods(ns_event),
},
is_synthetic: false,
})
} else {
None

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,26 +82,27 @@ 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),
{
unsafe {
let _pool = NSAutoreleasePool::new(nil);
let app = NSApp();
assert_ne!(app, nil);
AppState::set_callback(callback, self.window_target);
let _: () = msg_send![app, run];
AppState::exit();
process::exit(0)
}
self.run_return(callback);
process::exit(0);
}
pub fn run_return<F>(&mut self, _callback: F)
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unimplemented!();
unsafe {
let pool = NSAutoreleasePool::new(nil);
let app = NSApp();
assert_ne!(app, nil);
AppState::set_callback(callback, Rc::clone(&self.window_target));
let _: () = msg_send![app, run];
AppState::exit();
pool.drain();
}
}
pub fn create_proxy(&self) -> Proxy<T> {
@@ -108,26 +110,30 @@ impl<T> EventLoop<T> {
}
}
#[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
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
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);
@@ -137,8 +143,10 @@ impl<T> Proxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);

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,26 @@ 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)
.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 +269,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 {}
@@ -99,14 +93,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) -> ffi::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)>,
}
// begin is queued with the highest priority to ensure it is processed before other observers
@@ -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

@@ -60,11 +60,15 @@ pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowSty
}
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
if msg_send![class!(NSThread), isMainThread] {
set_style_mask_callback(context as *mut _);
} else {
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
}
}
struct SetContentSizeData {
@@ -206,7 +210,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 +235,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

@@ -3,7 +3,8 @@ use cocoa::{
base::{id, nil},
foundation::{NSDictionary, NSPoint, NSString},
};
use objc::runtime::Sel;
use objc::{runtime::Sel, runtime::NO};
use std::cell::RefCell;
use crate::window::CursorIcon;
@@ -15,10 +16,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"),
@@ -124,3 +127,38 @@ pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id {
hotSpot:point
]
}
pub unsafe fn invisible_cursor() -> id {
// 16x16 GIF data for invisible cursor
// You can reproduce this via ImageMagick.
// $ convert -size 16x16 xc:none cursor.gif
static CURSOR_BYTES: &[u8] = &[
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F,
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
];
thread_local! {
// We can't initialize this at startup.
static CURSOR_OBJECT: RefCell<id> = RefCell::new(nil);
}
CURSOR_OBJECT.with(|cursor_obj| {
if *cursor_obj.borrow() == nil {
// Create a cursor from `CURSOR_BYTES`
let cursor_data: id = msg_send![class!(NSData),
dataWithBytesNoCopy:CURSOR_BYTES as *const [u8]
length:CURSOR_BYTES.len()
freeWhenDone:NO
];
let ns_image: id = msg_send![class!(NSImage), alloc];
let _: id = msg_send![ns_image, initWithData: cursor_data];
let cursor: id = msg_send![class!(NSCursor), alloc];
*cursor_obj.borrow_mut() =
msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)];
}
*cursor_obj.borrow()
})
}

View File

@@ -9,7 +9,7 @@ use std::{
use cocoa::{
appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow},
base::{id, nil},
foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger},
foundation::{NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
};
use objc::{
declare::ClassDecl,
@@ -18,8 +18,8 @@ use objc::{
use crate::{
event::{
DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode, WindowEvent,
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
},
platform_impl::platform::{
app_state::AppState,
@@ -35,33 +35,41 @@ use crate::{
window::WindowId,
};
#[derive(Default)]
struct Modifiers {
shift_pressed: bool,
ctrl_pressed: bool,
win_pressed: bool,
alt_pressed: bool,
pub struct CursorState {
pub visible: bool,
pub cursor: util::Cursor,
}
impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
cursor: Default::default(),
}
}
}
struct ViewState {
ns_window: id,
pub cursor: Arc<Mutex<util::Cursor>>,
pub cursor_state: Arc<Mutex<CursorState>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
modifiers: Modifiers,
modifiers: ModifiersState,
tracking_rect: Option<NSInteger>,
}
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor_state);
let state = ViewState {
ns_window,
cursor,
cursor_state,
ime_spot: None,
raw_characters: None,
is_key_down: false,
modifiers: Default::default(),
tracking_rect: None,
};
unsafe {
// This is free'd in `dealloc`
@@ -104,7 +112,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),
@@ -236,6 +244,10 @@ lazy_static! {
sel!(cancelOperation:),
cancel_operation as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(frameDidChange:),
frame_did_change as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
@@ -261,6 +273,19 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i
let marked_text =
<id as NSMutableAttributedString>::init(NSMutableAttributedString::alloc(nil));
(*this).set_ivar("markedText", marked_text);
let _: () = msg_send![this, setPostsFrameChangedNotifications: YES];
let notification_center: &Object =
msg_send![class!(NSNotificationCenter), defaultCenter];
let notification_name =
NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification");
let _: () = msg_send![
notification_center,
addObserver: this
selector: sel!(frameDidChange:)
name: notification_name
object: this
];
}
this
}
@@ -269,18 +294,47 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i
extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) {
trace!("Triggered `viewDidMoveToWindow`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(tracking_rect) = state.tracking_rect.take() {
let _: () = msg_send![this, removeTrackingRect: tracking_rect];
}
let rect: NSRect = msg_send![this, visibleRect];
let _: () = msg_send![this,
let tracking_rect: NSInteger = msg_send![this,
addTrackingRect:rect
owner:this
userData:nil
assumeInside:NO
];
state.tracking_rect = Some(tracking_rect);
}
trace!("Completed `viewDidMoveToWindow`");
}
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: id) {
extern "C" fn frame_did_change(this: &Object, _sel: Sel, _event: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(tracking_rect) = state.tracking_rect.take() {
let _: () = msg_send![this, removeTrackingRect: tracking_rect];
}
let rect: NSRect = msg_send![this, visibleRect];
let tracking_rect: NSInteger = msg_send![this,
addTrackingRect:rect
owner:this
userData:nil
assumeInside:NO
];
state.tracking_rect = Some(tracking_rect);
}
}
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);
@@ -309,7 +363,12 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) {
let state = &mut *(state_ptr as *mut ViewState);
let bounds: NSRect = msg_send![this, bounds];
let cursor = state.cursor.lock().unwrap().load();
let cursor_state = state.cursor_state.lock().unwrap();
let cursor = if cursor_state.visible {
cursor_state.cursor.load()
} else {
util::invisible_cursor()
};
let _: () = msg_send![this,
addCursorRect:bounds
cursor:cursor
@@ -452,7 +511,7 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran
//let event: id = msg_send![NSApp(), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
for character in string.chars().filter(|c| !is_corporate_character(*c)) {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter(character),
@@ -484,7 +543,10 @@ extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
} else {
let raw_characters = state.raw_characters.take();
if let Some(raw_characters) = raw_characters {
for character in raw_characters.chars() {
for character in raw_characters
.chars()
.filter(|c| !is_corporate_character(*c))
{
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter(character),
@@ -515,15 +577,27 @@ fn get_characters(event: id, ignore_modifiers: bool) -> String {
}
}
// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
fn is_corporate_character(c: char) -> bool {
match c {
'\u{F700}'..='\u{F747}'
| '\u{F802}'..='\u{F84F}'
| '\u{F850}'
| '\u{F85C}'
| '\u{F85D}'
| '\u{F85F}'
| '\u{F860}'..='\u{F86B}'
| '\u{F870}'..='\u{F8FF}' => true,
_ => false,
}
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters
.chars()
.next()
.map_or(None, |c| char_to_keycode(c))
characters.chars().next().and_then(|c| char_to_keycode(c))
}
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
@@ -567,6 +641,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
@@ -574,7 +649,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
AppState::queue_event(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down {
for character in characters.chars() {
for character in characters.chars().filter(|c| !is_corporate_character(*c)) {
AppState::queue_event(Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
@@ -618,6 +693,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
@@ -637,36 +713,36 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSShiftKeyMask,
state.modifiers.shift_pressed,
state.modifiers.shift(),
) {
state.modifiers.shift_pressed = !state.modifiers.shift_pressed;
state.modifiers.toggle(ModifiersState::SHIFT);
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSControlKeyMask,
state.modifiers.ctrl_pressed,
state.modifiers.ctrl(),
) {
state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed;
state.modifiers.toggle(ModifiersState::CTRL);
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSCommandKeyMask,
state.modifiers.win_pressed,
state.modifiers.logo(),
) {
state.modifiers.win_pressed = !state.modifiers.win_pressed;
state.modifiers.toggle(ModifiersState::LOGO);
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSAlternateKeyMask,
state.modifiers.alt_pressed,
state.modifiers.alt(),
) {
state.modifiers.alt_pressed = !state.modifiers.alt_pressed;
state.modifiers.toggle(ModifiersState::ALT);
events.push_back(window_event);
}
@@ -676,6 +752,11 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
event,
});
}
AppState::queue_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::ModifiersChanged(state.modifiers),
});
}
trace!("Completed `flagsChanged`");
}
@@ -726,6 +807,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};

View File

@@ -1,3 +1,4 @@
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
use std::{
collections::VecDeque,
f64,
@@ -8,6 +9,24 @@ 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::CursorState,
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 +36,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 +67,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 +88,15 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
}
}
unsafe fn create_view(ns_window: id) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
let (ns_view, cursor) = new_view(ns_window);
unsafe fn create_view(
ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<CursorState>>)> {
let (ns_view, cursor_state) = 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
@@ -102,7 +109,7 @@ unsafe fn create_view(ns_window: id) -> Option<(IdRef, Weak<Mutex<util::Cursor>>
ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*ns_view);
(ns_view, cursor)
(ns_view, cursor_state)
})
}
@@ -113,11 +120,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 +243,22 @@ lazy_static! {
#[derive(Default)]
pub struct SharedState {
pub resizable: bool,
pub fullscreen: Option<RootMonitorHandle>,
pub fullscreen: Option<Fullscreen>,
// This is true between windowWillEnterFullScreen and windowDidEnterFullScreen
// or windowWillExitFullScreen and windowDidExitFullScreen.
// We must not toggle fullscreen when this is true.
pub in_fullscreen_transition: bool,
// If it is attempted to toggle fullscreen when in_fullscreen_transition is true,
// Set target_fullscreen and do after fullscreen transition is end.
pub target_fullscreen: Option<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 {
@@ -271,8 +291,7 @@ pub struct UnownedWindow {
input_context: IdRef, // never changes
pub shared_state: Arc<Mutex<SharedState>>,
decorations: AtomicBool,
cursor: Weak<Mutex<util::Cursor>>,
cursor_visible: AtomicBool,
cursor_state: Weak<Mutex<CursorState>>,
}
unsafe impl Send for UnownedWindow {}
@@ -301,10 +320,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_state) =
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) };
@@ -348,23 +368,13 @@ impl UnownedWindow {
input_context,
shared_state: Arc::new(Mutex::new(win_attribs.into())),
decorations: AtomicBool::new(decorations),
cursor,
cursor_visible: AtomicBool::new(true),
cursor_state,
});
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
@@ -505,8 +515,8 @@ impl UnownedWindow {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let cursor = util::Cursor::from(cursor);
if let Some(cursor_access) = self.cursor.upgrade() {
*cursor_access.lock().unwrap() = cursor;
if let Some(cursor_access) = self.cursor_state.upgrade() {
cursor_access.lock().unwrap().cursor = cursor;
}
unsafe {
let _: () = msg_send![*self.ns_window,
@@ -524,16 +534,17 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let cursor_class = class!(NSCursor);
// macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once.
// (otherwise, `hide_cursor(false)` would need to be called n times!)
if visible != self.cursor_visible.load(Ordering::Acquire) {
if visible {
let _: () = unsafe { msg_send![cursor_class, unhide] };
} else {
let _: () = unsafe { msg_send![cursor_class, hide] };
if let Some(cursor_access) = self.cursor_state.upgrade() {
let mut cursor_state = cursor_access.lock().unwrap();
if visible != cursor_state.visible {
cursor_state.visible = visible;
drop(cursor_state);
unsafe {
let _: () = msg_send![*self.ns_window,
invalidateCursorRectsForView:*self.ns_view
];
}
}
self.cursor_visible.store(visible, Ordering::Release);
}
}
@@ -594,22 +605,44 @@ 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);
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
let is_minimized: BOOL = unsafe { msg_send![*self.ns_window, isMiniaturized] };
let is_minimized: bool = is_minimized == YES;
if is_minimized == minimized {
return;
}
if minimized {
unsafe {
NSWindow::miniaturize_(*self.ns_window, *self.ns_window);
}
} else {
unsafe {
NSWindow::deminiaturize_(*self.ns_window, *self.ns_window);
}
}
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let is_zoomed = self.is_zoomed();
@@ -627,44 +660,175 @@ 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>) {
let shared_state_lock = self.shared_state.lock().unwrap();
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
trace!("Locked shared state in `set_fullscreen`");
let mut 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,
_ => (),
}
if shared_state_lock.in_fullscreen_transition {
// We can't set fullscreen here.
// Set fullscreen after transition.
shared_state_lock.target_fullscreen = Some(fullscreen);
trace!("Unlocked shared state in `set_fullscreen`");
current.is_none()
};
return;
}
let old_fullscreen = shared_state_lock.fullscreen.clone();
if fullscreen == old_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");
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 +928,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),
@@ -194,6 +195,10 @@ lazy_static! {
sel!(windowDidExitFullScreen:),
window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowWillExitFullScreen:),
window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidFailToEnterFullScreen:),
window_did_fail_to_enter_fullscreen as extern "C" fn(&Object, Sel, id),
@@ -401,24 +406,80 @@ 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()))
}
}
shared_state.in_fullscreen_transition = true;
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
})
});
trace!("Completed `windowWillEnterFullscreen:`");
}
/// Invoked when before exit fullscreen
extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillExitFullScreen:`");
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_will_exit_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = true;
trace!("Unlocked shared state in `window_will_exit_fullscreen`");
});
});
trace!("Completed `windowWillExitFullScreen:`");
}
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;
state.with_window(|window| {
trace!("Locked shared state in `window_did_enter_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
trace!("Unlocked shared state in `window_did_enter_fullscreen`");
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
window.set_fullscreen(target_fullscreen);
}
});
});
trace!("Completed `windowDidEnterFullscreen:`");
}
@@ -429,6 +490,15 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
with_state(this, |state| {
state.with_window(|window| {
window.restore_state_from_fullscreen();
trace!("Locked shared state in `window_did_exit_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
trace!("Unlocked shared state in `window_did_exit_fullscreen`");
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
window.set_fullscreen(target_fullscreen);
}
})
});
trace!("Completed `windowDidExitFullscreen:`");
@@ -453,6 +523,13 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidFailToEnterFullscreen:`");
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None;
trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`");
});
if state.initial_fullscreen {
let _: () = unsafe {
msg_send![*state.ns_window,

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<T>> {
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,251 @@
use super::{backend, state::State};
use crate::event::{Event, StartCause};
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);
}
self.handle_event(Event::MainEventsCleared, &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::RedrawRequested(window_id), &mut control);
}
self.handle_event(Event::RedrawEventsCleared, &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,199 @@
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,
},
is_synthetic: false,
},
});
});
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,
},
is_synthetic: false,
},
});
});
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,31 @@
// 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;
#[cfg(not(any(feature = "web-sys", feature = "stdweb")))]
compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`");
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 {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
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 {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
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: Option<TimeoutHandle>,
}
impl Timeout {
pub fn new<F>(f: F, duration: Duration) -> Timeout
where
F: 'static + FnMut(),
{
Timeout {
handle: Some(window().set_clearable_timeout(f, duration.as_millis() as u32)),
}
}
}
impl Drop for Timeout {
fn drop(&mut self) {
let handle = self.handle.take().unwrap();
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 {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
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 {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
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,293 @@
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_minimized(&self, _minimized: bool) {
// Intentionally a no-op, as canvases cannot be 'minimized'
}
#[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

@@ -0,0 +1,216 @@
/// This is a simple implementation of support for Windows Dark Mode,
/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use winapi::{
shared::{
basetsd::SIZE_T,
minwindef::{BOOL, DWORD, UINT, ULONG, WORD},
ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR},
windef::HWND,
},
um::{libloaderapi, uxtheme, winuser},
};
lazy_static! {
static ref WIN10_BUILD_VERSION: Option<DWORD> = {
// FIXME: RtlGetVersion is a documented windows API,
// should be part of winapi!
#[allow(non_snake_case)]
#[repr(C)]
struct OSVERSIONINFOW {
dwOSVersionInfoSize: ULONG,
dwMajorVersion: ULONG,
dwMinorVersion: ULONG,
dwBuildNumber: ULONG,
dwPlatformId: ULONG,
szCSDVersion: [WCHAR; 128],
}
type RtlGetVersion = unsafe extern "system" fn (*mut OSVERSIONINFOW) -> NTSTATUS;
let handle = get_function!("ntdll.dll", RtlGetVersion);
if let Some(rtl_get_version) = handle {
unsafe {
let mut vi = OSVERSIONINFOW {
dwOSVersionInfoSize: 0,
dwMajorVersion: 0,
dwMinorVersion: 0,
dwBuildNumber: 0,
dwPlatformId: 0,
szCSDVersion: [0; 128],
};
let status = (rtl_get_version)(&mut vi as _);
assert!(NT_SUCCESS(status));
if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
Some(vi.dwBuildNumber)
} else {
None
}
}
} else {
None
}
};
static ref DARK_MODE_SUPPORTED: bool = {
// We won't try to do anything for windows versions < 17763
// (Windows 10 October 2018 update)
match *WIN10_BUILD_VERSION {
Some(v) => v >= 17763,
None => false
}
};
static ref DARK_THEME_NAME: Vec<u16> = widestring("DarkMode_Explorer");
static ref LIGHT_THEME_NAME: Vec<u16> = widestring("");
}
/// Attempt to set dark mode on a window, if necessary.
/// Returns true if dark mode was set, false if not.
pub fn try_dark_mode(hwnd: HWND) -> bool {
if *DARK_MODE_SUPPORTED {
let is_dark_mode = should_use_dark_mode();
let theme_name = if is_dark_mode {
DARK_THEME_NAME.as_ptr()
} else {
LIGHT_THEME_NAME.as_ptr()
};
unsafe {
assert_eq!(
0,
uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null())
);
set_dark_mode_for_window(hwnd, is_dark_mode)
}
is_dark_mode
} else {
false
}
}
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
// Uses Windows undocumented API SetWindowCompositionAttribute,
// as seen in win32-darkmode example linked at top of file.
type SetWindowCompositionAttribute =
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
#[allow(non_snake_case)]
type WINDOWCOMPOSITIONATTRIB = u32;
const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26;
#[allow(non_snake_case)]
#[repr(C)]
struct WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WINDOWCOMPOSITIONATTRIB,
pvData: PVOID,
cbData: SIZE_T,
}
lazy_static! {
static ref SET_WINDOW_COMPOSITION_ATTRIBUTE: Option<SetWindowCompositionAttribute> =
get_function!("user32.dll", SetWindowCompositionAttribute);
}
if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE {
unsafe {
// SetWindowCompositionAttribute needs a bigbool (i32), not bool.
let mut is_dark_mode_bigbool = is_dark_mode as BOOL;
let mut data = WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WCA_USEDARKMODECOLORS,
pvData: &mut is_dark_mode_bigbool as *mut _ as _,
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
};
assert_eq!(
1,
set_window_composition_attribute(hwnd, &mut data as *mut _)
);
}
}
}
fn should_use_dark_mode() -> bool {
should_apps_use_dark_mode() && !is_high_contrast()
}
fn should_apps_use_dark_mode() -> bool {
type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool;
lazy_static! {
static ref SHOULD_APPS_USE_DARK_MODE: Option<ShouldAppsUseDarkMode> = {
unsafe {
const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: WORD = 132;
let module = libloaderapi::LoadLibraryA("uxtheme.dll\0".as_ptr() as _);
if module.is_null() {
return None;
}
let handle = libloaderapi::GetProcAddress(
module,
winuser::MAKEINTRESOURCEA(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL),
);
if handle.is_null() {
None
} else {
Some(std::mem::transmute(handle))
}
}
};
}
SHOULD_APPS_USE_DARK_MODE
.map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() })
.unwrap_or(false)
}
// FIXME: This definition was missing from winapi. Can remove from
// here and use winapi once the following PR is released:
// https://github.com/retep998/winapi-rs/pull/815
#[repr(C)]
#[allow(non_snake_case)]
struct HIGHCONTRASTA {
cbSize: UINT,
dwFlags: DWORD,
lpszDefaultScheme: LPSTR,
}
const HCF_HIGHCONTRASTON: DWORD = 1;
fn is_high_contrast() -> bool {
let mut hc = HIGHCONTRASTA {
cbSize: 0,
dwFlags: 0,
lpszDefaultScheme: std::ptr::null_mut(),
};
let ok = unsafe {
winuser::SystemParametersInfoA(
winuser::SPI_GETHIGHCONTRAST,
std::mem::size_of_val(&hc) as _,
&mut hc as *mut _ as _,
0,
)
};
(ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1)
}
fn widestring(src: &'static str) -> Vec<u16> {
OsStr::new(src)
.encode_wide()
.chain(Some(0).into_iter())
.collect()
}

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

@@ -17,16 +17,84 @@ fn key_pressed(vkey: c_int) -> bool {
}
pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU);
mods.shift = key_pressed(winuser::VK_SHIFT);
mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr;
mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr;
mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN);
let mut mods = ModifiersState::empty();
mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT));
mods.set(
ModifiersState::CTRL,
key_pressed(winuser::VK_CONTROL) && !filter_out_altgr,
);
mods.set(
ModifiersState::ALT,
key_pressed(winuser::VK_MENU) && !filter_out_altgr,
);
mods.set(
ModifiersState::LOGO,
key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN),
);
mods
}
bitflags! {
#[derive(Default)]
pub struct ModifiersStateSide: u32 {
const LSHIFT = 0b010 << 0;
const RSHIFT = 0b001 << 0;
const LCTRL = 0b010 << 3;
const RCTRL = 0b001 << 3;
const LALT = 0b010 << 6;
const RALT = 0b001 << 6;
const LLOGO = 0b010 << 9;
const RLOGO = 0b001 << 9;
}
}
impl ModifiersStateSide {
pub fn filter_out_altgr(&self) -> ModifiersStateSide {
match layout_uses_altgr() && self.contains(Self::RALT) {
false => *self,
true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT),
}
}
}
impl From<ModifiersStateSide> for ModifiersState {
fn from(side: ModifiersStateSide) -> Self {
let mut state = ModifiersState::default();
state.set(
Self::SHIFT,
side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT),
);
state.set(
Self::CTRL,
side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL),
);
state.set(
Self::ALT,
side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT),
);
state.set(
Self::LOGO,
side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO),
);
state
}
}
pub fn get_pressed_keys() -> impl Iterator<Item = c_int> {
let mut keyboard_state = vec![0u8; 256];
unsafe { winuser::GetKeyboardState(keyboard_state.as_mut_ptr()) };
keyboard_state
.into_iter()
.enumerate()
.filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit
.map(|(i, _)| i as c_int)
}
unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<char> {
let mut unicode_bytes = [0u16; 5];
let len = winuser::ToUnicodeEx(
@@ -39,7 +107,7 @@ unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<c
hkl,
);
if len >= 1 {
char::decode_utf16(unicode_bytes.into_iter().cloned())
char::decode_utf16(unicode_bytes.iter().cloned())
.next()
.and_then(|c| c.ok())
} else {
@@ -177,8 +245,8 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
0x58 => Some(VirtualKeyCode::X),
0x59 => Some(VirtualKeyCode::Y),
0x5A => Some(VirtualKeyCode::Z),
//winuser::VK_LWIN => Some(VirtualKeyCode::Lwin),
//winuser::VK_RWIN => Some(VirtualKeyCode::Rwin),
winuser::VK_LWIN => Some(VirtualKeyCode::LWin),
winuser::VK_RWIN => Some(VirtualKeyCode::RWin),
winuser::VK_APPS => Some(VirtualKeyCode::Apps),
winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep),
winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0),

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