mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b33fbc0806 | ||
|
|
341fe47c56 | ||
|
|
8119a7df13 | ||
|
|
5c02c20b05 | ||
|
|
744913d482 | ||
|
|
7dc48ed9ef | ||
|
|
30c0058a88 | ||
|
|
8794157370 | ||
|
|
1a92c46ad7 | ||
|
|
22288ec4c1 | ||
|
|
ffa6815321 | ||
|
|
8ddc7fdaf6 | ||
|
|
594cd18567 | ||
|
|
4469f29e70 | ||
|
|
4e5321b0aa | ||
|
|
4b2e9da4e4 | ||
|
|
b94572621a | ||
|
|
7203ec45b5 | ||
|
|
fc835f383b | ||
|
|
2f9607694f | ||
|
|
cd5caf6a22 | ||
|
|
8522071c2c | ||
|
|
dfa972eab1 | ||
|
|
69585fe2f2 | ||
|
|
c0b2cad3f9 | ||
|
|
57680d2d17 | ||
|
|
0019ff210c | ||
|
|
4a103387e5 | ||
|
|
b6ca584ecf | ||
|
|
e0340d52b0 | ||
|
|
f928a4b917 | ||
|
|
c5d22fda2b | ||
|
|
9ea2810b46 | ||
|
|
9a23ec3c37 | ||
|
|
84c812e568 | ||
|
|
f0ce5b0c8d | ||
|
|
7be1d16263 | ||
|
|
c91dfdd6fe | ||
|
|
8e733543cd | ||
|
|
26e37590e8 | ||
|
|
ddf133dd66 | ||
|
|
4584e7629a | ||
|
|
139686ddce | ||
|
|
5a0b4dba47 | ||
|
|
33c8aa660f | ||
|
|
cb76dcae2a | ||
|
|
d622de4797 | ||
|
|
9ae75c0c03 | ||
|
|
45a4281413 | ||
|
|
bfbcab3a01 | ||
|
|
4b4c73cee4 | ||
|
|
fd349f1822 | ||
|
|
cb0a085968 | ||
|
|
aabf0e13b7 | ||
|
|
92873b06ed | ||
|
|
04ca2cf9f4 | ||
|
|
b049a4dc66 | ||
|
|
5be52c9753 | ||
|
|
3c59283b3f | ||
|
|
3ba808e3c6 | ||
|
|
df5d66b5e8 | ||
|
|
7fe90e6c80 | ||
|
|
8dcd514393 | ||
|
|
2c3e420f82 | ||
|
|
917db35a84 | ||
|
|
dd52364d33 | ||
|
|
30aa5a5057 | ||
|
|
a46fcaee31 | ||
|
|
52e2748869 | ||
|
|
d2d127a4c4 | ||
|
|
0fca8e8cb5 | ||
|
|
6bec912961 | ||
|
|
214e157e5d | ||
|
|
da1d479e55 | ||
|
|
062bb0cef2 | ||
|
|
c744b016ce | ||
|
|
f486845f7f | ||
|
|
7baa96c5c7 | ||
|
|
ea07ec1fda | ||
|
|
26b70e457b | ||
|
|
5d5fcb3911 | ||
|
|
50008dff3d | ||
|
|
808638fee3 | ||
|
|
b0e3865562 | ||
|
|
bc03ffb317 | ||
|
|
1edbca1775 | ||
|
|
5a0bc016e7 | ||
|
|
bb66b7f28e | ||
|
|
0331491b2b | ||
|
|
54a782c8ae | ||
|
|
a70bc20829 | ||
|
|
102ed3b800 | ||
|
|
c8e339fe6d | ||
|
|
e4e53fe315 | ||
|
|
1c795c3f1c | ||
|
|
b2b740fed7 | ||
|
|
09550397d7 | ||
|
|
a32f7f2ec5 | ||
|
|
e8e9fa2418 | ||
|
|
1a119bdfe9 | ||
|
|
21ff2e0ffc | ||
|
|
df9b23c96a | ||
|
|
4c117aa282 | ||
|
|
88427262a6 | ||
|
|
72b24a9348 | ||
|
|
01cb8e59e3 | ||
|
|
3910326709 | ||
|
|
7ee46d80e6 | ||
|
|
0cb5450999 | ||
|
|
8c78013257 | ||
|
|
bd944898f0 | ||
|
|
c1ef1acfc0 | ||
|
|
040d3f5d8b | ||
|
|
ec393e4a90 | ||
|
|
1703d0417a | ||
|
|
fad72c0441 | ||
|
|
2f7321a076 | ||
|
|
85ee422acd | ||
|
|
089816d9ba | ||
|
|
c873c2db15 | ||
|
|
047c67baf3 | ||
|
|
8f394f117b | ||
|
|
fb7528c239 | ||
|
|
042f5fe4b3 | ||
|
|
289fb47a34 | ||
|
|
38bc6babb7 | ||
|
|
e7a8efcfa0 | ||
|
|
1b74822cfc | ||
|
|
f083dae328 | ||
|
|
23c384bd30 | ||
|
|
ced1616e51 | ||
|
|
233ac4aed2 | ||
|
|
be5a2b0e87 | ||
|
|
2b4b64f499 | ||
|
|
262490d074 | ||
|
|
8891cfd85e | ||
|
|
2cc8fa1eac | ||
|
|
79aebf06dc | ||
|
|
19dd961752 | ||
|
|
bf413ecb83 | ||
|
|
fd1a3eda1c | ||
|
|
0e2488db32 | ||
|
|
58a00bffbb | ||
|
|
bbfe57400d | ||
|
|
4372f6fdac | ||
|
|
30f798b246 | ||
|
|
282770f11a | ||
|
|
17373a4e91 | ||
|
|
a34147b602 | ||
|
|
cebd15bfd1 | ||
|
|
f51f7c0ca8 | ||
|
|
f6d26df64d | ||
|
|
fddfb2e2d6 | ||
|
|
dec728cfa2 | ||
|
|
8440091a4e | ||
|
|
2464a135b3 | ||
|
|
87fa120ebb | ||
|
|
d86f53a02c | ||
|
|
15a4fec3d9 | ||
|
|
1819be1173 | ||
|
|
ffa9b51d27 | ||
|
|
b4a8c08f43 | ||
|
|
741bcc4672 | ||
|
|
e48f1fc5f1 | ||
|
|
374f131f1e | ||
|
|
363261077f | ||
|
|
102dd07456 | ||
|
|
1e97103094 | ||
|
|
cc8907b956 | ||
|
|
c4b92ebd45 | ||
|
|
fee874b5b7 | ||
|
|
eba888207e | ||
|
|
ea28791da6 | ||
|
|
fe2d37fcdc | ||
|
|
3407a8dd78 | ||
|
|
5761fb6b30 | ||
|
|
7aeb2c083b | ||
|
|
8f47fdbe67 | ||
|
|
2ea42b3947 | ||
|
|
7510b95d8c |
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
- [ ] Tested on all platforms changed
|
||||
- [ ] 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 an example program if it would help users understand this functionality
|
||||
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
*~
|
||||
#*#
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/tomaka/android-rs-glue
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
|
||||
@@ -50,8 +50,12 @@ install:
|
||||
|
||||
script:
|
||||
- cargo build --target $TARGET --verbose
|
||||
- cargo build --target $TARGET --features serde --verbose
|
||||
- cargo build --target $TARGET --features icon_loading --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
|
||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features icon_loading --verbose; fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
|
||||
219
CHANGELOG.md
219
CHANGELOG.md
@@ -1,5 +1,224 @@
|
||||
# Unreleased
|
||||
|
||||
# Version 0.19.4 (2019-10-16)
|
||||
|
||||
- Add support for `raw-window-handle` 0.3.
|
||||
|
||||
# Version 0.19.3 (2019-08-26)
|
||||
|
||||
- Update parking_lot version.
|
||||
|
||||
# Version 0.19.2 (2019-07-29)
|
||||
|
||||
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.
|
||||
|
||||
# Version 0.19.1 (2019-04-08)
|
||||
|
||||
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
|
||||
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
|
||||
- On macOS, fix command key event left and right reverse.
|
||||
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
|
||||
- On Windows, fix icon not showing up in corner of window.
|
||||
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
|
||||
|
||||
# Version 0.19.0 (2019-03-06)
|
||||
|
||||
- On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available.
|
||||
- On macOS, fix keycodes being incorrect when using a non-US keyboard layout.
|
||||
- On Wayland, fix `with_title()` not setting the windows title
|
||||
- On Wayland, add `set_wayland_theme()` to control client decoration color theme
|
||||
- Added serde serialization to `os::unix::XWindowType`.
|
||||
- **Breaking:** `image` crate upgraded to 0.21. This is exposed as part of the `icon_loading` API.
|
||||
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
|
||||
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
|
||||
- Refactored Windows state/flag-setting code.
|
||||
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.
|
||||
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
|
||||
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
|
||||
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
|
||||
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
|
||||
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
|
||||
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
|
||||
|
||||
# Version 0.18.1 (2018-12-30)
|
||||
|
||||
- On macOS, fix `Yen` (JIS) so applications receive the event.
|
||||
- On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors.
|
||||
- On X11, fixed panic caused by dropping the window before running the event loop.
|
||||
- on macOS, added `WindowExt::set_simple_fullscreen` which does not require a separate space
|
||||
- Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland.
|
||||
- On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code.
|
||||
- On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program.
|
||||
- On Windows, fix issue where resizing or moving window would eat `Awakened` events.
|
||||
- On Windows, exiting fullscreen after entering fullscreen with disabled decorations no longer shrinks window.
|
||||
- On X11, fixed a segfault when using virtual monitors with XRandR.
|
||||
- Derive `Ord` and `PartialOrd` for `VirtualKeyCode` enum.
|
||||
- On Windows, fix issue where hovering or dropping a non file item would create a panic.
|
||||
- On Wayland, fix resizing and DPI calculation when a `wl_output` is removed without sending a `leave` event to the `wl_surface`, such as disconnecting a monitor from a laptop.
|
||||
- On Wayland, DPI calculation is handled by smithay-client-toolkit.
|
||||
- On X11, `WindowBuilder::with_min_dimensions` and `WindowBuilder::with_max_dimensions` now correctly account for DPI.
|
||||
- Added support for generating dummy `DeviceId`s and `WindowId`s to better support unit testing.
|
||||
- On macOS, fixed unsoundness in drag-and-drop that could result in drops being rejected.
|
||||
- On macOS, implemented `WindowEvent::Refresh`.
|
||||
- On macOS, all `MouseCursor` variants are now implemented and the cursor will no longer reset after unfocusing.
|
||||
- Removed minimum supported Rust version guarantee.
|
||||
|
||||
# Version 0.18.0 (2018-11-07)
|
||||
|
||||
- **Breaking:** `image` crate upgraded to 0.20. This is exposed as part of the `icon_loading` API.
|
||||
- On Wayland, pointer events will now provide the current modifiers state.
|
||||
- On Wayland, titles will now be displayed in the window header decoration.
|
||||
- On Wayland, key repetition is now ended when keyboard loses focus.
|
||||
- On Wayland, windows will now use more stylish and modern client side decorations.
|
||||
- On Wayland, windows will use server-side decorations when available.
|
||||
- **Breaking:** Added support for F16-F24 keys (variants were added to the `VirtualKeyCode` enum).
|
||||
- Fixed graphical glitches when resizing on Wayland.
|
||||
- On Windows, fix freezes when performing certain actions after a window resize has been triggered. Reintroduces some visual artifacts when resizing.
|
||||
- Updated window manager hints under X11 to v1.5 of [Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm140200472629520).
|
||||
- Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions.
|
||||
- Fixed UTF8 handling bug in X11 `set_title` function.
|
||||
- On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first.
|
||||
- On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented.
|
||||
- On Windows, fix `Window::set_maximized`.
|
||||
- On Windows 10, fix transparency (#260).
|
||||
- On macOS, fix modifiers during key repeat.
|
||||
- Implemented the `Debug` trait for `Window`, `EventsLoop`, `EventsLoopProxy` and `WindowBuilder`.
|
||||
- On X11, now a `Resized` event will always be generated after a DPI change to ensure the window's logical size is consistent with the new DPI.
|
||||
- Added further clarifications to the DPI docs.
|
||||
- On Linux, if neither X11 nor Wayland manage to initialize, the corresponding panic now consists of a single line only.
|
||||
- Add optional `serde` feature with implementations of `Serialize`/`Deserialize` for DPI types and various event types.
|
||||
- Add `PartialEq`, `Eq`, and `Hash` implementations on public types that could have them but were missing them.
|
||||
- On X11, drag-and-drop receiving an unsupported drop type can no longer cause the WM to freeze.
|
||||
- Fix issue whereby the OpenGL context would not appear at startup on macOS Mojave (#1069).
|
||||
- **Breaking:** Removed `From<NSApplicationActivationPolicy>` impl from `ActivationPolicy` on macOS.
|
||||
- On macOS, the application can request the user's attention with `WindowExt::request_user_attention`.
|
||||
|
||||
# Version 0.17.2 (2018-08-19)
|
||||
|
||||
- On macOS, fix `<C-Tab>` so applications receive the event.
|
||||
- On macOS, fix `<Cmd-{key}>` so applications receive the event.
|
||||
- On Wayland, key press events will now be repeated.
|
||||
|
||||
# Version 0.17.1 (2018-08-05)
|
||||
|
||||
- On X11, prevent a compilation failure in release mode for versions of Rust greater than or equal to 1.30.
|
||||
- Fixed deadlock that broke fullscreen mode on Windows.
|
||||
|
||||
# Version 0.17.0 (2018-08-02)
|
||||
|
||||
- Cocoa and core-graphics updates.
|
||||
- Fixed thread-safety issues in several `Window` functions on Windows.
|
||||
- On MacOS, the key state for modifiers key events is now properly set.
|
||||
- On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again.
|
||||
- Added NetBSD support.
|
||||
- **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK).
|
||||
- On iOS, the `UIApplication` is not started until `Window::new` is called.
|
||||
- Fixed thread unsafety with cursor hiding on macOS.
|
||||
- On iOS, fixed the size of the `JmpBuf` type used for `setjmp`/`longjmp` calls. Previously this was a buffer overflow on most architectures.
|
||||
- On Windows, use cached window DPI instead of repeatedly querying the system. This fixes sporadic crashes on Windows 7.
|
||||
|
||||
# Version 0.16.2 (2018-07-07)
|
||||
|
||||
- On Windows, non-resizable windows now have the maximization button disabled. This is consistent with behavior on macOS and popular X11 WMs.
|
||||
- Corrected incorrect `unreachable!` usage when guessing the DPI factor with no detected monitors.
|
||||
|
||||
# Version 0.16.1 (2018-07-02)
|
||||
|
||||
- Added logging through `log`. Logging will become more extensive over time.
|
||||
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
|
||||
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
|
||||
|
||||
# Version 0.16.0 (2018-06-25)
|
||||
|
||||
- Windows additionally has `WindowBuilderExt::with_no_redirection_bitmap`.
|
||||
- **Breaking:** Removed `VirtualKeyCode::LMenu` and `VirtualKeyCode::RMenu`; Windows now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead.
|
||||
- On X11, exiting fullscreen no longer leaves the window in the monitor's top left corner.
|
||||
- **Breaking:** `Window::hidpi_factor` has been renamed to `Window::get_hidpi_factor` for better consistency. `WindowEvent::HiDPIFactorChanged` has been renamed to `WindowEvent::HiDpiFactorChanged`. DPI factors are always represented as `f64` instead of `f32` now.
|
||||
- The Windows backend is now DPI aware. `WindowEvent::HiDpiFactorChanged` is implemented, and `MonitorId::get_hidpi_factor` and `Window::hidpi_factor` return accurate values.
|
||||
- Implemented `WindowEvent::HiDpiFactorChanged` on X11.
|
||||
- On macOS, `Window::set_cursor_position` is now relative to the client area.
|
||||
- On macOS, setting the maximum and minimum dimensions now applies to the client area dimensions rather than to the window dimensions.
|
||||
- On iOS, `MonitorId::get_dimensions` has been implemented and both `MonitorId::get_hidpi_factor` and `Window::get_hidpi_factor` return accurate values.
|
||||
- On Emscripten, `MonitorId::get_hidpi_factor` now returns the same value as `Window::get_hidpi_factor` (it previously would always return 1.0).
|
||||
- **Breaking:** The entire API for sizes, positions, etc. has changed. In the majority of cases, winit produces and consumes positions and sizes as `LogicalPosition` and `LogicalSize`, respectively. The notable exception is `MonitorId` methods, which deal in `PhysicalPosition` and `PhysicalSize`. See the documentation for specifics and explanations of the types. Additionally, winit automatically conserves logical size when the DPI factor changes.
|
||||
- **Breaking:** All deprecated methods have been removed. For `Window::platform_display` and `Window::platform_window`, switch to the appropriate platform-specific `WindowExt` methods. For `Window::get_inner_size_points` and `Window::get_inner_size_pixels`, use the `LogicalSize` returned by `Window::get_inner_size` and convert as needed.
|
||||
- HiDPI support for Wayland.
|
||||
- `EventsLoop::get_available_monitors` and `EventsLoop::get_primary_monitor` now have identical counterparts on `Window`, so this information can be acquired without an `EventsLoop` borrow.
|
||||
- `AvailableMonitorsIter` now implements `Debug`.
|
||||
- Fixed quirk on macOS where certain keys would generate characters at twice the normal rate when held down.
|
||||
- On X11, all event loops now share the same `XConnection`.
|
||||
- **Breaking:** `Window::set_cursor_state` and `CursorState` enum removed in favor of the more composable `Window::grab_cursor` and `Window::hide_cursor`. As a result, grabbing the cursor no longer automatically hides it; you must call both methods to retain the old behavior on Windows and macOS. `Cursor::NoneCursor` has been removed, as it's no longer useful.
|
||||
- **Breaking:** `Window::set_cursor_position` now returns `Result<(), String>`, thus allowing for `Box<Error>` conversion via `?`.
|
||||
|
||||
# Version 0.15.1 (2018-06-13)
|
||||
|
||||
- On X11, the `Moved` event is no longer sent when the window is resized without changing position.
|
||||
- `MouseCursor` and `CursorState` now implement `Default`.
|
||||
- `WindowBuilder::with_resizable` implemented for Windows, X11, Wayland, and macOS.
|
||||
- `Window::set_resizable` implemented for Windows, X11, Wayland, and macOS.
|
||||
- On X11, if the monitor's width or height in millimeters is reported as 0, the DPI is now 1.0 instead of +inf.
|
||||
- On X11, the environment variable `WINIT_HIDPI_FACTOR` has been added for overriding DPI factor.
|
||||
- On X11, enabling transparency no longer causes the window contents to flicker when resizing.
|
||||
- On X11, `with_override_redirect` now actually enables override redirect.
|
||||
- macOS now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead of `None` for both.
|
||||
- On macOS, `VirtualKeyCode::RWin` and `VirtualKeyCode::LWin` are no longer switched.
|
||||
- On macOS, windows without decorations can once again be resized.
|
||||
- Fixed race conditions when creating an `EventsLoop` on X11, most commonly manifesting as "[xcb] Unknown sequence number while processing queue".
|
||||
- On macOS, `CursorMoved` and `MouseInput` events are only generated if they occurs within the window's client area.
|
||||
- On macOS, resizing the window no longer generates a spurious `MouseInput` event.
|
||||
|
||||
# Version 0.15.0 (2018-05-22)
|
||||
|
||||
- `Icon::to_cardinals` is no longer public, since it was never supposed to be.
|
||||
- Wayland: improve diagnostics if initialization fails
|
||||
- Fix some system event key doesn't work when focused, do not block keyevent forward to system on macOS
|
||||
- On X11, the scroll wheel position is now correctly reset on i3 and other WMs that have the same quirk.
|
||||
- On X11, `Window::get_current_monitor` now reliably returns the correct monitor.
|
||||
- On X11, `Window::hidpi_factor` returns values from XRandR rather than the inaccurate values previously queried from the core protocol.
|
||||
- On X11, the primary monitor is detected correctly even when using versions of XRandR less than 1.5.
|
||||
- `MonitorId` now implements `Debug`.
|
||||
- Fixed bug on macOS where using `with_decorations(false)` would cause `set_decorations(true)` to produce a transparent titlebar with no title.
|
||||
- Implemented `MonitorId::get_position` on macOS.
|
||||
- On macOS, `Window::get_current_monitor` now returns accurate values.
|
||||
- Added `WindowBuilderExt::with_resize_increments` to macOS.
|
||||
- **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`.
|
||||
- macOS keyboard handling has been overhauled, allowing for the use of dead keys, IME, etc. Right modifier keys are also no longer reported as being left.
|
||||
- Added the `Window::set_ime_spot(x: i32, y: i32)` method, which is implemented on X11 and macOS.
|
||||
- **Breaking**: `os::unix::WindowExt::send_xim_spot(x: i16, y: i16)` no longer exists. Switch to the new `Window::set_ime_spot(x: i32, y: i32)`, which has equivalent functionality.
|
||||
- Fixed detection of `Pause` and `Scroll` keys on Windows.
|
||||
- On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the cursor.
|
||||
- On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first.
|
||||
- Implemented `MouseCursor::NoneCursor` on Windows.
|
||||
- Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11.
|
||||
- On X11, `WindowBuilderExt` now has `with_class`, `with_override_redirect`, and `with_x11_window_type` to allow for more control over window creation. `WindowExt` additionally has `set_urgent`.
|
||||
- More hints are set by default on X11, including `_NET_WM_PID` and `WM_CLIENT_MACHINE`. Note that prior to this, the `WM_CLASS` hint was automatically set to whatever value was passed to `with_title`. It's now set to the executable name to better conform to expectations and the specification; if this is undesirable, you must explicitly use `WindowBuilderExt::with_class`.
|
||||
|
||||
# Version 0.14.0 (2018-05-09)
|
||||
|
||||
- Created the `Copy`, `Paste` and `Cut` `VirtualKeyCode`s and added support for them on X11 and Wayland
|
||||
- Fix `.with_decorations(false)` in macOS
|
||||
- On Mac, `NSWindow` and supporting objects might be alive long after they were `closed` which resulted in apps consuming more heap then needed. Mainly it was affecting multi window applications. Not expecting any user visible change of behaviour after the fix.
|
||||
- Fix regression of Window platform extensions for macOS where `NSFullSizeContentViewWindowMask` was not being correctly applied to `.fullsize_content_view`.
|
||||
- Corrected `get_position` on Windows to be relative to the screen rather than to the taskbar.
|
||||
- Corrected `Moved` event on Windows to use position values equivalent to those returned by `get_position`. It previously supplied client area positions instead of window positions, and would additionally interpret negative values as being very large (around `u16::MAX`).
|
||||
- Implemented `Moved` event on macOS.
|
||||
- On X11, the `Moved` event correctly use window positions rather than client area positions. Additionally, a stray `Moved` that unconditionally accompanied `Resized` with the client area position relative to the parent has been eliminated; `Moved` is still received alongside `Resized`, but now only once and always correctly.
|
||||
- On Windows, implemented all variants of `DeviceEvent` other than `Text`. Mouse `DeviceEvent`s are now received even if the window isn't in the foreground.
|
||||
- `DeviceId` on Windows is no longer a unit struct, and now contains a `u32`. For `WindowEvent`s, this will always be 0, but on `DeviceEvent`s it will be the handle to that device. `DeviceIdExt::get_persistent_identifier` can be used to acquire a unique identifier for that device that persists across replugs/reboots/etc.
|
||||
- Corrected `run_forever` on X11 to stop discarding `Awakened` events.
|
||||
- Various safety and correctness improvements to the X11 backend internals.
|
||||
- Fixed memory leak on X11 every time the mouse entered the window.
|
||||
- On X11, drag and drop now works reliably in release mode.
|
||||
- Added `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` to X11, allowing for more optional hints to be set.
|
||||
- Rework of the wayland backend, migrating it to use [Smithay's Client Toolkit](https://github.com/Smithay/client-toolkit).
|
||||
- Added `WindowBuilder::with_window_icon` and `Window::set_window_icon`, finally making it possible to set the window icon on Windows and X11. The `icon_loading` feature can be enabled to allow for icons to be easily loaded; see example program `window_icon.rs` for usage.
|
||||
- Windows additionally has `WindowBuilderExt::with_taskbar_icon` and `WindowExt::set_taskbar_icon`.
|
||||
- On Windows, fix panic when trying to call `set_fullscreen(None)` on a window that has not been fullscreened prior.
|
||||
|
||||
# Version 0.13.1 (2018-04-26)
|
||||
|
||||
- Ensure necessary `x11-dl` version is used.
|
||||
|
||||
# Version 0.13.0 (2018-04-25)
|
||||
|
||||
- Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for MacOS.
|
||||
|
||||
58
CONTRIBUTING.md
Normal file
58
CONTRIBUTING.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# PLEASE MAKE PRs AGAINST THE `eventloop-2.0` BRANCH.
|
||||
|
||||
All development work for our next version is being done against that branch. Refer to [#459](https://github.com/rust-windowing/winit/issues/459) and [the associated milestone](https://github.com/rust-windowing/winit/milestone/2) for details on what that branch changes.
|
||||
|
||||
# Winit Contributing Guidelines
|
||||
|
||||
## Scope
|
||||
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
|
||||
consider whether or not it's directly related to window creation or input handling. If it isn't, it
|
||||
may be worth creating a separate crate that extends Winit's API to add that functionality.
|
||||
|
||||
|
||||
## Reporting an issue
|
||||
|
||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||
your description of the issue as detailed as possible:
|
||||
|
||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
||||
should be supported by winit
|
||||
|
||||
## Making a pull request
|
||||
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||
maintainers and future contributors don't have to try to guess what your code is supposed to do
|
||||
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
|
||||
- if your PR affects the platform compatibility of one or more features or adds another feature, the
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
|
||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||
|
||||
## Maintainers & Testers
|
||||
|
||||
Winit is managed by several people, each with their specialities, and each maintaining a subset of the
|
||||
backends of winit. As such, depending on your platform of interest, your contacts will be different.
|
||||
|
||||
This table summarizes who can be contacted in which case, with the following legend:
|
||||
|
||||
- `M` - Maintainer: is a main maintainer for this platform
|
||||
- `C` - Collaborator: can review code and address issues on this platform
|
||||
- `T` - Tester: has the ability of testing the platform
|
||||
- ` `: knows nothing of this platform
|
||||
|
||||
| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten |
|
||||
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
||||
| @mitchmindtree | T | | T | T | | | |
|
||||
| @Osspial | M | | T | T | T | | T |
|
||||
| @vberger | | | T | M | | | |
|
||||
| @mtak- | | T | | | T | M | |
|
||||
71
Cargo.toml
71
Cargo.toml
@@ -1,52 +1,71 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.13.0"
|
||||
authors = ["The winit contributors, Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
version = "0.19.4"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tomaka/winit"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["icon_loading", "serde"]
|
||||
|
||||
[features]
|
||||
icon_loading = ["image"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
image = { version = "0.21", optional = true }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
raw-window-handle = "0.3"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc = "0.2"
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2"
|
||||
cocoa = "0.14"
|
||||
core-foundation = "0.5"
|
||||
core-graphics = "0.13"
|
||||
objc = "0.2.3"
|
||||
cocoa = "0.18.4"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
backtrace = "0.3"
|
||||
bitflags = "1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||
version = "0.3"
|
||||
version = "0.3.6"
|
||||
features = [
|
||||
"combaseapi",
|
||||
"dwmapi",
|
||||
"errhandlingapi",
|
||||
"hidusage",
|
||||
"libloaderapi",
|
||||
"objbase",
|
||||
"ole2",
|
||||
"processthreadsapi",
|
||||
"shellapi",
|
||||
"shellscalingapi",
|
||||
"shobjidl_core",
|
||||
"unknwnbase",
|
||||
"winbase",
|
||||
"windowsx",
|
||||
"winerror",
|
||||
"wingdi",
|
||||
"winnt",
|
||||
"winuser",
|
||||
"wingdi",
|
||||
"shellapi",
|
||||
"dwmapi",
|
||||
"processthreadsapi",
|
||||
"libloaderapi",
|
||||
"windowsx",
|
||||
"hidusage",
|
||||
"combaseapi",
|
||||
"objbase",
|
||||
"unknwnbase",
|
||||
]
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.12.0", features = ["dlopen"] }
|
||||
wayland-protocols = { version = "0.12.0", features = ["unstable_protocols"] }
|
||||
wayland-kbd = "0.13.0"
|
||||
wayland-window = "0.13.0"
|
||||
x11-dl = "2.17"
|
||||
percent-encoding = "1.0"
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.21", features = [ "dlopen", "egl", "cursor"] }
|
||||
smithay-client-toolkit = "0.4.3"
|
||||
x11-dl = "2.18.3"
|
||||
parking_lot = "0.9"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
209
FEATURES.md
Normal file
209
FEATURES.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Winit Scope
|
||||
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
||||
be used to create both games and applications. It supports the main graphical platforms:
|
||||
- Desktop
|
||||
- Windows
|
||||
- macOS
|
||||
- Unix
|
||||
- via X11
|
||||
- via Wayland
|
||||
- Mobile
|
||||
- iOS
|
||||
- Android
|
||||
- Web
|
||||
- via Emscripten
|
||||
- via WASM
|
||||
|
||||
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
||||
aim to support every single feature of every platform, but rather to abstract over the common features
|
||||
available everywhere. In this context, APIs exposed in winit can be split into different "support tiers":
|
||||
|
||||
- **Core:** Features that are essential to providing a well-formed abstraction over each platform's
|
||||
windowing and input APIs.
|
||||
- **Platform:** Platform-specific features that can't be meaningfully exposed through a common API and
|
||||
cannot be implemented outside of Winit without exposing a significant amount of Winit's internals
|
||||
or interfering with Winit's abstractions.
|
||||
- **Usability:** Features that are not strictly essential to Winit's functionality, but provide meaningful
|
||||
usability improvements and cannot be reasonably implemented in an external crate. These are
|
||||
generally optional and exposed through Cargo features.
|
||||
|
||||
Core features are taken care of by the core Winit maintainers. Platform features are not.
|
||||
When a platform feature is submitted, the submitter is considered the expert in the
|
||||
feature and may be asked to support the feature should it break in the future.
|
||||
|
||||
Winit ***does not*** directly expose functionality for drawing inside windows or creating native
|
||||
menus, but ***does*** commit to providing APIs that higher-level crates can use to implement that
|
||||
functionality.
|
||||
|
||||
## `1.0` and stability
|
||||
|
||||
When all core features are implemented to the satisfaction of the Winit maintainers, Winit 1.0 will
|
||||
be released and the library will enter maintenance mode. For the most part, new core features will not
|
||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||
|
||||
### Tier upgrades
|
||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
||||
through the implementation work necessary to function on all platforms. When one of these features
|
||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||
If that gets accepted, the platform-specific functions gets deprecated and become permanently
|
||||
exposed through the core, cross-platform API.
|
||||
|
||||
# Features
|
||||
|
||||
## Extending this section
|
||||
|
||||
If your PR makes notable changes to Winit's features, please update this section as follows:
|
||||
|
||||
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
|
||||
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
|
||||
|
||||
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
|
||||
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
|
||||
|
||||
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
|
||||
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
|
||||
|
||||
## Core
|
||||
|
||||
### Windowing
|
||||
- **Window initialization**: Winit allows the creation of a window
|
||||
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
|
||||
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
|
||||
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
|
||||
be deactivated
|
||||
- **Window decorations toggle**: Decorations can be turned on or off after window creation
|
||||
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
|
||||
when they are. The application can precisely control its window size if desired.
|
||||
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
|
||||
size to specific values.
|
||||
- **Window transparency**: Winit allows the creation of windows with a transparent background.
|
||||
- **Window maximization**: The windows created by winit can be maximized upon creation.
|
||||
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
|
||||
creation.
|
||||
- **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.
|
||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
||||
get drawn above their owner.
|
||||
|
||||
|
||||
### System Information
|
||||
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
|
||||
|
||||
### Input Handling
|
||||
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
||||
- **Cursor 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.
|
||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
||||
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
||||
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
|
||||
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
|
||||
|
||||
## Platform
|
||||
### Windows
|
||||
* Setting the taskbar icon
|
||||
* Setting the parent window
|
||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||
|
||||
### macOS
|
||||
* Window activation policy
|
||||
* Window movable by background
|
||||
* Transparent titlebar
|
||||
* Hidden titlebar
|
||||
* Hidden titlebar buttons
|
||||
* Full-size content view
|
||||
|
||||
### Unix
|
||||
* Window urgency
|
||||
* X11 Window Class
|
||||
* X11 Override Redirect Flag
|
||||
* GTK Theme Variant
|
||||
* Base window size
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
Legend:
|
||||
|
||||
- ✔️: Works as intended
|
||||
- ▢: Mostly works but some bugs are known
|
||||
- ❌: Missing feature or large bugs making it unusable
|
||||
- **N/A**: Not applicable for this platform
|
||||
- ❓: Unknown status
|
||||
|
||||
### Windowing
|
||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|
||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|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 |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|
||||
### Input handling
|
||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|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 |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
||||
|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|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|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|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|
||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||
[#219]: https://github.com/rust-windowing/winit/issues/219
|
||||
[#242]: https://github.com/rust-windowing/winit/issues/242
|
||||
[#306]: https://github.com/rust-windowing/winit/issues/306
|
||||
[#315]: https://github.com/rust-windowing/winit/issues/315
|
||||
[#319]: https://github.com/rust-windowing/winit/issues/319
|
||||
[#33]: https://github.com/rust-windowing/winit/issues/33
|
||||
[#459]: https://github.com/rust-windowing/winit/issues/459
|
||||
[#5]: https://github.com/rust-windowing/winit/issues/5
|
||||
[#63]: https://github.com/rust-windowing/winit/issues/63
|
||||
[#720]: https://github.com/rust-windowing/winit/issues/720
|
||||
[#721]: https://github.com/rust-windowing/winit/issues/721
|
||||
[#750]: https://github.com/rust-windowing/winit/issues/750
|
||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
||||
[#812]: https://github.com/rust-windowing/winit/issues/812
|
||||
11
HALL_OF_CHAMPIONS.md
Normal file
11
HALL_OF_CHAMPIONS.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Hall of Champions
|
||||
|
||||
The Winit maintainers would like to recognize the following former Winit
|
||||
contributors, without whom Winit would not exist in its current form. We thank
|
||||
them deeply for their time and efforts, and wish them best of luck in their
|
||||
future endeavors:
|
||||
|
||||
* @tomaka: For creating the Winit project and guiding it through its early
|
||||
years of existence.
|
||||
* @francesca64: For taking over the responsibility of maintaining almost every
|
||||
Winit backend, and standardizing HiDPI support across all of them
|
||||
36
README.md
36
README.md
@@ -1,19 +1,35 @@
|
||||
# This branch has been archived
|
||||
|
||||
Please direct all work against `master`.
|
||||
|
||||
--------
|
||||
|
||||
# winit - Cross-platform window creation and management in Rust
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
|
||||
[](https://travis-ci.org/tomaka/winit)
|
||||
[](https://ci.appveyor.com/project/tomaka/winit/branch/master)
|
||||
[](https://travis-ci.org/rust-windowing/winit)
|
||||
[](https://ci.appveyor.com/project/Osspial/winit/branch/master)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.13"
|
||||
winit = "0.19.3"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
|
||||
|
||||
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
|
||||
|
||||
## Contact Us
|
||||
|
||||
Join us in any of these:
|
||||
|
||||
[](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
|
||||
[](https://matrix.to/#/#Glutin:matrix.org)
|
||||
[](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Usage
|
||||
|
||||
Winit is a window creation and management library. It can create windows and lets you handle
|
||||
@@ -43,6 +59,14 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Winit is only officially supported on the latest stable version of the Rust compiler.
|
||||
|
||||
### Cargo Features
|
||||
|
||||
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
|
||||
* `icon_loading`: Enables loading window icons directly from files. Depends on the [`image` crate](https://crates.io/crates/image).
|
||||
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
#### Emscripten and WebAssembly
|
||||
|
||||
13
appveyor.yml
13
appveyor.yml
@@ -1,12 +1,17 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
install:
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe"
|
||||
- rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
|
||||
- SET PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- SET PATH=%PATH%;C:\MinGW\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
@@ -15,3 +20,5 @@ build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
- cargo test --features serde --verbose
|
||||
- cargo test --features icon_loading --verbose
|
||||
|
||||
@@ -8,7 +8,7 @@ fn main() {
|
||||
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::NoneCursor, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
|
||||
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
|
||||
38
examples/cursor_grab.rs
Normal file
38
examples/cursor_grab.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
extern crate winit;
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
if let winit::Event::WindowEvent { event, .. } = event {
|
||||
use winit::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => return winit::ControlFlow::Break,
|
||||
KeyboardInput {
|
||||
input: winit::KeyboardInput {
|
||||
state: winit::ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
use winit::VirtualKeyCode::*;
|
||||
match key {
|
||||
Escape => return winit::ControlFlow::Break,
|
||||
G => window.grab_cursor(!modifiers.shift).unwrap(),
|
||||
H => window.hide_cursor(!modifiers.shift),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
@@ -6,35 +6,46 @@ use winit::{ControlFlow, Event, WindowEvent};
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
// enumerating monitors
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut macos_use_simple_fullscreen = false;
|
||||
|
||||
let monitor = {
|
||||
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
||||
// 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();
|
||||
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,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Prompt for monitor when using native fullscreen
|
||||
if !macos_use_simple_fullscreen {
|
||||
Some(prompt_for_monitor(&events_loop))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
print!("Please write the number of the monitor to use: ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {:?}", monitor.get_name());
|
||||
|
||||
monitor
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Some(prompt_for_monitor(&events_loop))
|
||||
};
|
||||
|
||||
let mut is_fullscreen = monitor.is_some();
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(Some(monitor))
|
||||
.with_fullscreen(monitor)
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut is_fullscreen = true;
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
|
||||
@@ -52,6 +63,18 @@ fn main() {
|
||||
} => match (virtual_code, state) {
|
||||
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
||||
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if macos_use_simple_fullscreen {
|
||||
use winit::os::macos::WindowExt;
|
||||
if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) {
|
||||
is_fullscreen = !is_fullscreen;
|
||||
}
|
||||
|
||||
return ControlFlow::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
is_fullscreen = !is_fullscreen;
|
||||
if !is_fullscreen {
|
||||
window.set_fullscreen(None);
|
||||
@@ -59,6 +82,15 @@ fn main() {
|
||||
window.set_fullscreen(Some(window.get_current_monitor()));
|
||||
}
|
||||
}
|
||||
(winit::VirtualKeyCode::S, winit::ElementState::Pressed) => {
|
||||
println!("window.get_fullscreen {:?}", window.get_fullscreen());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use winit::os::macos::WindowExt;
|
||||
println!("window.get_simple_fullscreen {:?}", WindowExt::get_simple_fullscreen(&window));
|
||||
}
|
||||
}
|
||||
(winit::VirtualKeyCode::M, winit::ElementState::Pressed) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
@@ -77,3 +109,22 @@ fn main() {
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
// Enumerate monitors and prompt user to choose one
|
||||
fn prompt_for_monitor(events_loop: &winit::EventsLoop) -> winit::MonitorId {
|
||||
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
||||
}
|
||||
|
||||
print!("Please write the number of the monitor to use: ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {:?}", monitor.get_name());
|
||||
|
||||
monitor
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::{ControlFlow, WindowEvent, ElementState, KeyboardInput};
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
|
||||
window.set_title("winit - Cursor grabbing test");
|
||||
|
||||
let mut grabbed = false;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
winit::Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
|
||||
if grabbed {
|
||||
grabbed = false;
|
||||
window.set_cursor_state(winit::CursorState::Normal)
|
||||
.ok().expect("could not ungrab mouse cursor");
|
||||
} else {
|
||||
grabbed = true;
|
||||
window.set_cursor_state(winit::CursorState::Grab)
|
||||
.ok().expect("could not grab mouse cursor");
|
||||
}
|
||||
},
|
||||
|
||||
WindowEvent::CloseRequested => return ControlFlow::Break,
|
||||
|
||||
a @ WindowEvent::CursorMoved { .. } => {
|
||||
println!("{:?}", a);
|
||||
},
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
BIN
examples/icon.png
Normal file
BIN
examples/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -1,5 +1,7 @@
|
||||
extern crate winit;
|
||||
|
||||
use winit::dpi::LogicalSize;
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
@@ -7,8 +9,8 @@ fn main() {
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
window.set_min_dimensions(Some((400, 200)));
|
||||
window.set_max_dimensions(Some((800, 400)));
|
||||
window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0)));
|
||||
window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0)));
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
|
||||
7
examples/monitor_list.rs
Normal file
7
examples/monitor_list.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
extern crate winit;
|
||||
|
||||
fn main() {
|
||||
let event_loop = winit::EventsLoop::new();
|
||||
let window = winit::WindowBuilder::new().build(&event_loop).unwrap();
|
||||
println!("{:#?}\nPrimary: {:#?}", window.get_available_monitors(), window.get_primary_monitor());
|
||||
}
|
||||
38
examples/resizable.rs
Normal file
38
examples/resizable.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
extern crate winit;
|
||||
|
||||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.with_title("Hit space to toggle resizability.")
|
||||
.with_dimensions((400, 200).into())
|
||||
.with_resizable(resizable)
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
match event {
|
||||
winit::Event::WindowEvent { event, .. } => match event {
|
||||
winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break,
|
||||
winit::WindowEvent::KeyboardInput {
|
||||
input:
|
||||
winit::KeyboardInput {
|
||||
virtual_keycode: Some(winit::VirtualKeyCode::Space),
|
||||
state: winit::ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
resizable = !resizable;
|
||||
println!("Resizable: {}", resizable);
|
||||
window.set_resizable(resizable);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
94
examples/window_icon.rs
Normal file
94
examples/window_icon.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// Heads up: you need to compile this example with `--features icon_loading`.
|
||||
// `Icon::from_path` won't be available otherwise, though for your own applications, you could use
|
||||
// `Icon::from_rgba` if you don't want to depend on the `image` crate.
|
||||
|
||||
extern crate winit;
|
||||
#[cfg(feature = "icon_loading")]
|
||||
extern crate image;
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
fn main() {
|
||||
use winit::Icon;
|
||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
|
||||
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
|
||||
// since it seems to work well enough in most cases. Be careful about going too high, or
|
||||
// you'll be bitten by the low-quality downscaling built into the WM.
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
|
||||
// While `Icon::from_path` is the most straightforward, you have a few other options. If you
|
||||
// want to use the `include_bytes` macro, then pass the result to `Icon::from_bytes`. See the
|
||||
// docs for the full list of options (you'll have to generate the docs with the `icon_loading`
|
||||
// feature enabled).
|
||||
let icon = Icon::from_path(path).expect("Failed to open icon");
|
||||
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
let window = winit::WindowBuilder::new()
|
||||
.with_title("An iconic window!")
|
||||
// At present, this only does anything on Windows and X11, so if you want to save load
|
||||
// time, you can put icon loading behind a function that returns `None` on other platforms.
|
||||
.with_window_icon(Some(icon))
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
if let winit::Event::WindowEvent { event, .. } = event {
|
||||
use winit::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => return winit::ControlFlow::Break,
|
||||
DroppedFile(path) => {
|
||||
use image::GenericImageView;
|
||||
|
||||
let icon_image = image::open(path).expect("Failed to open window icon");
|
||||
|
||||
let (width, height) = icon_image.dimensions();
|
||||
const DESIRED_SIZE: u32 = 32;
|
||||
let (new_width, new_height) = if width == height {
|
||||
(DESIRED_SIZE, DESIRED_SIZE)
|
||||
} else {
|
||||
// Note that this will never divide by zero, due to the previous condition.
|
||||
let aspect_adjustment = DESIRED_SIZE as f64
|
||||
/ std::cmp::max(width, height) as f64;
|
||||
(
|
||||
(width as f64 * aspect_adjustment) as u32,
|
||||
(height as f64 * aspect_adjustment) as u32,
|
||||
)
|
||||
};
|
||||
|
||||
// By scaling the icon ourselves, we get higher-quality filtering and save
|
||||
// some memory.
|
||||
let icon = image::imageops::resize(
|
||||
&icon_image,
|
||||
new_width,
|
||||
new_height,
|
||||
image::FilterType::Lanczos3,
|
||||
);
|
||||
|
||||
let (offset_x, offset_y) = (
|
||||
(DESIRED_SIZE - new_width) / 2,
|
||||
(DESIRED_SIZE - new_height) / 2,
|
||||
);
|
||||
|
||||
let mut canvas = image::ImageBuffer::new(DESIRED_SIZE, DESIRED_SIZE);
|
||||
image::imageops::replace(
|
||||
&mut canvas,
|
||||
&icon,
|
||||
offset_x,
|
||||
offset_y,
|
||||
);
|
||||
|
||||
window.set_window_icon(Some(canvas.into()));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
winit::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "icon_loading"))]
|
||||
fn main() {
|
||||
print!(
|
||||
r#"This example requires the `icon_loading` feature:
|
||||
cargo run --example window_icon --features icon_loading
|
||||
"#);
|
||||
}
|
||||
331
src/dpi.rs
Normal file
331
src/dpi.rs
Normal file
@@ -0,0 +1,331 @@
|
||||
|
||||
//! DPI is important, so read the docs for this module if you don't want to be confused.
|
||||
//!
|
||||
//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all
|
||||
//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical
|
||||
//! pixels, as do any context-related functions in `glutin`.
|
||||
//!
|
||||
//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the
|
||||
//! concepts.
|
||||
//!
|
||||
//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of
|
||||
//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two
|
||||
//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the
|
||||
//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays.
|
||||
//!
|
||||
//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the
|
||||
//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the
|
||||
//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably
|
||||
//! small.
|
||||
//!
|
||||
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
|
||||
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
|
||||
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
|
||||
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
|
||||
//! Ideally, the button now has approximately the same perceived size across varying displays.
|
||||
//!
|
||||
//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users
|
||||
//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in
|
||||
//! 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
|
||||
//! 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
|
||||
//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or the
|
||||
//! current DPI factor applied to a window by calling
|
||||
//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor), which is roughly equivalent
|
||||
//! to `window.get_current_monitor().get_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)
|
||||
//! 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:
|
||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings.
|
||||
//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e.
|
||||
//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out.
|
||||
//! - **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
|
||||
//! 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.
|
||||
//!
|
||||
//! 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`](../enum.WindowEvent.html#variant.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`](../enum.WindowEvent.html#variant.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
|
||||
//! 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,
|
||||
//! then your window's DPI factor is 1.
|
||||
|
||||
/// Checks that the DPI factor is a normal positive `f64`.
|
||||
///
|
||||
/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from
|
||||
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
|
||||
/// otherwise, you risk panics.
|
||||
#[inline]
|
||||
pub fn validate_hidpi_factor(dpi_factor: f64) -> bool {
|
||||
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
|
||||
}
|
||||
|
||||
/// A position represented in logical pixels.
|
||||
///
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
|
||||
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
|
||||
/// does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalPosition {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl LogicalPosition {
|
||||
#[inline]
|
||||
pub fn new(x: f64, y: f64) -> Self {
|
||||
LogicalPosition { x, y }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalPosition>>(physical: T, dpi_factor: f64) -> Self {
|
||||
physical.into().to_logical(dpi_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let x = self.x * dpi_factor;
|
||||
let y = self.y * dpi_factor;
|
||||
PhysicalPosition::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f64, f64)> for LogicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (f64, f64)) -> Self {
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(i32, i32)> for LogicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (i32, i32)) -> Self {
|
||||
Self::new(x as f64, y as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for LogicalPosition {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(i32, i32)> for LogicalPosition {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (i32, i32) {
|
||||
(self.x.round() as _, self.y.round() as _)
|
||||
}
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
///
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
|
||||
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
|
||||
/// does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalPosition {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl PhysicalPosition {
|
||||
#[inline]
|
||||
pub fn new(x: f64, y: f64) -> Self {
|
||||
PhysicalPosition { x, y }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalPosition>>(logical: T, dpi_factor: f64) -> Self {
|
||||
logical.into().to_physical(dpi_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let x = self.x / dpi_factor;
|
||||
let y = self.y / dpi_factor;
|
||||
LogicalPosition::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f64, f64)> for PhysicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (f64, f64)) -> Self {
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(i32, i32)> for PhysicalPosition {
|
||||
#[inline]
|
||||
fn from((x, y): (i32, i32)) -> Self {
|
||||
Self::new(x as f64, y as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for PhysicalPosition {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(i32, i32)> for PhysicalPosition {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (i32, i32) {
|
||||
(self.x.round() as _, self.y.round() as _)
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in logical pixels.
|
||||
///
|
||||
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
|
||||
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
|
||||
/// does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalSize {
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
impl LogicalSize {
|
||||
#[inline]
|
||||
pub fn new(width: f64, height: f64) -> Self {
|
||||
LogicalSize { width, height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalSize>>(physical: T, dpi_factor: f64) -> Self {
|
||||
physical.into().to_logical(dpi_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let width = self.width * dpi_factor;
|
||||
let height = self.height * dpi_factor;
|
||||
PhysicalSize::new(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f64, f64)> for LogicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (f64, f64)) -> Self {
|
||||
Self::new(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u32, u32)> for LogicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (u32, u32)) -> Self {
|
||||
Self::new(width as f64, height as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for LogicalSize {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(u32, u32)> for LogicalSize {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (u32, u32) {
|
||||
(self.width.round() as _, self.height.round() as _)
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in physical pixels.
|
||||
///
|
||||
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
|
||||
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
|
||||
/// does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalSize {
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
impl PhysicalSize {
|
||||
#[inline]
|
||||
pub fn new(width: f64, height: f64) -> Self {
|
||||
PhysicalSize { width, height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalSize>>(logical: T, dpi_factor: f64) -> Self {
|
||||
logical.into().to_physical(dpi_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize {
|
||||
assert!(validate_hidpi_factor(dpi_factor));
|
||||
let width = self.width / dpi_factor;
|
||||
let height = self.height / dpi_factor;
|
||||
LogicalSize::new(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f64, f64)> for PhysicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (f64, f64)) -> Self {
|
||||
Self::new(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u32, u32)> for PhysicalSize {
|
||||
#[inline]
|
||||
fn from((width, height): (u32, u32)) -> Self {
|
||||
Self::new(width as f64, height as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(f64, f64)> for PhysicalSize {
|
||||
#[inline]
|
||||
fn into(self) -> (f64, f64) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<(u32, u32)> for PhysicalSize {
|
||||
/// Note that this rounds instead of truncating.
|
||||
#[inline]
|
||||
fn into(self) -> (u32, u32) {
|
||||
(self.width.round() as _, self.height.round() as _)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::path::PathBuf;
|
||||
use {WindowId, DeviceId};
|
||||
|
||||
use {DeviceId, LogicalPosition, LogicalSize, WindowId};
|
||||
|
||||
/// Describes a generic event.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Event {
|
||||
WindowEvent {
|
||||
window_id: WindowId,
|
||||
@@ -21,14 +22,13 @@ pub enum Event {
|
||||
}
|
||||
|
||||
/// Describes an event from a `Window`.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum WindowEvent {
|
||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||
Resized(LogicalSize),
|
||||
|
||||
/// The size of the window has changed.
|
||||
Resized(u32, u32),
|
||||
|
||||
/// The position of the window has changed.
|
||||
Moved(i32, i32),
|
||||
/// The position of the window has changed. Contains the window's new position.
|
||||
Moved(LogicalPosition),
|
||||
|
||||
/// The window has been requested to close.
|
||||
CloseRequested,
|
||||
@@ -37,12 +37,21 @@ pub enum WindowEvent {
|
||||
Destroyed,
|
||||
|
||||
/// A file has been dropped into the window.
|
||||
///
|
||||
/// When the user drops multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
DroppedFile(PathBuf),
|
||||
|
||||
/// A file is being hovered over the window.
|
||||
///
|
||||
/// When the user hovers multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
HoveredFile(PathBuf),
|
||||
|
||||
/// A file was hovered, but has exited the window.
|
||||
///
|
||||
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
|
||||
/// hovered.
|
||||
HoveredFileCancelled,
|
||||
|
||||
/// The window received a unicode character.
|
||||
@@ -63,7 +72,7 @@ pub enum WindowEvent {
|
||||
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
|
||||
/// 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: (f64, f64),
|
||||
position: LogicalPosition,
|
||||
modifiers: ModifiersState
|
||||
},
|
||||
|
||||
@@ -96,14 +105,16 @@ pub enum WindowEvent {
|
||||
/// Touch event has been received
|
||||
Touch(Touch),
|
||||
|
||||
/// DPI scaling factor of the window has changed.
|
||||
/// The DPI factor of the window has changed.
|
||||
///
|
||||
/// The following actions cause DPI changes:
|
||||
/// The following user actions can cause DPI changes:
|
||||
///
|
||||
/// * A user changes the resolution.
|
||||
/// * A user changes the desktop scaling value (e.g. in Control Panel on Windows).
|
||||
/// * A user moves the application window to a display with a different DPI.
|
||||
HiDPIFactorChanged(f32),
|
||||
/// * Changing the display's resolution.
|
||||
/// * 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.
|
||||
HiDpiFactorChanged(f64),
|
||||
}
|
||||
|
||||
/// Represents raw hardware events that are not associated with any particular window.
|
||||
@@ -114,7 +125,7 @@ pub enum WindowEvent {
|
||||
/// may not match.
|
||||
///
|
||||
/// Note that these events are delivered regardless of input focus.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum DeviceEvent {
|
||||
Added,
|
||||
Removed,
|
||||
@@ -145,7 +156,8 @@ pub enum DeviceEvent {
|
||||
}
|
||||
|
||||
/// Describes a keyboard input event.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct KeyboardInput {
|
||||
/// Identifies the physical key pressed
|
||||
///
|
||||
@@ -171,6 +183,7 @@ pub struct KeyboardInput {
|
||||
|
||||
/// Describes touch-screen input state.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum TouchPhase {
|
||||
Started,
|
||||
Moved,
|
||||
@@ -193,11 +206,11 @@ pub enum TouchPhase {
|
||||
/// 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.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Touch {
|
||||
pub device_id: DeviceId,
|
||||
pub phase: TouchPhase,
|
||||
pub location: (f64,f64),
|
||||
pub location: LogicalPosition,
|
||||
/// unique identifier of a finger.
|
||||
pub id: u64
|
||||
}
|
||||
@@ -213,6 +226,7 @@ pub type ButtonId = u32;
|
||||
|
||||
/// Describes the input state of a key.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ElementState {
|
||||
Pressed,
|
||||
Released,
|
||||
@@ -220,6 +234,7 @@ pub enum ElementState {
|
||||
|
||||
/// Describes a button of a mouse controller.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
@@ -229,6 +244,7 @@ pub enum MouseButton {
|
||||
|
||||
/// Describes a difference in the mouse scroll wheel state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseScrollDelta {
|
||||
/// Amount in lines or rows to scroll in the horizontal
|
||||
/// and vertical directions.
|
||||
@@ -242,12 +258,13 @@ pub enum MouseScrollDelta {
|
||||
/// Scroll events are expressed as a PixelDelta if
|
||||
/// supported by the device (eg. a touchpad) and
|
||||
/// platform.
|
||||
PixelDelta(f32, f32)
|
||||
PixelDelta(LogicalPosition),
|
||||
}
|
||||
|
||||
/// Symbolic name for a keyboard key.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
||||
#[repr(u32)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum VirtualKeyCode {
|
||||
/// The '1' key over the letters.
|
||||
Key1,
|
||||
@@ -315,6 +332,15 @@ pub enum VirtualKeyCode {
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
|
||||
/// Print Screen/SysRq.
|
||||
Snapshot,
|
||||
@@ -383,7 +409,6 @@ pub enum VirtualKeyCode {
|
||||
LAlt,
|
||||
LBracket,
|
||||
LControl,
|
||||
LMenu,
|
||||
LShift,
|
||||
LWin,
|
||||
Mail,
|
||||
@@ -408,7 +433,6 @@ pub enum VirtualKeyCode {
|
||||
RAlt,
|
||||
RBracket,
|
||||
RControl,
|
||||
RMenu,
|
||||
RShift,
|
||||
RWin,
|
||||
Semicolon,
|
||||
@@ -431,12 +455,17 @@ pub enum VirtualKeyCode {
|
||||
WebSearch,
|
||||
WebStop,
|
||||
Yen,
|
||||
Copy,
|
||||
Paste,
|
||||
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,
|
||||
|
||||
170
src/icon.rs
Normal file
170
src/icon.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use std::{fmt, mem};
|
||||
use std::error::Error;
|
||||
#[cfg(feature = "icon_loading")]
|
||||
use std::io::{BufRead, Seek};
|
||||
#[cfg(feature = "icon_loading")]
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
use image;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pixel {
|
||||
pub(crate) r: u8,
|
||||
pub(crate) g: u8,
|
||||
pub(crate) b: u8,
|
||||
pub(crate) a: u8,
|
||||
}
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// An error produced when using `Icon::from_rgba` with invalid arguments.
|
||||
pub enum BadIcon {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
ByteCountNotDivisibleBy4 {
|
||||
byte_count: usize,
|
||||
},
|
||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
||||
/// At least one of your arguments is incorrect.
|
||||
DimensionsVsPixelCount {
|
||||
width: u32,
|
||||
height: u32,
|
||||
width_x_height: usize,
|
||||
pixel_count: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for BadIcon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
let msg = match self {
|
||||
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
|
||||
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
byte_count,
|
||||
),
|
||||
&BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => format!(
|
||||
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
|
||||
width, height, pixel_count, width_x_height,
|
||||
),
|
||||
};
|
||||
write!(formatter, "{}", msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn description(&self) -> &str {
|
||||
"A valid icon cannot be created from these arguments"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
///
|
||||
/// Enabling the `icon_loading` feature provides you with several convenience methods for creating
|
||||
/// an `Icon` from any format supported by the [image](https://github.com/PistonDevelopers/image)
|
||||
/// crate.
|
||||
pub struct Icon {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u32,
|
||||
pub(crate) height: u32,
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Creates an `Icon` from 32bpp RGBA data.
|
||||
///
|
||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
Err(BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(Icon { rgba, width, height })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from the path of an image on the filesystem.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> image::ImageResult<Self> {
|
||||
image::open(path).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from anything implementing `BufRead` and `Seek`.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_reader<R: BufRead + Seek>(
|
||||
reader: R,
|
||||
format: image::ImageFormat,
|
||||
) -> image::ImageResult<Self> {
|
||||
image::load(reader, format).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from the unprocessed bytes of an image file.
|
||||
/// Uses heuristics to determine format.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_bytes(bytes: &[u8]) -> image::ImageResult<Self> {
|
||||
image::load_from_memory(bytes).map(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Loads an `Icon` from the unprocessed bytes of an image.
|
||||
///
|
||||
/// Requires the `icon_loading` feature.
|
||||
pub fn from_bytes_with_format(
|
||||
bytes: &[u8],
|
||||
format: image::ImageFormat,
|
||||
) -> image::ImageResult<Self> {
|
||||
image::load_from_memory_with_format(bytes, format).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Requires the `icon_loading` feature.
|
||||
impl From<image::DynamicImage> for Icon {
|
||||
fn from(image: image::DynamicImage) -> Self {
|
||||
use image::{GenericImageView, Pixel};
|
||||
let (width, height) = image.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
|
||||
for (_, _, pixel) in image.pixels() {
|
||||
rgba.extend_from_slice(&pixel.to_rgba().data);
|
||||
}
|
||||
Icon { rgba, width, height }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "icon_loading")]
|
||||
/// Requires the `icon_loading` feature.
|
||||
impl From<image::RgbaImage> for Icon {
|
||||
fn from(buf: image::RgbaImage) -> Self {
|
||||
let (width, height) = buf.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
|
||||
for (_, _, pixel) in buf.enumerate_pixels() {
|
||||
rgba.extend_from_slice(&pixel.data);
|
||||
}
|
||||
Icon { rgba, width, height }
|
||||
}
|
||||
}
|
||||
157
src/lib.rs
157
src/lib.rs
@@ -15,7 +15,7 @@
|
||||
//! - Calling `Window::new(&events_loop)`.
|
||||
//! - Calling `let builder = WindowBuilder::new()` then `builder.build(&events_loop)`.
|
||||
//!
|
||||
//! The first way is the simpliest way and will give you default values for everything.
|
||||
//! The first way is the simplest way and will give you default values for everything.
|
||||
//!
|
||||
//! The second way allows you to customize the way your window will look and behave by modifying
|
||||
//! the fields of the `WindowBuilder` object before you create the window.
|
||||
@@ -25,24 +25,28 @@
|
||||
//! Once a window has been created, it will *generate events*. For example whenever the user moves
|
||||
//! the window, resizes the window, moves the mouse, etc. an event is generated.
|
||||
//!
|
||||
//! The events generated by a window can be retreived from the `EventsLoop` the window was created
|
||||
//! The events generated by a window can be retrieved from the `EventsLoop` the window was created
|
||||
//! with.
|
||||
//!
|
||||
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
|
||||
//! retreive all the events pending on the windows and immediately return after no new event is
|
||||
//! retrieve all the events pending on the windows and immediately return after no new event is
|
||||
//! available. You usually want to use this method in application that render continuously on the
|
||||
//! screen, such as video games.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::{Event, WindowEvent};
|
||||
//! use winit::dpi::LogicalSize;
|
||||
//! # use winit::EventsLoop;
|
||||
//! # let mut events_loop = EventsLoop::new();
|
||||
//!
|
||||
//! loop {
|
||||
//! events_loop.poll_events(|event| {
|
||||
//! match event {
|
||||
//! Event::WindowEvent { event: WindowEvent::Resized(w, h), .. } => {
|
||||
//! println!("The window was resized to {}x{}", w, h);
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::Resized(LogicalSize { width, height }),
|
||||
//! ..
|
||||
//! } => {
|
||||
//! println!("The window was resized to {}x{}", width, height);
|
||||
//! },
|
||||
//! _ => ()
|
||||
//! }
|
||||
@@ -76,19 +80,29 @@
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't provide any function that allows drawing on a window. However it allows you to
|
||||
//! retreive the raw handle of the window (see the `os` module for that), which in turn allows you
|
||||
//! retrieve the raw handle of the window (see the `os` module for that), which in turn allows you
|
||||
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
|
||||
//!
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))]
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "icon_loading")]
|
||||
extern crate image;
|
||||
#[cfg(feature = "serde")]
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[macro_use]
|
||||
extern crate winapi;
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate backtrace;
|
||||
#[macro_use]
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate bitflags;
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
@@ -98,19 +112,25 @@ extern crate cocoa;
|
||||
extern crate core_foundation;
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate core_graphics;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate x11_dl;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate parking_lot;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate percent_encoding;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
|
||||
#[macro_use]
|
||||
extern crate wayland_client;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
extern crate smithay_client_toolkit as sctk;
|
||||
extern crate raw_window_handle;
|
||||
|
||||
pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase.
|
||||
pub use events::*;
|
||||
pub use window::{AvailableMonitorsIter, MonitorId};
|
||||
pub use icon::*;
|
||||
|
||||
mod platform;
|
||||
pub mod dpi;
|
||||
mod events;
|
||||
mod icon;
|
||||
mod platform;
|
||||
mod window;
|
||||
|
||||
pub mod os;
|
||||
@@ -138,6 +158,12 @@ pub struct Window {
|
||||
window: platform::Window,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Window {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.pad("Window { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of a window. Unique for each window.
|
||||
///
|
||||
/// Can be obtained with `window.id()`.
|
||||
@@ -147,6 +173,17 @@ pub struct Window {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(platform::WindowId);
|
||||
|
||||
impl WindowId {
|
||||
/// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return
|
||||
/// value of this function is that it will always be equal to itself and to future values returned
|
||||
/// by this function. No other guarantees are made. This may be equal to a real `WindowId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(platform::WindowId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of an input device.
|
||||
///
|
||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
||||
@@ -155,7 +192,18 @@ pub struct WindowId(platform::WindowId);
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(platform::DeviceId);
|
||||
|
||||
/// Provides a way to retreive events from the system and from the windows that were registered to
|
||||
impl DeviceId {
|
||||
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
|
||||
/// value of this function is that it will always be equal to itself and to future values returned
|
||||
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
|
||||
///
|
||||
/// **Passing this into a winit function will result in undefined behavior.**
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(platform::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
///
|
||||
/// An `EventsLoop` can be seen more or less as a "context". Calling `EventsLoop::new()`
|
||||
@@ -173,10 +221,17 @@ pub struct EventsLoop {
|
||||
_marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventsLoop {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.pad("EventsLoop { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned by the user callback given to the `EventsLoop::run_forever` method.
|
||||
///
|
||||
/// Indicates whether the `run_forever` method should continue or complete.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ControlFlow {
|
||||
/// Continue looping and waiting for events.
|
||||
Continue,
|
||||
@@ -225,6 +280,11 @@ impl EventsLoop {
|
||||
/// Calls `callback` every time an event is received. If no event is available, sleeps the
|
||||
/// current thread and waits for an event. If the callback returns `ControlFlow::Break` then
|
||||
/// `run_forever` will immediately return.
|
||||
///
|
||||
/// # Danger!
|
||||
///
|
||||
/// The callback is run after *every* event, so if its execution time is non-trivial the event queue may not empty
|
||||
/// at a sufficient rate. Rendering in the callback with vsync enabled **will** cause significant lag.
|
||||
#[inline]
|
||||
pub fn run_forever<F>(&mut self, callback: F)
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
@@ -247,6 +307,12 @@ pub struct EventsLoopProxy {
|
||||
events_loop_proxy: platform::EventsLoopProxy,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EventsLoopProxy {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.pad("EventsLoopProxy { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoopProxy {
|
||||
/// Wake up the `EventsLoop` from which this proxy was created.
|
||||
///
|
||||
@@ -285,6 +351,14 @@ pub struct WindowBuilder {
|
||||
platform_specific: platform::PlatformSpecificWindowBuilderAttributes,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WindowBuilder {
|
||||
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmtr.debug_struct("WindowBuilder")
|
||||
.field("window", &self.window)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can happen while creating a window or a headless renderer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CreationError {
|
||||
@@ -315,7 +389,8 @@ impl std::error::Error for CreationError {
|
||||
}
|
||||
|
||||
/// Describes the appearance of the mouse cursor.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseCursor {
|
||||
/// The platform-dependent default cursor.
|
||||
Default,
|
||||
@@ -341,7 +416,6 @@ pub enum MouseCursor {
|
||||
/// Cursor showing that something cannot be done.
|
||||
NotAllowed,
|
||||
ContextMenu,
|
||||
NoneCursor,
|
||||
Cell,
|
||||
VerticalText,
|
||||
Alias,
|
||||
@@ -371,41 +445,35 @@ pub enum MouseCursor {
|
||||
RowResize,
|
||||
}
|
||||
|
||||
/// Describes how winit handles the cursor.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum CursorState {
|
||||
/// Normal cursor behavior.
|
||||
Normal,
|
||||
|
||||
/// The cursor will be invisible when over the window.
|
||||
Hide,
|
||||
|
||||
/// Grabs the mouse cursor. The cursor's motion will be confined to this
|
||||
/// window and the window has exclusive access to further events regarding
|
||||
/// the cursor.
|
||||
///
|
||||
/// This is useful for first-person cameras for example.
|
||||
Grab,
|
||||
impl Default for MouseCursor {
|
||||
fn default() -> Self {
|
||||
MouseCursor::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// Attributes to use when creating a window.
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindowAttributes {
|
||||
/// The dimensions of the window. If this is `None`, some platform-specific dimensions will be
|
||||
/// used.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub dimensions: Option<(u32, u32)>,
|
||||
pub dimensions: Option<LogicalSize>,
|
||||
|
||||
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub min_dimensions: Option<(u32, u32)>,
|
||||
pub min_dimensions: Option<LogicalSize>,
|
||||
|
||||
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub max_dimensions: Option<(u32, u32)>,
|
||||
pub max_dimensions: Option<LogicalSize>,
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub resizable: bool,
|
||||
|
||||
/// Whether the window should be set as fullscreen upon creation.
|
||||
///
|
||||
@@ -438,6 +506,16 @@ pub struct WindowAttributes {
|
||||
/// The default is `true`.
|
||||
pub decorations: bool,
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
///
|
||||
/// The default is `false`.
|
||||
pub always_on_top: bool,
|
||||
|
||||
/// The window icon.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub window_icon: Option<Icon>,
|
||||
|
||||
/// [iOS only] Enable multitouch,
|
||||
/// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled)
|
||||
pub multitouch: bool,
|
||||
@@ -450,12 +528,15 @@ impl Default for WindowAttributes {
|
||||
dimensions: None,
|
||||
min_dimensions: None,
|
||||
max_dimensions: None,
|
||||
resizable: true,
|
||||
title: "winit window".to_owned(),
|
||||
maximized: false,
|
||||
fullscreen: None,
|
||||
visible: true,
|
||||
transparent: false,
|
||||
decorations: true,
|
||||
always_on_top: false,
|
||||
window_icon: None,
|
||||
multitouch: false,
|
||||
}
|
||||
}
|
||||
|
||||
59
src/os/ios.rs
Normal file
59
src/os/ios.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use {MonitorId, Window, WindowBuilder};
|
||||
|
||||
/// Additional methods on `Window` that are specific to iOS.
|
||||
pub trait WindowExt {
|
||||
/// Returns a pointer to the `UIWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_uiwindow(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the `UIView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_uiview(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
#[inline]
|
||||
fn get_uiwindow(&self) -> *mut c_void {
|
||||
self.window.get_uiwindow() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_uiview(&self) -> *mut c_void {
|
||||
self.window.get_uiview() as _
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to iOS.
|
||||
pub trait WindowBuilderExt {
|
||||
/// 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]`
|
||||
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
|
||||
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to iOS.
|
||||
pub trait MonitorIdExt {
|
||||
/// Returns a pointer to the `UIScreen` that is used by this monitor.
|
||||
fn get_uiscreen(&self) -> *mut c_void;
|
||||
}
|
||||
|
||||
impl MonitorIdExt for MonitorId {
|
||||
#[inline]
|
||||
fn get_uiscreen(&self) -> *mut c_void {
|
||||
self.inner.get_uiscreen() as _
|
||||
}
|
||||
}
|
||||
74
src/os/macos.rs
Executable file → Normal file
74
src/os/macos.rs
Executable file → Normal file
@@ -1,9 +1,7 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
use std::convert::From;
|
||||
use std::os::raw::c_void;
|
||||
use cocoa::appkit::NSApplicationActivationPolicy;
|
||||
use {MonitorId, Window, WindowBuilder};
|
||||
use {LogicalSize, MonitorId, Window, WindowBuilder};
|
||||
|
||||
/// Additional methods on `Window` that are specific to MacOS.
|
||||
pub trait WindowExt {
|
||||
@@ -16,6 +14,26 @@ pub trait WindowExt {
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
fn get_nsview(&self) -> *mut c_void;
|
||||
|
||||
/// Request user attention, causing the application's dock icon to bounce.
|
||||
/// Note that this has no effect if the application is already focused.
|
||||
///
|
||||
/// The `is_critical` flag has the following effects:
|
||||
/// - `false`: the dock icon will only bounce once.
|
||||
/// - `true`: the dock icon will bounce until the application is focused.
|
||||
fn request_user_attention(&self, is_critical: bool);
|
||||
|
||||
/// Returns whether or not the window is in simple fullscreen mode.
|
||||
fn get_simple_fullscreen(&self) -> bool;
|
||||
|
||||
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
||||
/// Returns a boolean indicating whether the transition was successful (this
|
||||
/// won't work if the window was already in the native fullscreen).
|
||||
///
|
||||
/// This is how fullscreen used to work on macOS in versions before Lion.
|
||||
/// And allows the user to have a fullscreen window without using another
|
||||
/// space or taking control over the entire monitor.
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
@@ -28,6 +46,21 @@ impl WindowExt for Window {
|
||||
fn get_nsview(&self) -> *mut c_void {
|
||||
self.window.get_nsview()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn request_user_attention(&self, is_critical: bool) {
|
||||
self.window.request_user_attention(is_critical)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_simple_fullscreen(&self) -> bool {
|
||||
self.window.get_simple_fullscreen()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
self.window.set_simple_fullscreen(fullscreen)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
@@ -47,19 +80,6 @@ impl Default for ActivationPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActivationPolicy> for NSApplicationActivationPolicy {
|
||||
fn from(activation_policy: ActivationPolicy) -> Self {
|
||||
match activation_policy {
|
||||
ActivationPolicy::Regular =>
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory =>
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited =>
|
||||
NSApplicationActivationPolicy::NSApplicationActivationPolicyProhibited,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to MacOS.
|
||||
///
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
|
||||
@@ -71,64 +91,72 @@ impl From<ActivationPolicy> for NSApplicationActivationPolicy {
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExt {
|
||||
/// Sets the activation policy for the window being built.
|
||||
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
|
||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> WindowBuilder;
|
||||
/// Makes the titlebar transparent and allows the content to appear behind it.
|
||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
|
||||
/// Hides the window title.
|
||||
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
|
||||
/// Hides the window titlebar.
|
||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
|
||||
/// Hides the window titlebar buttons.
|
||||
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
|
||||
/// Makes the window content appear behind the titlebar.
|
||||
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;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
/// Sets the activation policy for the window being built
|
||||
#[inline]
|
||||
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> WindowBuilder {
|
||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the titlebar transparent and allows the content to appear behind it
|
||||
#[inline]
|
||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
||||
self
|
||||
}
|
||||
|
||||
/// Hides the window titlebar
|
||||
#[inline]
|
||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_hidden = titlebar_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
/// Hides the window titlebar buttons
|
||||
#[inline]
|
||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
/// Hides the window title
|
||||
#[inline]
|
||||
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
|
||||
self.platform_specific.title_hidden = title_hidden;
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the window content appear behind the titlebar
|
||||
#[inline]
|
||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
|
||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to MacOS.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Contains the follow modules:
|
||||
//!
|
||||
//! - `android`
|
||||
//! - `ios`
|
||||
//! - `macos`
|
||||
//! - `unix`
|
||||
//! - `windows`
|
||||
@@ -10,6 +11,7 @@
|
||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
||||
//!
|
||||
pub mod android;
|
||||
pub mod ios;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
|
||||
201
src/os/unix.rs
201
src/os/unix.rs
@@ -1,14 +1,22 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::os::raw;
|
||||
use std::sync::Arc;
|
||||
use std::ptr;
|
||||
use EventsLoop;
|
||||
use MonitorId;
|
||||
use Window;
|
||||
use platform::EventsLoop as LinuxEventsLoop;
|
||||
use platform::Window as LinuxWindow;
|
||||
use WindowBuilder;
|
||||
use std::sync::Arc;
|
||||
|
||||
use sctk::window::{ButtonState, Theme};
|
||||
|
||||
use {
|
||||
EventsLoop,
|
||||
LogicalSize,
|
||||
MonitorId,
|
||||
Window,
|
||||
WindowBuilder,
|
||||
};
|
||||
use platform::{
|
||||
EventsLoop as LinuxEventsLoop,
|
||||
Window as LinuxWindow,
|
||||
};
|
||||
use platform::x11::XConnection;
|
||||
use platform::x11::ffi::XVisualInfo;
|
||||
|
||||
@@ -17,6 +25,75 @@ use platform::x11::ffi::XVisualInfo;
|
||||
pub use platform::x11;
|
||||
|
||||
pub use platform::XNotSupported;
|
||||
pub use platform::x11::util::WindowType as XWindowType;
|
||||
|
||||
/// Theme for wayland client side decorations
|
||||
///
|
||||
/// Colors must be in ARGB8888 format
|
||||
pub struct WaylandTheme {
|
||||
/// Primary color when the window is focused
|
||||
pub primary_active: [u8; 4],
|
||||
/// Primary color when the window is unfocused
|
||||
pub primary_inactive: [u8; 4],
|
||||
/// Secondary color when the window is focused
|
||||
pub secondary_active: [u8; 4],
|
||||
/// Secondary color when the window is unfocused
|
||||
pub secondary_inactive: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub close_button_hovered: [u8; 4],
|
||||
/// Close button color
|
||||
pub close_button: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub maximize_button_hovered: [u8; 4],
|
||||
/// Maximize button color
|
||||
pub maximize_button: [u8; 4],
|
||||
/// Minimize button color when hovered over
|
||||
pub minimize_button_hovered: [u8; 4],
|
||||
/// Minimize button color
|
||||
pub minimize_button: [u8; 4],
|
||||
}
|
||||
|
||||
struct WaylandThemeObject(WaylandTheme);
|
||||
|
||||
impl Theme for WaylandThemeObject {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.primary_active
|
||||
} else {
|
||||
self.0.primary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
// Used for division line
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.secondary_active
|
||||
} else {
|
||||
self.0.secondary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.close_button_hovered,
|
||||
_ => self.0.close_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.maximize_button_hovered,
|
||||
_ => self.0.maximize_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.minimize_button_hovered,
|
||||
_ => self.0.minimize_button,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Linux.
|
||||
pub trait EventsLoopExt {
|
||||
@@ -36,6 +113,13 @@ pub trait EventsLoopExt {
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventsLoop`.
|
||||
///
|
||||
/// Returns `None` if the `EventsLoop` doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `EventsLoop` is destroyed.
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
@@ -71,9 +155,18 @@ impl EventsLoopExt for EventsLoop {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
self.events_loop.x_connection().cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.events_loop {
|
||||
LinuxEventsLoop::Wayland(ref e) => Some(e.get_display().c_ptr() as *mut _),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Unix.
|
||||
@@ -92,9 +185,11 @@ pub trait WindowExt {
|
||||
|
||||
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
fn send_xim_spot(&self, x: i16, y: i16);
|
||||
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
|
||||
fn set_urgent(&self, is_urgent: bool);
|
||||
|
||||
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
|
||||
///
|
||||
@@ -117,6 +212,9 @@ pub trait WindowExt {
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Sets the color theme of the client side window decorations on wayland
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme);
|
||||
|
||||
/// Check if the window is ready for drawing
|
||||
///
|
||||
/// It is a remnant of a previous implementation detail for the
|
||||
@@ -144,6 +242,7 @@ impl WindowExt for Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
|
||||
@@ -151,6 +250,8 @@ impl WindowExt for Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
|
||||
@@ -158,6 +259,7 @@ impl WindowExt for Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
|
||||
@@ -165,30 +267,37 @@ impl WindowExt for Window {
|
||||
}
|
||||
}
|
||||
|
||||
fn send_xim_spot(&self, x: i16, y: i16) {
|
||||
#[inline]
|
||||
fn set_urgent(&self, is_urgent: bool) {
|
||||
if let LinuxWindow::X(ref w) = self.window {
|
||||
w.send_xim_spot(x, y);
|
||||
w.set_urgent(is_urgent);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
use wayland_client::Proxy;
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_surface().ptr() as *mut _),
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_surface().c_ptr() as *mut _),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
use wayland_client::Proxy;
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_display().ptr() as *mut _),
|
||||
LinuxWindow::Wayland(ref w) => Some(w.get_display().c_ptr() as *mut _),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_ready(&self) -> bool {
|
||||
true
|
||||
@@ -199,6 +308,26 @@ impl WindowExt for Window {
|
||||
pub trait WindowBuilderExt {
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
|
||||
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
|
||||
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
@@ -215,6 +344,48 @@ impl WindowBuilderExt for WindowBuilder {
|
||||
self.platform_specific.screen_id = Some(screen_id);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_class(mut self, instance: String, class: String) -> WindowBuilder {
|
||||
self.platform_specific.class = Some((instance, class));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
|
||||
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;
|
||||
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 {
|
||||
self.platform_specific.gtk_theme_variant = Some(variant);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
|
||||
self.platform_specific.app_id = Some(app_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to Linux.
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use libc;
|
||||
use MonitorId;
|
||||
use Window;
|
||||
use WindowBuilder;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
use {DeviceId, EventsLoop, Icon, MonitorId, Window, WindowBuilder};
|
||||
use platform::EventsLoop as WindowsEventsLoop;
|
||||
|
||||
/// Additional methods on `EventsLoop` that are specific to Windows.
|
||||
pub trait EventsLoopExt {
|
||||
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
|
||||
/// undesirable, you can create an `EventsLoop` using this function instead.
|
||||
fn new_dpi_unaware() -> Self where Self: Sized;
|
||||
}
|
||||
|
||||
impl EventsLoopExt for EventsLoop {
|
||||
#[inline]
|
||||
fn new_dpi_unaware() -> Self {
|
||||
EventsLoop {
|
||||
events_loop: WindowsEventsLoop::with_dpi_awareness(false),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExt {
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn get_hwnd(&self) -> *mut libc::c_void;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
}
|
||||
|
||||
impl WindowExt for Window {
|
||||
@@ -20,20 +41,43 @@ impl WindowExt for Window {
|
||||
fn get_hwnd(&self) -> *mut libc::c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExt {
|
||||
/// Sets a parent to the window to be created.
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
||||
|
||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
||||
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExt for WindowBuilder {
|
||||
/// Sets a parent to the window to be created.
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
|
||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
|
||||
self.platform_specific.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorId` that are specific to Windows.
|
||||
@@ -56,3 +100,18 @@ impl MonitorIdExt for MonitorId {
|
||||
self.inner.get_hmonitor() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
||||
pub trait DeviceIdExt {
|
||||
/// Returns an identifier that persistently refers to this specific device.
|
||||
///
|
||||
/// Will return `None` if the device is no longer available.
|
||||
fn get_persistent_identifier(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl DeviceIdExt for DeviceId {
|
||||
#[inline]
|
||||
fn get_persistent_identifier(&self) -> Option<String> {
|
||||
self.0.get_persistent_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,33 @@
|
||||
|
||||
extern crate android_glue;
|
||||
|
||||
use libc;
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
mod ffi;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::os::raw::c_void;
|
||||
use {CreationError, Event, WindowEvent, MouseCursor};
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
|
||||
use {
|
||||
CreationError,
|
||||
Event,
|
||||
LogicalPosition,
|
||||
LogicalSize,
|
||||
MouseCursor,
|
||||
PhysicalPosition,
|
||||
PhysicalSize,
|
||||
WindowAttributes,
|
||||
WindowEvent,
|
||||
WindowId as RootWindowId,
|
||||
};
|
||||
use CreationError::OsError;
|
||||
use WindowId as RootWindowId;
|
||||
use events::{Touch, TouchPhase};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use CursorState;
|
||||
use WindowAttributes;
|
||||
|
||||
pub struct EventsLoop {
|
||||
event_rx: Receiver<android_glue::Event>,
|
||||
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>
|
||||
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -31,13 +40,13 @@ impl EventsLoop {
|
||||
android_glue::add_sender(tx);
|
||||
EventsLoop {
|
||||
event_rx: rx,
|
||||
suspend_callback: RefCell::new(None),
|
||||
suspend_callback: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::new();
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
@@ -47,14 +56,17 @@ impl EventsLoop {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
while let Ok(event) = self.event_rx.try_recv() {
|
||||
|
||||
let e = match event{
|
||||
android_glue::Event::EventMotion(motion) => {
|
||||
let dpi_factor = MonitorId.get_hidpi_factor();
|
||||
let location = LogicalPosition::from_physical(
|
||||
(motion.x as f64, motion.y as f64),
|
||||
dpi_factor,
|
||||
);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
@@ -64,7 +76,7 @@ impl EventsLoop {
|
||||
android_glue::MotionAction::Up => TouchPhase::Ended,
|
||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
||||
},
|
||||
location: (motion.x as f64, motion.y as f64),
|
||||
location,
|
||||
id: motion.pointer_id as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
@@ -91,11 +103,12 @@ impl EventsLoop {
|
||||
if native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let w = unsafe { ffi::ANativeWindow_getWidth(native_window as *const _) } as u32;
|
||||
let h = unsafe { ffi::ANativeWindow_getHeight(native_window as *const _) } as u32;
|
||||
let dpi_factor = MonitorId.get_hidpi_factor();
|
||||
let physical_size = MonitorId.get_dimensions();
|
||||
let size = LogicalSize::from_physical(physical_size, dpi_factor);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Resized(w, h),
|
||||
event: WindowEvent::Resized(size),
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -157,9 +170,21 @@ impl EventsLoopProxy {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId;
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
native_window: *const c_void,
|
||||
}
|
||||
@@ -167,7 +192,26 @@ pub struct Window {
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
|
||||
mod ffi;
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
@@ -176,21 +220,24 @@ impl MonitorId {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
unsafe {
|
||||
let window = android_glue::get_native_window();
|
||||
(ffi::ANativeWindow_getWidth(window) as u32, ffi::ANativeWindow_getHeight(window) as u32)
|
||||
(
|
||||
ffi::ANativeWindow_getWidth(window) as f64,
|
||||
ffi::ANativeWindow_getHeight(window) as f64,
|
||||
).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
// Android assumes single screen
|
||||
(0, 0)
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
@@ -201,14 +248,10 @@ pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
pub struct PlatformSpecificHeadlessBuilderAttributes;
|
||||
|
||||
impl Window {
|
||||
pub fn new(_: &EventsLoop, win_attribs: &WindowAttributes,
|
||||
_: &PlatformSpecificWindowBuilderAttributes)
|
||||
pub fn new(_: &EventsLoop, win_attribs: WindowAttributes,
|
||||
_: PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
// not implemented
|
||||
assert!(win_attribs.min_dimensions.is_none());
|
||||
assert!(win_attribs.max_dimensions.is_none());
|
||||
|
||||
let native_window = unsafe { android_glue::get_native_window() };
|
||||
if native_window.is_null() {
|
||||
return Err(OsError(format!("Android's native window is null")));
|
||||
@@ -228,93 +271,113 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _: &str) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _x: i32, _y: i32) {
|
||||
pub fn set_position(&self, _position: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
if self.native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
unsafe { ffi::ANativeWindow_getWidth(self.native_window as *const _) } as u32,
|
||||
unsafe { ffi::ANativeWindow_getHeight(self.native_window as *const _) } as u32
|
||||
))
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let physical_size = self.get_current_monitor().get_dimensions();
|
||||
Some(LogicalSize::from_physical(physical_size, dpi_factor))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, _x: u32, _y: u32) {
|
||||
pub fn set_inner_size(&self, _size: LogicalSize) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut libc::c_void {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut libc::c_void {
|
||||
unimplemented!()
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.get_current_monitor().get_hidpi_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _: MouseCursor) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, _state: CursorState) -> Result<(), String> {
|
||||
Ok(())
|
||||
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
|
||||
Err("Cursor grabbing is not possible on Android.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
pub fn hide_cursor(&self, _hide: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
Ok(())
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting cursor position is not possible on Android.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
@@ -324,10 +387,38 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId{inner: MonitorId}
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _spot: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId { inner: MonitorId }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||
|
||||
use std::os::raw::{c_int, c_char, c_void, c_ulong, c_double, c_long, c_ushort};
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
|
||||
mod ffi;
|
||||
|
||||
use std::mem;
|
||||
use std::os::raw::{c_char, c_void, c_double, c_ulong, c_int};
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Mutex, Arc};
|
||||
use std::{mem, ptr, str};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::os::raw::{c_char, c_void, c_double, c_ulong, c_int};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Mutex, Arc};
|
||||
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
const DOCUMENT_NAME: &'static str = "#document\0";
|
||||
|
||||
fn get_hidpi_factor() -> f64 {
|
||||
unsafe { ffi::emscripten_get_device_pixel_ratio() as f64 }
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
|
||||
@@ -21,10 +27,16 @@ unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificHeadlessBuilderAttributes;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorId;
|
||||
|
||||
impl MonitorId {
|
||||
@@ -34,18 +46,18 @@ impl MonitorId {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
(0, 0)
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
get_hidpi_factor()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,17 +102,19 @@ impl EventsLoop {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn interrupt(&self) {
|
||||
self.interrupted.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut list = VecDeque::new();
|
||||
let mut list = VecDeque::with_capacity(1);
|
||||
list.push_back(MonitorId);
|
||||
list
|
||||
}
|
||||
@@ -115,7 +129,7 @@ impl EventsLoop {
|
||||
{
|
||||
let ref mut window = *self.window.lock().unwrap();
|
||||
if let &mut Some(ref mut window) = window {
|
||||
while let Some(event) = window.events.borrow_mut().pop_front() {
|
||||
while let Some(event) = window.events.lock().unwrap().pop_front() {
|
||||
callback(event)
|
||||
}
|
||||
}
|
||||
@@ -141,10 +155,17 @@ impl EventsLoop {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(usize);
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window2 {
|
||||
cursor_state: Mutex<::CursorState>,
|
||||
cursor_grabbed: Mutex<bool>,
|
||||
cursor_hidden: Mutex<bool>,
|
||||
is_fullscreen: bool,
|
||||
events: Box<RefCell<VecDeque<::Event>>>,
|
||||
events: Box<Mutex<VecDeque<::Event>>>,
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
@@ -176,7 +197,7 @@ extern "C" fn mouse_callback(
|
||||
event_queue: *mut c_void) -> ffi::EM_BOOL
|
||||
{
|
||||
unsafe {
|
||||
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
|
||||
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
|
||||
|
||||
let modifiers = ::ModifiersState {
|
||||
shift: (*event).shiftKey == ffi::EM_TRUE,
|
||||
@@ -187,15 +208,20 @@ extern "C" fn mouse_callback(
|
||||
|
||||
match event_type {
|
||||
ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => {
|
||||
queue.borrow_mut().push_back(::Event::WindowEvent {
|
||||
let dpi_factor = get_hidpi_factor();
|
||||
let position = LogicalPosition::from_physical(
|
||||
((*event).canvasX as f64, (*event).canvasY as f64),
|
||||
dpi_factor,
|
||||
);
|
||||
queue.lock().unwrap().push_back(::Event::WindowEvent {
|
||||
window_id: ::WindowId(WindowId(0)),
|
||||
event: ::WindowEvent::CursorMoved {
|
||||
device_id: ::DeviceId(DeviceId),
|
||||
position: ((*event).canvasX as f64, (*event).canvasY as f64),
|
||||
position,
|
||||
modifiers: modifiers,
|
||||
}
|
||||
});
|
||||
queue.borrow_mut().push_back(::Event::DeviceEvent {
|
||||
queue.lock().unwrap().push_back(::Event::DeviceEvent {
|
||||
device_id: ::DeviceId(DeviceId),
|
||||
event: ::DeviceEvent::MouseMotion {
|
||||
delta: ((*event).movementX as f64, (*event).movementY as f64),
|
||||
@@ -215,7 +241,7 @@ extern "C" fn mouse_callback(
|
||||
ffi::EMSCRIPTEN_EVENT_MOUSEUP => ::ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
queue.borrow_mut().push_back(::Event::WindowEvent {
|
||||
queue.lock().unwrap().push_back(::Event::WindowEvent {
|
||||
window_id: ::WindowId(WindowId(0)),
|
||||
event: ::WindowEvent::MouseInput {
|
||||
device_id: ::DeviceId(DeviceId),
|
||||
@@ -238,7 +264,7 @@ extern "C" fn keyboard_callback(
|
||||
event_queue: *mut c_void) -> ffi::EM_BOOL
|
||||
{
|
||||
unsafe {
|
||||
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
|
||||
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
|
||||
|
||||
let modifiers = ::ModifiersState {
|
||||
shift: (*event).shiftKey == ffi::EM_TRUE,
|
||||
@@ -249,7 +275,7 @@ extern "C" fn keyboard_callback(
|
||||
|
||||
match event_type {
|
||||
ffi::EMSCRIPTEN_EVENT_KEYDOWN => {
|
||||
queue.borrow_mut().push_back(::Event::WindowEvent {
|
||||
queue.lock().unwrap().push_back(::Event::WindowEvent {
|
||||
window_id: ::WindowId(WindowId(0)),
|
||||
event: ::WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(DeviceId),
|
||||
@@ -263,7 +289,7 @@ extern "C" fn keyboard_callback(
|
||||
});
|
||||
},
|
||||
ffi::EMSCRIPTEN_EVENT_KEYUP => {
|
||||
queue.borrow_mut().push_back(::Event::WindowEvent {
|
||||
queue.lock().unwrap().push_back(::Event::WindowEvent {
|
||||
window_id: ::WindowId(WindowId(0)),
|
||||
event: ::WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(DeviceId),
|
||||
@@ -289,7 +315,7 @@ extern fn touch_callback(
|
||||
event_queue: *mut c_void) -> ffi::EM_BOOL
|
||||
{
|
||||
unsafe {
|
||||
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
|
||||
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
|
||||
|
||||
let phase = match event_type {
|
||||
ffi::EMSCRIPTEN_EVENT_TOUCHSTART => ::TouchPhase::Started,
|
||||
@@ -302,13 +328,18 @@ extern fn touch_callback(
|
||||
for touch in 0..(*event).numTouches as usize {
|
||||
let touch = (*event).touches[touch];
|
||||
if touch.isChanged == ffi::EM_TRUE {
|
||||
queue.borrow_mut().push_back(::Event::WindowEvent {
|
||||
let dpi_factor = get_hidpi_factor();
|
||||
let location = LogicalPosition::from_physical(
|
||||
(touch.canvasX as f64, touch.canvasY as f64),
|
||||
dpi_factor,
|
||||
);
|
||||
queue.lock().unwrap().push_back(::Event::WindowEvent {
|
||||
window_id: ::WindowId(WindowId(0)),
|
||||
event: ::WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(DeviceId),
|
||||
phase,
|
||||
id: touch.identifier as u64,
|
||||
location: (touch.canvasX as f64, touch.canvasY as f64),
|
||||
location,
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -347,8 +378,8 @@ fn em_try(res: ffi::EMSCRIPTEN_RESULT) -> Result<(), String> {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(events_loop: &EventsLoop, attribs: &::WindowAttributes,
|
||||
_pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
||||
pub fn new(events_loop: &EventsLoop, attribs: ::WindowAttributes,
|
||||
_pl_attribs: PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, ::CreationError>
|
||||
{
|
||||
if events_loop.window.lock().unwrap().is_some() {
|
||||
@@ -356,8 +387,9 @@ impl Window {
|
||||
}
|
||||
|
||||
let w = Window2 {
|
||||
cursor_state: Mutex::new(::CursorState::Normal),
|
||||
events: Box::new(RefCell::new(VecDeque::new())),
|
||||
cursor_grabbed: Default::default(),
|
||||
cursor_hidden: Default::default(),
|
||||
events: Default::default(),
|
||||
is_fullscreen: attribs.fullscreen.is_some(),
|
||||
};
|
||||
|
||||
@@ -395,8 +427,8 @@ impl Window {
|
||||
em_try(ffi::emscripten_set_fullscreenchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(fullscreen_callback)))
|
||||
.map_err(|e| ::CreationError::OsError(e))?;
|
||||
}
|
||||
} else if let Some((w, h)) = attribs.dimensions {
|
||||
window.set_inner_size(w, h);
|
||||
} else if let Some(size) = attribs.dimensions {
|
||||
window.set_inner_size(size);
|
||||
}
|
||||
|
||||
*events_loop.window.lock().unwrap() = Some(window.window.clone());
|
||||
@@ -413,22 +445,22 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
Some((0, 0))
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
Some((0, 0).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
Some((0, 0))
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
Some((0, 0).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _: i32, _: i32) {
|
||||
pub fn set_position(&self, _: LogicalPosition) {
|
||||
}
|
||||
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
unsafe {
|
||||
use std::{mem, ptr};
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
let mut fullscreen = 0;
|
||||
@@ -438,94 +470,109 @@ impl Window {
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((width as u32, height as u32))
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor);
|
||||
Some(logical)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, width: u32, height: u32) {
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
unsafe {
|
||||
use std::ptr;
|
||||
ffi::emscripten_set_element_css_size(ptr::null(), width as c_double, height
|
||||
as c_double);
|
||||
let dpi_factor = self.get_hidpi_factor();
|
||||
let physical = PhysicalSize::from_logical(size, dpi_factor);
|
||||
let (width, height): (u32, u32) = physical.into();
|
||||
ffi::emscripten_set_element_css_size(
|
||||
ptr::null(),
|
||||
width as c_double,
|
||||
height as c_double,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {}
|
||||
#[inline]
|
||||
pub fn hide(&self) {}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut ::libc::c_void {
|
||||
unimplemented!()
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut ::libc::c_void {
|
||||
unimplemented!()
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _cursor: ::MouseCursor) {}
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, state: ::CursorState) -> Result<(), String> {
|
||||
pub fn show(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _cursor: ::MouseCursor) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap();
|
||||
if grab == *grabbed_lock { return Ok(()); }
|
||||
unsafe {
|
||||
use ::CursorState::*;
|
||||
|
||||
let mut old_state = self.window.cursor_state.lock().unwrap();
|
||||
if state == *old_state {
|
||||
return Ok(());
|
||||
if grab {
|
||||
em_try(ffi::emscripten_set_pointerlockchange_callback(
|
||||
ptr::null(),
|
||||
0 as *mut c_void,
|
||||
ffi::EM_FALSE,
|
||||
Some(pointerlockchange_callback),
|
||||
))?;
|
||||
em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?;
|
||||
} else {
|
||||
em_try(ffi::emscripten_set_pointerlockchange_callback(
|
||||
ptr::null(),
|
||||
0 as *mut c_void,
|
||||
ffi::EM_FALSE,
|
||||
None,
|
||||
))?;
|
||||
em_try(ffi::emscripten_exit_pointerlock())?;
|
||||
}
|
||||
|
||||
// Set or unset grab callback
|
||||
match state {
|
||||
Hide | Normal => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, None))?,
|
||||
Grab => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(pointerlockchange_callback)))?,
|
||||
}
|
||||
|
||||
// Go back to normal cursor state
|
||||
match *old_state {
|
||||
Hide => show_mouse(),
|
||||
Grab => em_try(ffi::emscripten_exit_pointerlock())?,
|
||||
Normal => (),
|
||||
}
|
||||
|
||||
// Set cursor from normal cursor state
|
||||
match state {
|
||||
Hide => ffi::emscripten_hide_mouse(),
|
||||
Grab => em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?,
|
||||
Normal => (),
|
||||
}
|
||||
|
||||
// Update
|
||||
*old_state = state;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*grabbed_lock = grab;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
unsafe { ffi::emscripten_get_device_pixel_ratio() as f32 }
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
let mut hidden_lock = self.window.cursor_hidden.lock().unwrap();
|
||||
if hide == *hidden_lock { return; }
|
||||
if hide {
|
||||
unsafe { ffi::emscripten_hide_mouse() };
|
||||
} else {
|
||||
show_mouse();
|
||||
}
|
||||
*hidden_lock = hide;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
Err(())
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
get_hidpi_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting cursor position is not possible on Emscripten.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -533,6 +580,11 @@ impl Window {
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<::MonitorId> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<::MonitorId>) {
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
@@ -544,8 +596,35 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> ::MonitorId {
|
||||
::MonitorId{inner: MonitorId}
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId { inner: MonitorId }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut list = VecDeque::with_capacity(1);
|
||||
list.push_back(MonitorId);
|
||||
list
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +638,8 @@ impl Drop for Window {
|
||||
|
||||
unsafe {
|
||||
// Return back to normal cursor state
|
||||
let _ = self.set_cursor_state(::CursorState::Normal);
|
||||
self.hide_cursor(false);
|
||||
self.grab_cursor(false);
|
||||
|
||||
// Exit fullscreen if on
|
||||
if self.window.is_fullscreen {
|
||||
@@ -592,7 +672,6 @@ fn error_to_str(code: ffi::EMSCRIPTEN_RESULT) -> &'static str {
|
||||
}
|
||||
|
||||
fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) -> u8 {
|
||||
use std::str;
|
||||
let slice = &input[0..input.iter().take_while(|x| **x != 0).count()];
|
||||
let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) };
|
||||
let key = match maybe_key {
|
||||
@@ -609,7 +688,6 @@ fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) ->
|
||||
fn key_translate_virt(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES],
|
||||
location: c_ulong) -> Option<::VirtualKeyCode>
|
||||
{
|
||||
use std::str;
|
||||
let slice = &input[0..input.iter().take_while(|x| **x != 0).count()];
|
||||
let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) };
|
||||
let key = match maybe_key {
|
||||
@@ -749,11 +827,15 @@ fn key_translate_virt(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES
|
||||
"F13" => Some(F13),
|
||||
"F14" => Some(F14),
|
||||
"F15" => Some(F15),
|
||||
"F16" => None,
|
||||
"F17" => None,
|
||||
"F18" => None,
|
||||
"F19" => None,
|
||||
"F20" => None,
|
||||
"F16" => Some(F16),
|
||||
"F17" => Some(F17),
|
||||
"F18" => Some(F18),
|
||||
"F19" => Some(F19),
|
||||
"F20" => Some(F20),
|
||||
"F21" => Some(F21),
|
||||
"F22" => Some(F22),
|
||||
"F23" => Some(F23),
|
||||
"F24" => Some(F24),
|
||||
"Soft1" => None,
|
||||
"Soft2" => None,
|
||||
"Soft3" => None,
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::*;
|
||||
|
||||
use libc;
|
||||
use objc::runtime::{ Object, Class };
|
||||
use objc::runtime::Object;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type id = *mut Object;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const nil: id = 0 as id;
|
||||
|
||||
pub type CFStringRef = *const libc::c_void;
|
||||
pub type CFStringRef = *const c_void;
|
||||
pub type CFTimeInterval = f64;
|
||||
pub type Boolean = u32;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const kCFRunLoopRunHandledSource: i32 = 4;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
@@ -22,11 +19,6 @@ pub type CGFloat = f32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type CGFloat = f64;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type NSUInteger = u32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type NSUInteger = u64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGPoint {
|
||||
@@ -38,14 +30,14 @@ pub struct CGPoint {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGSize {
|
||||
pub width: CGFloat,
|
||||
pub height: CGFloat
|
||||
pub height: CGFloat,
|
||||
}
|
||||
|
||||
#[link(name = "UIKit", kind = "framework")]
|
||||
@@ -55,33 +47,51 @@ extern {
|
||||
pub static kCFRunLoopDefaultMode: CFStringRef;
|
||||
|
||||
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
|
||||
pub fn UIApplicationMain(argc: libc::c_int, argv: *const libc::c_char, principalClassName: id, delegateClassName: id) -> libc::c_int;
|
||||
pub fn UIApplicationMain(
|
||||
argc: c_int,
|
||||
argv: *const c_char,
|
||||
principalClassName: id,
|
||||
delegateClassName: id,
|
||||
) -> c_int;
|
||||
|
||||
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
|
||||
pub fn CFRunLoopRunInMode(mode: CFStringRef, seconds: CFTimeInterval, returnAfterSourceHandled: Boolean) -> i32;
|
||||
pub fn CFRunLoopRunInMode(
|
||||
mode: CFStringRef,
|
||||
seconds: CFTimeInterval,
|
||||
returnAfterSourceHandled: Boolean,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
extern {
|
||||
pub fn setjmp(env: *mut libc::c_void) -> libc::c_int;
|
||||
pub fn longjmp(env: *mut libc::c_void, val: libc::c_int);
|
||||
pub fn setjmp(env: *mut c_void) -> c_int;
|
||||
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
|
||||
}
|
||||
|
||||
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
|
||||
#[cfg(any(target_arch = "x86_64"))]
|
||||
pub const JBLEN: usize = (9 * 2) + 3 + 16;
|
||||
#[cfg(any(target_arch = "x86"))]
|
||||
pub const JBLEN: usize = 18;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub const JBLEN: usize = 10 + 16 + 2;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const JBLEN: usize = (14 + 8 + 2) * 2;
|
||||
|
||||
pub type JmpBuf = [c_int; JBLEN];
|
||||
|
||||
pub trait NSString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class("NSString"), alloc]
|
||||
msg_send![class!(NSString), alloc]
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id;
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
|
||||
unsafe fn stringByAppendingString_(self, other: id) -> id;
|
||||
unsafe fn init_str(self, string: &str) -> Self;
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn UTF8String(self) -> *const libc::c_char;
|
||||
unsafe fn UTF8String(self) -> *const c_char;
|
||||
}
|
||||
|
||||
impl NSString for id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id {
|
||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
|
||||
msg_send![self, initWithUTF8String:c_string as id]
|
||||
}
|
||||
|
||||
@@ -94,14 +104,7 @@ impl NSString for id {
|
||||
self.initWithUTF8String_(cstring.as_ptr())
|
||||
}
|
||||
|
||||
unsafe fn UTF8String(self) -> *const libc::c_char {
|
||||
unsafe fn UTF8String(self) -> *const c_char {
|
||||
msg_send![self, UTF8String]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn class(name: &str) -> *mut Class {
|
||||
unsafe {
|
||||
::std::mem::transmute(Class::get(name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
//!
|
||||
//! ```rust, ignore
|
||||
//! #[no_mangle]
|
||||
//! pub extern fn start_glutin_app() {
|
||||
//! pub extern fn start_winit_app() {
|
||||
//! start_inner()
|
||||
//! }
|
||||
//!
|
||||
@@ -29,13 +29,13 @@
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode.
|
||||
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! void start_glutin_app();
|
||||
//! void start_winit_app();
|
||||
//! ```
|
||||
//!
|
||||
//! Use start_glutin_app inside your xcode's main function.
|
||||
//! Use start_winit_app inside your xcode's main function.
|
||||
//!
|
||||
//!
|
||||
//! # App lifecycle and events
|
||||
@@ -45,7 +45,7 @@
|
||||
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
|
||||
//!
|
||||
//!
|
||||
//! This is how those event are represented in glutin:
|
||||
//! This is how those event are represented in winit:
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Focused(true)
|
||||
//! - applicationWillResignActive is Focused(false)
|
||||
@@ -60,100 +60,148 @@
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
|
||||
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
|
||||
use std::{fmt, mem, ptr};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::ptr;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::os::raw::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use libc;
|
||||
use libc::c_int;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL, YES };
|
||||
use objc::declare::{ ClassDecl };
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{BOOL, Class, Object, Sel, YES};
|
||||
|
||||
use { CreationError, CursorState, MouseCursor, WindowAttributes };
|
||||
use WindowId as RootEventId;
|
||||
use WindowEvent;
|
||||
use Event;
|
||||
use events::{ Touch, TouchPhase };
|
||||
use {
|
||||
CreationError,
|
||||
Event,
|
||||
LogicalPosition,
|
||||
LogicalSize,
|
||||
MouseCursor,
|
||||
PhysicalPosition,
|
||||
PhysicalSize,
|
||||
WindowAttributes,
|
||||
WindowEvent,
|
||||
WindowId as RootEventId,
|
||||
};
|
||||
use events::{Touch, TouchPhase};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
mod ffi;
|
||||
use self::ffi::{
|
||||
setjmp,
|
||||
UIApplicationMain,
|
||||
CFTimeInterval,
|
||||
CFRunLoopRunInMode,
|
||||
CGFloat,
|
||||
CGPoint,
|
||||
CGRect,
|
||||
id,
|
||||
JBLEN,
|
||||
JmpBuf,
|
||||
kCFRunLoopDefaultMode,
|
||||
kCFRunLoopRunHandledSource,
|
||||
id,
|
||||
longjmp,
|
||||
nil,
|
||||
NSString,
|
||||
CGFloat,
|
||||
longjmp,
|
||||
CGRect,
|
||||
CGPoint
|
||||
setjmp,
|
||||
UIApplicationMain,
|
||||
};
|
||||
|
||||
static mut jmpbuf: [c_int;27] = [0;27];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
static mut JMPBUF: Option<Box<JmpBuf>> = None;
|
||||
|
||||
pub struct Window {
|
||||
delegate_state: *mut DelegateState
|
||||
_events_queue: Arc<RefCell<VecDeque<Event>>>,
|
||||
delegate_state: Box<DelegateState>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WindowProxy;
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DelegateState {
|
||||
events_queue: VecDeque<Event>,
|
||||
window: id,
|
||||
controller: id,
|
||||
size: (u32,u32),
|
||||
scale: f32
|
||||
view: id,
|
||||
size: LogicalSize,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
|
||||
impl DelegateState {
|
||||
#[inline]
|
||||
fn new(window: id, controller:id, size: (u32,u32), scale: f32) -> DelegateState {
|
||||
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
|
||||
DelegateState {
|
||||
events_queue: VecDeque::new(),
|
||||
window: window,
|
||||
controller: controller,
|
||||
size: size,
|
||||
scale: scale
|
||||
window,
|
||||
controller,
|
||||
view,
|
||||
size,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DelegateState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.window, release];
|
||||
let _: () = msg_send![self.controller, release];
|
||||
let _: () = msg_send![self.view, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId;
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
#[inline]
|
||||
pub fn get_uiscreen(&self) -> id {
|
||||
let class = class!(UIScreen);
|
||||
unsafe { msg_send![class, mainScreen] }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some("Primary".to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
unimplemented!()
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
|
||||
(bounds.size.width as f64, bounds.size.height as f64).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
// iOS assumes single screen
|
||||
(0, 0)
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
1.0
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
|
||||
scale as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventsLoop {
|
||||
delegate_state: *mut DelegateState
|
||||
events_queue: Arc<RefCell<VecDeque<Event>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -162,30 +210,16 @@ pub struct EventsLoopProxy;
|
||||
impl EventsLoop {
|
||||
pub fn new() -> EventsLoop {
|
||||
unsafe {
|
||||
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
|
||||
let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication];
|
||||
let delegate: id = msg_send![app, delegate];
|
||||
let state: *mut c_void = *(&*delegate).get_ivar("glutinState");
|
||||
let state = state as *mut DelegateState;
|
||||
|
||||
let events_loop = EventsLoop {
|
||||
delegate_state: state
|
||||
};
|
||||
|
||||
return events_loop;
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("`EventsLoop` can only be created on the main thread on iOS");
|
||||
}
|
||||
}
|
||||
|
||||
create_delegate_class();
|
||||
create_view_class();
|
||||
start_app();
|
||||
|
||||
panic!("Couldn't create UIApplication")
|
||||
EventsLoop { events_queue: Default::default() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::new();
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
@@ -198,29 +232,30 @@ impl EventsLoop {
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
{
|
||||
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let state = &mut *self.delegate_state;
|
||||
|
||||
if let Some(event) = state.events_queue.pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// jump hack, so we won't quit on willTerminate event before processing it
|
||||
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
|
||||
if let Some(event) = state.events_queue.pop_front() {
|
||||
assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS");
|
||||
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
|
||||
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
|
||||
callback(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// run runloop
|
||||
let seconds: CFTimeInterval = 0.000002;
|
||||
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
|
||||
}
|
||||
|
||||
if let Some(event) = state.events_queue.pop_front() {
|
||||
callback(event)
|
||||
}
|
||||
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
|
||||
callback(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,112 +291,199 @@ impl EventsLoopProxy {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId;
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
PlatformSpecificWindowBuilderAttributes {
|
||||
root_view_class: class!(UIView),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: AFAIK transparency is enabled by default on iOS,
|
||||
// so to be consistent with other platforms we have to change that.
|
||||
impl Window {
|
||||
pub fn new(ev: &EventsLoop, _: &WindowAttributes, _: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
Ok(Window {
|
||||
delegate_state: ev.delegate_state,
|
||||
})
|
||||
pub fn new(
|
||||
ev: &EventsLoop,
|
||||
_attributes: WindowAttributes,
|
||||
pl_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, CreationError> {
|
||||
unsafe {
|
||||
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
|
||||
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
|
||||
let app_class = class!(UIApplication);
|
||||
let app: id = msg_send![app_class, sharedApplication];
|
||||
let delegate: id = msg_send![app, delegate];
|
||||
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
|
||||
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
|
||||
let events_queue = &*ev.events_queue;
|
||||
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
|
||||
|
||||
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
|
||||
let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
|
||||
|
||||
let uiview_class = class!(UIView);
|
||||
let root_view_class = pl_attributes.root_view_class;
|
||||
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
|
||||
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
|
||||
|
||||
delegate_state.view = msg_send![root_view_class, alloc];
|
||||
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
|
||||
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
|
||||
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
|
||||
|
||||
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
|
||||
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
|
||||
|
||||
return Ok(Window {
|
||||
_events_queue: ev.events_queue.clone(),
|
||||
delegate_state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
create_delegate_class();
|
||||
start_app();
|
||||
|
||||
panic!("Couldn't create `UIApplication`!")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _: &str) {
|
||||
pub fn get_uiwindow(&self) -> id {
|
||||
self.delegate_state.window
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_uiview(&self) -> id {
|
||||
self.delegate_state.view
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _title: &str) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _x: i32, _y: i32) {
|
||||
pub fn set_position(&self, _position: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
unsafe { Some((&*self.delegate_state).size) }
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
Some(self.delegate_state.size)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.get_inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, _x: u32, _y: u32) {
|
||||
pub fn set_inner_size(&self, _size: LogicalSize) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut libc::c_void {
|
||||
unimplemented!();
|
||||
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut libc::c_void {
|
||||
unimplemented!()
|
||||
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) {
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _: MouseCursor) {
|
||||
pub fn set_cursor(&self, _cursor: MouseCursor) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> {
|
||||
Ok(())
|
||||
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
|
||||
Err("Cursor grabbing is not possible on iOS.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
unsafe { (&*self.delegate_state) }.scale
|
||||
pub fn hide_cursor(&self, _hide: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
unimplemented!();
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.delegate_state.scale
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_window_proxy(&self) -> WindowProxy {
|
||||
WindowProxy
|
||||
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting cursor position is not possible on iOS.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
// N/A
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
// N/A
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
// N/A
|
||||
// iOS has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
@@ -370,54 +492,93 @@ impl Window {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
RootMonitorId{inner: MonitorId}
|
||||
RootMonitorId { inner: MonitorId }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorId);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let handle = IOSHandle {
|
||||
ui_window: self.get_uiwindow() as *mut _,
|
||||
ui_view: self.get_uiview() as *mut _,
|
||||
..IOSHandle::empty()
|
||||
};
|
||||
RawWindowHandle::IOS(handle)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_delegate_class() {
|
||||
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
let screen_class = class!(UIScreen);
|
||||
let window_class = class!(UIWindow);
|
||||
let controller_class = class!(UIViewController);
|
||||
unsafe {
|
||||
let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen];
|
||||
let main_screen: id = msg_send![screen_class, mainScreen];
|
||||
let bounds: CGRect = msg_send![main_screen, bounds];
|
||||
let scale: CGFloat = msg_send![main_screen, nativeScale];
|
||||
|
||||
let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc];
|
||||
let window: id = msg_send![window_class, alloc];
|
||||
let window: id = msg_send![window, initWithFrame:bounds.clone()];
|
||||
|
||||
let size = (bounds.size.width as u32, bounds.size.height as u32);
|
||||
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
|
||||
|
||||
let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc];
|
||||
let view_controller: id = msg_send![controller_class, alloc];
|
||||
let view_controller: id = msg_send![view_controller, init];
|
||||
|
||||
let _: () = msg_send![window, setRootViewController:view_controller];
|
||||
let _: () = msg_send![window, makeKeyAndVisible];
|
||||
|
||||
let state = Box::new(DelegateState::new(window, view_controller, size, scale as f32));
|
||||
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
|
||||
let state_ptr: *mut DelegateState = mem::transmute(state);
|
||||
this.set_ivar("glutinState", state_ptr as *mut c_void);
|
||||
|
||||
this.set_ivar("winitState", state_ptr as *mut c_void);
|
||||
|
||||
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
|
||||
// This makes it easier to configure the specific `UIView` type.
|
||||
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern fn post_launch(_: &Object, _: Sel, _: id) {
|
||||
unsafe { longjmp(mem::transmute(&mut jmpbuf),1); }
|
||||
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
|
||||
}
|
||||
|
||||
extern fn did_become_active(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::WindowEvent {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Focused(true),
|
||||
});
|
||||
@@ -426,9 +587,9 @@ fn create_delegate_class() {
|
||||
|
||||
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::WindowEvent {
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
@@ -437,38 +598,38 @@ fn create_delegate_class() {
|
||||
|
||||
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::Suspended(false));
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::Suspended(false));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.events_queue.push_back(Event::Suspended(true));
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
events_queue.borrow_mut().push_back(Event::Suspended(true));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn will_terminate(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
// push event to the front to garantee that we'll process it
|
||||
// immidiatly after jump
|
||||
state.events_queue.push_front(Event::WindowEvent {
|
||||
events_queue.borrow_mut().push_front(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
longjmp(mem::transmute(&mut jmpbuf),1);
|
||||
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("glutinState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
|
||||
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
|
||||
|
||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||
|
||||
@@ -481,12 +642,12 @@ fn create_delegate_class() {
|
||||
let touch_id = touch as u64;
|
||||
let phase: i32 = msg_send![touch, phase];
|
||||
|
||||
state.events_queue.push_back(Event::WindowEvent {
|
||||
events_queue.borrow_mut().push_back(Event::WindowEvent {
|
||||
window_id: RootEventId(WindowId),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: DEVICE_ID,
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64),
|
||||
location: (location.x as f64, location.y as f64).into(),
|
||||
phase: match phase {
|
||||
0 => TouchPhase::Started,
|
||||
1 => TouchPhase::Moved,
|
||||
@@ -501,8 +662,8 @@ fn create_delegate_class() {
|
||||
}
|
||||
}
|
||||
|
||||
let ui_responder = Class::get("UIResponder").unwrap();
|
||||
let mut decl = ClassDecl::new("AppDelegate", ui_responder).unwrap();
|
||||
let ui_responder = class!(UIResponder);
|
||||
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
|
||||
|
||||
unsafe {
|
||||
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
|
||||
@@ -540,19 +701,13 @@ fn create_delegate_class() {
|
||||
decl.add_method(sel!(postLaunch:),
|
||||
post_launch as extern fn(&Object, Sel, id));
|
||||
|
||||
decl.add_ivar::<*mut c_void>("glutinState");
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
decl.add_ivar::<*mut c_void>("eventsQueue");
|
||||
|
||||
decl.register();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_view_class() {
|
||||
let ui_view_controller = Class::get("UIViewController").unwrap();
|
||||
let decl = ClassDecl::new("MainViewController", ui_view_controller).unwrap();
|
||||
|
||||
decl.register();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_app() {
|
||||
unsafe {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#![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_void, c_char, c_int};
|
||||
@@ -6,7 +6,7 @@ use std::os::raw::{c_void, c_char, c_int};
|
||||
pub const RTLD_LAZY: c_int = 0x001;
|
||||
pub const RTLD_NOW: c_int = 0x002;
|
||||
|
||||
#[link="dl"]
|
||||
#[link(name = "dl")]
|
||||
extern {
|
||||
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
|
||||
pub fn dlerror() -> *mut c_char;
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::{env, mem};
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::*;
|
||||
use std::sync::Arc;
|
||||
use std::env;
|
||||
|
||||
use {CreationError, CursorState, EventsLoopClosed, MouseCursor, ControlFlow};
|
||||
use libc;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
use sctk::reexports::client::ConnectError;
|
||||
|
||||
use self::x11::XConnection;
|
||||
use self::x11::XError;
|
||||
use self::x11::ffi::XVisualInfo;
|
||||
|
||||
pub use self::x11::XNotSupported;
|
||||
use {
|
||||
CreationError,
|
||||
EventsLoopClosed,
|
||||
Icon,
|
||||
MouseCursor,
|
||||
ControlFlow,
|
||||
WindowAttributes,
|
||||
};
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
use self::x11::{XConnection, XError};
|
||||
use self::x11::ffi::XVisualInfo;
|
||||
pub use self::x11::XNotSupported;
|
||||
|
||||
mod dlopen;
|
||||
pub mod wayland;
|
||||
@@ -31,32 +41,51 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
pub screen_id: Option<i32>,
|
||||
pub resize_increments: Option<(u32, u32)>,
|
||||
pub base_size: Option<(u32, u32)>,
|
||||
pub class: Option<(String, String)>,
|
||||
pub override_redirect: bool,
|
||||
pub x11_window_type: x11::util::WindowType,
|
||||
pub gtk_theme_variant: Option<String>,
|
||||
pub app_id: Option<String>
|
||||
}
|
||||
|
||||
lazy_static!(
|
||||
pub static ref X11_BACKEND: Result<Arc<XConnection>, XNotSupported> = {
|
||||
XConnection::new(Some(x_error_callback)).map(Arc::new)
|
||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
|
||||
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
|
||||
};
|
||||
);
|
||||
|
||||
pub enum Window {
|
||||
X(x11::Window),
|
||||
Wayland(wayland::Window)
|
||||
Wayland(wayland::Window),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum WindowId {
|
||||
X(x11::WindowId),
|
||||
Wayland(wayland::WindowId)
|
||||
Wayland(wayland::WindowId),
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId::X(x11::WindowId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceId {
|
||||
X(x11::DeviceId),
|
||||
Wayland(wayland::DeviceId)
|
||||
Wayland(wayland::DeviceId),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId::X(x11::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MonitorId {
|
||||
X(x11::MonitorId),
|
||||
Wayland(wayland::MonitorId),
|
||||
@@ -80,7 +109,7 @@ impl MonitorId {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_dimensions(),
|
||||
&MonitorId::Wayland(ref m) => m.get_dimensions(),
|
||||
@@ -88,7 +117,7 @@ impl MonitorId {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_position(),
|
||||
&MonitorId::Wayland(ref m) => m.get_position(),
|
||||
@@ -96,28 +125,27 @@ impl MonitorId {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&MonitorId::X(ref m) => m.get_hidpi_factor(),
|
||||
&MonitorId::Wayland(ref m) => m.get_hidpi_factor(),
|
||||
&MonitorId::Wayland(ref m) => m.get_hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new(events_loop: &EventsLoop,
|
||||
window: &::WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Self, CreationError>
|
||||
{
|
||||
pub fn new(
|
||||
events_loop: &EventsLoop,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, CreationError> {
|
||||
match *events_loop {
|
||||
EventsLoop::Wayland(ref evlp) => {
|
||||
wayland::Window::new(evlp, window).map(Window::Wayland)
|
||||
EventsLoop::Wayland(ref events_loop) => {
|
||||
wayland::Window::new(events_loop, attribs, pl_attribs).map(Window::Wayland)
|
||||
},
|
||||
|
||||
EventsLoop::X(ref el) => {
|
||||
x11::Window::new(el, window, pl_attribs).map(Window::X)
|
||||
EventsLoop::X(ref events_loop) => {
|
||||
x11::Window::new(events_loop, attribs, pl_attribs).map(Window::X)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -126,7 +154,7 @@ impl Window {
|
||||
pub fn id(&self) -> WindowId {
|
||||
match self {
|
||||
&Window::X(ref w) => WindowId::X(w.id()),
|
||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id())
|
||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +162,7 @@ impl Window {
|
||||
pub fn set_title(&self, title: &str) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_title(title),
|
||||
&Window::Wayland(ref w) => w.set_title(title)
|
||||
&Window::Wayland(ref w) => w.set_title(title),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +170,7 @@ impl Window {
|
||||
pub fn show(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.show(),
|
||||
&Window::Wayland(ref w) => w.show()
|
||||
&Window::Wayland(ref w) => w.show(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,20 +178,20 @@ impl Window {
|
||||
pub fn hide(&self) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.hide(),
|
||||
&Window::Wayland(ref w) => w.hide()
|
||||
&Window::Wayland(ref w) => w.hide(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_position(),
|
||||
&Window::Wayland(ref w) => w.get_position()
|
||||
&Window::Wayland(ref w) => w.get_position(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
match self {
|
||||
&Window::X(ref m) => m.get_inner_position(),
|
||||
&Window::Wayland(ref m) => m.get_inner_position(),
|
||||
@@ -171,50 +199,58 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, x: i32, y: i32) {
|
||||
pub fn set_position(&self, position: LogicalPosition) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_position(x, y),
|
||||
&Window::Wayland(ref w) => w.set_position(x, y)
|
||||
&Window::X(ref w) => w.set_position(position),
|
||||
&Window::Wayland(ref w) => w.set_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_inner_size(),
|
||||
&Window::Wayland(ref w) => w.get_inner_size()
|
||||
&Window::Wayland(ref w) => w.get_inner_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_outer_size(),
|
||||
&Window::Wayland(ref w) => w.get_outer_size()
|
||||
&Window::Wayland(ref w) => w.get_outer_size(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, x: u32, y: u32) {
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_inner_size(x, y),
|
||||
&Window::Wayland(ref w) => w.set_inner_size(x, y)
|
||||
&Window::X(ref w) => w.set_inner_size(size),
|
||||
&Window::Wayland(ref w) => w.set_inner_size(size),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_min_dimensions(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions)
|
||||
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_max_dimensions(dimensions),
|
||||
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions)
|
||||
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_resizable(resizable),
|
||||
&Window::Wayland(ref w) => w.set_resizable(resizable),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,44 +263,34 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_state(state),
|
||||
&Window::Wayland(ref w) => w.set_cursor_state(state)
|
||||
&Window::X(ref window) => window.grab_cursor(grab),
|
||||
&Window::Wayland(ref window) => window.grab_cursor(grab),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
match self {
|
||||
&Window::X(ref window) => window.hide_cursor(hide),
|
||||
&Window::Wayland(ref window) => window.hide_cursor(hide),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
match self {
|
||||
&Window::X(ref w) => w.hidpi_factor(),
|
||||
&Window::Wayland(ref w) => w.hidpi_factor()
|
||||
&Window::X(ref w) => w.get_hidpi_factor(),
|
||||
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_cursor_position(x, y),
|
||||
&Window::Wayland(ref w) => w.set_cursor_position(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_display(&self) -> *mut libc::c_void {
|
||||
use wayland_client::Proxy;
|
||||
match self {
|
||||
&Window::X(ref w) => w.platform_display(),
|
||||
&Window::Wayland(ref w) => w.get_display().ptr() as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn platform_window(&self) -> *mut libc::c_void {
|
||||
use wayland_client::Proxy;
|
||||
match self {
|
||||
&Window::X(ref w) => w.platform_window(),
|
||||
&Window::Wayland(ref w) => w.get_surface().ptr() as *mut _
|
||||
&Window::X(ref w) => w.set_cursor_position(position),
|
||||
&Window::Wayland(ref w) => w.set_cursor_position(position),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +302,15 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.get_fullscreen(),
|
||||
&Window::Wayland(ref w) => w.get_fullscreen()
|
||||
.map(|monitor_id| RootMonitorId { inner: MonitorId::Wayland(monitor_id) })
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
match self {
|
||||
@@ -292,24 +327,82 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_always_on_top(always_on_top),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_window_icon(window_icon),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, position: LogicalPosition) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_ime_spot(position),
|
||||
&Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
match self {
|
||||
&Window::X(ref w) => RootMonitorId{inner: MonitorId::X(w.get_current_monitor())},
|
||||
&Window::Wayland(ref w) => RootMonitorId{inner: MonitorId::Wayland(w.get_current_monitor())},
|
||||
&Window::X(ref window) => RootMonitorId { inner: MonitorId::X(window.get_current_monitor()) },
|
||||
&Window::Wayland(ref window) => RootMonitorId { inner: MonitorId::Wayland(window.get_current_monitor()) },
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
match self {
|
||||
&Window::X(ref window) => window.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::X)
|
||||
.collect(),
|
||||
&Window::Wayland(ref window) => window.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::Wayland)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
match self {
|
||||
&Window::X(ref window) => MonitorId::X(window.get_primary_monitor()),
|
||||
&Window::Wayland(ref window) => MonitorId::Wayland(window.get_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(dpy: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent)
|
||||
-> libc::c_int
|
||||
{
|
||||
use std::ffi::CStr;
|
||||
|
||||
if let Ok(ref x) = *X11_BACKEND {
|
||||
let mut buff: Vec<u8> = Vec::with_capacity(1024);
|
||||
(x.xlib.XGetErrorText)(dpy, (*event).error_code as i32, buff.as_mut_ptr() as *mut libc::c_char, buff.capacity() as i32);
|
||||
let description = CStr::from_ptr(buff.as_mut_ptr() as *const libc::c_char).to_string_lossy();
|
||||
unsafe extern "C" fn x_error_callback(
|
||||
display: *mut x11::ffi::Display,
|
||||
event: *mut x11::ffi::XErrorEvent,
|
||||
) -> c_int {
|
||||
let xconn_lock = X11_BACKEND.lock();
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
let mut buf: [c_char; 1024] = mem::uninitialized();
|
||||
(xconn.xlib.XGetErrorText)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as c_int,
|
||||
);
|
||||
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
|
||||
|
||||
let error = XError {
|
||||
description: description.into_owned(),
|
||||
@@ -318,9 +411,11 @@ unsafe extern "C" fn x_error_callback(dpy: *mut x11::ffi::Display, event: *mut x
|
||||
minor_code: (*event).minor_code,
|
||||
};
|
||||
|
||||
*x.latest_error.lock().unwrap() = Some(error);
|
||||
}
|
||||
error!("X11 error: {:#?}", error);
|
||||
|
||||
*xconn.latest_error.lock() = Some(error);
|
||||
}
|
||||
// Fun fact: this return value is completely ignored.
|
||||
0
|
||||
}
|
||||
|
||||
@@ -340,54 +435,67 @@ impl EventsLoop {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
return EventsLoop::new_x11().unwrap(); // TODO: propagate
|
||||
// TODO: propagate
|
||||
return EventsLoop::new_x11().expect("Failed to initialize X11 backend");
|
||||
},
|
||||
"wayland" => {
|
||||
match EventsLoop::new_wayland() {
|
||||
Ok(e) => return e,
|
||||
Err(_) => panic!() // TODO: propagate
|
||||
}
|
||||
return EventsLoop::new_wayland()
|
||||
.expect("Failed to initialize Wayland backend");
|
||||
},
|
||||
_ => panic!("Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR),
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(el) = EventsLoop::new_wayland() {
|
||||
return el;
|
||||
}
|
||||
let wayland_err = match EventsLoop::new_wayland() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
if let Ok(el) = EventsLoop::new_x11() {
|
||||
return el;
|
||||
}
|
||||
let x11_err = match EventsLoop::new_x11() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
panic!("No backend is available")
|
||||
let err_string = format!(
|
||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
||||
wayland_err,
|
||||
x11_err,
|
||||
);
|
||||
panic!(err_string);
|
||||
}
|
||||
|
||||
pub fn new_wayland() -> Result<EventsLoop, ()> {
|
||||
pub fn new_wayland() -> Result<EventsLoop, ConnectError> {
|
||||
wayland::EventsLoop::new()
|
||||
.map(EventsLoop::Wayland)
|
||||
.ok_or(())
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventsLoop, XNotSupported> {
|
||||
match *X11_BACKEND {
|
||||
Ok(ref x) => Ok(EventsLoop::X(x11::EventsLoop::new(x.clone()))),
|
||||
Err(ref err) => Err(err.clone()),
|
||||
}
|
||||
X11_BACKEND
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(Arc::clone)
|
||||
.map(x11::EventsLoop::new)
|
||||
.map(EventsLoop::X)
|
||||
.map_err(|err| err.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => evlp.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::Wayland)
|
||||
.collect(),
|
||||
EventsLoop::X(ref evlp) => x11::get_available_monitors(evlp.x_connection())
|
||||
.into_iter()
|
||||
.map(MonitorId::X)
|
||||
.collect(),
|
||||
EventsLoop::Wayland(ref evlp) => evlp
|
||||
.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::Wayland)
|
||||
.collect(),
|
||||
EventsLoop::X(ref evlp) => evlp
|
||||
.x_connection()
|
||||
.get_available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorId::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +503,7 @@ impl EventsLoop {
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
match *self {
|
||||
EventsLoop::Wayland(ref evlp) => MonitorId::Wayland(evlp.get_primary_monitor()),
|
||||
EventsLoop::X(ref evlp) => MonitorId::X(x11::get_primary_monitor(evlp.x_connection())),
|
||||
EventsLoop::X(ref evlp) => MonitorId::X(evlp.x_connection().get_primary_monitor()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use {EventsLoopClosed, ControlFlow};
|
||||
use {ControlFlow, EventsLoopClosed, PhysicalPosition, PhysicalSize};
|
||||
|
||||
use super::WindowId;
|
||||
use super::window::WindowStore;
|
||||
use super::keyboard::init_keyboard;
|
||||
use super::WindowId;
|
||||
|
||||
use wayland_client::{EnvHandler, EnvNotify, default_connect, EventQueue, EventQueueHandle, Proxy, StateToken};
|
||||
use wayland_client::protocol::{wl_compositor, wl_seat, wl_shell, wl_shm, wl_subcompositor,
|
||||
wl_display, wl_registry, wl_output, wl_surface,
|
||||
wl_pointer, wl_keyboard, wl_touch};
|
||||
use sctk::output::OutputMgr;
|
||||
use sctk::reexports::client::protocol::{
|
||||
wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch,
|
||||
};
|
||||
use sctk::reexports::client::{ConnectError, Display, EventQueue, GlobalEvent, Proxy};
|
||||
use sctk::Environment;
|
||||
|
||||
use super::wayland_window::{Frame, Shell, create_frame, FrameImplementation};
|
||||
use super::wayland_protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6;
|
||||
use sctk::reexports::client::protocol::wl_display::RequestsTrait as DisplayRequests;
|
||||
use sctk::reexports::client::protocol::wl_surface::RequestsTrait;
|
||||
|
||||
use ModifiersState;
|
||||
|
||||
pub struct EventsLoopSink {
|
||||
buffer: VecDeque<::Event>
|
||||
buffer: VecDeque<::Event>,
|
||||
}
|
||||
|
||||
unsafe impl Send for EventsLoopSink { }
|
||||
|
||||
impl EventsLoopSink {
|
||||
pub fn new() -> EventsLoopSink{
|
||||
pub fn new() -> EventsLoopSink {
|
||||
EventsLoopSink {
|
||||
buffer: VecDeque::new()
|
||||
buffer: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&mut self, evt: ::WindowEvent, wid: WindowId) {
|
||||
let evt = ::Event::WindowEvent {
|
||||
event: evt,
|
||||
window_id: ::WindowId(::platform::WindowId::Wayland(wid))
|
||||
window_id: ::WindowId(::platform::WindowId::Wayland(wid)),
|
||||
};
|
||||
self.buffer.push_back(evt);
|
||||
}
|
||||
@@ -42,7 +44,10 @@ impl EventsLoopSink {
|
||||
self.buffer.push_back(evt);
|
||||
}
|
||||
|
||||
fn empty_with<F>(&mut self, callback: &mut F) where F: FnMut(::Event) {
|
||||
fn empty_with<F>(&mut self, callback: &mut F)
|
||||
where
|
||||
F: FnMut(::Event),
|
||||
{
|
||||
for evt in self.buffer.drain(..) {
|
||||
callback(evt)
|
||||
}
|
||||
@@ -57,15 +62,15 @@ pub struct EventsLoop {
|
||||
// Whether or not there is a pending `Awakened` event to be emitted.
|
||||
pending_wakeup: Arc<AtomicBool>,
|
||||
// The window store
|
||||
pub store: StateToken<WindowStore>,
|
||||
pub store: Arc<Mutex<WindowStore>>,
|
||||
// the env
|
||||
env_token: StateToken<EnvHandler<InnerEnv>>,
|
||||
// the ctxt
|
||||
pub ctxt_token: StateToken<StateContext>,
|
||||
pub env: Environment,
|
||||
// a cleanup switch to prune dead windows
|
||||
pub cleanup_needed: Arc<Mutex<bool>>,
|
||||
// The wayland display
|
||||
pub display: Arc<wl_display::WlDisplay>,
|
||||
pub display: Arc<Display>,
|
||||
// The list of seats
|
||||
pub seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
|
||||
}
|
||||
|
||||
// A handle that can be sent across threads and used to wake up the `EventsLoop`.
|
||||
@@ -73,7 +78,7 @@ pub struct EventsLoop {
|
||||
// We should only try and wake up the `EventsLoop` if it still exists, so we hold Weak ptrs.
|
||||
#[derive(Clone)]
|
||||
pub struct EventsLoopProxy {
|
||||
display: Weak<wl_display::WlDisplay>,
|
||||
display: Weak<Display>,
|
||||
pending_wakeup: Weak<AtomicBool>,
|
||||
}
|
||||
|
||||
@@ -89,69 +94,64 @@ impl EventsLoopProxy {
|
||||
// Update the `EventsLoop`'s `pending_wakeup` flag.
|
||||
wakeup.store(true, Ordering::Relaxed);
|
||||
// Cause the `EventsLoop` to break from `dispatch` if it is currently blocked.
|
||||
display.sync();
|
||||
let _ = display.sync(|callback| callback.implement(|_, _| {}, ()));
|
||||
display.flush().map_err(|_| EventsLoopClosed)?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
_ => Err(EventsLoopClosed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn new() -> Option<EventsLoop> {
|
||||
let (display, mut event_queue) = match default_connect() {
|
||||
Ok(ret) => ret,
|
||||
Err(_) => return None
|
||||
};
|
||||
|
||||
let registry = display.get_registry();
|
||||
let ctxt_token = event_queue.state().insert(
|
||||
StateContext::new(registry.clone().unwrap())
|
||||
);
|
||||
let env_token = EnvHandler::init_with_notify(
|
||||
&mut event_queue,
|
||||
®istry,
|
||||
env_notify(),
|
||||
ctxt_token.clone()
|
||||
);
|
||||
|
||||
// two round trips to fully initialize
|
||||
event_queue.sync_roundtrip().expect("Wayland connection unexpectedly lost");
|
||||
event_queue.sync_roundtrip().expect("Wayland connection unexpectedly lost");
|
||||
|
||||
event_queue.state().with_value(&ctxt_token, |proxy, ctxt| {
|
||||
ctxt.ensure_shell(proxy.get_mut(&env_token))
|
||||
});
|
||||
pub fn new() -> Result<EventsLoop, ConnectError> {
|
||||
let (display, mut event_queue) = Display::connect_to_env()?;
|
||||
|
||||
let display = Arc::new(display);
|
||||
let pending_wakeup = Arc::new(AtomicBool::new(false));
|
||||
let sink = Arc::new(Mutex::new(EventsLoopSink::new()));
|
||||
let store = Arc::new(Mutex::new(WindowStore::new()));
|
||||
let seats = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let store = event_queue.state().insert(WindowStore::new());
|
||||
|
||||
let seat_idata = SeatIData {
|
||||
let mut seat_manager = SeatManager {
|
||||
sink: sink.clone(),
|
||||
keyboard: None,
|
||||
pointer: None,
|
||||
touch: None,
|
||||
windows_token: store.clone()
|
||||
store: store.clone(),
|
||||
seats: seats.clone(),
|
||||
events_loop_proxy: EventsLoopProxy {
|
||||
display: Arc::downgrade(&display),
|
||||
pending_wakeup: Arc::downgrade(&pending_wakeup),
|
||||
},
|
||||
};
|
||||
|
||||
let mut me = EventsLoop {
|
||||
display: Arc::new(display),
|
||||
let env = Environment::from_display_with_cb(
|
||||
&display,
|
||||
&mut event_queue,
|
||||
move |event, registry| {
|
||||
match event {
|
||||
GlobalEvent::New { id, ref interface, version } => {
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.add_seat(id, version, registry)
|
||||
}
|
||||
},
|
||||
GlobalEvent::Removed { id, ref interface } => {
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.remove_seat(id)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
Ok(EventsLoop {
|
||||
display,
|
||||
evq: RefCell::new(event_queue),
|
||||
sink: sink,
|
||||
pending_wakeup: Arc::new(AtomicBool::new(false)),
|
||||
store: store,
|
||||
ctxt_token: ctxt_token,
|
||||
env_token: env_token,
|
||||
cleanup_needed: Arc::new(Mutex::new(false))
|
||||
};
|
||||
|
||||
me.init_seat(|evqh, seat| {
|
||||
evqh.register(seat, seat_implementation(), seat_idata);
|
||||
});
|
||||
|
||||
Some(me)
|
||||
sink,
|
||||
pending_wakeup,
|
||||
store,
|
||||
env,
|
||||
cleanup_needed: Arc::new(Mutex::new(false)),
|
||||
seats,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventsLoopProxy {
|
||||
@@ -162,7 +162,8 @@ impl EventsLoop {
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event)
|
||||
where
|
||||
F: FnMut(::Event),
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
@@ -175,7 +176,10 @@ impl EventsLoop {
|
||||
h.read_events().expect("Wayland connection lost.");
|
||||
}
|
||||
// dispatch wayland events
|
||||
self.evq.get_mut().dispatch_pending().expect("Wayland connection lost.");
|
||||
self.evq
|
||||
.get_mut()
|
||||
.dispatch_pending()
|
||||
.expect("Wayland connection lost.");
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// dispatch buffered events to client
|
||||
@@ -183,15 +187,18 @@ impl EventsLoop {
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where F: FnMut(::Event) -> ControlFlow,
|
||||
where
|
||||
F: FnMut(::Event) -> ControlFlow,
|
||||
{
|
||||
// send pending events to the server
|
||||
self.display.flush().expect("Wayland connection lost.");
|
||||
|
||||
// Check for control flow by wrapping the callback.
|
||||
let control_flow = ::std::cell::Cell::new(ControlFlow::Continue);
|
||||
let mut callback = |event| if let ControlFlow::Break = callback(event) {
|
||||
control_flow.set(ControlFlow::Break);
|
||||
let mut callback = |event| {
|
||||
if let ControlFlow::Break = callback(event) {
|
||||
control_flow.set(ControlFlow::Break);
|
||||
}
|
||||
};
|
||||
|
||||
// dispatch any pre-buffered events
|
||||
@@ -200,7 +207,10 @@ impl EventsLoop {
|
||||
|
||||
loop {
|
||||
// dispatch events blocking if needed
|
||||
self.evq.get_mut().dispatch().expect("Wayland connection lost.");
|
||||
self.evq
|
||||
.get_mut()
|
||||
.dispatch()
|
||||
.expect("Wayland connection lost.");
|
||||
self.post_dispatch_triggers();
|
||||
|
||||
// empty buffer of events
|
||||
@@ -213,25 +223,15 @@ impl EventsLoop {
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
let state = guard.state();
|
||||
let state_ctxt = state.get(&self.ctxt_token);
|
||||
if let Some(info) = state_ctxt.monitors.iter().next() {
|
||||
MonitorId {
|
||||
info: info.clone()
|
||||
}
|
||||
} else {
|
||||
panic!("No monitor is available.")
|
||||
}
|
||||
get_primary_monitor(&self.env.outputs)
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
let state = guard.state();
|
||||
let state_ctxt = state.get(&self.ctxt_token);
|
||||
state_ctxt.monitors.iter()
|
||||
.map(|m| MonitorId { info: m.clone() })
|
||||
.collect()
|
||||
get_available_monitors(&self.env.outputs)
|
||||
}
|
||||
|
||||
pub fn get_display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,93 +239,9 @@ impl EventsLoop {
|
||||
* Private EventsLoop Internals
|
||||
*/
|
||||
|
||||
wayland_env!(InnerEnv,
|
||||
compositor: wl_compositor::WlCompositor,
|
||||
shm: wl_shm::WlShm,
|
||||
subcompositor: wl_subcompositor::WlSubcompositor
|
||||
);
|
||||
|
||||
pub struct StateContext {
|
||||
registry: wl_registry::WlRegistry,
|
||||
seat: Option<wl_seat::WlSeat>,
|
||||
shell: Option<Shell>,
|
||||
monitors: Vec<Arc<Mutex<OutputInfo>>>
|
||||
}
|
||||
|
||||
impl StateContext {
|
||||
fn new(registry: wl_registry::WlRegistry) -> StateContext {
|
||||
StateContext {
|
||||
registry: registry,
|
||||
seat: None,
|
||||
shell: None,
|
||||
monitors: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a shell is available
|
||||
///
|
||||
/// If a shell is already bound, do nothing. Otherwise,
|
||||
/// try to bind wl_shell as a fallback. If this fails,
|
||||
/// panic, as this is a bug from the compositor.
|
||||
fn ensure_shell(&mut self, env: &mut EnvHandler<InnerEnv>) {
|
||||
if self.shell.is_some() {
|
||||
return;
|
||||
}
|
||||
// xdg_shell is not available, so initialize wl_shell
|
||||
for &(name, ref interface, _) in env.globals() {
|
||||
if interface == "wl_shell" {
|
||||
self.shell = Some(Shell::Wl(self.registry.bind::<wl_shell::WlShell>(1, name)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// This is a compositor bug, it _must_ at least support wl_shell
|
||||
panic!("Compositor didi not advertize xdg_shell not wl_shell.");
|
||||
}
|
||||
|
||||
pub fn monitor_id_for(&self, output: &wl_output::WlOutput) -> MonitorId {
|
||||
for info in &self.monitors {
|
||||
let guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
return MonitorId {
|
||||
info: info.clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
panic!("Received an inexistent wl_output?!");
|
||||
}
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn init_seat<F>(&mut self, f: F)
|
||||
where F: FnOnce(&mut EventQueueHandle, &wl_seat::WlSeat)
|
||||
{
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
if guard.state().get(&self.ctxt_token).seat.is_some() {
|
||||
// seat has already been init
|
||||
return;
|
||||
}
|
||||
|
||||
// clone the token to make borrow checker happy
|
||||
let ctxt_token = self.ctxt_token.clone();
|
||||
let seat = guard.state().with_value(&self.env_token, |proxy, env| {
|
||||
let ctxt = proxy.get(&ctxt_token);
|
||||
for &(name, ref interface, _) in env.globals() {
|
||||
if interface == wl_seat::WlSeat::interface_name() {
|
||||
return Some(ctxt.registry.bind::<wl_seat::WlSeat>(5, name));
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
if let Some(seat) = seat {
|
||||
f(&mut *guard, &seat);
|
||||
guard.state().get_mut(&self.ctxt_token).seat = Some(seat)
|
||||
}
|
||||
}
|
||||
|
||||
fn post_dispatch_triggers(&mut self) {
|
||||
let mut sink = self.sink.lock().unwrap();
|
||||
let evq = self.evq.get_mut();
|
||||
// process a possible pending wakeup call
|
||||
if self.pending_wakeup.load(Ordering::Relaxed) {
|
||||
sink.send_raw_event(::Event::Awakened);
|
||||
@@ -335,7 +251,7 @@ impl EventsLoop {
|
||||
{
|
||||
let mut cleanup_needed = self.cleanup_needed.lock().unwrap();
|
||||
if *cleanup_needed {
|
||||
let pruned = evq.state().get_mut(&self.store).cleanup();
|
||||
let pruned = self.store.lock().unwrap().cleanup();
|
||||
*cleanup_needed = false;
|
||||
for wid in pruned {
|
||||
sink.send_event(::WindowEvent::Destroyed, wid);
|
||||
@@ -343,157 +259,175 @@ impl EventsLoop {
|
||||
}
|
||||
}
|
||||
// process pending resize/refresh
|
||||
evq.state().get_mut(&self.store).for_each(
|
||||
|newsize, refresh, frame_refresh, closed, wid, frame| {
|
||||
self.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 as i32, h as i32);
|
||||
frame.resize(w, h);
|
||||
frame.refresh();
|
||||
sink.send_event(::WindowEvent::Resized(w as u32, h as u32), wid);
|
||||
let logical_size = ::LogicalSize::new(w as f64, h as f64);
|
||||
sink.send_event(::WindowEvent::Resized(logical_size), wid);
|
||||
*size = (w, h);
|
||||
} else if frame_refresh {
|
||||
frame.refresh();
|
||||
if !refresh {
|
||||
frame.surface().commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(dpi) = new_dpi {
|
||||
sink.send_event(::WindowEvent::HiDpiFactorChanged(dpi as f64), wid);
|
||||
}
|
||||
if refresh {
|
||||
sink.send_event(::WindowEvent::Refresh, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_event(::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new window with given dimensions
|
||||
///
|
||||
/// Grabs a lock on the event queue in the process
|
||||
pub fn create_window<ID: 'static, F>(&self, width: u32, height: u32, implem: FrameImplementation<ID>, idata: F)
|
||||
-> (wl_surface::WlSurface, Frame)
|
||||
where F: FnOnce(&wl_surface::WlSurface) -> ID
|
||||
{
|
||||
let (surface, frame) = {
|
||||
let mut guard = self.evq.borrow_mut();
|
||||
let env = guard.state().get(&self.env_token).clone_inner().unwrap();
|
||||
let shell = match guard.state().get(&self.ctxt_token).shell {
|
||||
Some(Shell::Wl(ref wl_shell)) => Shell::Wl(wl_shell.clone().unwrap()),
|
||||
Some(Shell::Xdg(ref xdg_shell)) => Shell::Xdg(xdg_shell.clone().unwrap()),
|
||||
None => unreachable!()
|
||||
};
|
||||
let seat = guard.state().get(&self.ctxt_token).seat.as_ref().and_then(|s| s.clone());
|
||||
let surface = env.compositor.create_surface();
|
||||
let frame = create_frame(
|
||||
&mut guard,
|
||||
implem,
|
||||
idata(&surface),
|
||||
&surface, width as i32, height as i32,
|
||||
&env.compositor,
|
||||
&env.subcompositor,
|
||||
&env.shm,
|
||||
&shell,
|
||||
seat
|
||||
).expect("Failed to create a tmpfile buffer.");
|
||||
(surface, frame)
|
||||
};
|
||||
|
||||
(surface, frame)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wayland protocol implementations
|
||||
*/
|
||||
|
||||
fn env_notify() -> EnvNotify<StateToken<StateContext>> {
|
||||
EnvNotify {
|
||||
new_global: |evqh, token, registry, id, interface, version| {
|
||||
use std::cmp::min;
|
||||
if interface == wl_output::WlOutput::interface_name() {
|
||||
// a new output is available
|
||||
let output = registry.bind::<wl_output::WlOutput>(min(version, 3), id);
|
||||
evqh.register(&output, output_impl(), token.clone());
|
||||
evqh.state().get_mut(&token).monitors.push(
|
||||
Arc::new(Mutex::new(OutputInfo::new(output, id)))
|
||||
);
|
||||
} else if interface == zxdg_shell_v6::ZxdgShellV6::interface_name() {
|
||||
// We have an xdg_shell, bind it
|
||||
let xdg_shell = registry.bind::<zxdg_shell_v6::ZxdgShellV6>(1, id);
|
||||
evqh.register(&xdg_shell, xdg_ping_implementation(), ());
|
||||
evqh.state().get_mut(&token).shell = Some(Shell::Xdg(xdg_shell));
|
||||
}
|
||||
},
|
||||
del_global: |evqh, token, _, id| {
|
||||
// maybe this was a monitor, cleanup
|
||||
evqh.state().get_mut(&token).monitors.retain(
|
||||
|m| m.lock().unwrap().id != id
|
||||
);
|
||||
},
|
||||
ready: |_, _, _| {}
|
||||
}
|
||||
struct SeatManager {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
}
|
||||
|
||||
fn xdg_ping_implementation() -> zxdg_shell_v6::Implementation<()> {
|
||||
zxdg_shell_v6::Implementation {
|
||||
ping: |_, _, shell, serial| {
|
||||
shell.pong(serial);
|
||||
impl SeatManager {
|
||||
fn add_seat(&mut self, id: u32, version: u32, registry: Proxy<wl_registry::WlRegistry>) {
|
||||
use self::wl_registry::RequestsTrait as RegistryRequests;
|
||||
use std::cmp::min;
|
||||
|
||||
let mut seat_data = SeatData {
|
||||
sink: self.sink.clone(),
|
||||
store: self.store.clone(),
|
||||
pointer: None,
|
||||
keyboard: None,
|
||||
touch: None,
|
||||
events_loop_proxy: self.events_loop_proxy.clone(),
|
||||
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
|
||||
};
|
||||
let seat = registry
|
||||
.bind(min(version, 5), id, move |seat| {
|
||||
seat.implement(move |event, seat| {
|
||||
seat_data.receive(event, seat)
|
||||
}, ())
|
||||
})
|
||||
.unwrap();
|
||||
self.store.lock().unwrap().new_seat(&seat);
|
||||
self.seats.lock().unwrap().push((id, seat));
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, id: u32) {
|
||||
use self::wl_seat::RequestsTrait as SeatRequests;
|
||||
let mut seats = self.seats.lock().unwrap();
|
||||
if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
|
||||
let (_, seat) = seats.swap_remove(idx);
|
||||
if seat.version() >= 5 {
|
||||
seat.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SeatIData {
|
||||
struct SeatData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
pointer: Option<wl_pointer::WlPointer>,
|
||||
keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
touch: Option<wl_touch::WlTouch>,
|
||||
windows_token: StateToken<WindowStore>
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
pointer: Option<Proxy<wl_pointer::WlPointer>>,
|
||||
keyboard: Option<Proxy<wl_keyboard::WlKeyboard>>,
|
||||
touch: Option<Proxy<wl_touch::WlTouch>>,
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
}
|
||||
|
||||
fn seat_implementation() -> wl_seat::Implementation<SeatIData> {
|
||||
wl_seat::Implementation {
|
||||
name: |_, _, _, _| {},
|
||||
capabilities: |evqh, idata, seat, capabilities| {
|
||||
// create pointer if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Pointer) && idata.pointer.is_none() {
|
||||
let pointer = seat.get_pointer().expect("Seat is not dead");
|
||||
let p_idata = super::pointer::PointerIData::new(
|
||||
&idata.sink,
|
||||
idata.windows_token.clone()
|
||||
);
|
||||
evqh.register(&pointer, super::pointer::pointer_implementation(), p_idata);
|
||||
idata.pointer = Some(pointer);
|
||||
}
|
||||
// destroy pointer if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
if let Some(pointer) = idata.pointer.take() {
|
||||
pointer.release();
|
||||
impl SeatData {
|
||||
fn receive(&mut self, evt: wl_seat::Event, seat: Proxy<wl_seat::WlSeat>) {
|
||||
match evt {
|
||||
wl_seat::Event::Name { .. } => (),
|
||||
wl_seat::Event::Capabilities { capabilities } => {
|
||||
// create pointer if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Pointer) && self.pointer.is_none() {
|
||||
self.pointer = Some(super::pointer::implement_pointer(
|
||||
&seat,
|
||||
self.sink.clone(),
|
||||
self.store.clone(),
|
||||
self.modifiers_tracker.clone(),
|
||||
))
|
||||
}
|
||||
// destroy pointer if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.version() >= 3 {
|
||||
use self::wl_pointer::RequestsTrait;
|
||||
pointer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
// create keyboard if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() {
|
||||
self.keyboard = Some(super::keyboard::init_keyboard(
|
||||
&seat,
|
||||
self.sink.clone(),
|
||||
self.events_loop_proxy.clone(),
|
||||
self.modifiers_tracker.clone(),
|
||||
))
|
||||
}
|
||||
// destroy keyboard if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
if let Some(kbd) = self.keyboard.take() {
|
||||
if kbd.version() >= 3 {
|
||||
use self::wl_keyboard::RequestsTrait;
|
||||
kbd.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
// create touch if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Touch) && self.touch.is_none() {
|
||||
self.touch = Some(super::touch::implement_touch(
|
||||
&seat,
|
||||
self.sink.clone(),
|
||||
self.store.clone(),
|
||||
))
|
||||
}
|
||||
// destroy touch if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Touch) {
|
||||
if let Some(touch) = self.touch.take() {
|
||||
if touch.version() >= 3 {
|
||||
use self::wl_touch::RequestsTrait;
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// create keyboard if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) && idata.keyboard.is_none() {
|
||||
let kbd = seat.get_keyboard().expect("Seat is not dead");
|
||||
init_keyboard(evqh, &kbd, &idata.sink);
|
||||
idata.keyboard = Some(kbd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SeatData {
|
||||
fn drop(&mut self) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.version() >= 3 {
|
||||
use self::wl_pointer::RequestsTrait;
|
||||
pointer.release();
|
||||
}
|
||||
// destroy keyboard if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
if let Some(kbd) = idata.keyboard.take() {
|
||||
kbd.release();
|
||||
}
|
||||
}
|
||||
if let Some(kbd) = self.keyboard.take() {
|
||||
if kbd.version() >= 3 {
|
||||
use self::wl_keyboard::RequestsTrait;
|
||||
kbd.release();
|
||||
}
|
||||
// create touch if applicable
|
||||
if capabilities.contains(wl_seat::Capability::Touch) && idata.touch.is_none() {
|
||||
let touch = seat.get_touch().expect("Seat is not dead");
|
||||
let t_idata = super::touch::TouchIData::new(
|
||||
&idata.sink,
|
||||
idata.windows_token.clone()
|
||||
);
|
||||
evqh.register(&touch, super::touch::touch_implementation(), t_idata);
|
||||
idata.touch = Some(touch);
|
||||
}
|
||||
// destroy touch if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Touch) {
|
||||
if let Some(touch) = idata.touch.take() {
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
if let Some(touch) = self.touch.take() {
|
||||
if touch.version() >= 3 {
|
||||
use self::wl_touch::RequestsTrait;
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,92 +437,102 @@ fn seat_implementation() -> wl_seat::Implementation<SeatIData> {
|
||||
* Monitor stuff
|
||||
*/
|
||||
|
||||
fn output_impl() -> wl_output::Implementation<StateToken<StateContext>> {
|
||||
wl_output::Implementation {
|
||||
geometry: |evqh, token, output, x, y, _, _, _, make, model, _| {
|
||||
let ctxt = evqh.state().get_mut(token);
|
||||
for info in &ctxt.monitors {
|
||||
let mut guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
guard.pix_pos = (x, y);
|
||||
guard.name = format!("{} - {}", make, model);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
mode: |evqh, token, output, flags, w, h, _refresh| {
|
||||
if flags.contains(wl_output::Mode::Current) {
|
||||
let ctxt = evqh.state().get_mut(token);
|
||||
for info in &ctxt.monitors {
|
||||
let mut guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
guard.pix_size = (w as u32, h as u32);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
done: |_, _, _| {},
|
||||
scale: |evqh, token, output, scale| {
|
||||
let ctxt = evqh.state().get_mut(token);
|
||||
for info in &ctxt.monitors {
|
||||
let mut guard = info.lock().unwrap();
|
||||
if guard.output.equals(output) {
|
||||
guard.scale = scale as f32;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputInfo {
|
||||
pub output: wl_output::WlOutput,
|
||||
pub id: u32,
|
||||
pub scale: f32,
|
||||
pub pix_size: (u32, u32),
|
||||
pub pix_pos: (i32, i32),
|
||||
pub name: String
|
||||
}
|
||||
|
||||
impl OutputInfo {
|
||||
fn new(output: wl_output::WlOutput, id: u32) -> OutputInfo {
|
||||
OutputInfo {
|
||||
output: output,
|
||||
id: id,
|
||||
scale: 1.0,
|
||||
pix_size: (0, 0),
|
||||
pix_pos: (0, 0),
|
||||
name: "".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId {
|
||||
pub info: Arc<Mutex<OutputInfo>>
|
||||
pub(crate) proxy: Proxy<wl_output::WlOutput>,
|
||||
pub(crate) mgr: OutputMgr,
|
||||
}
|
||||
|
||||
impl Clone for MonitorId {
|
||||
fn clone(&self) -> MonitorId {
|
||||
MonitorId {
|
||||
proxy: self.proxy.clone(),
|
||||
mgr: self.mgr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: i32,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
native_identifier: self.get_native_identifier(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.info.lock().unwrap().name.clone())
|
||||
self.mgr.with_info(&self.proxy, |_, info| {
|
||||
format!("{} ({})", info.model, info.make)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> u32 {
|
||||
self.info.lock().unwrap().id
|
||||
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
self.info.lock().unwrap().pix_size
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
match self.mgr.with_info(&self.proxy, |_, info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find(|m| m.is_current)
|
||||
.map(|m| m.dimensions)
|
||||
}) {
|
||||
Some(Some((w, h))) => (w as u32, h as u32),
|
||||
_ => (0, 0),
|
||||
}.into()
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
self.info.lock().unwrap().pix_pos
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.location)
|
||||
.unwrap_or((0, 0))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
self.info.lock().unwrap().scale
|
||||
pub fn get_hidpi_factor(&self) -> i32 {
|
||||
self.mgr
|
||||
.with_info(&self.proxy, |_, info| info.scale_factor)
|
||||
.unwrap_or(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorId {
|
||||
outputs.with_all(|list| {
|
||||
if let Some(&(_, ref proxy, _)) = list.first() {
|
||||
MonitorId {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
}
|
||||
} else {
|
||||
panic!("No monitor is available.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorId> {
|
||||
outputs.with_all(|list| {
|
||||
list.iter()
|
||||
.map(|&(_, ref proxy, _)| MonitorId {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,150 +1,196 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {VirtualKeyCode, ElementState, WindowEvent as Event, KeyboardInput, ModifiersState};
|
||||
use super::{make_wid, DeviceId, EventsLoopProxy, EventsLoopSink};
|
||||
use sctk::keyboard::{
|
||||
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
|
||||
};
|
||||
use sctk::reexports::client::protocol::wl_keyboard;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_seat;
|
||||
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
|
||||
|
||||
use super::{EventsLoopSink, WindowId, make_wid, DeviceId};
|
||||
use super::wayland_kbd::{MappedKeyboardImplementation, register_kbd};
|
||||
use wayland_client::protocol::wl_keyboard;
|
||||
use wayland_client::EventQueueHandle;
|
||||
use {ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
|
||||
|
||||
pub fn init_keyboard(evq: &mut EventQueueHandle, keyboard: &wl_keyboard::WlKeyboard, sink: &Arc<Mutex<EventsLoopSink>>) {
|
||||
let idata = KeyboardIData {
|
||||
sink: sink.clone(),
|
||||
target: None
|
||||
};
|
||||
|
||||
if register_kbd(evq, keyboard, mapped_keyboard_impl(), idata).is_err() {
|
||||
// initializing libxkbcommon failed :(
|
||||
// fallback implementation
|
||||
let idata = KeyboardIData {
|
||||
sink: sink.clone(),
|
||||
target: None
|
||||
};
|
||||
evq.register(keyboard, raw_keyboard_impl(), idata);
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyboardIData {
|
||||
pub fn init_keyboard(
|
||||
seat: &Proxy<wl_seat::WlSeat>,
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
target: Option<WindowId>
|
||||
}
|
||||
|
||||
fn mapped_keyboard_impl() -> MappedKeyboardImplementation<KeyboardIData> {
|
||||
MappedKeyboardImplementation {
|
||||
enter: |_, idata, _, _, surface, _, _, _| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(true), wid);
|
||||
idata.target = Some(wid);
|
||||
},
|
||||
leave: |_, idata, _, _, surface| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(false), wid);
|
||||
idata.target = None;
|
||||
},
|
||||
key: |_, idata, _, _, _, mods, rawkey, keysym, state, utf8| {
|
||||
if let Some(wid) = idata.target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
};
|
||||
let vkcode = key_to_vkey(rawkey, keysym);
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: ModifiersState {
|
||||
shift: mods.shift,
|
||||
ctrl: mods.ctrl,
|
||||
alt: mods.alt,
|
||||
logo: mods.logo
|
||||
events_loop_proxy: EventsLoopProxy,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> Proxy<wl_keyboard::WlKeyboard> {
|
||||
// { variables to be captured by the closures
|
||||
let target = Arc::new(Mutex::new(None));
|
||||
let my_sink = sink.clone();
|
||||
let repeat_sink = sink.clone();
|
||||
let repeat_target = target.clone();
|
||||
let my_modifiers = modifiers_tracker.clone();
|
||||
// }
|
||||
let ret = map_keyboard_auto_with_repeat(
|
||||
seat,
|
||||
KeyRepeatKind::System,
|
||||
move |evt: KbEvent, _| match evt {
|
||||
KbEvent::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(true), wid);
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(false), wid);
|
||||
*target.lock().unwrap() = None;
|
||||
}
|
||||
KbEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
state,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = *target.lock().unwrap() {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
};
|
||||
let vkcode = key_to_vkey(rawkey, keysym);
|
||||
let mut guard = my_sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wid
|
||||
);
|
||||
// send char event only on key press, not release
|
||||
if let ElementState::Released = state { return }
|
||||
if let Some(txt) = utf8 {
|
||||
for chr in txt.chars() {
|
||||
guard.send_event(Event::ReceivedCharacter(chr), wid);
|
||||
wid,
|
||||
);
|
||||
// send char event only on key press, not release
|
||||
if let ElementState::Released = state {
|
||||
return;
|
||||
}
|
||||
if let Some(txt) = utf8 {
|
||||
for chr in txt.chars() {
|
||||
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||
KbEvent::Modifiers { modifiers: event_modifiers } => {
|
||||
*modifiers_tracker.lock().unwrap() = event_modifiers.into()
|
||||
}
|
||||
},
|
||||
repeat_info: |_, _idata, _, _rate, _delay| {
|
||||
// TODO: handle repeat info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This is fallback impl if libxkbcommon was not available
|
||||
// This case should probably never happen, as most wayland
|
||||
// compositors _need_ libxkbcommon anyway...
|
||||
//
|
||||
// In this case, we don't have the keymap information (it is
|
||||
// supposed to be serialized by the compositor using libxkbcommon)
|
||||
fn raw_keyboard_impl() -> wl_keyboard::Implementation<KeyboardIData> {
|
||||
wl_keyboard::Implementation {
|
||||
enter: |_, idata, _, _, surface, _| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(true), wid);
|
||||
idata.target = Some(wid);
|
||||
},
|
||||
leave: |_, idata, _, _, surface| {
|
||||
let wid = make_wid(surface);
|
||||
idata.sink.lock().unwrap().send_event(Event::Focused(false), wid);
|
||||
idata.target = None;
|
||||
},
|
||||
key: |_, idata, _, _, _, key, state| {
|
||||
if let Some(wid) = idata.target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
};
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::KeyboardInput {
|
||||
move |repeat_event: KeyRepeatEvent, _| {
|
||||
if let Some(wid) = *repeat_target.lock().unwrap() {
|
||||
let state = ElementState::Pressed;
|
||||
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
|
||||
let mut guard = repeat_sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
scancode: repeat_event.rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: my_modifiers.lock().unwrap().clone(),
|
||||
},
|
||||
},
|
||||
wid
|
||||
wid,
|
||||
);
|
||||
if let Some(txt) = repeat_event.utf8 {
|
||||
for chr in txt.chars() {
|
||||
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
|
||||
}
|
||||
}
|
||||
events_loop_proxy.wakeup().unwrap();
|
||||
}
|
||||
},
|
||||
repeat_info: |_, _idata, _, _rate, _delay| {},
|
||||
keymap: |_, _, _, _, _, _| {},
|
||||
modifiers: |_, _, _, _, _, _, _, _| {}
|
||||
);
|
||||
|
||||
match ret {
|
||||
Ok(keyboard) => keyboard,
|
||||
Err(_) => {
|
||||
// This is a fallback impl if libxkbcommon was not available
|
||||
// This case should probably never happen, as most wayland
|
||||
// compositors _need_ libxkbcommon anyway...
|
||||
//
|
||||
// In this case, we don't have the keymap information (it is
|
||||
// supposed to be serialized by the compositor using libxkbcommon)
|
||||
|
||||
// { variables to be captured by the closure
|
||||
let mut target = None;
|
||||
let my_sink = sink;
|
||||
// }
|
||||
seat.get_keyboard(|keyboard| {
|
||||
keyboard.implement(move |evt, _| match evt {
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(true), wid);
|
||||
target = Some(wid);
|
||||
}
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(WindowEvent::Focused(false), wid);
|
||||
target = None;
|
||||
}
|
||||
wl_keyboard::Event::Key { key, state, .. } => {
|
||||
if let Some(wid) = target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
};
|
||||
my_sink.lock().unwrap().send_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}, ())
|
||||
}).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
|
||||
match rawkey {
|
||||
1 => Some(VirtualKeyCode::Escape),
|
||||
2 => Some(VirtualKeyCode::Key1),
|
||||
3 => Some(VirtualKeyCode::Key2),
|
||||
4 => Some(VirtualKeyCode::Key3),
|
||||
5 => Some(VirtualKeyCode::Key4),
|
||||
6 => Some(VirtualKeyCode::Key5),
|
||||
7 => Some(VirtualKeyCode::Key6),
|
||||
8 => Some(VirtualKeyCode::Key7),
|
||||
9 => Some(VirtualKeyCode::Key8),
|
||||
1 => Some(VirtualKeyCode::Escape),
|
||||
2 => Some(VirtualKeyCode::Key1),
|
||||
3 => Some(VirtualKeyCode::Key2),
|
||||
4 => Some(VirtualKeyCode::Key3),
|
||||
5 => Some(VirtualKeyCode::Key4),
|
||||
6 => Some(VirtualKeyCode::Key5),
|
||||
7 => Some(VirtualKeyCode::Key6),
|
||||
8 => Some(VirtualKeyCode::Key7),
|
||||
9 => Some(VirtualKeyCode::Key8),
|
||||
10 => Some(VirtualKeyCode::Key9),
|
||||
11 => Some(VirtualKeyCode::Key0),
|
||||
_ => keysym_to_vkey(keysym)
|
||||
_ => keysym_to_vkey(keysym),
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
use super::wayland_kbd::keysyms;
|
||||
use sctk::keyboard::keysyms;
|
||||
match keysym {
|
||||
// letters
|
||||
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
|
||||
@@ -174,21 +220,30 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
|
||||
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
|
||||
// F--
|
||||
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
|
||||
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
|
||||
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
|
||||
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
|
||||
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
|
||||
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
|
||||
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
|
||||
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
|
||||
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
|
||||
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
|
||||
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
|
||||
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
|
||||
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
|
||||
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
|
||||
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
|
||||
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
|
||||
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
|
||||
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
|
||||
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
|
||||
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
|
||||
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
|
||||
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
|
||||
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
|
||||
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
|
||||
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
|
||||
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
|
||||
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
|
||||
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
|
||||
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
|
||||
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
|
||||
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
|
||||
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
|
||||
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
|
||||
// flow control
|
||||
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
|
||||
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
|
||||
@@ -243,7 +298,6 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
||||
// => Some(VirtualKeyCode::LBracket),
|
||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
||||
// => Some(VirtualKeyCode::LMenu),
|
||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
||||
// => Some(VirtualKeyCode::LWin),
|
||||
// => Some(VirtualKeyCode::Mail),
|
||||
@@ -258,6 +312,13 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
|
||||
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
|
||||
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
|
||||
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add),
|
||||
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
|
||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
|
||||
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
|
||||
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
|
||||
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
|
||||
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
|
||||
// => Some(VirtualKeyCode::OEM102),
|
||||
// => Some(VirtualKeyCode::Period),
|
||||
// => Some(VirtualKeyCode::Playpause),
|
||||
@@ -266,7 +327,6 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
||||
// => Some(VirtualKeyCode::RBracket),
|
||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
||||
// => Some(VirtualKeyCode::RMenu),
|
||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
||||
// => Some(VirtualKeyCode::RWin),
|
||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
@@ -290,7 +350,21 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
// => Some(VirtualKeyCode::WebSearch),
|
||||
// => Some(VirtualKeyCode::WebStop),
|
||||
// => Some(VirtualKeyCode::Yen),
|
||||
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
|
||||
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
|
||||
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
|
||||
// fallback
|
||||
_ => None
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
|
||||
target_os = "netbsd", target_os = "openbsd"))]
|
||||
|
||||
pub use self::window::Window;
|
||||
pub use self::event_loop::{EventsLoop, EventsLoopProxy, EventsLoopSink, MonitorId};
|
||||
|
||||
extern crate wayland_kbd;
|
||||
extern crate wayland_window;
|
||||
extern crate wayland_protocols;
|
||||
|
||||
use wayland_client::protocol::wl_surface;
|
||||
use wayland_client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_surface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
mod event_loop;
|
||||
mod pointer;
|
||||
@@ -23,6 +20,6 @@ pub struct DeviceId;
|
||||
pub struct WindowId(usize);
|
||||
|
||||
#[inline]
|
||||
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
|
||||
WindowId(s.ptr() as usize)
|
||||
}
|
||||
fn make_wid(s: &Proxy<wl_surface::WlSurface>) -> WindowId {
|
||||
WindowId(s.c_ptr() as usize)
|
||||
}
|
||||
|
||||
@@ -1,194 +1,189 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {WindowEvent as Event, ElementState, MouseButton, MouseScrollDelta, TouchPhase};
|
||||
use {ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
|
||||
use events::ModifiersState;
|
||||
|
||||
use super::{WindowId, DeviceId};
|
||||
use super::DeviceId;
|
||||
use super::event_loop::EventsLoopSink;
|
||||
use super::window::WindowStore;
|
||||
|
||||
use wayland_client::{Proxy, StateToken};
|
||||
use wayland_client::protocol::wl_pointer;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PtrEvent, WlPointer};
|
||||
use sctk::reexports::client::protocol::wl_seat;
|
||||
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
|
||||
|
||||
pub struct PointerIData {
|
||||
pub fn implement_pointer(
|
||||
seat: &Proxy<wl_seat::WlSeat>,
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
windows_token: StateToken<WindowStore>,
|
||||
mouse_focus: Option<WindowId>,
|
||||
axis_buffer: Option<(f32, f32)>,
|
||||
axis_discrete_buffer: Option<(i32, i32)>,
|
||||
axis_state: TouchPhase,
|
||||
}
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> Proxy<WlPointer> {
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
let mut axis_state = TouchPhase::Ended;
|
||||
|
||||
impl PointerIData {
|
||||
pub fn new(sink: &Arc<Mutex<EventsLoopSink>>, token: StateToken<WindowStore>)
|
||||
-> PointerIData
|
||||
{
|
||||
PointerIData {
|
||||
sink: sink.clone(),
|
||||
windows_token: token,
|
||||
mouse_focus: None,
|
||||
axis_buffer: None,
|
||||
axis_discrete_buffer: None,
|
||||
axis_state: TouchPhase::Cancelled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pointer_implementation() -> wl_pointer::Implementation<PointerIData> {
|
||||
wl_pointer::Implementation {
|
||||
enter: |evqh, idata, _, _, surface, x, y| {
|
||||
let wid = evqh.state().get(&idata.windows_token).find_wid(surface);
|
||||
if let Some(wid) = wid {
|
||||
idata.mouse_focus = Some(wid);
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::CursorEntered {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
guard.send_event(
|
||||
Event::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
position: (x, y),
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
leave: |evqh, idata, _, _, surface| {
|
||||
idata.mouse_focus = None;
|
||||
let wid = evqh.state().get(&idata.windows_token).find_wid(surface);
|
||||
if let Some(wid) = wid {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::CursorLeft {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
motion: |_, idata, _, _, x, y| {
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
position: (x, y),
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
},
|
||||
button: |_, idata, _, _, _, button, state| {
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released
|
||||
};
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO figure out the translation ?
|
||||
_ => return
|
||||
};
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
state: state,
|
||||
button: button,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
},
|
||||
axis: |_, idata, pointer, _, axis, value| {
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
if pointer.version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
seat.get_pointer(|pointer| {
|
||||
pointer.implement(move |evt, pointer| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
let store = store.lock().unwrap();
|
||||
match evt {
|
||||
PtrEvent::Enter {
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
mouse_focus = Some(wid);
|
||||
sink.send_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
sink.send_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
position: (surface_x, surface_y).into(),
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Leave { surface, .. } => {
|
||||
mouse_focus = None;
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
sink.send_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
position: (surface_x, surface_y).into(),
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Button { button, state, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
};
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO figure out the translation ?
|
||||
_ => return,
|
||||
};
|
||||
sink.send_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
state: state,
|
||||
button: button,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if pointer.version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
}
|
||||
sink.send_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
}
|
||||
axis_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrEvent::Frame => {
|
||||
let axis_buffer = axis_buffer.take();
|
||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
sink.send_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
|
||||
phase: axis_state,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
sink.send_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
|
||||
phase: axis_state,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrEvent::AxisSource { .. } => (),
|
||||
PtrEvent::AxisStop { .. } => {
|
||||
axis_state = TouchPhase::Ended;
|
||||
}
|
||||
PtrEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete,
|
||||
wl_pointer::Axis::HorizontalScroll => x += discrete,
|
||||
}
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::PixelDelta(x as f32, y as f32),
|
||||
phase: TouchPhase::Moved,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = idata.axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32
|
||||
}
|
||||
idata.axis_buffer = Some((x,y));
|
||||
idata.axis_state = match idata.axis_state {
|
||||
axis_discrete_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
frame: |_, idata, _| {
|
||||
let axis_buffer = idata.axis_buffer.take();
|
||||
let axis_discrete_buffer = idata.axis_discrete_buffer.take();
|
||||
if let Some(wid) = idata.mouse_focus {
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
|
||||
phase: idata.axis_state,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
idata.sink.lock().unwrap().send_event(
|
||||
Event::MouseWheel {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
delta: MouseScrollDelta::PixelDelta(x as f32, y as f32),
|
||||
phase: idata.axis_state,
|
||||
// TODO: replace dummy value with actual modifier state
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
wid
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
axis_source: |_, _, _, _| {},
|
||||
axis_stop: |_, idata, _, _, _| {
|
||||
idata.axis_state = TouchPhase::Ended;
|
||||
},
|
||||
axis_discrete: |_, idata, _, axis, discrete| {
|
||||
let (mut x, mut y) = idata.axis_discrete_buffer.unwrap_or((0,0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete,
|
||||
wl_pointer::Axis::HorizontalScroll => x += discrete
|
||||
}
|
||||
idata.axis_discrete_buffer = Some((x,y));
|
||||
idata.axis_state = match idata.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started
|
||||
}
|
||||
},
|
||||
}
|
||||
}, ())
|
||||
}).unwrap()
|
||||
}
|
||||
|
||||
@@ -1,106 +1,97 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use {WindowEvent as Event, TouchPhase};
|
||||
use {TouchPhase, WindowEvent};
|
||||
|
||||
use super::{WindowId, DeviceId};
|
||||
use super::{DeviceId, WindowId};
|
||||
use super::event_loop::EventsLoopSink;
|
||||
use super::window::WindowStore;
|
||||
|
||||
use wayland_client::StateToken;
|
||||
use wayland_client::protocol::wl_touch;
|
||||
|
||||
pub struct TouchIData {
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
windows_token: StateToken<WindowStore>,
|
||||
pending_ids: Vec<TouchPoint>,
|
||||
}
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::protocol::wl_touch::{Event as TouchEvent, WlTouch};
|
||||
use sctk::reexports::client::protocol::wl_seat;
|
||||
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
|
||||
|
||||
struct TouchPoint {
|
||||
wid: WindowId,
|
||||
location: (f64, f64),
|
||||
id: i32
|
||||
id: i32,
|
||||
}
|
||||
|
||||
impl TouchIData {
|
||||
pub fn new(sink: &Arc<Mutex<EventsLoopSink>>, token: StateToken<WindowStore>)
|
||||
-> TouchIData
|
||||
{
|
||||
TouchIData {
|
||||
sink: sink.clone(),
|
||||
windows_token: token,
|
||||
pending_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn implement_touch(
|
||||
seat: &Proxy<wl_seat::WlSeat>,
|
||||
sink: Arc<Mutex<EventsLoopSink>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
) -> Proxy<WlTouch> {
|
||||
let mut pending_ids = Vec::new();
|
||||
seat.get_touch(|touch| {
|
||||
touch.implement(move |evt, _| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
let store = store.lock().unwrap();
|
||||
match evt {
|
||||
TouchEvent::Down {
|
||||
surface, id, x, y, ..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y).into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
pending_ids.push(TouchPoint {
|
||||
wid: wid,
|
||||
location: (x, y),
|
||||
id: id,
|
||||
});
|
||||
}
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let idx = pending_ids.iter().position(|p| p.id == id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = pending_ids.remove(idx);
|
||||
sink.send_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Ended,
|
||||
location: pt.location.into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let pt = pending_ids.iter_mut().find(|p| p.id == id);
|
||||
if let Some(pt) = pt {
|
||||
pt.location = (x, y);
|
||||
sink.send_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Moved,
|
||||
location: (x, y).into(),
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => for pt in pending_ids.drain(..) {
|
||||
sink.send_event(
|
||||
WindowEvent::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location.into(),
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
},
|
||||
}
|
||||
}, ())
|
||||
}).unwrap()
|
||||
}
|
||||
|
||||
pub fn touch_implementation() -> wl_touch::Implementation<TouchIData> {
|
||||
wl_touch::Implementation {
|
||||
down: |evqh, idata, _, _serial, _time, surface, touch_id, x, y| {
|
||||
let wid = evqh.state().get(&idata.windows_token).find_wid(surface);
|
||||
if let Some(wid) = wid {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y),
|
||||
id: touch_id as u64
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
idata.pending_ids.push(TouchPoint {
|
||||
wid: wid,
|
||||
location: (x, y),
|
||||
id: touch_id
|
||||
});
|
||||
}
|
||||
},
|
||||
up: |_, idata, _, _serial, _time, touch_id| {
|
||||
let idx = idata.pending_ids.iter().position(|p| p.id == touch_id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = idata.pending_ids.remove(idx);
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Ended,
|
||||
location: pt.location,
|
||||
id: touch_id as u64
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
motion: |_, idata, _, _time, touch_id, x, y| {
|
||||
let pt = idata.pending_ids.iter_mut().find(|p| p.id == touch_id);
|
||||
if let Some(pt) = pt {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
pt.location = (x, y);
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Moved,
|
||||
location: (x, y),
|
||||
id: touch_id as u64
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
frame: |_, _, _| {},
|
||||
cancel: |_, idata, _| {
|
||||
let mut guard = idata.sink.lock().unwrap();
|
||||
for pt in idata.pending_ids.drain(..) {
|
||||
guard.send_event(
|
||||
Event::Touch(::Touch {
|
||||
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location,
|
||||
id: pt.id as u64
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +1,145 @@
|
||||
use raw_window_handle::unix::WaylandHandle;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use wayland_client::protocol::{wl_display,wl_surface};
|
||||
use wayland_client::{Proxy, StateToken};
|
||||
|
||||
use {CreationError, MouseCursor, CursorState, WindowAttributes};
|
||||
use platform::MonitorId as PlatformMonitorId;
|
||||
use {CreationError, MouseCursor, WindowAttributes};
|
||||
use dpi::{LogicalPosition, LogicalSize};
|
||||
use platform::{MonitorId as PlatformMonitorId, PlatformSpecificWindowBuilderAttributes as PlAttributes};
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
use super::{EventsLoop, WindowId, make_wid, MonitorId};
|
||||
use super::wayland_window::{Frame, FrameImplementation, State as FrameState};
|
||||
use super::event_loop::StateContext;
|
||||
use sctk::surface::{get_dpi_factor, get_outputs};
|
||||
use sctk::window::{ConceptFrame, Event as WEvent, State as WState, Window as SWindow, Theme};
|
||||
use sctk::reexports::client::{Display, Proxy};
|
||||
use sctk::reexports::client::protocol::{wl_seat, wl_surface};
|
||||
use sctk::reexports::client::protocol::wl_surface::RequestsTrait as SurfaceRequests;
|
||||
use sctk::output::OutputMgr;
|
||||
|
||||
use super::{make_wid, EventsLoop, MonitorId, WindowId};
|
||||
use platform::platform::wayland::event_loop::{get_available_monitors, get_primary_monitor};
|
||||
|
||||
pub struct Window {
|
||||
surface: wl_surface::WlSurface,
|
||||
frame: Arc<Mutex<Frame>>,
|
||||
monitors: Arc<Mutex<MonitorList>>,
|
||||
surface: Proxy<wl_surface::WlSurface>,
|
||||
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
|
||||
outputs: OutputMgr, // Access to info for all monitors
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
|
||||
display: Arc<wl_display::WlDisplay>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>
|
||||
display: Arc<Display>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(evlp: &EventsLoop, attributes: &WindowAttributes) -> Result<Window, CreationError>
|
||||
{
|
||||
let (width, height) = attributes.dimensions.unwrap_or((800,600));
|
||||
|
||||
// Create the decorated surface
|
||||
pub fn new(evlp: &EventsLoop, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result<Window, CreationError> {
|
||||
let (width, height) = attributes.dimensions.map(Into::into).unwrap_or((800, 600));
|
||||
// Create the window
|
||||
let size = Arc::new(Mutex::new((width, height)));
|
||||
let store_token = evlp.store.clone();
|
||||
let (surface, mut frame) = evlp.create_window(
|
||||
width, height, decorated_impl(),
|
||||
|surface| FrameIData {
|
||||
surface: surface.clone().unwrap(),
|
||||
store_token: store_token.clone()
|
||||
}
|
||||
);
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorId { inner: PlatformMonitorId::Wayland(ref monitor_id) }) = attributes.fullscreen {
|
||||
let info = monitor_id.info.lock().unwrap();
|
||||
frame.set_state(FrameState::Fullscreen(Some(&info.output)));
|
||||
} else if attributes.maximized {
|
||||
frame.set_state(FrameState::Maximized);
|
||||
let fullscreen = Arc::new(Mutex::new(false));
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
let surface = evlp.env.create_surface(move |dpi, surface| {
|
||||
window_store.lock().unwrap().dpi_change(&surface, dpi);
|
||||
surface.set_buffer_scale(dpi);
|
||||
});
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
let my_surface = surface.clone();
|
||||
let mut frame = SWindow::<ConceptFrame>::init_from_env(
|
||||
&evlp.env,
|
||||
surface.clone(),
|
||||
(width, height),
|
||||
move |event| match event {
|
||||
WEvent::Configure { new_size, states } => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
||||
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
window.newsize = new_size;
|
||||
window.need_refresh = true;
|
||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Refresh => {
|
||||
let store = window_store.lock().unwrap();
|
||||
for window in &store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Close => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&my_surface) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
if let Some(app_id) = pl_attribs.app_id {
|
||||
frame.set_app_id(app_id);
|
||||
}
|
||||
|
||||
frame.set_title(attributes.title);
|
||||
|
||||
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
|
||||
frame.new_seat(seat);
|
||||
}
|
||||
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorId {
|
||||
inner: PlatformMonitorId::Wayland(ref monitor_id),
|
||||
}) = attributes.fullscreen
|
||||
{
|
||||
frame.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
}
|
||||
|
||||
frame.set_resizable(attributes.resizable);
|
||||
|
||||
// set decorations
|
||||
frame.set_decorate(attributes.decorations);
|
||||
|
||||
// min-max dimensions
|
||||
frame.set_min_size(attributes.min_dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
frame.set_max_size(attributes.max_dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
|
||||
// setup the monitor tracking
|
||||
let monitor_list = Arc::new(Mutex::new(MonitorList::default()));
|
||||
{
|
||||
let mut evq = evlp.evq.borrow_mut();
|
||||
let idata = (evlp.ctxt_token.clone(), monitor_list.clone());
|
||||
evq.register(&surface, surface_impl(), idata);
|
||||
}
|
||||
frame.set_min_size(attributes.min_dimensions.map(Into::into));
|
||||
frame.set_max_size(attributes.max_dimensions.map(Into::into));
|
||||
|
||||
let kill_switch = Arc::new(Mutex::new(false));
|
||||
let need_frame_refresh = Arc::new(Mutex::new(true));
|
||||
let frame = Arc::new(Mutex::new(frame));
|
||||
|
||||
{
|
||||
let mut evq = evlp.evq.borrow_mut();
|
||||
evq.state().get_mut(&store_token).windows.push(InternalWindow {
|
||||
closed: false,
|
||||
newsize: None,
|
||||
need_refresh: false,
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: surface.clone().unwrap(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame)
|
||||
});
|
||||
evq.sync_roundtrip().unwrap();
|
||||
}
|
||||
evlp.store.lock().unwrap().windows.push(InternalWindow {
|
||||
closed: false,
|
||||
newsize: None,
|
||||
size: size.clone(),
|
||||
fullscreen: fullscreen.clone(),
|
||||
need_refresh: false,
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: surface.clone(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame),
|
||||
current_dpi: 1,
|
||||
new_dpi: None,
|
||||
});
|
||||
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
|
||||
|
||||
Ok(Window {
|
||||
display: evlp.display.clone(),
|
||||
surface: surface,
|
||||
frame: frame,
|
||||
monitors: monitor_list,
|
||||
outputs: evlp.env.outputs.clone(),
|
||||
size: size,
|
||||
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
|
||||
need_frame_refresh: need_frame_refresh
|
||||
need_frame_refresh: need_frame_refresh,
|
||||
fullscreen: fullscreen,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,75 +163,59 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
// Not possible with wayland
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
// Not possible with wayland
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_position(&self, _x: i32, _y: i32) {
|
||||
pub fn set_position(&self, _pos: LogicalPosition) {
|
||||
// Not possible with wayland
|
||||
}
|
||||
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
Some(self.size.lock().unwrap().clone())
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
Some(self.size.lock().unwrap().clone().into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
let (w, h) = self.size.lock().unwrap().clone();
|
||||
let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
||||
Some((w as u32, h as u32))
|
||||
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
||||
Some((w, h).into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// NOTE: This will only resize the borders, the contents must be updated by the user
|
||||
pub fn set_inner_size(&self, x: u32, y: u32) {
|
||||
self.frame.lock().unwrap().resize(x as i32, y as i32);
|
||||
*(self.size.lock().unwrap()) = (x, y);
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
let (w, h) = size.into();
|
||||
self.frame.lock().unwrap().resize(w, h);
|
||||
*(self.size.lock().unwrap()) = (w, h);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.frame.lock().unwrap().set_min_size(dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
self.frame.lock().unwrap().set_max_size(dimensions.map(|(w, h)| (w as i32, h as i32)));
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _cursor: MouseCursor) {
|
||||
// TODO
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.frame.lock().unwrap().set_resizable(resizable);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
use CursorState::{Grab, Normal, Hide};
|
||||
// TODO : not yet possible on wayland to grab cursor
|
||||
match state {
|
||||
Grab => Err("Cursor cannot be grabbed on wayland yet.".to_string()),
|
||||
Hide => Err("Cursor cannot be hidden on wayland yet.".to_string()),
|
||||
Normal => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
let mut factor = 1.0;
|
||||
let guard = self.monitors.lock().unwrap();
|
||||
for monitor_id in &guard.monitors {
|
||||
let info = monitor_id.info.lock().unwrap();
|
||||
if info.scale > factor { factor = info.scale; }
|
||||
}
|
||||
factor
|
||||
pub fn hidpi_factor(&self) -> i32 {
|
||||
get_dpi_factor(&self.surface)
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
@@ -186,40 +225,89 @@ impl Window {
|
||||
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
if maximized {
|
||||
self.frame.lock().unwrap().set_state(FrameState::Maximized);
|
||||
self.frame.lock().unwrap().set_maximized();
|
||||
} else {
|
||||
self.frame.lock().unwrap().set_state(FrameState::Regular);
|
||||
self.frame.lock().unwrap().unset_maximized();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fullscreen(&self) -> Option<MonitorId> {
|
||||
if *(self.fullscreen.lock().unwrap()) {
|
||||
Some(self.get_current_monitor())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
if let Some(RootMonitorId { inner: PlatformMonitorId::Wayland(ref monitor_id) }) = monitor {
|
||||
let info = monitor_id.info.lock().unwrap();
|
||||
self.frame.lock().unwrap().set_state(FrameState::Fullscreen(Some(&info.output)));
|
||||
if let Some(RootMonitorId {
|
||||
inner: PlatformMonitorId::Wayland(ref monitor_id),
|
||||
}) = monitor
|
||||
{
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else {
|
||||
self.frame.lock().unwrap().set_state(FrameState::Regular);
|
||||
self.frame.lock().unwrap().unset_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
|
||||
// TODO: not yet possible on wayland
|
||||
Err(())
|
||||
|
||||
pub fn set_theme<T: Theme>(&self, theme: T) {
|
||||
self.frame.lock().unwrap().set_theme(theme)
|
||||
}
|
||||
|
||||
pub fn get_display(&self) -> &wl_display::WlDisplay {
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, _cursor: MouseCursor) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, _hide: bool) {
|
||||
// TODO: This isn't possible on Wayland yet
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
|
||||
Err("Cursor grabbing is not yet possible on Wayland.".to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), String> {
|
||||
Err("Setting the cursor position is not yet possible on Wayland.".to_owned())
|
||||
}
|
||||
|
||||
pub fn get_display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn get_surface(&self) -> &wl_surface::WlSurface {
|
||||
pub fn get_surface(&self) -> &Proxy<wl_surface::WlSurface> {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(&self) -> MonitorId {
|
||||
// we don't know how much each monitor sees us so...
|
||||
// just return the most recent one ?
|
||||
let guard = self.monitors.lock().unwrap();
|
||||
guard.monitors.last().unwrap().clone()
|
||||
let output = get_outputs(&self.surface).last().unwrap().clone();
|
||||
MonitorId {
|
||||
proxy: output,
|
||||
mgr: self.outputs.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> WaylandHandle {
|
||||
WaylandHandle {
|
||||
surface: self.get_surface().c_ptr() as *mut _,
|
||||
display: self.get_display().c_ptr() as *mut _,
|
||||
..WaylandHandle::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,25 +323,31 @@ impl Drop for Window {
|
||||
*/
|
||||
|
||||
struct InternalWindow {
|
||||
surface: wl_surface::WlSurface,
|
||||
newsize: Option<(i32, i32)>,
|
||||
surface: Proxy<wl_surface::WlSurface>,
|
||||
newsize: Option<(u32, u32)>,
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
need_refresh: bool,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
closed: bool,
|
||||
kill_switch: Arc<Mutex<bool>>,
|
||||
frame: Weak<Mutex<Frame>>
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
current_dpi: i32,
|
||||
new_dpi: Option<i32>,
|
||||
}
|
||||
|
||||
pub struct WindowStore {
|
||||
windows: Vec<InternalWindow>
|
||||
windows: Vec<InternalWindow>,
|
||||
}
|
||||
|
||||
impl WindowStore {
|
||||
pub fn new() -> WindowStore {
|
||||
WindowStore { windows: Vec::new() }
|
||||
WindowStore {
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
|
||||
pub fn find_wid(&self, surface: &Proxy<wl_surface::WlSurface>) -> Option<WindowId> {
|
||||
for window in &self.windows {
|
||||
if surface.equals(&window.surface) {
|
||||
return Some(make_wid(surface));
|
||||
@@ -277,88 +371,45 @@ impl WindowStore {
|
||||
pruned
|
||||
}
|
||||
|
||||
pub fn new_seat(&self, seat: &Proxy<wl_seat::WlSeat>) {
|
||||
for window in &self.windows {
|
||||
if let Some(w) = window.frame.upgrade() {
|
||||
w.lock().unwrap().new_seat(seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dpi_change(&mut self, surface: &Proxy<wl_surface::WlSurface>, new: i32) {
|
||||
for window in &mut self.windows {
|
||||
if surface.equals(&window.surface) {
|
||||
window.new_dpi = Some(new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&mut self, mut f: F)
|
||||
where F: FnMut(Option<(i32, i32)>, bool, bool, bool, WindowId, Option<&mut Frame>)
|
||||
where
|
||||
F: FnMut(Option<(u32, u32)>, &mut (u32, u32), Option<i32>, bool, 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(
|
||||
window.newsize.take(),
|
||||
&mut *(window.size.lock().unwrap()),
|
||||
window.new_dpi,
|
||||
window.need_refresh,
|
||||
::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)
|
||||
opt_mutex_lock.as_mut().map(|m| &mut **m),
|
||||
);
|
||||
if let Some(dpi) = window.new_dpi.take() {
|
||||
window.current_dpi = dpi;
|
||||
}
|
||||
window.need_refresh = false;
|
||||
// avoid re-spamming the event
|
||||
window.closed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Protocol implementation
|
||||
*/
|
||||
|
||||
struct FrameIData {
|
||||
store_token: StateToken<WindowStore>,
|
||||
surface: wl_surface::WlSurface
|
||||
}
|
||||
|
||||
fn decorated_impl() -> FrameImplementation<FrameIData> {
|
||||
FrameImplementation {
|
||||
configure: |evqh, idata, _, newsize| {
|
||||
let store = evqh.state().get_mut(&idata.store_token);
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&idata.surface) {
|
||||
window.newsize = newsize;
|
||||
window.need_refresh = true;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
close: |evqh, idata| {
|
||||
let store = evqh.state().get_mut(&idata.store_token);
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&idata.surface) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
refresh: |evqh, idata| {
|
||||
let store = evqh.state().get_mut(&idata.store_token);
|
||||
for window in &mut store.windows {
|
||||
if window.surface.equals(&idata.surface) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MonitorList {
|
||||
monitors: Vec<MonitorId>
|
||||
}
|
||||
|
||||
fn surface_impl() -> wl_surface::Implementation<(StateToken<StateContext>, Arc<Mutex<MonitorList>>)> {
|
||||
wl_surface::Implementation {
|
||||
enter: |evqh, &mut (ref token, ref list), _, output| {
|
||||
let mut guard = list.lock().unwrap();
|
||||
let ctxt = evqh.state().get(token);
|
||||
let monitor = ctxt.monitor_id_for(output);
|
||||
guard.monitors.push(monitor);
|
||||
},
|
||||
leave: |evqh, &mut (ref token, ref list), _, output| {
|
||||
let mut guard = list.lock().unwrap();
|
||||
let ctxt = evqh.state().get(token);
|
||||
let monitor = ctxt.monitor_id_for(output);
|
||||
guard.monitors.retain(|m| !Arc::ptr_eq(&m.info, &monitor.info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::Utf8Error;
|
||||
use std::os::raw::*;
|
||||
|
||||
use libc::{c_char, c_int, c_long, c_uchar, c_ulong};
|
||||
use percent_encoding::percent_decode;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
const DND_ATOMS_LEN: usize = 12;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DndAtoms {
|
||||
pub aware: ffi::Atom,
|
||||
@@ -28,36 +26,21 @@ pub struct DndAtoms {
|
||||
|
||||
impl DndAtoms {
|
||||
pub fn new(xconn: &Arc<XConnection>) -> Result<Self, XError> {
|
||||
let mut atoms = Vec::with_capacity(DND_ATOMS_LEN);
|
||||
|
||||
let mut names = [
|
||||
b"XdndAware\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndEnter\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndLeave\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndDrop\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndPosition\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndStatus\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndActionPrivate\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndSelection\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndFinished\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"XdndTypeList\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"text/uri-list\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
b"None\0".to_owned().as_mut_ptr() as *mut c_char,
|
||||
let names = [
|
||||
b"XdndAware\0".as_ptr() as *mut c_char,
|
||||
b"XdndEnter\0".as_ptr() as *mut c_char,
|
||||
b"XdndLeave\0".as_ptr() as *mut c_char,
|
||||
b"XdndDrop\0".as_ptr() as *mut c_char,
|
||||
b"XdndPosition\0".as_ptr() as *mut c_char,
|
||||
b"XdndStatus\0".as_ptr() as *mut c_char,
|
||||
b"XdndActionPrivate\0".as_ptr() as *mut c_char,
|
||||
b"XdndSelection\0".as_ptr() as *mut c_char,
|
||||
b"XdndFinished\0".as_ptr() as *mut c_char,
|
||||
b"XdndTypeList\0".as_ptr() as *mut c_char,
|
||||
b"text/uri-list\0".as_ptr() as *mut c_char,
|
||||
b"None\0".as_ptr() as *mut c_char,
|
||||
];
|
||||
|
||||
unsafe {
|
||||
(xconn.xlib.XInternAtoms)(
|
||||
xconn.display,
|
||||
names.as_mut_ptr(),
|
||||
DND_ATOMS_LEN as c_int,
|
||||
ffi::False,
|
||||
atoms.as_mut_ptr(),
|
||||
);
|
||||
}
|
||||
xconn.check_errors()?;
|
||||
unsafe {
|
||||
atoms.set_len(DND_ATOMS_LEN);
|
||||
}
|
||||
let atoms = unsafe { xconn.get_atoms(&names) }?;
|
||||
Ok(DndAtoms {
|
||||
aware: atoms[0],
|
||||
enter: atoms[1],
|
||||
@@ -144,14 +127,13 @@ impl Dnd {
|
||||
DndState::Accepted => (1, self.atoms.action_private as c_long),
|
||||
DndState::Rejected => (0, self.atoms.none as c_long),
|
||||
};
|
||||
util::send_client_msg(
|
||||
&self.xconn,
|
||||
self.xconn.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.status,
|
||||
None,
|
||||
(this_window as c_long, accepted, 0, 0, action),
|
||||
)
|
||||
[this_window as c_long, accepted, 0, 0, action],
|
||||
).flush()
|
||||
}
|
||||
|
||||
pub unsafe fn send_finished(
|
||||
@@ -164,22 +146,20 @@ impl Dnd {
|
||||
DndState::Accepted => (1, self.atoms.action_private as c_long),
|
||||
DndState::Rejected => (0, self.atoms.none as c_long),
|
||||
};
|
||||
util::send_client_msg(
|
||||
&self.xconn,
|
||||
self.xconn.send_client_msg(
|
||||
target_window,
|
||||
target_window,
|
||||
self.atoms.finished,
|
||||
None,
|
||||
(this_window as c_long, accepted, action, 0, 0),
|
||||
)
|
||||
[this_window as c_long, accepted, action, 0, 0],
|
||||
).flush()
|
||||
}
|
||||
|
||||
pub unsafe fn get_type_list(
|
||||
&self,
|
||||
source_window: c_ulong,
|
||||
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
|
||||
util::get_property(
|
||||
&self.xconn,
|
||||
self.xconn.get_property(
|
||||
source_window,
|
||||
self.atoms.type_list,
|
||||
ffi::XA_ATOM,
|
||||
@@ -201,8 +181,7 @@ impl Dnd {
|
||||
&self,
|
||||
window: c_ulong,
|
||||
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
|
||||
util::get_property(
|
||||
&self.xconn,
|
||||
self.xconn.get_property(
|
||||
window,
|
||||
self.atoms.selection,
|
||||
self.atoms.uri_list,
|
||||
|
||||
@@ -82,11 +82,11 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
ffi::XK_KP_Delete => events::VirtualKeyCode::Delete,
|
||||
ffi::XK_KP_Equal => events::VirtualKeyCode::NumpadEquals,
|
||||
//ffi::XK_KP_Multiply => events::VirtualKeyCode::NumpadMultiply,
|
||||
//ffi::XK_KP_Add => events::VirtualKeyCode::NumpadAdd,
|
||||
ffi::XK_KP_Add => events::VirtualKeyCode::Add,
|
||||
//ffi::XK_KP_Separator => events::VirtualKeyCode::Kp_separator,
|
||||
//ffi::XK_KP_Subtract => events::VirtualKeyCode::NumpadSubtract,
|
||||
ffi::XK_KP_Subtract => events::VirtualKeyCode::Subtract,
|
||||
//ffi::XK_KP_Decimal => events::VirtualKeyCode::Kp_decimal,
|
||||
//ffi::XK_KP_Divide => events::VirtualKeyCode::NumpadDivide,
|
||||
ffi::XK_KP_Divide => events::VirtualKeyCode::Divide,
|
||||
ffi::XK_KP_0 => events::VirtualKeyCode::Numpad0,
|
||||
ffi::XK_KP_1 => events::VirtualKeyCode::Numpad1,
|
||||
ffi::XK_KP_2 => events::VirtualKeyCode::Numpad2,
|
||||
@@ -117,23 +117,23 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_L4 => events::VirtualKeyCode::L4,
|
||||
ffi::XK_F15 => events::VirtualKeyCode::F15,
|
||||
//ffi::XK_L5 => events::VirtualKeyCode::L5,
|
||||
//ffi::XK_F16 => events::VirtualKeyCode::F16,
|
||||
ffi::XK_F16 => events::VirtualKeyCode::F16,
|
||||
//ffi::XK_L6 => events::VirtualKeyCode::L6,
|
||||
//ffi::XK_F17 => events::VirtualKeyCode::F17,
|
||||
ffi::XK_F17 => events::VirtualKeyCode::F17,
|
||||
//ffi::XK_L7 => events::VirtualKeyCode::L7,
|
||||
//ffi::XK_F18 => events::VirtualKeyCode::F18,
|
||||
ffi::XK_F18 => events::VirtualKeyCode::F18,
|
||||
//ffi::XK_L8 => events::VirtualKeyCode::L8,
|
||||
//ffi::XK_F19 => events::VirtualKeyCode::F19,
|
||||
ffi::XK_F19 => events::VirtualKeyCode::F19,
|
||||
//ffi::XK_L9 => events::VirtualKeyCode::L9,
|
||||
//ffi::XK_F20 => events::VirtualKeyCode::F20,
|
||||
ffi::XK_F20 => events::VirtualKeyCode::F20,
|
||||
//ffi::XK_L10 => events::VirtualKeyCode::L10,
|
||||
//ffi::XK_F21 => events::VirtualKeyCode::F21,
|
||||
ffi::XK_F21 => events::VirtualKeyCode::F21,
|
||||
//ffi::XK_R1 => events::VirtualKeyCode::R1,
|
||||
//ffi::XK_F22 => events::VirtualKeyCode::F22,
|
||||
ffi::XK_F22 => events::VirtualKeyCode::F22,
|
||||
//ffi::XK_R2 => events::VirtualKeyCode::R2,
|
||||
//ffi::XK_F23 => events::VirtualKeyCode::F23,
|
||||
ffi::XK_F23 => events::VirtualKeyCode::F23,
|
||||
//ffi::XK_R3 => events::VirtualKeyCode::R3,
|
||||
//ffi::XK_F24 => events::VirtualKeyCode::F24,
|
||||
ffi::XK_F24 => events::VirtualKeyCode::F24,
|
||||
//ffi::XK_R4 => events::VirtualKeyCode::R4,
|
||||
//ffi::XK_F25 => events::VirtualKeyCode::F25,
|
||||
//ffi::XK_R5 => events::VirtualKeyCode::R5,
|
||||
@@ -1000,6 +1000,9 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_Hebrew_switch => events::VirtualKeyCode::Hebrew_switch,
|
||||
ffi::XF86XK_Back => VirtualKeyCode::NavigateBackward,
|
||||
ffi::XF86XK_Forward => VirtualKeyCode::NavigateForward,
|
||||
ffi::XF86XK_Copy => VirtualKeyCode::Copy,
|
||||
ffi::XF86XK_Paste => VirtualKeyCode::Paste,
|
||||
ffi::XF86XK_Cut => VirtualKeyCode::Cut,
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,12 +5,20 @@ use std::sync::Arc;
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::{CStr, CString, IntoStringError};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
lazy_static! {
|
||||
static ref GLOBAL_LOCK: Mutex<()> = Default::default();
|
||||
}
|
||||
|
||||
unsafe fn open_im(
|
||||
xconn: &Arc<XConnection>,
|
||||
locale_modifiers: &CStr,
|
||||
) -> Option<ffi::XIM> {
|
||||
let _lock = GLOBAL_LOCK.lock();
|
||||
|
||||
// XSetLocaleModifiers returns...
|
||||
// * The current locale modifiers if it's given a NULL pointer.
|
||||
// * The new locale modifiers if we succeeded in setting them.
|
||||
@@ -85,13 +93,11 @@ enum GetXimServersError {
|
||||
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
|
||||
// XMODIFIERS to `@server=ibus`?!?"
|
||||
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
|
||||
let servers_atom = util::get_atom(&xconn, b"XIM_SERVERS\0")
|
||||
.map_err(GetXimServersError::XError)?;
|
||||
let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0");
|
||||
|
||||
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
|
||||
|
||||
let mut atoms: Vec<ffi::Atom> = util::get_property(
|
||||
&xconn,
|
||||
let mut atoms: Vec<ffi::Atom> = xconn.get_property(
|
||||
root,
|
||||
servers_atom,
|
||||
ffi::XA_ATOM,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,110 +1,85 @@
|
||||
use std::sync::Arc;
|
||||
use std::slice;
|
||||
use std::os::raw::*;
|
||||
|
||||
use super::XConnection;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[derive(Clone)]
|
||||
use {PhysicalPosition, PhysicalSize};
|
||||
use super::{util, XConnection, XError};
|
||||
use super::ffi::{
|
||||
RRCrtcChangeNotifyMask,
|
||||
RROutputPropertyNotifyMask,
|
||||
RRScreenChangeNotifyMask,
|
||||
True,
|
||||
Window,
|
||||
XRRScreenResources,
|
||||
};
|
||||
|
||||
// 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.
|
||||
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<MonitorId>>> = 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<MonitorId>> {
|
||||
// We update this lazily.
|
||||
(*MONITORS.lock()).take()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorId {
|
||||
/// The actual id
|
||||
id: u32,
|
||||
/// The name of the monitor
|
||||
name: String,
|
||||
pub(crate) name: String,
|
||||
/// The size of the monitor
|
||||
dimensions: (u32, u32),
|
||||
/// The position of the monitor in the X screen
|
||||
position: (i32, i32),
|
||||
/// If the monitor is the primary one
|
||||
primary: bool,
|
||||
/// The DPI scaling factor
|
||||
hidpi_factor: f32,
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(x: &Arc<XConnection>) -> Vec<MonitorId> {
|
||||
let mut available = Vec::new();
|
||||
unsafe {
|
||||
let root = (x.xlib.XDefaultRootWindow)(x.display);
|
||||
let resources = (x.xrandr.XRRGetScreenResources)(x.display, root);
|
||||
|
||||
if let Some(ref xrandr_1_5) = x.xrandr_1_5 {
|
||||
// We're in XRandR >= 1.5, enumerate Monitors to handle things like MST and videowalls
|
||||
let mut nmonitors = 0;
|
||||
let monitors = (xrandr_1_5.XRRGetMonitors)(x.display, root, 1, &mut nmonitors);
|
||||
for i in 0..nmonitors {
|
||||
let monitor = *(monitors.offset(i as isize));
|
||||
let output = (xrandr_1_5.XRRGetOutputInfo)(x.display, resources, *(monitor.outputs.offset(0)));
|
||||
let nameslice = slice::from_raw_parts((*output).name as *mut u8, (*output).nameLen as usize);
|
||||
let name = String::from_utf8_lossy(nameslice).into_owned();
|
||||
let hidpi_factor = {
|
||||
let x_mm = (*output).mm_width as f32;
|
||||
let y_mm = (*output).mm_height as f32;
|
||||
let x_px = monitor.width as f32;
|
||||
let y_px = monitor.height as f32;
|
||||
let ppmm = ((x_px * y_px) / (x_mm * y_mm)).sqrt();
|
||||
|
||||
// Quantize 1/12 step size
|
||||
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
|
||||
};
|
||||
(xrandr_1_5.XRRFreeOutputInfo)(output);
|
||||
available.push(MonitorId{
|
||||
id: i as u32,
|
||||
name,
|
||||
hidpi_factor,
|
||||
dimensions: (monitor.width as u32, monitor.height as u32),
|
||||
position: (monitor.x as i32, monitor.y as i32),
|
||||
primary: (monitor.primary != 0),
|
||||
});
|
||||
}
|
||||
(xrandr_1_5.XRRFreeMonitors)(monitors);
|
||||
} else {
|
||||
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work but MST and
|
||||
// videowall setups will show more monitors than the logical groups the user
|
||||
// cares about
|
||||
for i in 0..(*resources).ncrtc {
|
||||
let crtcid = *((*resources).crtcs.offset(i as isize));
|
||||
let crtc = (x.xrandr.XRRGetCrtcInfo)(x.display, resources, crtcid);
|
||||
if (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0 {
|
||||
let output = (x.xrandr.XRRGetOutputInfo)(x.display, resources, *((*crtc).outputs.offset(0)));
|
||||
let nameslice = slice::from_raw_parts((*output).name as *mut u8, (*output).nameLen as usize);
|
||||
let name = String::from_utf8_lossy(nameslice).into_owned();
|
||||
|
||||
let hidpi_factor = {
|
||||
let x_mm = (*output).mm_width as f32;
|
||||
let y_mm = (*output).mm_height as f32;
|
||||
let x_px = (*crtc).width as f32;
|
||||
let y_px = (*crtc).height as f32;
|
||||
let ppmm = ((x_px * y_px) / (x_mm * y_mm)).sqrt();
|
||||
|
||||
// Quantize 1/12 step size
|
||||
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
|
||||
};
|
||||
|
||||
(x.xrandr.XRRFreeOutputInfo)(output);
|
||||
available.push(MonitorId{
|
||||
id: crtcid as u32,
|
||||
name,
|
||||
hidpi_factor,
|
||||
dimensions: ((*crtc).width as u32, (*crtc).height as u32),
|
||||
position: ((*crtc).x as i32, (*crtc).y as i32),
|
||||
primary: true,
|
||||
});
|
||||
}
|
||||
(x.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||
}
|
||||
}
|
||||
(x.xrandr.XRRFreeScreenResources)(resources);
|
||||
}
|
||||
available
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(x: &Arc<XConnection>) -> MonitorId {
|
||||
get_available_monitors(x).into_iter().find(|m| m.primary)
|
||||
// 'no primary' case is better handled picking some existing monitor
|
||||
.or_else(|| get_available_monitors(x).into_iter().next())
|
||||
.expect("[winit] Failed to find any x11 monitor")
|
||||
/// The DPI scale factor
|
||||
pub(crate) hidpi_factor: f64,
|
||||
/// Used to determine which windows are on this monitor
|
||||
pub(crate) rect: util::AaRect,
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
fn from_repr(
|
||||
xconn: &XConnection,
|
||||
resources: *mut XRRScreenResources,
|
||||
id: u32,
|
||||
repr: util::MonitorRepr,
|
||||
primary: bool,
|
||||
) -> Option<Self> {
|
||||
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
|
||||
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
|
||||
let rect = util::AaRect::new(position, dimensions);
|
||||
Some(MonitorId {
|
||||
id,
|
||||
name,
|
||||
hidpi_factor,
|
||||
dimensions,
|
||||
position,
|
||||
primary,
|
||||
rect,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.name.clone())
|
||||
}
|
||||
@@ -114,16 +89,186 @@ impl MonitorId {
|
||||
self.id as u32
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
self.dimensions
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
self.dimensions.into()
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
self.position
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.position.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorId {
|
||||
let monitors = self.get_available_monitors();
|
||||
let default = monitors
|
||||
.get(0)
|
||||
.expect("[winit] Failed to find any monitors using XRandR.");
|
||||
|
||||
let window_rect = match window_rect {
|
||||
Some(rect) => rect,
|
||||
None => return default.to_owned(),
|
||||
};
|
||||
|
||||
let mut largest_overlap = 0;
|
||||
let mut matched_monitor = default;
|
||||
for monitor in &monitors {
|
||||
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
|
||||
if overlapping_area > largest_overlap {
|
||||
largest_overlap = overlapping_area;
|
||||
matched_monitor = &monitor;
|
||||
}
|
||||
}
|
||||
|
||||
matched_monitor.to_owned()
|
||||
}
|
||||
|
||||
fn query_monitor_list(&self) -> Vec<MonitorId> {
|
||||
unsafe {
|
||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||
let resources = if version_is_at_least(1, 3) {
|
||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||
} else {
|
||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
||||
// Upon failure, `resources` will be null.
|
||||
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
||||
};
|
||||
|
||||
if resources.is_null() {
|
||||
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
|
||||
}
|
||||
|
||||
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;
|
||||
has_primary |= is_primary;
|
||||
MonitorId::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;
|
||||
MonitorId::from_repr(
|
||||
self,
|
||||
resources,
|
||||
crtc_id as u32,
|
||||
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!
|
||||
if !has_primary {
|
||||
if let Some(ref mut fallback) = available.first_mut() {
|
||||
// Setting this here will come in handy if we ever add an `is_primary` method.
|
||||
fallback.primary = true;
|
||||
}
|
||||
}
|
||||
|
||||
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||
available
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_monitors(&self) -> Vec<MonitorId> {
|
||||
let mut monitors_lock = MONITORS.lock();
|
||||
(*monitors_lock)
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
let monitors = Some(self.query_monitor_list());
|
||||
if !DISABLE_MONITOR_LIST_CACHING {
|
||||
(*monitors_lock) = monitors.clone();
|
||||
}
|
||||
monitors
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
self.get_available_monitors()
|
||||
.into_iter()
|
||||
.find(|monitor| monitor.primary)
|
||||
.expect("[winit] Failed to find any monitors using XRandR.")
|
||||
}
|
||||
|
||||
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 mut event_offset = 0;
|
||||
let mut error_offset = 0;
|
||||
let status = unsafe {
|
||||
(self.xrandr.XRRQueryExtension)(
|
||||
self.display,
|
||||
&mut event_offset,
|
||||
&mut error_offset,
|
||||
)
|
||||
};
|
||||
|
||||
if status != True {
|
||||
self.check_errors()?;
|
||||
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
|
||||
}
|
||||
|
||||
let mask = RRCrtcChangeNotifyMask
|
||||
| RROutputPropertyNotifyMask
|
||||
| RRScreenChangeNotifyMask;
|
||||
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
|
||||
|
||||
Ok(event_offset)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::raw::{c_char, c_double, c_int, c_long, c_short, c_uchar, c_uint, c_ulong};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
use events::ModifiersState;
|
||||
|
||||
pub struct XSmartPointer<'a, T> {
|
||||
xconn: &'a Arc<XConnection>,
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a Arc<XConnection>, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer {
|
||||
xconn,
|
||||
ptr,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for XSmartPointer<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFree)(self.ptr as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_atom(xconn: &Arc<XConnection>, name: &[u8]) -> Result<ffi::Atom, XError> {
|
||||
let atom_name: *const c_char = name.as_ptr() as _;
|
||||
let atom = (xconn.xlib.XInternAtom)(xconn.display, atom_name, ffi::False);
|
||||
xconn.check_errors().map(|_| atom)
|
||||
}
|
||||
|
||||
pub unsafe fn send_client_msg(
|
||||
xconn: &Arc<XConnection>,
|
||||
window: c_ulong, // the window this is "about"; not necessarily this window
|
||||
target_window: c_ulong, // the window we're sending to
|
||||
message_type: ffi::Atom,
|
||||
event_mask: Option<c_long>,
|
||||
data: (c_long, c_long, c_long, c_long, c_long),
|
||||
) -> Result<(), XError> {
|
||||
let mut event: ffi::XClientMessageEvent = mem::uninitialized();
|
||||
event.type_ = ffi::ClientMessage;
|
||||
event.display = xconn.display;
|
||||
event.window = window;
|
||||
event.message_type = message_type;
|
||||
event.format = 32;
|
||||
event.data = ffi::ClientMessageData::new();
|
||||
event.data.set_long(0, data.0);
|
||||
event.data.set_long(1, data.1);
|
||||
event.data.set_long(2, data.2);
|
||||
event.data.set_long(3, data.3);
|
||||
event.data.set_long(4, data.4);
|
||||
|
||||
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
|
||||
|
||||
(xconn.xlib.XSendEvent)(
|
||||
xconn.display,
|
||||
target_window,
|
||||
ffi::False,
|
||||
event_mask,
|
||||
&mut event.into(),
|
||||
);
|
||||
|
||||
xconn.check_errors().map(|_| ())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GetPropertyError {
|
||||
XError(XError),
|
||||
TypeMismatch(ffi::Atom),
|
||||
FormatMismatch(c_int),
|
||||
NothingAllocated,
|
||||
}
|
||||
|
||||
impl GetPropertyError {
|
||||
pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool {
|
||||
if let GetPropertyError::TypeMismatch(actual_type) = *self {
|
||||
actual_type == t
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_property<T>(
|
||||
xconn: &Arc<XConnection>,
|
||||
window: c_ulong,
|
||||
property: ffi::Atom,
|
||||
property_type: ffi::Atom,
|
||||
) -> Result<Vec<T>, GetPropertyError> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
let mut done = false;
|
||||
while !done {
|
||||
let mut actual_type: ffi::Atom = mem::uninitialized();
|
||||
let mut actual_format: c_int = mem::uninitialized();
|
||||
let mut byte_count: c_ulong = mem::uninitialized();
|
||||
let mut bytes_after: c_ulong = mem::uninitialized();
|
||||
let mut buf: *mut c_uchar = ptr::null_mut();
|
||||
(xconn.xlib.XGetWindowProperty)(
|
||||
xconn.display,
|
||||
window,
|
||||
property,
|
||||
(data.len() / 4) as c_long,
|
||||
1024,
|
||||
ffi::False,
|
||||
property_type,
|
||||
&mut actual_type,
|
||||
&mut actual_format,
|
||||
&mut byte_count,
|
||||
&mut bytes_after,
|
||||
&mut buf,
|
||||
);
|
||||
|
||||
if let Err(e) = xconn.check_errors() {
|
||||
return Err(GetPropertyError::XError(e));
|
||||
}
|
||||
|
||||
if actual_type != property_type {
|
||||
return Err(GetPropertyError::TypeMismatch(actual_type));
|
||||
}
|
||||
|
||||
// Fun fact: actual_format ISN'T the size of the type; it's more like a really bad enum
|
||||
let format_mismatch = match actual_format as usize {
|
||||
8 => mem::size_of::<T>() != mem::size_of::<c_char>(),
|
||||
16 => mem::size_of::<T>() != mem::size_of::<c_short>(),
|
||||
32 => mem::size_of::<T>() != mem::size_of::<c_long>(),
|
||||
_ => true, // this won't actually be reached; the XError condition above is triggered
|
||||
};
|
||||
|
||||
if format_mismatch {
|
||||
return Err(GetPropertyError::FormatMismatch(actual_format));
|
||||
}
|
||||
|
||||
if !buf.is_null() {
|
||||
let mut buf =
|
||||
Vec::from_raw_parts(buf as *mut T, byte_count as usize, byte_count as usize);
|
||||
data.append(&mut buf);
|
||||
} else {
|
||||
return Err(GetPropertyError::NothingAllocated);
|
||||
}
|
||||
|
||||
done = bytes_after == 0;
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PointerState {
|
||||
#[allow(dead_code)]
|
||||
root: ffi::Window,
|
||||
#[allow(dead_code)]
|
||||
child: ffi::Window,
|
||||
#[allow(dead_code)]
|
||||
root_x: c_double,
|
||||
#[allow(dead_code)]
|
||||
root_y: c_double,
|
||||
#[allow(dead_code)]
|
||||
win_x: c_double,
|
||||
#[allow(dead_code)]
|
||||
win_y: c_double,
|
||||
#[allow(dead_code)]
|
||||
buttons: ffi::XIButtonState,
|
||||
modifiers: ffi::XIModifierState,
|
||||
#[allow(dead_code)]
|
||||
group: ffi::XIGroupState,
|
||||
#[allow(dead_code)]
|
||||
relative_to_window: bool,
|
||||
}
|
||||
|
||||
impl PointerState {
|
||||
pub fn get_modifier_state(&self) -> ModifiersState {
|
||||
self.modifiers.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn query_pointer(
|
||||
xconn: &Arc<XConnection>,
|
||||
window: ffi::Window,
|
||||
device_id: c_int,
|
||||
) -> Result<PointerState, XError> {
|
||||
let mut root_return = mem::uninitialized();
|
||||
let mut child_return = mem::uninitialized();
|
||||
let mut root_x_return = mem::uninitialized();
|
||||
let mut root_y_return = mem::uninitialized();
|
||||
let mut win_x_return = mem::uninitialized();
|
||||
let mut win_y_return = mem::uninitialized();
|
||||
let mut buttons_return = mem::uninitialized();
|
||||
let mut modifiers_return = mem::uninitialized();
|
||||
let mut group_return = mem::uninitialized();
|
||||
|
||||
let relative_to_window = (xconn.xinput2.XIQueryPointer)(
|
||||
xconn.display,
|
||||
device_id,
|
||||
window,
|
||||
&mut root_return,
|
||||
&mut child_return,
|
||||
&mut root_x_return,
|
||||
&mut root_y_return,
|
||||
&mut win_x_return,
|
||||
&mut win_y_return,
|
||||
&mut buttons_return,
|
||||
&mut modifiers_return,
|
||||
&mut group_return,
|
||||
) == ffi::True;
|
||||
|
||||
xconn.check_errors()?;
|
||||
|
||||
Ok(PointerState {
|
||||
root: root_return,
|
||||
child: child_return,
|
||||
root_x: root_x_return,
|
||||
root_y: root_y_return,
|
||||
win_x: win_x_return,
|
||||
win_y: win_y_return,
|
||||
buttons: buttons_return,
|
||||
modifiers: modifiers_return,
|
||||
group: group_return,
|
||||
relative_to_window,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn lookup_utf8_inner(
|
||||
xconn: &Arc<XConnection>,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: &mut [u8],
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = (xconn.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
buffer.len() as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
);
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub unsafe fn lookup_utf8(
|
||||
xconn: &Arc<XConnection>,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
) -> String {
|
||||
const INIT_BUFF_SIZE: usize = 16;
|
||||
|
||||
// Buffer allocated on heap instead of stack, due to the possible reallocation
|
||||
let mut buffer: Vec<u8> = vec![mem::uninitialized(); INIT_BUFF_SIZE];
|
||||
let (_, status, mut count) = lookup_utf8_inner(
|
||||
xconn,
|
||||
ic,
|
||||
key_event,
|
||||
&mut buffer,
|
||||
);
|
||||
|
||||
// Buffer overflowed, dynamically reallocate
|
||||
if status == ffi::XBufferOverflow {
|
||||
buffer = vec![mem::uninitialized(); count as usize];
|
||||
let (_, _, new_count) = lookup_utf8_inner(
|
||||
xconn,
|
||||
ic,
|
||||
key_event,
|
||||
&mut buffer,
|
||||
);
|
||||
count = new_count;
|
||||
}
|
||||
|
||||
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FrameExtents {
|
||||
pub left: c_ulong,
|
||||
pub right: c_ulong,
|
||||
pub top: c_ulong,
|
||||
pub bottom: c_ulong,
|
||||
}
|
||||
|
||||
impl FrameExtents {
|
||||
pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
|
||||
FrameExtents { left, right, top, bottom }
|
||||
}
|
||||
|
||||
pub fn from_border(border: c_ulong) -> Self {
|
||||
Self::new(border, border, border, border)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowGeometry {
|
||||
pub x: c_int,
|
||||
pub y: c_int,
|
||||
pub width: c_uint,
|
||||
pub height: c_uint,
|
||||
pub frame: FrameExtents,
|
||||
}
|
||||
|
||||
impl WindowGeometry {
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
(self.x as _, self.y as _)
|
||||
}
|
||||
|
||||
pub fn get_inner_position(&self) -> (i32, i32) {
|
||||
(
|
||||
self.x.saturating_add(self.frame.left as c_int) as _,
|
||||
self.y.saturating_add(self.frame.top as c_int) as _,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_inner_size(&self) -> (u32, u32) {
|
||||
(self.width as _, self.height as _)
|
||||
}
|
||||
|
||||
pub fn get_outer_size(&self) -> (u32, u32) {
|
||||
(
|
||||
self.width.saturating_add(
|
||||
self.frame.left.saturating_add(self.frame.right) as c_uint
|
||||
) as _,
|
||||
self.height.saturating_add(
|
||||
self.frame.top.saturating_add(self.frame.bottom) as c_uint
|
||||
) as _,
|
||||
)
|
||||
}
|
||||
}
|
||||
72
src/platform/linux/x11/util/atom.rs
Normal file
72
src/platform/linux/x11/util/atom.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::*;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
type AtomCache = HashMap<CString, ffi::Atom>;
|
||||
|
||||
lazy_static! {
|
||||
static ref ATOM_CACHE: Mutex<AtomCache> = Mutex::new(HashMap::with_capacity(2048));
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_atom<T: AsRef<CStr> + Debug>(&self, name: T) -> ffi::Atom {
|
||||
let name = name.as_ref();
|
||||
let mut atom_cache_lock = ATOM_CACHE.lock();
|
||||
let cached_atom = (*atom_cache_lock).get(name).cloned();
|
||||
if let Some(atom) = cached_atom {
|
||||
atom
|
||||
} else {
|
||||
let atom = unsafe { (self.xlib.XInternAtom)(
|
||||
self.display,
|
||||
name.as_ptr() as *const c_char,
|
||||
ffi::False,
|
||||
) };
|
||||
if atom == 0 {
|
||||
let msg = format!(
|
||||
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
|
||||
name,
|
||||
self.check_errors(),
|
||||
);
|
||||
panic!(msg);
|
||||
}
|
||||
/*println!(
|
||||
"XInternAtom name:{:?} atom:{:?}",
|
||||
name,
|
||||
atom,
|
||||
);*/
|
||||
(*atom_cache_lock).insert(name.to_owned(), atom);
|
||||
atom
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom {
|
||||
debug_assert!(CStr::from_bytes_with_nul(name).is_ok());
|
||||
let name = CStr::from_bytes_with_nul_unchecked(name);
|
||||
self.get_atom(name)
|
||||
}
|
||||
|
||||
// Note: this doesn't use caching, for the sake of simplicity.
|
||||
// If you're dealing with this many atoms, you'll usually want to cache them locally anyway.
|
||||
pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result<Vec<ffi::Atom>, XError> {
|
||||
let mut atoms = Vec::with_capacity(names.len());
|
||||
(self.xlib.XInternAtoms)(
|
||||
self.display,
|
||||
names.as_ptr() as *mut _,
|
||||
names.len() as c_int,
|
||||
ffi::False,
|
||||
atoms.as_mut_ptr(),
|
||||
);
|
||||
self.check_errors()?;
|
||||
atoms.set_len(names.len());
|
||||
/*println!(
|
||||
"XInternAtoms atoms:{:?}",
|
||||
atoms,
|
||||
);*/
|
||||
Ok(atoms)
|
||||
}
|
||||
}
|
||||
95
src/platform/linux/x11/util/client_msg.rs
Normal file
95
src/platform/linux/x11/util/client_msg.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use super::*;
|
||||
|
||||
pub type ClientMsgPayload = [c_long; 5];
|
||||
|
||||
impl XConnection {
|
||||
pub fn send_event<T: Into<ffi::XEvent>>(
|
||||
&self,
|
||||
target_window: c_ulong,
|
||||
event_mask: Option<c_long>,
|
||||
event: T,
|
||||
) -> Flusher {
|
||||
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
|
||||
unsafe {
|
||||
(self.xlib.XSendEvent)(
|
||||
self.display,
|
||||
target_window,
|
||||
ffi::False,
|
||||
event_mask,
|
||||
&mut event.into(),
|
||||
);
|
||||
}
|
||||
Flusher::new(self)
|
||||
}
|
||||
|
||||
pub fn send_client_msg(
|
||||
&self,
|
||||
window: c_ulong, // The window this is "about"; not necessarily this window
|
||||
target_window: c_ulong, // The window we're sending to
|
||||
message_type: ffi::Atom,
|
||||
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) };
|
||||
self.send_event(target_window, event_mask, event)
|
||||
}
|
||||
|
||||
// Prepare yourself for the ultimate in unsafety!
|
||||
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
|
||||
// to send more than one message worth of data.
|
||||
pub fn send_client_msg_multi<T: Formattable>(
|
||||
&self,
|
||||
window: c_ulong, // The window this is "about"; not necessarily this window
|
||||
target_window: c_ulong, // The window we're sending to
|
||||
message_type: ffi::Atom,
|
||||
event_mask: Option<c_long>,
|
||||
data: &[T],
|
||||
) -> Flusher {
|
||||
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 t_per_payload = format.get_payload_size() / size_of_t;
|
||||
assert!(t_per_payload > 0);
|
||||
let payload_count = data.len() / t_per_payload;
|
||||
let payload_remainder = data.len() % t_per_payload;
|
||||
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
|
||||
|
||||
let mut payload_index = 0;
|
||||
while payload_index < payload_count {
|
||||
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
|
||||
payload_index += 1;
|
||||
event.data = unsafe { mem::transmute(*payload) };
|
||||
self.send_event(target_window, event_mask, &event).queue();
|
||||
}
|
||||
|
||||
if payload_remainder > 0 {
|
||||
let mut payload: ClientMsgPayload = [0; 5];
|
||||
let t_payload = payload.as_mut_ptr() as *mut T;
|
||||
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
|
||||
let invalid_t_payload = invalid_payload as *const T;
|
||||
let mut t_index = 0;
|
||||
while t_index < payload_remainder {
|
||||
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
|
||||
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
|
||||
t_index += 1;
|
||||
}
|
||||
event.data = unsafe { mem::transmute(payload) };
|
||||
self.send_event(target_window, event_mask, &event).queue();
|
||||
}
|
||||
|
||||
Flusher::new(self)
|
||||
}
|
||||
}
|
||||
58
src/platform/linux/x11/util/format.rs
Normal file
58
src/platform/linux/x11/util/format.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
use std::os::raw::*;
|
||||
|
||||
// This isn't actually the number of the bits in the format.
|
||||
// X11 does a match on this value to determine which type to call sizeof on.
|
||||
// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64.
|
||||
// ...if that sounds confusing, then you know why this enum is here.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Format {
|
||||
Char = 8,
|
||||
Short = 16,
|
||||
Long = 32,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
pub fn from_format(format: usize) -> Option<Self> {
|
||||
match format {
|
||||
8 => Some(Format::Char),
|
||||
16 => Some(Format::Short),
|
||||
32 => Some(Format::Long),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_same_size_as<T>(&self) -> bool {
|
||||
mem::size_of::<T>() == self.get_actual_size()
|
||||
}
|
||||
|
||||
pub fn get_actual_size(&self) -> usize {
|
||||
match self {
|
||||
&Format::Char => mem::size_of::<c_char>(),
|
||||
&Format::Short => mem::size_of::<c_short>(),
|
||||
&Format::Long => mem::size_of::<c_long>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_payload_size(&self) -> usize {
|
||||
match self {
|
||||
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
|
||||
&Format::Char => mem::size_of::<c_char>() * 20,
|
||||
&Format::Short => mem::size_of::<c_short>() * 10,
|
||||
&Format::Long => mem::size_of::<c_long>() * 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
|
||||
const FORMAT: Format;
|
||||
}
|
||||
|
||||
// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it.
|
||||
impl Formattable for c_schar { const FORMAT: Format = Format::Char; }
|
||||
impl Formattable for c_uchar { const FORMAT: Format = Format::Char; }
|
||||
impl Formattable for c_short { const FORMAT: Format = Format::Short; }
|
||||
impl Formattable for c_ushort { const FORMAT: Format = Format::Short; }
|
||||
impl Formattable for c_long { const FORMAT: Format = Format::Long; }
|
||||
impl Formattable for c_ulong { const FORMAT: Format = Format::Long; }
|
||||
387
src/platform/linux/x11/util/geometry.rs
Normal file
387
src/platform/linux/x11/util/geometry.rs
Normal file
@@ -0,0 +1,387 @@
|
||||
use std::cmp;
|
||||
|
||||
use super::*;
|
||||
use {LogicalPosition, LogicalSize};
|
||||
|
||||
// Friendly neighborhood axis-aligned rectangle
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AaRect {
|
||||
x: i64,
|
||||
y: i64,
|
||||
width: i64,
|
||||
height: i64,
|
||||
}
|
||||
|
||||
impl AaRect {
|
||||
pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
|
||||
let (x, y) = (x as i64, y as i64);
|
||||
let (width, height) = (width as i64, height as i64);
|
||||
AaRect { x, y, width, height }
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, x: i64, y: i64) -> bool {
|
||||
x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
|
||||
}
|
||||
|
||||
pub fn get_overlapping_area(&self, other: &Self) -> i64 {
|
||||
let x_overlap = cmp::max(
|
||||
0,
|
||||
cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
|
||||
);
|
||||
let y_overlap = cmp::max(
|
||||
0,
|
||||
cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
|
||||
);
|
||||
x_overlap * y_overlap
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TranslatedCoords {
|
||||
pub x_rel_root: c_int,
|
||||
pub y_rel_root: c_int,
|
||||
pub child: ffi::Window,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Geometry {
|
||||
pub root: ffi::Window,
|
||||
// If you want positions relative to the root window, use translate_coords.
|
||||
// Note that the overwhelming majority of window managers are reparenting WMs, thus the window
|
||||
// ID we get from window creation is for a nested window used as the window's client area. If
|
||||
// you call get_geometry with that window ID, then you'll get the position of that client area
|
||||
// window relative to the parent it's nested in (the frame), which isn't helpful if you want
|
||||
// to know the frame position.
|
||||
pub x_rel_parent: c_int,
|
||||
pub y_rel_parent: c_int,
|
||||
// In that same case, this will give you client area size.
|
||||
pub width: c_uint,
|
||||
pub height: c_uint,
|
||||
// xmonad and dwm were the only WMs tested that use the border return at all.
|
||||
// The majority of WMs seem to simply fill it with 0 unconditionally.
|
||||
pub border: c_uint,
|
||||
pub depth: c_uint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameExtents {
|
||||
pub left: c_ulong,
|
||||
pub right: c_ulong,
|
||||
pub top: c_ulong,
|
||||
pub bottom: c_ulong,
|
||||
}
|
||||
|
||||
impl FrameExtents {
|
||||
pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
|
||||
FrameExtents { left, right, top, bottom }
|
||||
}
|
||||
|
||||
pub fn from_border(border: c_ulong) -> Self {
|
||||
Self::new(border, border, border, border)
|
||||
}
|
||||
|
||||
pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
|
||||
let logicalize = |value: c_ulong| value as f64 / factor;
|
||||
LogicalFrameExtents {
|
||||
left: logicalize(self.left),
|
||||
right: logicalize(self.right),
|
||||
top: logicalize(self.top),
|
||||
bottom: logicalize(self.bottom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LogicalFrameExtents {
|
||||
pub left: f64,
|
||||
pub right: f64,
|
||||
pub top: f64,
|
||||
pub bottom: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FrameExtentsHeuristicPath {
|
||||
Supported,
|
||||
UnsupportedNested,
|
||||
UnsupportedBordered,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameExtentsHeuristic {
|
||||
pub frame_extents: FrameExtents,
|
||||
pub heuristic_path: FrameExtentsHeuristicPath,
|
||||
}
|
||||
|
||||
impl FrameExtentsHeuristic {
|
||||
pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
if self.heuristic_path != UnsupportedBordered {
|
||||
(x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
|
||||
} else {
|
||||
(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_pos_to_outer_logical(&self, mut logical: LogicalPosition, factor: f64) -> LogicalPosition {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
if self.heuristic_path != UnsupportedBordered {
|
||||
let frame_extents = self.frame_extents.as_logical(factor);
|
||||
logical.x -= frame_extents.left;
|
||||
logical.y -= frame_extents.top;
|
||||
}
|
||||
logical
|
||||
}
|
||||
|
||||
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
|
||||
(
|
||||
width.saturating_add(
|
||||
self.frame_extents.left.saturating_add(self.frame_extents.right) as u32
|
||||
),
|
||||
height.saturating_add(
|
||||
self.frame_extents.top.saturating_add(self.frame_extents.bottom) as u32
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn inner_size_to_outer_logical(&self, mut logical: LogicalSize, factor: f64) -> LogicalSize {
|
||||
let frame_extents = self.frame_extents.as_logical(factor);
|
||||
logical.width += frame_extents.left + frame_extents.right;
|
||||
logical.height += frame_extents.top + frame_extents.bottom;
|
||||
logical
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is adequate for get_inner_position
|
||||
pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result<TranslatedCoords, XError> {
|
||||
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
|
||||
unsafe {
|
||||
(self.xlib.XTranslateCoordinates)(
|
||||
self.display,
|
||||
window,
|
||||
root,
|
||||
0,
|
||||
0,
|
||||
&mut translated_coords.x_rel_root,
|
||||
&mut translated_coords.y_rel_root,
|
||||
&mut translated_coords.child,
|
||||
);
|
||||
}
|
||||
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
|
||||
self.check_errors().map(|_| translated_coords)
|
||||
}
|
||||
|
||||
// This is adequate for get_inner_size
|
||||
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
|
||||
let mut geometry: Geometry = unsafe { mem::uninitialized() };
|
||||
let _status = unsafe {
|
||||
(self.xlib.XGetGeometry)(
|
||||
self.display,
|
||||
window,
|
||||
&mut geometry.root,
|
||||
&mut geometry.x_rel_parent,
|
||||
&mut geometry.y_rel_parent,
|
||||
&mut geometry.width,
|
||||
&mut geometry.height,
|
||||
&mut geometry.border,
|
||||
&mut geometry.depth,
|
||||
)
|
||||
};
|
||||
//println!("XGetGeometry geo:{:?}", geometry);
|
||||
self.check_errors().map(|_| geometry)
|
||||
}
|
||||
|
||||
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
|
||||
let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") };
|
||||
|
||||
if !hint_is_supported(extents_atom) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
|
||||
// support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
|
||||
// be unsupported by many smaller WMs.
|
||||
let extents: Option<Vec<c_ulong>> = self.get_property(
|
||||
window,
|
||||
extents_atom,
|
||||
ffi::XA_CARDINAL,
|
||||
).ok();
|
||||
|
||||
extents.and_then(|extents| {
|
||||
if extents.len() >= 4 {
|
||||
Some(FrameExtents {
|
||||
left: extents[0],
|
||||
right: extents[1],
|
||||
top: extents[2],
|
||||
bottom: extents[3],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool> {
|
||||
let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") };
|
||||
|
||||
if !hint_is_supported(client_list_atom) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let client_list: Option<Vec<ffi::Window>> = self.get_property(
|
||||
root,
|
||||
client_list_atom,
|
||||
ffi::XA_WINDOW,
|
||||
).ok();
|
||||
|
||||
client_list.map(|client_list| client_list.contains(&window))
|
||||
}
|
||||
|
||||
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 children: *mut ffi::Window = ptr::null_mut();
|
||||
let mut nchildren: c_uint = mem::uninitialized();
|
||||
|
||||
// What's filled into `parent` if `window` is the root window?
|
||||
let _status = (self.xlib.XQueryTree)(
|
||||
self.display,
|
||||
window,
|
||||
&mut root,
|
||||
&mut parent,
|
||||
&mut children,
|
||||
&mut nchildren,
|
||||
);
|
||||
|
||||
// The list of children isn't used
|
||||
if children != ptr::null_mut() {
|
||||
(self.xlib.XFree)(children as *mut _);
|
||||
}
|
||||
|
||||
parent
|
||||
};
|
||||
self.check_errors().map(|_| parent)
|
||||
}
|
||||
|
||||
fn climb_hierarchy(&self, window: ffi::Window, root: ffi::Window) -> Result<ffi::Window, XError> {
|
||||
let mut outer_window = window;
|
||||
loop {
|
||||
let candidate = self.get_parent_window(outer_window)?;
|
||||
if candidate == root {
|
||||
break;
|
||||
}
|
||||
outer_window = candidate;
|
||||
}
|
||||
Ok(outer_window)
|
||||
}
|
||||
|
||||
pub fn get_frame_extents_heuristic(&self, window: ffi::Window, root: ffi::Window) -> FrameExtentsHeuristic {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
|
||||
// Position relative to root window.
|
||||
// With rare exceptions, this is the position of a nested window. Cases where the window
|
||||
// isn't nested are outlined in the comments throghout this function, but in addition to
|
||||
// that, fullscreen windows often aren't nested.
|
||||
let (inner_y_rel_root, child) = {
|
||||
let coords = self.translate_coords(window, root).expect("Failed to translate window coordinates");
|
||||
(
|
||||
coords.y_rel_root,
|
||||
coords.child,
|
||||
)
|
||||
};
|
||||
|
||||
let (width, height, border) = {
|
||||
let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry");
|
||||
(
|
||||
inner_geometry.width,
|
||||
inner_geometry.height,
|
||||
inner_geometry.border,
|
||||
)
|
||||
};
|
||||
|
||||
// The first condition is only false for un-nested windows, but isn't always false for
|
||||
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
|
||||
// when y is on the range [0, 2] and if the window has been unfocused since being
|
||||
// undecorated (or was undecorated upon construction), the first condition is true,
|
||||
// requiring us to rely on the second condition.
|
||||
let nested = !(window == child || self.is_top_level(child, root) == Some(true));
|
||||
|
||||
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
|
||||
if let Some(mut frame_extents) = self.get_frame_extents(window) {
|
||||
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
|
||||
// decorations are disabled, but since the window becomes un-nested, it's easy to
|
||||
// catch.
|
||||
if !nested {
|
||||
frame_extents = FrameExtents::new(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// The difference between the nested window's position and the outermost window's
|
||||
// position is equivalent to the frame size. In most scenarios, this is equivalent to
|
||||
// manually climbing the hierarchy as is done in the case below. Here's a list of
|
||||
// known discrepancies:
|
||||
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
|
||||
// addition to a 1px semi-transparent border. The margin can be easily observed by
|
||||
// using a screenshot tool to get a screenshot of a selected window, and is
|
||||
// presumably used for drawing drop shadows. Getting window geometry information
|
||||
// via hierarchy-climbing results in this margin being included in both the
|
||||
// position and outer size, so a window positioned at (0, 0) would be reported as
|
||||
// having a position (-10, -8).
|
||||
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
|
||||
// on all sides, and there's no additional border.
|
||||
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
|
||||
// Without decorations, there's no difference. This is presumably related to
|
||||
// Enlightenment's fairly unique concept of window position; it interprets
|
||||
// positions given to XMoveWindow as a client area position rather than a position
|
||||
// of the overall window.
|
||||
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: Supported,
|
||||
}
|
||||
} else if nested {
|
||||
// If the position value we have is for a nested window used as the client area, we'll
|
||||
// just climb up the hierarchy and get the geometry of the outermost window we're
|
||||
// nested in.
|
||||
let outer_window = self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy");
|
||||
let (outer_y, outer_width, outer_height) = {
|
||||
let outer_geometry = self.get_geometry(outer_window).expect("Failed to get outer window geometry");
|
||||
(
|
||||
outer_geometry.y_rel_parent,
|
||||
outer_geometry.width,
|
||||
outer_geometry.height,
|
||||
)
|
||||
};
|
||||
|
||||
// Since we have the geometry of the outermost window and the geometry of the client
|
||||
// area, we can figure out what's in between.
|
||||
let diff_x = outer_width.saturating_sub(width);
|
||||
let diff_y = outer_height.saturating_sub(height);
|
||||
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
|
||||
|
||||
let left = diff_x / 2;
|
||||
let right = left;
|
||||
let top = offset_y;
|
||||
let bottom = diff_y.saturating_sub(offset_y);
|
||||
|
||||
let frame_extents = FrameExtents::new(
|
||||
left.into(),
|
||||
right.into(),
|
||||
top.into(),
|
||||
bottom.into(),
|
||||
);
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: UnsupportedNested,
|
||||
}
|
||||
} else {
|
||||
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
|
||||
// border value. This is convenient, since we can use it to get an accurate frame.
|
||||
let frame_extents = FrameExtents::from_border(border.into());
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: UnsupportedBordered,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
236
src/platform/linux/x11/util/hint.rs
Normal file
236
src/platform/linux/x11/util/hint.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const MWM_HINTS_DECORATIONS: c_ulong = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StateOperation {
|
||||
Remove = 0, // _NET_WM_STATE_REMOVE
|
||||
Add = 1, // _NET_WM_STATE_ADD
|
||||
Toggle = 2, // _NET_WM_STATE_TOGGLE
|
||||
}
|
||||
|
||||
impl From<bool> for StateOperation {
|
||||
fn from(op: bool) -> Self {
|
||||
if op {
|
||||
StateOperation::Add
|
||||
} else {
|
||||
StateOperation::Remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// X window type. Maps directly to
|
||||
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum WindowType {
|
||||
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
|
||||
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
|
||||
/// root window clicks.
|
||||
Desktop,
|
||||
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
|
||||
Dock,
|
||||
/// Toolbar windows. "Torn off" from the main application.
|
||||
Toolbar,
|
||||
/// Pinnable menu windows. "Torn off" from the main application.
|
||||
Menu,
|
||||
/// A small persistent utility window, such as a palette or toolbox.
|
||||
Utility,
|
||||
/// The window is a splash screen displayed as an application is starting up.
|
||||
Splash,
|
||||
/// This is a dialog window.
|
||||
Dialog,
|
||||
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
DropdownMenu,
|
||||
/// A popup menu that usually appears when the user right clicks on an object.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
PopupMenu,
|
||||
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Tooltip,
|
||||
/// The window is a notification.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Notification,
|
||||
/// This should be used on the windows that are popped up by combo boxes.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Combo,
|
||||
/// This indicates the the window is being dragged.
|
||||
/// This property is typically used on override-redirect windows.
|
||||
Dnd,
|
||||
/// This is a normal, top-level window.
|
||||
Normal,
|
||||
}
|
||||
|
||||
impl Default for WindowType {
|
||||
fn default() -> Self {
|
||||
WindowType::Normal
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
};
|
||||
unsafe { xconn.get_atom_unchecked(atom_name) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NormalHints<'a> {
|
||||
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
|
||||
}
|
||||
|
||||
impl<'a> NormalHints<'a> {
|
||||
pub fn new(xconn: &'a XConnection) -> Self {
|
||||
NormalHints { size_hints: xconn.alloc_size_hints() }
|
||||
}
|
||||
|
||||
pub fn has_flag(&self, flag: c_long) -> bool {
|
||||
has_flag(self.size_hints.flags, flag)
|
||||
}
|
||||
|
||||
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
|
||||
if self.has_flag(flag) {
|
||||
Some((*field1 as _, *field2 as _))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
|
||||
}
|
||||
|
||||
// WARNING: This hint is obsolete
|
||||
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
|
||||
if let Some((width, height)) = size {
|
||||
self.size_hints.flags |= ffi::PSize;
|
||||
self.size_hints.width = width as c_int;
|
||||
self.size_hints.height = height as c_int;
|
||||
} else {
|
||||
self.size_hints.flags &= !ffi::PSize;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_max_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(ffi::PMaxSize, &self.size_hints.max_width, &self.size_hints.max_height)
|
||||
}
|
||||
|
||||
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
|
||||
if let Some((max_width, max_height)) = max_size {
|
||||
self.size_hints.flags |= ffi::PMaxSize;
|
||||
self.size_hints.max_width = max_width as c_int;
|
||||
self.size_hints.max_height = max_height as c_int;
|
||||
} else {
|
||||
self.size_hints.flags &= !ffi::PMaxSize;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(ffi::PMinSize, &self.size_hints.min_width, &self.size_hints.min_height)
|
||||
}
|
||||
|
||||
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
|
||||
if let Some((min_width, min_height)) = min_size {
|
||||
self.size_hints.flags |= ffi::PMinSize;
|
||||
self.size_hints.min_width = min_width as c_int;
|
||||
self.size_hints.min_height = min_height as c_int;
|
||||
} else {
|
||||
self.size_hints.flags &= !ffi::PMinSize;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
|
||||
self.getter(ffi::PResizeInc, &self.size_hints.width_inc, &self.size_hints.height_inc)
|
||||
}
|
||||
|
||||
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
|
||||
if let Some((width_inc, height_inc)) = resize_increments {
|
||||
self.size_hints.flags |= ffi::PResizeInc;
|
||||
self.size_hints.width_inc = width_inc as c_int;
|
||||
self.size_hints.height_inc = height_inc as c_int;
|
||||
} else {
|
||||
self.size_hints.flags &= !ffi::PResizeInc;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_base_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(ffi::PBaseSize, &self.size_hints.base_width, &self.size_hints.base_height)
|
||||
}
|
||||
|
||||
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
|
||||
if let Some((base_width, base_height)) = base_size {
|
||||
self.size_hints.flags |= ffi::PBaseSize;
|
||||
self.size_hints.base_width = base_width as c_int;
|
||||
self.size_hints.base_height = base_height as c_int;
|
||||
} else {
|
||||
self.size_hints.flags &= !ffi::PBaseSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_wm_hints(&self, window: ffi::Window) -> Result<XSmartPointer<ffi::XWMHints>, XError> {
|
||||
let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
|
||||
self.check_errors()?;
|
||||
let wm_hints = if wm_hints.is_null() {
|
||||
self.alloc_wm_hints()
|
||||
} else {
|
||||
XSmartPointer::new(self, wm_hints).unwrap()
|
||||
};
|
||||
Ok(wm_hints)
|
||||
}
|
||||
|
||||
pub fn set_wm_hints(&self, window: ffi::Window, wm_hints: XSmartPointer<ffi::XWMHints>) -> Flusher {
|
||||
unsafe {
|
||||
(self.xlib.XSetWMHints)(
|
||||
self.display,
|
||||
window,
|
||||
wm_hints.ptr,
|
||||
);
|
||||
}
|
||||
Flusher::new(self)
|
||||
}
|
||||
|
||||
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() };
|
||||
unsafe {
|
||||
(self.xlib.XGetWMNormalHints)(
|
||||
self.display,
|
||||
window,
|
||||
size_hints.ptr,
|
||||
&mut supplied_by_user,
|
||||
);
|
||||
}
|
||||
self.check_errors().map(|_| NormalHints { size_hints })
|
||||
}
|
||||
|
||||
pub fn set_normal_hints(&self, window: ffi::Window, normal_hints: NormalHints) -> Flusher {
|
||||
unsafe {
|
||||
(self.xlib.XSetWMNormalHints)(
|
||||
self.display,
|
||||
window,
|
||||
normal_hints.size_hints.ptr,
|
||||
);
|
||||
}
|
||||
Flusher::new(self)
|
||||
}
|
||||
}
|
||||
34
src/platform/linux/x11/util/icon.rs
Normal file
34
src/platform/linux/x11/util/icon.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use {Icon, Pixel, PIXEL_SIZE};
|
||||
use super::*;
|
||||
|
||||
impl Pixel {
|
||||
pub fn to_packed_argb(&self) -> Cardinal {
|
||||
let mut cardinal = 0;
|
||||
assert!(CARDINAL_SIZE >= PIXEL_SIZE);
|
||||
let as_bytes = &mut cardinal as *mut _ as *mut u8;
|
||||
unsafe {
|
||||
*as_bytes.offset(0) = self.b;
|
||||
*as_bytes.offset(1) = self.g;
|
||||
*as_bytes.offset(2) = self.r;
|
||||
*as_bytes.offset(3) = self.a;
|
||||
}
|
||||
cardinal
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
|
||||
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = self.rgba.len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (self.width * self.height) as usize);
|
||||
let mut data = Vec::with_capacity(pixel_count);
|
||||
data.push(self.width as Cardinal);
|
||||
data.push(self.height as Cardinal);
|
||||
let pixels = self.rgba.as_ptr() as *const Pixel;
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
|
||||
data.push(pixel.to_packed_argb());
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
159
src/platform/linux/x11/util/input.rs
Normal file
159
src/platform/linux/x11/util/input.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use std::str;
|
||||
|
||||
use super::*;
|
||||
use events::ModifiersState;
|
||||
|
||||
pub const VIRTUAL_CORE_POINTER: c_int = 2;
|
||||
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
|
||||
|
||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PointerState<'a> {
|
||||
xconn: &'a XConnection,
|
||||
root: ffi::Window,
|
||||
child: ffi::Window,
|
||||
pub root_x: c_double,
|
||||
pub root_y: c_double,
|
||||
win_x: c_double,
|
||||
win_y: c_double,
|
||||
buttons: ffi::XIButtonState,
|
||||
modifiers: ffi::XIModifierState,
|
||||
group: ffi::XIGroupState,
|
||||
relative_to_window: bool,
|
||||
}
|
||||
|
||||
impl<'a> PointerState<'a> {
|
||||
pub fn get_modifier_state(&self) -> ModifiersState {
|
||||
self.modifiers.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for PointerState<'a> {
|
||||
fn drop(&mut self) {
|
||||
if !self.buttons.mask.is_null() {
|
||||
unsafe {
|
||||
// This is why you need to read the docs carefully...
|
||||
(self.xconn.xlib.XFree)(self.buttons.mask as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn select_xinput_events(&self, window: c_ulong, device_id: c_int, mask: i32) -> Flusher {
|
||||
let mut event_mask = ffi::XIEventMask {
|
||||
deviceid: device_id,
|
||||
mask: &mask as *const _ as *mut c_uchar,
|
||||
mask_len: mem::size_of_val(&mask) as c_int,
|
||||
};
|
||||
unsafe {
|
||||
(self.xinput2.XISelectEvents)(
|
||||
self.display,
|
||||
window,
|
||||
&mut event_mask as *mut ffi::XIEventMask,
|
||||
1, // number of masks to read from pointer above
|
||||
);
|
||||
}
|
||||
Flusher::new(self)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option<Flusher> {
|
||||
let status = unsafe {
|
||||
(self.xlib.XkbSelectEvents)(
|
||||
self.display,
|
||||
device_id,
|
||||
mask,
|
||||
mask,
|
||||
)
|
||||
};
|
||||
if status == ffi::True {
|
||||
Some(Flusher::new(self))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_pointer(&self, window: ffi::Window, 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)(
|
||||
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,
|
||||
) == 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_utf8_inner(
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: &mut [u8],
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = unsafe {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
buffer.len() as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
};
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
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);
|
||||
debug_assert_eq!(count, new_count);
|
||||
str::from_utf8(&buffer[..count as usize])
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
} else {
|
||||
str::from_utf8(&buffer[..count as usize])
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/platform/linux/x11/util/memory.rs
Normal file
62
src/platform/linux/x11/util/memory.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct XSmartPointer<'a, T> {
|
||||
xconn: &'a XConnection,
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer {
|
||||
xconn,
|
||||
ptr,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for XSmartPointer<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFree)(self.ptr as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn alloc_class_hint(&self) -> XSmartPointer<ffi::XClassHint> {
|
||||
XSmartPointer::new(self, unsafe { (self.xlib.XAllocClassHint)() })
|
||||
.expect("`XAllocClassHint` returned null; out of memory")
|
||||
}
|
||||
|
||||
pub fn alloc_size_hints(&self) -> XSmartPointer<ffi::XSizeHints> {
|
||||
XSmartPointer::new(self, unsafe { (self.xlib.XAllocSizeHints)() })
|
||||
.expect("`XAllocSizeHints` returned null; out of memory")
|
||||
}
|
||||
|
||||
pub fn alloc_wm_hints(&self) -> XSmartPointer<ffi::XWMHints> {
|
||||
XSmartPointer::new(self, unsafe { (self.xlib.XAllocWMHints)() })
|
||||
.expect("`XAllocWMHints` returned null; out of memory")
|
||||
}
|
||||
}
|
||||
104
src/platform/linux/x11/util/mod.rs
Normal file
104
src/platform/linux/x11/util/mod.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
|
||||
// *results may vary
|
||||
|
||||
mod atom;
|
||||
mod client_msg;
|
||||
mod format;
|
||||
mod geometry;
|
||||
mod hint;
|
||||
mod icon;
|
||||
mod input;
|
||||
mod memory;
|
||||
mod randr;
|
||||
mod window_property;
|
||||
mod wm;
|
||||
|
||||
pub use self::atom::*;
|
||||
pub use self::client_msg::*;
|
||||
pub use self::format::*;
|
||||
pub use self::geometry::*;
|
||||
pub use self::hint::*;
|
||||
pub use self::icon::*;
|
||||
pub use self::input::*;
|
||||
pub use self::memory::*;
|
||||
pub use self::randr::*;
|
||||
pub use self::window_property::*;
|
||||
pub use self::wm::*;
|
||||
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::ops::BitAnd;
|
||||
use std::os::raw::*;
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
|
||||
let b_ptr = a as *const _ as *const B;
|
||||
unsafe { &*b_ptr }
|
||||
}
|
||||
|
||||
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
let wrapped = Some(value);
|
||||
if *field != wrapped {
|
||||
*field = wrapped;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where T:
|
||||
Copy + PartialEq + BitAnd<T, Output = T>
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
|
||||
pub struct Flusher<'a> {
|
||||
xconn: &'a XConnection,
|
||||
}
|
||||
|
||||
impl<'a> Flusher<'a> {
|
||||
pub fn new(xconn: &'a XConnection) -> Self {
|
||||
Flusher { xconn }
|
||||
}
|
||||
|
||||
// "I want this request sent now!"
|
||||
pub fn flush(self) -> Result<(), XError> {
|
||||
self.xconn.flush_requests()
|
||||
}
|
||||
|
||||
// "I want the response now too!"
|
||||
pub fn sync(self) -> Result<(), XError> {
|
||||
self.xconn.sync_with_server()
|
||||
}
|
||||
|
||||
// "I'm aware that this request hasn't been sent, and I'm okay with waiting."
|
||||
pub fn queue(self) {}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is impoartant, so pay attention!
|
||||
// Xlib has an output buffer, and tries to hide the async nature of X from you.
|
||||
// This buffer contains the requests you make, and is flushed under various circumstances:
|
||||
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"
|
||||
// 2. `XFlush` explicitly flushes
|
||||
// 3. `XSync` flushes and blocks until all requests are responded to
|
||||
// 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync internally.
|
||||
// When in doubt, check the X11 source; if a function calls `_XReply`, it flushes and waits.
|
||||
// All util functions that abstract an async function will return a `Flusher`.
|
||||
pub fn flush_requests(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XFlush)(self.display) };
|
||||
//println!("XFlush");
|
||||
// This isn't necessarily a useful time to check for errors (since our request hasn't
|
||||
// necessarily been processed yet)
|
||||
self.check_errors()
|
||||
}
|
||||
|
||||
pub fn sync_with_server(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XSync)(self.display, ffi::False) };
|
||||
//println!("XSync");
|
||||
self.check_errors()
|
||||
}
|
||||
}
|
||||
134
src/platform/linux/x11/util/randr.rs
Normal file
134
src/platform/linux/x11/util/randr.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use std::{env, slice};
|
||||
use std::str::FromStr;
|
||||
|
||||
use validate_hidpi_factor;
|
||||
use super::*;
|
||||
|
||||
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");
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let ppmm = (
|
||||
(width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)
|
||||
).sqrt();
|
||||
// Quantize 1/12 step size
|
||||
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
|
||||
assert!(validate_hidpi_factor(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 get_dimensions(&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 get_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> {
|
||||
(self.xlib.XrmInitialize)();
|
||||
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
|
||||
if resource_manager_str == ptr::null_mut() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
|
||||
let name : &str = "Xft.dpi:\t";
|
||||
for pair in res.split("\n") {
|
||||
if pair.starts_with(&name) {
|
||||
let res = &pair[name.len()..];
|
||||
return f64::from_str(&res).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub unsafe fn get_output_info(
|
||||
&self,
|
||||
resources: *mut ffi::XRRScreenResources,
|
||||
repr: &MonitorRepr,
|
||||
) -> Option<(String, f64)> {
|
||||
let output_info = (self.xrandr.XRRGetOutputInfo)(
|
||||
self.display,
|
||||
resources,
|
||||
repr.get_output(),
|
||||
);
|
||||
if output_info.is_null() {
|
||||
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
||||
// it's possible for it to return null.
|
||||
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596
|
||||
let _ = self.check_errors(); // discard `BadRROutput` error
|
||||
return None;
|
||||
}
|
||||
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.get_dimensions(),
|
||||
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
|
||||
)
|
||||
};
|
||||
|
||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||
Some((name, hidpi_factor))
|
||||
}
|
||||
}
|
||||
144
src/platform/linux/x11/util/window_property.rs
Normal file
144
src/platform/linux/x11/util/window_property.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use std;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub type Cardinal = c_long;
|
||||
pub const CARDINAL_SIZE: usize = mem::size_of::<c_long>();
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GetPropertyError {
|
||||
XError(XError),
|
||||
TypeMismatch(ffi::Atom),
|
||||
FormatMismatch(c_int),
|
||||
NothingAllocated,
|
||||
}
|
||||
|
||||
impl GetPropertyError {
|
||||
pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool {
|
||||
if let GetPropertyError::TypeMismatch(actual_type) = *self {
|
||||
actual_type == t
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
|
||||
// To test if `get_property` works correctly, set this to 1.
|
||||
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PropMode {
|
||||
Replace = ffi::PropModeReplace as isize,
|
||||
Prepend = ffi::PropModePrepend as isize,
|
||||
Append = ffi::PropModeAppend as isize,
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_property<T: Formattable>(
|
||||
&self,
|
||||
window: c_ulong,
|
||||
property: ffi::Atom,
|
||||
property_type: ffi::Atom,
|
||||
) -> Result<Vec<T>, GetPropertyError> {
|
||||
let mut data = Vec::new();
|
||||
let mut offset = 0;
|
||||
|
||||
let mut done = false;
|
||||
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,
|
||||
property,
|
||||
// This offset is in terms of 32-bit chunks.
|
||||
offset,
|
||||
// This is the quanity of 32-bit chunks to receive at once.
|
||||
PROPERTY_BUFFER_SIZE,
|
||||
ffi::False,
|
||||
property_type,
|
||||
&mut actual_type,
|
||||
&mut actual_format,
|
||||
// This is the quantity of items we retrieved in our format, NOT of 32-bit chunks!
|
||||
&mut quantity_returned,
|
||||
// ...and this is a quantity of bytes. So, this function deals in 3 different units.
|
||||
&mut bytes_after,
|
||||
&mut buf,
|
||||
);
|
||||
|
||||
if let Err(e) = self.check_errors() {
|
||||
return Err(GetPropertyError::XError(e));
|
||||
}
|
||||
|
||||
if actual_type != property_type {
|
||||
return Err(GetPropertyError::TypeMismatch(actual_type));
|
||||
}
|
||||
|
||||
let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT);
|
||||
if format_mismatch {
|
||||
return Err(GetPropertyError::FormatMismatch(actual_format));
|
||||
}
|
||||
|
||||
if !buf.is_null() {
|
||||
offset += PROPERTY_BUFFER_SIZE;
|
||||
let new_data = std::slice::from_raw_parts(
|
||||
buf as *mut T,
|
||||
quantity_returned as usize,
|
||||
);
|
||||
/*println!(
|
||||
"XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}",
|
||||
property,
|
||||
mem::size_of::<T>() * 8,
|
||||
data.len(),
|
||||
offset,
|
||||
quantity_returned,
|
||||
new_data,
|
||||
);*/
|
||||
data.extend_from_slice(&new_data);
|
||||
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
|
||||
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
|
||||
} else {
|
||||
return Err(GetPropertyError::NothingAllocated);
|
||||
}
|
||||
|
||||
done = bytes_after == 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn change_property<'a, T: Formattable>(
|
||||
&'a self,
|
||||
window: c_ulong,
|
||||
property: ffi::Atom,
|
||||
property_type: ffi::Atom,
|
||||
mode: PropMode,
|
||||
new_value: &[T],
|
||||
) -> Flusher<'a> {
|
||||
debug_assert_eq!(mem::size_of::<T>(), T::FORMAT.get_actual_size());
|
||||
unsafe {
|
||||
(self.xlib.XChangeProperty)(
|
||||
self.display,
|
||||
window,
|
||||
property,
|
||||
property_type,
|
||||
T::FORMAT as c_int,
|
||||
mode as c_int,
|
||||
new_value.as_ptr() as *const c_uchar,
|
||||
new_value.len() as c_int,
|
||||
);
|
||||
}
|
||||
/*println!(
|
||||
"XChangeProperty prop:{:?} val:{:?}",
|
||||
property,
|
||||
new_value,
|
||||
);*/
|
||||
Flusher::new(self)
|
||||
}
|
||||
}
|
||||
141
src/platform/linux/x11/util/wm.rs
Normal file
141
src/platform/linux/x11/util/wm.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
// This info is global to the window manager.
|
||||
lazy_static! {
|
||||
static ref SUPPORTED_HINTS: Mutex<Vec<ffi::Atom>> = Mutex::new(Vec::with_capacity(0));
|
||||
static ref WM_NAME: Mutex<Option<String>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
pub fn hint_is_supported(hint: ffi::Atom) -> bool {
|
||||
(*SUPPORTED_HINTS.lock()).contains(&hint)
|
||||
}
|
||||
|
||||
pub fn wm_name_is_one_of(names: &[&str]) -> bool {
|
||||
if let Some(ref name) = *WM_NAME.lock() {
|
||||
names.contains(&name.as_str())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn update_cached_wm_info(&self, root: ffi::Window) {
|
||||
*SUPPORTED_HINTS.lock() = self.get_supported_hints(root);
|
||||
*WM_NAME.lock() = self.get_wm_name(root);
|
||||
}
|
||||
|
||||
fn get_supported_hints(&self, root: ffi::Window) -> Vec<ffi::Atom> {
|
||||
let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") };
|
||||
self.get_property(
|
||||
root,
|
||||
supported_atom,
|
||||
ffi::XA_ATOM,
|
||||
).unwrap_or_else(|_| Vec::with_capacity(0))
|
||||
}
|
||||
|
||||
fn get_wm_name(&self, root: ffi::Window) -> Option<String> {
|
||||
let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") };
|
||||
let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") };
|
||||
|
||||
// Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
|
||||
// it working and being supported. This has been reported upstream, but due to the
|
||||
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
|
||||
// regardless of whether or not the WM claims to support it.
|
||||
//
|
||||
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
|
||||
// in 0.72.
|
||||
/*if !supported_hints.contains(&check_atom) {
|
||||
return None;
|
||||
}*/
|
||||
|
||||
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
|
||||
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
|
||||
/*if !supported_hints.contains(&wm_name_atom) {
|
||||
return None;
|
||||
}*/
|
||||
|
||||
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
|
||||
|
||||
// Querying this property on the root window will give us the ID of a child window created by
|
||||
// the WM.
|
||||
let root_window_wm_check = {
|
||||
let result = self.get_property(
|
||||
root,
|
||||
check_atom,
|
||||
ffi::XA_WINDOW,
|
||||
);
|
||||
|
||||
let wm_check = result
|
||||
.ok()
|
||||
.and_then(|wm_check| wm_check.get(0).cloned());
|
||||
|
||||
if let Some(wm_check) = wm_check {
|
||||
wm_check
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Querying the same property on the child window we were given, we should get this child
|
||||
// window's ID again.
|
||||
let child_window_wm_check = {
|
||||
let result = self.get_property(
|
||||
root_window_wm_check,
|
||||
check_atom,
|
||||
ffi::XA_WINDOW,
|
||||
);
|
||||
|
||||
let wm_check = result
|
||||
.ok()
|
||||
.and_then(|wm_check| wm_check.get(0).cloned());
|
||||
|
||||
if let Some(wm_check) = wm_check {
|
||||
wm_check
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// These values should be the same.
|
||||
if root_window_wm_check != child_window_wm_check {
|
||||
return None;
|
||||
}
|
||||
|
||||
// All of that work gives us a window ID that we can get the WM name from.
|
||||
let wm_name = {
|
||||
let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") };
|
||||
|
||||
let result = self.get_property(
|
||||
root_window_wm_check,
|
||||
wm_name_atom,
|
||||
utf8_string_atom,
|
||||
);
|
||||
|
||||
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
|
||||
// string. For more fun trivia, IceWM is also unique in including version and uname
|
||||
// information in this string (this means you'll have to be careful if you want to match
|
||||
// against it, though).
|
||||
// The unofficial 1.4 fork of IceWM still includes the extra details, but properly
|
||||
// returns a UTF8 string that isn't null-terminated.
|
||||
let no_utf8 = if let Err(ref err) = result {
|
||||
err.is_actual_property_type(ffi::XA_STRING)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if no_utf8 {
|
||||
self.get_property(
|
||||
root_window_wm_check,
|
||||
wm_name_atom,
|
||||
ffi::XA_STRING,
|
||||
)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}.ok();
|
||||
|
||||
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
||||
use std::ptr;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
use std::sync::Mutex;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
use libc;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::ffi;
|
||||
|
||||
@@ -18,6 +19,7 @@ pub struct XConnection {
|
||||
pub xinput2: ffi::XInput2,
|
||||
pub xlib_xcb: ffi::Xlib_xcb,
|
||||
pub display: *mut ffi::Display,
|
||||
pub x11_fd: c_int,
|
||||
pub latest_error: Mutex<Option<XError>>,
|
||||
}
|
||||
|
||||
@@ -29,12 +31,12 @@ pub type XErrorHandler = Option<unsafe extern fn(*mut ffi::Display, *mut ffi::XE
|
||||
impl XConnection {
|
||||
pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
|
||||
// opening the libraries
|
||||
let xlib = try!(ffi::Xlib::open());
|
||||
let xcursor = try!(ffi::Xcursor::open());
|
||||
let xrandr = try!(ffi::Xrandr_2_2_0::open());
|
||||
let xlib = ffi::Xlib::open()?;
|
||||
let xcursor = ffi::Xcursor::open()?;
|
||||
let xrandr = ffi::Xrandr_2_2_0::open()?;
|
||||
let xrandr_1_5 = ffi::Xrandr::open().ok();
|
||||
let xinput2 = try!(ffi::XInput2::open());
|
||||
let xlib_xcb = try!(ffi::Xlib_xcb::open());
|
||||
let xinput2 = ffi::XInput2::open()?;
|
||||
let xlib_xcb = ffi::Xlib_xcb::open()?;
|
||||
|
||||
unsafe { (xlib.XInitThreads)() };
|
||||
unsafe { (xlib.XSetErrorHandler)(error_handler) };
|
||||
@@ -48,14 +50,20 @@ impl XConnection {
|
||||
display
|
||||
};
|
||||
|
||||
// Get X11 socket file descriptor
|
||||
let fd = unsafe {
|
||||
(xlib.XConnectionNumber)(display)
|
||||
};
|
||||
|
||||
Ok(XConnection {
|
||||
xlib: xlib,
|
||||
xrandr: xrandr,
|
||||
xrandr_1_5: xrandr_1_5,
|
||||
xcursor: xcursor,
|
||||
xinput2: xinput2,
|
||||
xlib_xcb: xlib_xcb,
|
||||
display: display,
|
||||
xlib,
|
||||
xrandr,
|
||||
xrandr_1_5,
|
||||
xcursor,
|
||||
xinput2,
|
||||
xlib_xcb,
|
||||
display,
|
||||
x11_fd: fd,
|
||||
latest_error: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
@@ -63,8 +71,7 @@ impl XConnection {
|
||||
/// Checks whether an error has been triggered by the previous function calls.
|
||||
#[inline]
|
||||
pub fn check_errors(&self) -> Result<(), XError> {
|
||||
let error = self.latest_error.lock().unwrap().take();
|
||||
|
||||
let error = self.latest_error.lock().take();
|
||||
if let Some(error) = error {
|
||||
Err(error)
|
||||
} else {
|
||||
@@ -75,7 +82,13 @@ impl XConnection {
|
||||
/// Ignores any previous error.
|
||||
#[inline]
|
||||
pub fn ignore_error(&self) {
|
||||
*self.latest_error.lock().unwrap() = None;
|
||||
*self.latest_error.lock() = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XConnection {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.display.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use {ControlFlow, EventsLoopClosed};
|
||||
use cocoa::{self, appkit, foundation};
|
||||
use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow};
|
||||
use events::{self, ElementState, Event, MouseButton, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
|
||||
use events::{self, ElementState, Event, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use super::window::Window2;
|
||||
use std;
|
||||
use std::os::raw::*;
|
||||
use super::DeviceId;
|
||||
|
||||
|
||||
pub struct EventsLoop {
|
||||
modifiers: Modifiers,
|
||||
pub shared: Arc<Shared>,
|
||||
@@ -94,7 +94,7 @@ impl Shared {
|
||||
if let Ok(mut windows) = self.windows.lock() {
|
||||
windows.retain(|w| match w.upgrade() {
|
||||
Some(w) => w.id() != id,
|
||||
None => true,
|
||||
None => false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,7 @@ impl EventsLoop {
|
||||
where F: FnMut(Event),
|
||||
{
|
||||
unsafe {
|
||||
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("Events can only be polled from the main thread on macOS");
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ impl EventsLoop {
|
||||
where F: FnMut(Event) -> ControlFlow
|
||||
{
|
||||
unsafe {
|
||||
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!("Events can only be polled from the main thread on macOS");
|
||||
}
|
||||
}
|
||||
@@ -294,10 +294,7 @@ impl EventsLoop {
|
||||
|
||||
// FIXME: Document this. Why do we do this? Seems like it passes on events to window/app.
|
||||
// If we don't do this, window does not become main for some reason.
|
||||
match event_type {
|
||||
appkit::NSKeyDown => (),
|
||||
_ => appkit::NSApp().sendEvent_(ns_event),
|
||||
}
|
||||
appkit::NSApp().sendEvent_(ns_event);
|
||||
|
||||
let windows = self.shared.windows.lock().unwrap();
|
||||
let maybe_window = windows.iter()
|
||||
@@ -318,137 +315,74 @@ impl EventsLoop {
|
||||
});
|
||||
|
||||
match event_type {
|
||||
|
||||
appkit::NSKeyDown => {
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
let received_c_str = foundation::NSString::UTF8String(ns_event.characters());
|
||||
let received_str = std::ffi::CStr::from_ptr(received_c_str);
|
||||
|
||||
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
|
||||
let state = ElementState::Pressed;
|
||||
let code = NSEvent::keyCode(ns_event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: vkey,
|
||||
modifiers: event_mods(ns_event),
|
||||
},
|
||||
};
|
||||
for received_char in std::str::from_utf8(received_str.to_bytes()).unwrap().chars() {
|
||||
let window_event = WindowEvent::ReceivedCharacter(received_char);
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSKeyUp => {
|
||||
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
|
||||
|
||||
let state = ElementState::Released;
|
||||
let code = NSEvent::keyCode(ns_event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: vkey,
|
||||
modifiers: event_mods(ns_event),
|
||||
},
|
||||
};
|
||||
Some(into_event(window_event))
|
||||
},
|
||||
|
||||
appkit::NSFlagsChanged => {
|
||||
unsafe fn modifier_event(event: cocoa::base::id,
|
||||
keymask: NSEventModifierFlags,
|
||||
key: events::VirtualKeyCode,
|
||||
key_pressed: bool) -> Option<WindowEvent>
|
||||
{
|
||||
if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) {
|
||||
let state = ElementState::Pressed;
|
||||
let code = NSEvent::keyCode(event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
Some(window_event)
|
||||
|
||||
} else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) {
|
||||
let state = ElementState::Released;
|
||||
let code = NSEvent::keyCode(event) as u32;
|
||||
let window_event = WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: state,
|
||||
scancode: code,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
Some(window_event)
|
||||
|
||||
} else {
|
||||
None
|
||||
// https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881
|
||||
appkit::NSKeyUp => {
|
||||
if let Some(key_window) = maybe_key_window() {
|
||||
if event_mods(ns_event).logo {
|
||||
let _: () = msg_send![*key_window.window, sendEvent:ns_event];
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
},
|
||||
// similar to above, but for `<Cmd-.>`, the keyDown is suppressed instead of the
|
||||
// KeyUp, and the above trick does not appear to work.
|
||||
appkit::NSKeyDown => {
|
||||
let modifiers = event_mods(ns_event);
|
||||
let keycode = NSEvent::keyCode(ns_event);
|
||||
if modifiers.logo && keycode == 47 {
|
||||
modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false)
|
||||
.map(into_event)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
appkit::NSFlagsChanged => {
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSShiftKeyMask,
|
||||
events::VirtualKeyCode::LShift,
|
||||
self.modifiers.shift_pressed)
|
||||
{
|
||||
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSShiftKeyMask,
|
||||
self.modifiers.shift_pressed,
|
||||
) {
|
||||
self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSControlKeyMask,
|
||||
events::VirtualKeyCode::LControl,
|
||||
self.modifiers.ctrl_pressed)
|
||||
{
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSControlKeyMask,
|
||||
self.modifiers.ctrl_pressed,
|
||||
) {
|
||||
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSCommandKeyMask,
|
||||
events::VirtualKeyCode::LWin,
|
||||
self.modifiers.win_pressed)
|
||||
{
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSCommandKeyMask,
|
||||
self.modifiers.win_pressed,
|
||||
) {
|
||||
self.modifiers.win_pressed = !self.modifiers.win_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
if let Some(window_event) = modifier_event(ns_event,
|
||||
NSEventModifierFlags::NSAlternateKeyMask,
|
||||
events::VirtualKeyCode::LAlt,
|
||||
self.modifiers.alt_pressed)
|
||||
{
|
||||
if let Some(window_event) = modifier_event(
|
||||
ns_event,
|
||||
NSEventModifierFlags::NSAlternateKeyMask,
|
||||
self.modifiers.alt_pressed,
|
||||
) {
|
||||
self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
|
||||
events.push_back(into_event(window_event));
|
||||
}
|
||||
|
||||
let event = events.pop_front();
|
||||
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
|
||||
self.shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(events.into_iter());
|
||||
event
|
||||
},
|
||||
|
||||
appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Left, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Left, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Right, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Right, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle, modifiers: event_mods(ns_event) })) },
|
||||
appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Middle, modifiers: event_mods(ns_event) })) },
|
||||
|
||||
appkit::NSMouseEntered => {
|
||||
let window = match maybe_window.or_else(maybe_key_window) {
|
||||
Some(window) => window,
|
||||
@@ -464,14 +398,16 @@ impl EventsLoop {
|
||||
} else {
|
||||
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
|
||||
};
|
||||
|
||||
let view_rect = NSView::frame(*window.view);
|
||||
let scale_factor = window.hidpi_factor();
|
||||
|
||||
let x = (scale_factor * view_point.x as f32) as f64;
|
||||
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
|
||||
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
|
||||
let x = view_point.x as f64;
|
||||
let y = (view_rect.size.height - view_point.y) as f64;
|
||||
let window_event = WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
modifiers: event_mods(ns_event),
|
||||
};
|
||||
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
|
||||
|
||||
self.shared.pending_events.lock().unwrap().push_back(event);
|
||||
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
|
||||
},
|
||||
@@ -484,50 +420,30 @@ impl EventsLoop {
|
||||
// If the mouse movement was on one of our windows, use it.
|
||||
// Otherwise, if one of our windows is the key window (receiving input), use it.
|
||||
// Otherwise, return `None`.
|
||||
let window = match maybe_window.or_else(maybe_key_window) {
|
||||
Some(window) => window,
|
||||
match maybe_window.or_else(maybe_key_window) {
|
||||
Some(_window) => (),
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let window_point = ns_event.locationInWindow();
|
||||
let view_point = if ns_window == cocoa::base::nil {
|
||||
let ns_size = foundation::NSSize::new(0.0, 0.0);
|
||||
let ns_rect = foundation::NSRect::new(window_point, ns_size);
|
||||
let window_rect = window.window.convertRectFromScreen_(ns_rect);
|
||||
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
|
||||
} else {
|
||||
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
|
||||
};
|
||||
let view_rect = NSView::frame(*window.view);
|
||||
let scale_factor = window.hidpi_factor();
|
||||
|
||||
let mut events = std::collections::VecDeque::new();
|
||||
|
||||
{
|
||||
let x = (scale_factor * view_point.x as f32) as f64;
|
||||
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
|
||||
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
|
||||
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let delta_x = (scale_factor * ns_event.deltaX() as f32) as f64;
|
||||
let mut events = std::collections::VecDeque::with_capacity(3);
|
||||
|
||||
let delta_x = ns_event.deltaX() as f64;
|
||||
if delta_x != 0.0 {
|
||||
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
|
||||
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
|
||||
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
let delta_y = (scale_factor * ns_event.deltaY() as f32) as f64;
|
||||
let delta_y = ns_event.deltaY() as f64;
|
||||
if delta_y != 0.0 {
|
||||
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
|
||||
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
|
||||
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
if delta_x != 0.0 || delta_y != 0.0 {
|
||||
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
|
||||
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
|
||||
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
@@ -538,19 +454,22 @@ impl EventsLoop {
|
||||
|
||||
appkit::NSScrollWheel => {
|
||||
// If none of the windows received the scroll, return `None`.
|
||||
let window = match maybe_window {
|
||||
Some(window) => window,
|
||||
None => return None,
|
||||
};
|
||||
if maybe_window.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
use events::MouseScrollDelta::{LineDelta, PixelDelta};
|
||||
let scale_factor = window.hidpi_factor();
|
||||
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
|
||||
PixelDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
|
||||
scale_factor * ns_event.scrollingDeltaY() as f32)
|
||||
PixelDelta((
|
||||
ns_event.scrollingDeltaX() as f64,
|
||||
ns_event.scrollingDeltaY() as f64,
|
||||
).into())
|
||||
} else {
|
||||
LineDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
|
||||
scale_factor * ns_event.scrollingDeltaY() as f32)
|
||||
// TODO: This is probably wrong
|
||||
LineDelta(
|
||||
ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32,
|
||||
)
|
||||
};
|
||||
let phase = match ns_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
@@ -561,11 +480,15 @@ impl EventsLoop {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::MouseWheel {
|
||||
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
|
||||
PixelDelta(ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32)
|
||||
PixelDelta((
|
||||
ns_event.scrollingDeltaX() as f64,
|
||||
ns_event.scrollingDeltaY() as f64,
|
||||
).into())
|
||||
} else {
|
||||
LineDelta(ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32)
|
||||
LineDelta(
|
||||
ns_event.scrollingDeltaX() as f32,
|
||||
ns_event.scrollingDeltaY() as f32,
|
||||
)
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -621,8 +544,67 @@ impl Proxy {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn char_to_keycode(c: char) -> Option<events::VirtualKeyCode> {
|
||||
// We only translate keys that are affected by keyboard layout.
|
||||
//
|
||||
// Note that since keys are translated in a somewhat "dumb" way (reading character)
|
||||
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
|
||||
// letter to be received, and so we receive the wrong key.
|
||||
//
|
||||
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
|
||||
Some(match c {
|
||||
'a' | 'A' => events::VirtualKeyCode::A,
|
||||
'b' | 'B' => events::VirtualKeyCode::B,
|
||||
'c' | 'C' => events::VirtualKeyCode::C,
|
||||
'd' | 'D' => events::VirtualKeyCode::D,
|
||||
'e' | 'E' => events::VirtualKeyCode::E,
|
||||
'f' | 'F' => events::VirtualKeyCode::F,
|
||||
'g' | 'G' => events::VirtualKeyCode::G,
|
||||
'h' | 'H' => events::VirtualKeyCode::H,
|
||||
'i' | 'I' => events::VirtualKeyCode::I,
|
||||
'j' | 'J' => events::VirtualKeyCode::J,
|
||||
'k' | 'K' => events::VirtualKeyCode::K,
|
||||
'l' | 'L' => events::VirtualKeyCode::L,
|
||||
'm' | 'M' => events::VirtualKeyCode::M,
|
||||
'n' | 'N' => events::VirtualKeyCode::N,
|
||||
'o' | 'O' => events::VirtualKeyCode::O,
|
||||
'p' | 'P' => events::VirtualKeyCode::P,
|
||||
'q' | 'Q' => events::VirtualKeyCode::Q,
|
||||
'r' | 'R' => events::VirtualKeyCode::R,
|
||||
's' | 'S' => events::VirtualKeyCode::S,
|
||||
't' | 'T' => events::VirtualKeyCode::T,
|
||||
'u' | 'U' => events::VirtualKeyCode::U,
|
||||
'v' | 'V' => events::VirtualKeyCode::V,
|
||||
'w' | 'W' => events::VirtualKeyCode::W,
|
||||
'x' | 'X' => events::VirtualKeyCode::X,
|
||||
'y' | 'Y' => events::VirtualKeyCode::Y,
|
||||
'z' | 'Z' => events::VirtualKeyCode::Z,
|
||||
'1' | '!' => events::VirtualKeyCode::Key1,
|
||||
'2' | '@' => events::VirtualKeyCode::Key2,
|
||||
'3' | '#' => events::VirtualKeyCode::Key3,
|
||||
'4' | '$' => events::VirtualKeyCode::Key4,
|
||||
'5' | '%' => events::VirtualKeyCode::Key5,
|
||||
'6' | '^' => events::VirtualKeyCode::Key6,
|
||||
'7' | '&' => events::VirtualKeyCode::Key7,
|
||||
'8' | '*' => events::VirtualKeyCode::Key8,
|
||||
'9' | '(' => events::VirtualKeyCode::Key9,
|
||||
'0' | ')' => events::VirtualKeyCode::Key0,
|
||||
'=' | '+' => events::VirtualKeyCode::Equals,
|
||||
'-' | '_' => events::VirtualKeyCode::Minus,
|
||||
']' | '}' => events::VirtualKeyCode::RBracket,
|
||||
'[' | '{' => events::VirtualKeyCode::LBracket,
|
||||
'\''| '"' => events::VirtualKeyCode::Apostrophe,
|
||||
';' | ':' => events::VirtualKeyCode::Semicolon,
|
||||
'\\'| '|' => events::VirtualKeyCode::Backslash,
|
||||
',' | '<' => events::VirtualKeyCode::Comma,
|
||||
'/' | '?' => events::VirtualKeyCode::Slash,
|
||||
'.' | '>' => events::VirtualKeyCode::Period,
|
||||
'`' | '~' => events::VirtualKeyCode::Grave,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
pub fn scancode_to_keycode(code: c_ushort) -> Option<events::VirtualKeyCode> {
|
||||
Some(match code {
|
||||
0x00 => events::VirtualKeyCode::A,
|
||||
0x01 => events::VirtualKeyCode::S,
|
||||
@@ -682,13 +664,13 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
0x37 => events::VirtualKeyCode::LWin,
|
||||
0x38 => events::VirtualKeyCode::LShift,
|
||||
//0x39 => Caps lock,
|
||||
//0x3a => Left alt,
|
||||
0x3a => events::VirtualKeyCode::LAlt,
|
||||
0x3b => events::VirtualKeyCode::LControl,
|
||||
0x3c => events::VirtualKeyCode::RShift,
|
||||
//0x3d => Right alt,
|
||||
0x3d => events::VirtualKeyCode::RAlt,
|
||||
0x3e => events::VirtualKeyCode::RControl,
|
||||
//0x3f => Fn key,
|
||||
//0x40 => F17 Key,
|
||||
0x40 => events::VirtualKeyCode::F17,
|
||||
0x41 => events::VirtualKeyCode::Decimal,
|
||||
//0x42 -> unkown,
|
||||
0x43 => events::VirtualKeyCode::Multiply,
|
||||
@@ -701,10 +683,11 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
0x4a => events::VirtualKeyCode::VolumeDown,
|
||||
0x4b => events::VirtualKeyCode::Divide,
|
||||
0x4c => events::VirtualKeyCode::NumpadEnter,
|
||||
0x4e => events::VirtualKeyCode::Subtract,
|
||||
//0x4d => unkown,
|
||||
0x4e => events::VirtualKeyCode::Subtract,
|
||||
//0x4f => F18 key,
|
||||
//0x50 => F19 Key,
|
||||
0x4f => events::VirtualKeyCode::F18,
|
||||
0x50 => events::VirtualKeyCode::F19,
|
||||
0x51 => events::VirtualKeyCode::NumpadEquals,
|
||||
0x52 => events::VirtualKeyCode::Numpad0,
|
||||
0x53 => events::VirtualKeyCode::Numpad1,
|
||||
@@ -714,11 +697,11 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
0x57 => events::VirtualKeyCode::Numpad5,
|
||||
0x58 => events::VirtualKeyCode::Numpad6,
|
||||
0x59 => events::VirtualKeyCode::Numpad7,
|
||||
//0x5a => F20 Key,
|
||||
0x5a => events::VirtualKeyCode::F20,
|
||||
0x5b => events::VirtualKeyCode::Numpad8,
|
||||
0x5c => events::VirtualKeyCode::Numpad9,
|
||||
//0x5d => unkown,
|
||||
//0x5e => unkown,
|
||||
0x5d => events::VirtualKeyCode::Yen,
|
||||
//0x5e => JIS Ro,
|
||||
//0x5f => unkown,
|
||||
0x60 => events::VirtualKeyCode::F5,
|
||||
0x61 => events::VirtualKeyCode::F6,
|
||||
@@ -726,11 +709,11 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
0x63 => events::VirtualKeyCode::F3,
|
||||
0x64 => events::VirtualKeyCode::F8,
|
||||
0x65 => events::VirtualKeyCode::F9,
|
||||
//0x66 => unkown,
|
||||
//0x66 => JIS Eisuu (macOS),
|
||||
0x67 => events::VirtualKeyCode::F11,
|
||||
//0x68 => unkown,
|
||||
//0x68 => JIS Kana (macOS),
|
||||
0x69 => events::VirtualKeyCode::F13,
|
||||
//0x6a => F16 Key,
|
||||
0x6a => events::VirtualKeyCode::F16,
|
||||
0x6b => events::VirtualKeyCode::F14,
|
||||
//0x6c => unkown,
|
||||
0x6d => events::VirtualKeyCode::F10,
|
||||
@@ -758,7 +741,23 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
|
||||
})
|
||||
}
|
||||
|
||||
fn event_mods(event: cocoa::base::id) -> ModifiersState {
|
||||
pub fn check_function_keys(
|
||||
s: &String
|
||||
) -> Option<events::VirtualKeyCode> {
|
||||
if let Some(ch) = s.encode_utf16().next() {
|
||||
return Some(match ch {
|
||||
0xf718 => events::VirtualKeyCode::F21,
|
||||
0xf719 => events::VirtualKeyCode::F22,
|
||||
0xf71a => events::VirtualKeyCode::F23,
|
||||
0xf71b => events::VirtualKeyCode::F24,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
|
||||
let flags = unsafe {
|
||||
NSEvent::modifierFlags(event)
|
||||
};
|
||||
@@ -770,5 +769,44 @@ fn event_mods(event: cocoa::base::id) -> ModifiersState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
|
||||
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
|
||||
// and there is no easy way to navtively retrieve the layout-dependent character.
|
||||
// In winit, we use keycode to refer to the key's character, and so this function aligns
|
||||
// AppKit's terminology with ours.
|
||||
unsafe {
|
||||
msg_send![event, keyCode]
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn modifier_event(
|
||||
ns_event: cocoa::base::id,
|
||||
keymask: NSEventModifierFlags,
|
||||
was_key_pressed: bool,
|
||||
) -> Option<WindowEvent> {
|
||||
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|
||||
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
|
||||
let state = if was_key_pressed {
|
||||
ElementState::Released
|
||||
} else {
|
||||
ElementState::Pressed
|
||||
};
|
||||
|
||||
let scancode = get_scancode(ns_event);
|
||||
let virtual_keycode = scancode_to_keycode(scancode);
|
||||
Some(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: scancode as u32,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(ns_event),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
|
||||
107
src/platform/macos/ffi.rs
Normal file
107
src/platform/macos/ffi.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
// TODO: Upstream these
|
||||
|
||||
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use cocoa::base::id;
|
||||
use cocoa::foundation::{NSInteger, NSUInteger};
|
||||
use objc;
|
||||
|
||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||
|
||||
#[repr(C)]
|
||||
pub struct NSRange {
|
||||
pub location: NSUInteger,
|
||||
pub length: NSUInteger,
|
||||
}
|
||||
|
||||
impl NSRange {
|
||||
#[inline]
|
||||
pub fn new(location: NSUInteger, length: NSUInteger) -> NSRange {
|
||||
NSRange { location, length }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl objc::Encode for NSRange {
|
||||
fn encode() -> objc::Encoding {
|
||||
let encoding = format!(
|
||||
// TODO: Verify that this is correct
|
||||
"{{NSRange={}{}}}",
|
||||
NSUInteger::encode().as_str(),
|
||||
NSUInteger::encode().as_str(),
|
||||
);
|
||||
unsafe { objc::Encoding::from_str(&encoding) }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NSMutableAttributedString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSMutableAttributedString), alloc]
|
||||
}
|
||||
|
||||
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
|
||||
unsafe fn initWithString(self, string: id) -> id;
|
||||
unsafe fn initWithAttributedString(self, string: id) -> id;
|
||||
|
||||
unsafe fn string(self) -> id; // *mut NSString
|
||||
unsafe fn mutableString(self) -> id; // *mut NSMutableString
|
||||
unsafe fn length(self) -> NSUInteger;
|
||||
}
|
||||
|
||||
impl NSMutableAttributedString for id {
|
||||
unsafe fn init(self) -> id {
|
||||
msg_send![self, init]
|
||||
}
|
||||
|
||||
unsafe fn initWithString(self, string: id) -> id {
|
||||
msg_send![self, initWithString:string]
|
||||
}
|
||||
|
||||
unsafe fn initWithAttributedString(self, string: id) -> id {
|
||||
msg_send![self, initWithAttributedString:string]
|
||||
}
|
||||
|
||||
unsafe fn string(self) -> id {
|
||||
msg_send![self, string]
|
||||
}
|
||||
|
||||
unsafe fn mutableString(self) -> id {
|
||||
msg_send![self, mutableString]
|
||||
}
|
||||
|
||||
unsafe fn length(self) -> NSUInteger {
|
||||
msg_send![self, length]
|
||||
}
|
||||
}
|
||||
|
||||
pub const kCGBaseWindowLevelKey: NSInteger = 0;
|
||||
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
|
||||
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
|
||||
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
|
||||
pub const kCGNormalWindowLevelKey: NSInteger = 4;
|
||||
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
|
||||
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
|
||||
pub const kCGDockWindowLevelKey: NSInteger = 7;
|
||||
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
|
||||
pub const kCGStatusWindowLevelKey: NSInteger = 9;
|
||||
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
|
||||
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
|
||||
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
|
||||
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
|
||||
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
|
||||
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
|
||||
pub const kCGHelpWindowLevelKey: NSInteger = 16;
|
||||
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
|
||||
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
|
||||
pub const kCGCursorWindowLevelKey: NSInteger = 19;
|
||||
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
|
||||
|
||||
pub enum NSWindowLevel {
|
||||
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
|
||||
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
|
||||
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
|
||||
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
|
||||
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
|
||||
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
|
||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
||||
}
|
||||
@@ -8,6 +8,12 @@ use std::sync::Arc;
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
use {CreationError};
|
||||
|
||||
pub struct Window {
|
||||
@@ -25,8 +31,8 @@ impl ::std::ops::Deref for Window {
|
||||
impl Window {
|
||||
|
||||
pub fn new(events_loop: &EventsLoop,
|
||||
attributes: &::WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
|
||||
attributes: ::WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
|
||||
{
|
||||
let weak_shared = Arc::downgrade(&events_loop.shared);
|
||||
let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs)));
|
||||
@@ -38,5 +44,8 @@ impl Window {
|
||||
}
|
||||
|
||||
mod events_loop;
|
||||
mod ffi;
|
||||
mod monitor;
|
||||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
||||
use cocoa::appkit::NSScreen;
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSString, NSUInteger};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay};
|
||||
use std::collections::VecDeque;
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||
|
||||
use {PhysicalPosition, PhysicalSize};
|
||||
use super::EventsLoop;
|
||||
use super::window::IdRef;
|
||||
use super::window::{IdRef, Window2};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct MonitorId(CGDirectDisplayID);
|
||||
|
||||
impl EventsLoop {
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
let mut monitors = VecDeque::new();
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
for d in displays {
|
||||
monitors.push_back(MonitorId(d));
|
||||
}
|
||||
fn get_available_monitors() -> VecDeque<MonitorId> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for d in displays {
|
||||
monitors.push_back(MonitorId(d));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
VecDeque::with_capacity(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor() -> MonitorId {
|
||||
let id = MonitorId(CGDisplay::main().id);
|
||||
id
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
let id = MonitorId(CGDisplay::main().id);
|
||||
id
|
||||
get_primary_monitor()
|
||||
}
|
||||
|
||||
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorId {
|
||||
@@ -32,6 +47,41 @@ impl EventsLoop {
|
||||
}
|
||||
}
|
||||
|
||||
impl Window2 {
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorId {
|
||||
name: Option<String>,
|
||||
native_identifier: u32,
|
||||
dimensions: PhysicalSize,
|
||||
position: PhysicalPosition,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorId {
|
||||
name: self.get_name(),
|
||||
native_identifier: self.get_native_identifier(),
|
||||
dimensions: self.get_dimensions(),
|
||||
position: self.get_position(),
|
||||
hidpi_factor: self.get_hidpi_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
let MonitorId(display_id) = *self;
|
||||
@@ -44,29 +94,32 @@ impl MonitorId {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
let MonitorId(display_id) = *self;
|
||||
let display = CGDisplay::new(display_id);
|
||||
let dimension = {
|
||||
let height = display.pixels_high();
|
||||
let width = display.pixels_wide();
|
||||
(width as u32, height as u32)
|
||||
};
|
||||
dimension
|
||||
let height = display.pixels_high();
|
||||
let width = display.pixels_wide();
|
||||
PhysicalSize::from_logical(
|
||||
(width as f64, height as f64),
|
||||
self.get_hidpi_factor(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
unimplemented!()
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) };
|
||||
PhysicalPosition::from_logical(
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64),
|
||||
self.get_hidpi_factor(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
let screen = match self.get_nsscreen() {
|
||||
Some(screen) => screen,
|
||||
None => return 1.0, // default to 1.0 when we can't find the screen
|
||||
};
|
||||
|
||||
unsafe { NSScreen::backingScaleFactor(screen) as f32 }
|
||||
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
||||
}
|
||||
|
||||
pub(crate) fn get_nsscreen(&self) -> Option<id> {
|
||||
|
||||
149
src/platform/macos/util/cursor.rs
Normal file
149
src/platform/macos/util/cursor.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use cocoa::{
|
||||
appkit::NSImage, base::{id, nil, YES},
|
||||
foundation::{NSDictionary, NSPoint, NSString},
|
||||
};
|
||||
use objc::runtime::Sel;
|
||||
|
||||
use super::IntoOption;
|
||||
use MouseCursor;
|
||||
|
||||
pub enum Cursor {
|
||||
Native(&'static str),
|
||||
Undocumented(&'static str),
|
||||
WebKit(&'static str),
|
||||
}
|
||||
|
||||
impl From<MouseCursor> for Cursor {
|
||||
fn from(cursor: MouseCursor) -> Self {
|
||||
match cursor {
|
||||
MouseCursor::Arrow | MouseCursor::Default => Cursor::Native("arrowCursor"),
|
||||
MouseCursor::Hand => Cursor::Native("pointingHandCursor"),
|
||||
MouseCursor::Grabbing | MouseCursor::Grab => Cursor::Native("closedHandCursor"),
|
||||
MouseCursor::Text => Cursor::Native("IBeamCursor"),
|
||||
MouseCursor::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
|
||||
MouseCursor::Copy => Cursor::Native("dragCopyCursor"),
|
||||
MouseCursor::Alias => Cursor::Native("dragLinkCursor"),
|
||||
MouseCursor::NotAllowed | MouseCursor::NoDrop => Cursor::Native("operationNotAllowedCursor"),
|
||||
MouseCursor::ContextMenu => Cursor::Native("contextualMenuCursor"),
|
||||
MouseCursor::Crosshair => Cursor::Native("crosshairCursor"),
|
||||
MouseCursor::EResize => Cursor::Native("resizeRightCursor"),
|
||||
MouseCursor::NResize => Cursor::Native("resizeUpCursor"),
|
||||
MouseCursor::WResize => Cursor::Native("resizeLeftCursor"),
|
||||
MouseCursor::SResize => Cursor::Native("resizeDownCursor"),
|
||||
MouseCursor::EwResize | MouseCursor::ColResize => Cursor::Native("resizeLeftRightCursor"),
|
||||
MouseCursor::NsResize | MouseCursor::RowResize => Cursor::Native("resizeUpDownCursor"),
|
||||
|
||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
||||
MouseCursor::Help => Cursor::Undocumented("_helpCursor"),
|
||||
MouseCursor::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
|
||||
MouseCursor::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
|
||||
MouseCursor::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
|
||||
MouseCursor::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
|
||||
MouseCursor::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
|
||||
MouseCursor::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
|
||||
MouseCursor::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
|
||||
MouseCursor::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
|
||||
|
||||
// While these are available, the former just loads a white arrow,
|
||||
// and the latter loads an ugly deflated beachball!
|
||||
// MouseCursor::Move => Cursor::Undocumented("_moveCursor"),
|
||||
// MouseCursor::Wait => Cursor::Undocumented("_waitCursor"),
|
||||
|
||||
// An even more undocumented cursor...
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
MouseCursor::Wait | MouseCursor::Progress => Cursor::Undocumented("busyButClickableCursor"),
|
||||
|
||||
// For the rest, we can just snatch the cursors from WebKit...
|
||||
// They fit the style of the native cursors, and will seem
|
||||
// completely standard to macOS users.
|
||||
// https://stackoverflow.com/a/21786835/5435443
|
||||
MouseCursor::Move | MouseCursor::AllScroll => Cursor::WebKit("move"),
|
||||
MouseCursor::Cell => Cursor::WebKit("cell"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cursor {
|
||||
fn default() -> Self {
|
||||
Cursor::Native("arrowCursor")
|
||||
}
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub unsafe fn load(&self) -> id {
|
||||
match self {
|
||||
Cursor::Native(cursor_name) => {
|
||||
let sel = Sel::register(cursor_name);
|
||||
msg_send![class!(NSCursor), performSelector:sel]
|
||||
},
|
||||
Cursor::Undocumented(cursor_name) => {
|
||||
let class = class!(NSCursor);
|
||||
let sel = Sel::register(cursor_name);
|
||||
let sel = if msg_send![class, respondsToSelector:sel] {
|
||||
sel
|
||||
} else {
|
||||
warn!("Cursor `{}` appears to be invalid", cursor_name);
|
||||
sel!(arrowCursor)
|
||||
};
|
||||
msg_send![class, performSelector:sel]
|
||||
},
|
||||
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name)
|
||||
.unwrap_or_else(|message| {
|
||||
warn!("{}", message);
|
||||
Self::default().load()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note that loading `busybutclickable` with this code won't animate the frames;
|
||||
// instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result<id, String> {
|
||||
static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
|
||||
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
|
||||
let cursor_name = NSString::alloc(nil).init_str(cursor_name_str);
|
||||
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
|
||||
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
|
||||
let key_x = NSString::alloc(nil).init_str("hotx");
|
||||
let key_y = NSString::alloc(nil).init_str("hoty");
|
||||
|
||||
let cursor_path: id = msg_send![cursor_root,
|
||||
stringByAppendingPathComponent:cursor_name
|
||||
];
|
||||
let pdf_path: id = msg_send![cursor_path,
|
||||
stringByAppendingPathComponent:cursor_pdf
|
||||
];
|
||||
let info_path: id = msg_send![cursor_path,
|
||||
stringByAppendingPathComponent:cursor_plist
|
||||
];
|
||||
|
||||
let image = NSImage::alloc(nil)
|
||||
.initByReferencingFile_(pdf_path)
|
||||
// This will probably never be `None`, since images are loaded lazily...
|
||||
.into_option()
|
||||
// because of that, we need to check for validity.
|
||||
.filter(|image| image.isValid() == YES)
|
||||
.ok_or_else(||
|
||||
format!("Failed to read image for `{}` cursor", cursor_name_str)
|
||||
)?;
|
||||
let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path)
|
||||
.into_option()
|
||||
.ok_or_else(||
|
||||
format!("Failed to read info for `{}` cursor", cursor_name_str)
|
||||
)?;
|
||||
let x = info.valueForKey_(key_x);
|
||||
let y = info.valueForKey_(key_y);
|
||||
let point = NSPoint::new(
|
||||
msg_send![x, doubleValue],
|
||||
msg_send![y, doubleValue],
|
||||
);
|
||||
let cursor: id = msg_send![class!(NSCursor), alloc];
|
||||
let cursor: id = msg_send![cursor, initWithImage:image hotSpot:point];
|
||||
cursor
|
||||
.into_option()
|
||||
.ok_or_else(||
|
||||
format!("Failed to initialize `{}` cursor", cursor_name_str)
|
||||
)
|
||||
}
|
||||
14
src/platform/macos/util/into_option.rs
Normal file
14
src/platform/macos/util/into_option.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use cocoa::base::{id, nil};
|
||||
|
||||
pub trait IntoOption: Sized {
|
||||
fn into_option(self) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl IntoOption for id {
|
||||
fn into_option(self) -> Option<Self> {
|
||||
match self != nil {
|
||||
true => Some(self),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/platform/macos/util/mod.rs
Normal file
63
src/platform/macos/util/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
mod cursor;
|
||||
mod into_option;
|
||||
|
||||
pub use self::{cursor::Cursor, into_option::IntoOption};
|
||||
|
||||
use cocoa::appkit::NSWindowStyleMask;
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSRect, NSUInteger};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc::runtime::{Class, Object};
|
||||
|
||||
use platform::platform::ffi;
|
||||
use platform::platform::window::IdRef;
|
||||
|
||||
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
|
||||
location: ffi::NSNotFound as NSUInteger,
|
||||
length: 0,
|
||||
};
|
||||
|
||||
// For consistency with other platforms, this will...
|
||||
// 1. translate the bottom-left window corner into the top-left window corner
|
||||
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
|
||||
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
|
||||
}
|
||||
|
||||
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
window.setStyleMask_(mask);
|
||||
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
||||
window.makeFirstResponder_(view);
|
||||
}
|
||||
|
||||
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
|
||||
let current_style_mask = window.styleMask();
|
||||
if on {
|
||||
window.setStyleMask_(current_style_mask | mask);
|
||||
} else {
|
||||
window.setStyleMask_(current_style_mask & (!mask));
|
||||
}
|
||||
|
||||
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
||||
window.makeFirstResponder_(view);
|
||||
}
|
||||
|
||||
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
|
||||
let superclass: id = msg_send![this, superclass];
|
||||
&*(superclass as *const _)
|
||||
}
|
||||
|
||||
pub unsafe fn create_input_context(view: id) -> IdRef {
|
||||
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
|
||||
let input_context: id = msg_send![input_context, initWithClient:view];
|
||||
IdRef::new(input_context)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn open_emoji_picker() {
|
||||
let app: id = msg_send![class!(NSApplication), sharedApplication];
|
||||
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
|
||||
}
|
||||
663
src/platform/macos/view.rs
Normal file
663
src/platform/macos/view.rs
Normal file
@@ -0,0 +1,663 @@
|
||||
// This is a pretty close port of the implementation in GLFW:
|
||||
// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m
|
||||
|
||||
use std::{slice, str};
|
||||
use std::boxed::Box;
|
||||
use std::collections::VecDeque;
|
||||
use std::os::raw::*;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::appkit::{NSEvent, NSView, NSWindow};
|
||||
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES};
|
||||
|
||||
use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
|
||||
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, scancode_to_keycode, char_to_keycode, check_function_keys, get_scancode};
|
||||
use platform::platform::util;
|
||||
use platform::platform::ffi::*;
|
||||
use platform::platform::window::{get_window_id, IdRef};
|
||||
use events;
|
||||
|
||||
struct ViewState {
|
||||
window: id,
|
||||
shared: Weak<Shared>,
|
||||
cursor: Arc<Mutex<util::Cursor>>,
|
||||
ime_spot: Option<(f64, f64)>,
|
||||
raw_characters: Option<String>,
|
||||
is_key_down: bool,
|
||||
}
|
||||
|
||||
pub fn new_view(window: id, shared: Weak<Shared>) -> (IdRef, Weak<Mutex<util::Cursor>>) {
|
||||
let cursor = Default::default();
|
||||
let cursor_access = Arc::downgrade(&cursor);
|
||||
let state = ViewState {
|
||||
window,
|
||||
shared,
|
||||
cursor,
|
||||
ime_spot: None,
|
||||
raw_characters: None,
|
||||
is_key_down: false,
|
||||
};
|
||||
unsafe {
|
||||
// This is free'd in `dealloc`
|
||||
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
|
||||
let view: id = msg_send![VIEW_CLASS.0, alloc];
|
||||
(IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
let content_rect = NSWindow::contentRectForFrameRect_(
|
||||
state.window,
|
||||
NSWindow::frame(state.window),
|
||||
);
|
||||
let base_x = content_rect.origin.x as f64;
|
||||
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
|
||||
state.ime_spot = Some((base_x + x, base_y - y));
|
||||
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewClass(*const Class);
|
||||
unsafe impl Send for ViewClass {}
|
||||
unsafe impl Sync for ViewClass {}
|
||||
|
||||
lazy_static! {
|
||||
static ref VIEW_CLASS: ViewClass = unsafe {
|
||||
let superclass = class!(NSView);
|
||||
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
|
||||
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
|
||||
decl.add_method(
|
||||
sel!(initWithWinit:),
|
||||
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(drawRect:),
|
||||
draw_rect as extern fn(&Object, Sel, NSRect),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(resetCursorRects),
|
||||
reset_cursor_rects as extern fn(&Object, Sel),
|
||||
);
|
||||
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
|
||||
decl.add_method(
|
||||
sel!(markedRange),
|
||||
marked_range as extern fn(&Object, Sel) -> NSRange,
|
||||
);
|
||||
decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange);
|
||||
decl.add_method(
|
||||
sel!(setMarkedText:selectedRange:replacementRange:),
|
||||
set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange),
|
||||
);
|
||||
decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel));
|
||||
decl.add_method(
|
||||
sel!(validAttributesForMarkedText),
|
||||
valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(attributedSubstringForProposedRange:actualRange:),
|
||||
attributed_substring_for_proposed_range
|
||||
as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(insertText:replacementRange:),
|
||||
insert_text as extern fn(&Object, Sel, id, NSRange),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(characterIndexForPoint:),
|
||||
character_index_for_point as extern fn(&Object, Sel, NSPoint) -> NSUInteger,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(firstRectForCharacterRange:actualRange:),
|
||||
first_rect_for_character_range
|
||||
as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(doCommandBySelector:),
|
||||
do_command_by_selector as extern fn(&Object, Sel, Sel),
|
||||
);
|
||||
decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseDown:), mouse_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseUp:), mouse_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(rightMouseDown:), right_mouse_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(rightMouseUp:), right_mouse_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(otherMouseDown:), other_mouse_down as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(otherMouseUp:), other_mouse_up as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseMoved:), mouse_moved as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL);
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
decl.add_ivar::<id>("markedText");
|
||||
let protocol = Protocol::get("NSTextInputClient").unwrap();
|
||||
decl.add_protocol(&protocol);
|
||||
ViewClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern fn dealloc(this: &Object, _sel: Sel) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
let _: () = msg_send![marked_text, release];
|
||||
Box::from_raw(state as *mut ViewState);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
|
||||
unsafe {
|
||||
let this: id = msg_send![this, init];
|
||||
if this != nil {
|
||||
(*this).set_ivar("winitState", state);
|
||||
let marked_text = <id as NSMutableAttributedString>::init(
|
||||
NSMutableAttributedString::alloc(nil),
|
||||
);
|
||||
(*this).set_ivar("markedText", marked_text);
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
extern 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);
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::Refresh,
|
||||
};
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
|
||||
let superclass = util::superclass(this);
|
||||
let () = msg_send![super(this, superclass), drawRect:rect];
|
||||
}
|
||||
}
|
||||
|
||||
extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let bounds: NSRect = msg_send![this, bounds];
|
||||
let cursor = state.cursor.lock().unwrap().load();
|
||||
let _: () = msg_send![this,
|
||||
addCursorRect:bounds
|
||||
cursor:cursor
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
|
||||
//println!("hasMarkedText");
|
||||
unsafe {
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
(marked_text.length() > 0) as i8
|
||||
}
|
||||
}
|
||||
|
||||
extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
|
||||
//println!("markedRange");
|
||||
unsafe {
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
let length = marked_text.length();
|
||||
if length > 0 {
|
||||
NSRange::new(0, length - 1)
|
||||
} else {
|
||||
util::EMPTY_RANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange {
|
||||
//println!("selectedRange");
|
||||
util::EMPTY_RANGE
|
||||
}
|
||||
|
||||
extern fn set_marked_text(
|
||||
this: &mut Object,
|
||||
_sel: Sel,
|
||||
string: id,
|
||||
_selected_range: NSRange,
|
||||
_replacement_range: NSRange,
|
||||
) {
|
||||
//println!("setMarkedText");
|
||||
unsafe {
|
||||
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
|
||||
let _: () = msg_send![(*marked_text_ref), release];
|
||||
let marked_text = NSMutableAttributedString::alloc(nil);
|
||||
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
|
||||
if has_attr {
|
||||
marked_text.initWithAttributedString(string);
|
||||
} else {
|
||||
marked_text.initWithString(string);
|
||||
};
|
||||
*marked_text_ref = marked_text;
|
||||
}
|
||||
}
|
||||
|
||||
extern fn unmark_text(this: &Object, _sel: Sel) {
|
||||
//println!("unmarkText");
|
||||
unsafe {
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
let mutable_string = marked_text.mutableString();
|
||||
let _: () = msg_send![mutable_string, setString:""];
|
||||
let input_context: id = msg_send![this, inputContext];
|
||||
let _: () = msg_send![input_context, discardMarkedText];
|
||||
}
|
||||
}
|
||||
|
||||
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
|
||||
//println!("validAttributesForMarkedText");
|
||||
unsafe { msg_send![class!(NSArray), array] }
|
||||
}
|
||||
|
||||
extern fn attributed_substring_for_proposed_range(
|
||||
_this: &Object,
|
||||
_sel: Sel,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut c_void, // *mut NSRange
|
||||
) -> id {
|
||||
//println!("attributedSubstringForProposedRange");
|
||||
nil
|
||||
}
|
||||
|
||||
extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger {
|
||||
//println!("characterIndexForPoint");
|
||||
0
|
||||
}
|
||||
|
||||
extern fn first_rect_for_character_range(
|
||||
this: &Object,
|
||||
_sel: Sel,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut c_void, // *mut NSRange
|
||||
) -> NSRect {
|
||||
//println!("firstRectForCharacterRange");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
let (x, y) = state.ime_spot.unwrap_or_else(|| {
|
||||
let content_rect = NSWindow::contentRectForFrameRect_(
|
||||
state.window,
|
||||
NSWindow::frame(state.window),
|
||||
);
|
||||
let x = content_rect.origin.x;
|
||||
let y = util::bottom_left_to_top_left(content_rect);
|
||||
(x, y)
|
||||
});
|
||||
|
||||
NSRect::new(
|
||||
NSPoint::new(x as _, y as _),
|
||||
NSSize::new(0.0, 0.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) {
|
||||
//println!("insertText");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
|
||||
let characters = if has_attr {
|
||||
// This is a *mut NSAttributedString
|
||||
msg_send![string, string]
|
||||
} else {
|
||||
// This is already a *mut NSString
|
||||
string
|
||||
};
|
||||
|
||||
let slice = slice::from_raw_parts(
|
||||
characters.UTF8String() as *const c_uchar,
|
||||
characters.len(),
|
||||
);
|
||||
let string = str::from_utf8_unchecked(slice);
|
||||
state.is_key_down = true;
|
||||
|
||||
// We don't need this now, but it's here if that changes.
|
||||
//let event: id = msg_send![class!(NSApp), currentEvent];
|
||||
|
||||
let mut events = VecDeque::with_capacity(characters.len());
|
||||
for character in string.chars() {
|
||||
events.push_back(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::ReceivedCharacter(character),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.append(&mut events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
|
||||
//println!("doCommandBySelector");
|
||||
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character
|
||||
// happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let shared = if let Some(shared) = state.shared.upgrade() {
|
||||
shared
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
if command == sel!(insertNewline:) {
|
||||
// The `else` condition would emit the same character, but I'm keeping this here both...
|
||||
// 1) as a reminder for how `doCommandBySelector` works
|
||||
// 2) to make our use of carriage return explicit
|
||||
events.push_back(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::ReceivedCharacter('\r'),
|
||||
});
|
||||
} else {
|
||||
let raw_characters = state.raw_characters.take();
|
||||
if let Some(raw_characters) = raw_characters {
|
||||
for character in raw_characters.chars() {
|
||||
events.push_back(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::ReceivedCharacter(character),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.append(&mut events);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_characters(event: id, ignore_modifiers: bool) -> String {
|
||||
unsafe {
|
||||
let characters: id = if ignore_modifiers {
|
||||
msg_send![event, charactersIgnoringModifiers]
|
||||
} else {
|
||||
msg_send![event, characters]
|
||||
};
|
||||
|
||||
assert_ne!(characters, nil);
|
||||
let slice = slice::from_raw_parts(
|
||||
characters.UTF8String() as *const c_uchar,
|
||||
characters.len(),
|
||||
);
|
||||
|
||||
let string = str::from_utf8_unchecked(slice);
|
||||
|
||||
string.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves a layout-independent keycode given an event.
|
||||
fn retrieve_keycode(event: id) -> Option<events::VirtualKeyCode> {
|
||||
#[inline]
|
||||
fn get_code(ev: id, raw: bool) -> Option<events::VirtualKeyCode> {
|
||||
let characters = get_characters(ev, raw);
|
||||
characters.chars().next().map_or(None, |c| char_to_keycode(c))
|
||||
}
|
||||
|
||||
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
|
||||
// If we don't get a match, then we fall back to unmodified characters.
|
||||
let code = get_code(event, false)
|
||||
.or_else(|| {
|
||||
get_code(event, true)
|
||||
});
|
||||
|
||||
// We've checked all layout related keys, so fall through to scancode.
|
||||
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
|
||||
//
|
||||
// We're additionally checking here for F21-F24 keys, since their keycode
|
||||
// can vary, but we know that they are encoded
|
||||
// in characters property.
|
||||
code.or_else(|| {
|
||||
let scancode = get_scancode(event);
|
||||
scancode_to_keycode(scancode)
|
||||
.or_else(|| {
|
||||
check_function_keys(&get_characters(event, true))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
extern fn key_down(this: &Object, _sel: Sel, event: id) {
|
||||
//println!("keyDown");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
let window_id = WindowId(get_window_id(state.window));
|
||||
let characters = get_characters(event, false);
|
||||
|
||||
state.raw_characters = Some(characters.clone());
|
||||
|
||||
let scancode = get_scancode(event) as u32;
|
||||
let virtual_keycode = retrieve_keycode(event);
|
||||
let is_repeat = msg_send![event, isARepeat];
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
scancode,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
// Emit `ReceivedCharacter` for key repeats
|
||||
if is_repeat && state.is_key_down{
|
||||
for character in characters.chars() {
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ReceivedCharacter(character),
|
||||
};
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
} else {
|
||||
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
|
||||
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
|
||||
// keys to generate twice as many characters.
|
||||
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
|
||||
let (): _ = msg_send![this, interpretKeyEvents:array];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn key_up(this: &Object, _sel: Sel, event: id) {
|
||||
//println!("keyUp");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
state.is_key_down = false;
|
||||
|
||||
let scancode = get_scancode(event) as u32;
|
||||
let virtual_keycode = retrieve_keycode(event);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
scancode,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) {
|
||||
unsafe {
|
||||
let window: id = msg_send![this, window];
|
||||
let first_responder: id = msg_send![window, firstResponder];
|
||||
let this_ptr = this as *const _ as *mut _;
|
||||
if first_responder == this_ptr {
|
||||
let (): _ = msg_send![window, selectNextKeyView:this];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) {
|
||||
unsafe {
|
||||
let window: id = msg_send![this, window];
|
||||
let first_responder: id = msg_send![window, firstResponder];
|
||||
let this_ptr = this as *const _ as *mut _;
|
||||
if first_responder == this_ptr {
|
||||
let (): _ = msg_send![window, selectPreviousKeyView:this];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::MouseInput {
|
||||
device_id: DEVICE_ID,
|
||||
state: button_state,
|
||||
button,
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern fn mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Left, ElementState::Released);
|
||||
}
|
||||
|
||||
extern fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Right, ElementState::Released);
|
||||
}
|
||||
|
||||
extern fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
|
||||
}
|
||||
|
||||
fn mouse_motion(this: &Object, event: id) {
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
// We have to do this to have access to the `NSView` trait...
|
||||
let view: id = this as *const _ as *mut _;
|
||||
|
||||
let window_point = event.locationInWindow();
|
||||
let view_point = view.convertPoint_fromView_(window_point, nil);
|
||||
let view_rect = NSView::frame(view);
|
||||
|
||||
if view_point.x.is_sign_negative()
|
||||
|| view_point.y.is_sign_negative()
|
||||
|| view_point.x > view_rect.size.width
|
||||
|| view_point.y > view_rect.size.height {
|
||||
// Point is outside of the client area (view)
|
||||
return;
|
||||
}
|
||||
|
||||
let x = view_point.x as f64;
|
||||
let y = view_rect.size.height as f64 - view_point.y as f64;
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.window)),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(shared) = state.shared.upgrade() {
|
||||
shared.pending_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(window_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn mouse_moved(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern fn mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern fn right_mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
|
||||
extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL {
|
||||
YES
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ pub use self::platform::*;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path="windows/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
#[path="linux/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -21,5 +21,6 @@ mod platform;
|
||||
|
||||
#[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"),
|
||||
not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"),
|
||||
not(target_os = "freebsd"), not(target_os = "openbsd"), not(target_os = "emscripten")))]
|
||||
not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"),
|
||||
not(target_os = "emscripten")))]
|
||||
compile_error!("The platform you're compiling for is not supported by winit");
|
||||
|
||||
189
src/platform/windows/dpi.rs
Normal file
189
src/platform/windows/dpi.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
#![allow(non_snake_case, unused_unsafe)]
|
||||
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
|
||||
use winapi::shared::minwindef::{BOOL, UINT, FALSE};
|
||||
use winapi::shared::windef::{
|
||||
DPI_AWARENESS_CONTEXT,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
|
||||
HMONITOR,
|
||||
HWND,
|
||||
};
|
||||
use winapi::shared::winerror::S_OK;
|
||||
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
|
||||
use winapi::um::shellscalingapi::{
|
||||
MDT_EFFECTIVE_DPI,
|
||||
MONITOR_DPI_TYPE,
|
||||
PROCESS_DPI_AWARENESS,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
};
|
||||
use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX};
|
||||
use winapi::um::winnt::{HRESULT, LPCSTR};
|
||||
use winapi::um::winuser::{self, MONITOR_DEFAULTTONEAREST};
|
||||
|
||||
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
|
||||
|
||||
type SetProcessDPIAware = unsafe extern "system" fn () -> BOOL;
|
||||
type SetProcessDpiAwareness = unsafe extern "system" fn (
|
||||
value: PROCESS_DPI_AWARENESS,
|
||||
) -> HRESULT;
|
||||
type SetProcessDpiAwarenessContext = unsafe extern "system" fn (
|
||||
value: DPI_AWARENESS_CONTEXT,
|
||||
) -> BOOL;
|
||||
type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT;
|
||||
type GetDpiForMonitor = unsafe extern "system" fn (
|
||||
hmonitor: HMONITOR,
|
||||
dpi_type: MONITOR_DPI_TYPE,
|
||||
dpi_x: *mut UINT,
|
||||
dpi_y: *mut UINT,
|
||||
) -> 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
|
||||
);
|
||||
static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> = get_function!(
|
||||
"shcore.dll",
|
||||
GetDpiForMonitor
|
||||
);
|
||||
static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> = get_function!(
|
||||
"user32.dll",
|
||||
EnableNonClientDpiScaling
|
||||
);
|
||||
}
|
||||
|
||||
pub fn become_dpi_aware(enable: bool) {
|
||||
if !enable { return; }
|
||||
static ENABLE_DPI_AWARENESS: Once = ONCE_INIT;
|
||||
ENABLE_DPI_AWARENESS.call_once(|| { unsafe {
|
||||
if let Some(SetProcessDpiAwarenessContext) = get_function!(
|
||||
"user32.dll",
|
||||
SetProcessDpiAwarenessContext
|
||||
) {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
|
||||
== FALSE {
|
||||
// V2 only works with Windows 10 Creators Update (1703). Try using the older
|
||||
// V1 if we can't set V2.
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
||||
}
|
||||
} else if let Some(SetProcessDpiAwareness) = get_function!(
|
||||
"shcore.dll",
|
||||
SetProcessDpiAwareness
|
||||
) {
|
||||
// We are on Windows 8.1 or later.
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
} else if let Some(SetProcessDPIAware) = get_function!(
|
||||
"user32.dll",
|
||||
SetProcessDPIAware
|
||||
) {
|
||||
// We are on Vista or later.
|
||||
SetProcessDPIAware();
|
||||
}
|
||||
} });
|
||||
}
|
||||
|
||||
pub fn enable_non_client_dpi_scaling(hwnd: HWND) {
|
||||
unsafe {
|
||||
if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING {
|
||||
EnableNonClientDpiScaling(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option<u32> {
|
||||
unsafe {
|
||||
if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||
// MSDN says that "the values of *dpiX and *dpiY are identical. You only need to
|
||||
// record one of the values to determine the DPI and respond appropriately".
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
|
||||
return Some(dpi_x as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub const BASE_DPI: u32 = 96;
|
||||
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||
dpi as f64 / BASE_DPI as f64
|
||||
}
|
||||
|
||||
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
let hdc = winuser::GetDC(hwnd);
|
||||
if hdc.is_null() {
|
||||
panic!("[winit] `GetDC` returned null!");
|
||||
}
|
||||
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
match GetDpiForWindow(hwnd) {
|
||||
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||
dpi => dpi as u32,
|
||||
}
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if monitor.is_null() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||
dpi_x as u32
|
||||
} else {
|
||||
BASE_DPI
|
||||
}
|
||||
} else {
|
||||
// We are on Vista or later.
|
||||
if winuser::IsProcessDPIAware() != FALSE {
|
||||
// If the process is DPI aware, then scaling must be handled by the application using
|
||||
// this DPI value.
|
||||
GetDeviceCaps(hdc, LOGPIXELSX) as u32
|
||||
} else {
|
||||
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
|
||||
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
|
||||
// application and the WM.
|
||||
BASE_DPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
|
||||
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
|
||||
}
|
||||
226
src/platform/windows/drop_handler.rs
Normal file
226
src/platform/windows/drop_handler.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::{mem, ptr};
|
||||
|
||||
use winapi::ctypes::c_void;
|
||||
use winapi::shared::guiddef::REFIID;
|
||||
use winapi::shared::minwindef::{DWORD, MAX_PATH, UINT, ULONG};
|
||||
use winapi::shared::windef::{HWND, POINTL};
|
||||
use winapi::shared::winerror::S_OK;
|
||||
use winapi::um::objidl::IDataObject;
|
||||
use winapi::um::oleidl::{DROPEFFECT_COPY, DROPEFFECT_NONE, IDropTarget, IDropTargetVtbl};
|
||||
use winapi::um::winnt::HRESULT;
|
||||
use winapi::um::{shellapi, unknwnbase};
|
||||
|
||||
use platform::platform::events_loop::send_event;
|
||||
use platform::platform::WindowId;
|
||||
|
||||
use {Event, WindowId as SuperWindowId};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FileDropHandlerData {
|
||||
pub interface: IDropTarget,
|
||||
refcount: AtomicUsize,
|
||||
window: HWND,
|
||||
cursor_effect: DWORD,
|
||||
hovered_is_valid: bool, // If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted
|
||||
}
|
||||
|
||||
pub struct FileDropHandler {
|
||||
pub data: *mut FileDropHandlerData,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl FileDropHandler {
|
||||
pub fn new(window: HWND) -> FileDropHandler {
|
||||
let data = Box::new(FileDropHandlerData {
|
||||
interface: IDropTarget {
|
||||
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
|
||||
},
|
||||
refcount: AtomicUsize::new(1),
|
||||
window,
|
||||
cursor_effect: DROPEFFECT_NONE,
|
||||
hovered_is_valid: false,
|
||||
});
|
||||
FileDropHandler {
|
||||
data: Box::into_raw(data),
|
||||
}
|
||||
}
|
||||
|
||||
// Implement IUnknown
|
||||
pub unsafe extern "system" fn QueryInterface(
|
||||
_this: *mut unknwnbase::IUnknown,
|
||||
_riid: REFIID,
|
||||
_ppvObject: *mut *mut c_void,
|
||||
) -> HRESULT {
|
||||
// This function doesn't appear to be required for an `IDropTarget`.
|
||||
// An implementation would be nice however.
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn AddRef(this: *mut unknwnbase::IUnknown) -> ULONG {
|
||||
let drop_handler_data = Self::from_interface(this);
|
||||
let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1;
|
||||
count as ULONG
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn Release(this: *mut unknwnbase::IUnknown) -> ULONG {
|
||||
let drop_handler = Self::from_interface(this);
|
||||
let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1;
|
||||
if count == 0 {
|
||||
// Destroy the underlying data
|
||||
Box::from_raw(drop_handler as *mut FileDropHandlerData);
|
||||
}
|
||||
count as ULONG
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragEnter(
|
||||
this: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
_grfKeyState: DWORD,
|
||||
_pt: *const POINTL,
|
||||
pdwEffect: *mut DWORD,
|
||||
) -> HRESULT {
|
||||
use events::WindowEvent::HoveredFile;
|
||||
let drop_handler = Self::from_interface(this);
|
||||
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(drop_handler.window)),
|
||||
event: HoveredFile(filename),
|
||||
});
|
||||
});
|
||||
drop_handler.hovered_is_valid = hdrop.is_some();
|
||||
drop_handler.cursor_effect = if drop_handler.hovered_is_valid {
|
||||
DROPEFFECT_COPY
|
||||
} else {
|
||||
DROPEFFECT_NONE
|
||||
};
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragOver(
|
||||
this: *mut IDropTarget,
|
||||
_grfKeyState: DWORD,
|
||||
_pt: *const POINTL,
|
||||
pdwEffect: *mut DWORD,
|
||||
) -> HRESULT {
|
||||
let drop_handler = Self::from_interface(this);
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
|
||||
use events::WindowEvent::HoveredFileCancelled;
|
||||
let drop_handler = Self::from_interface(this);
|
||||
if drop_handler.hovered_is_valid {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(drop_handler.window)),
|
||||
event: HoveredFileCancelled,
|
||||
});
|
||||
}
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn Drop(
|
||||
this: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
_grfKeyState: DWORD,
|
||||
_pt: *const POINTL,
|
||||
_pdwEffect: *mut DWORD,
|
||||
) -> HRESULT {
|
||||
use events::WindowEvent::DroppedFile;
|
||||
let drop_handler = Self::from_interface(this);
|
||||
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
|
||||
send_event(Event::WindowEvent {
|
||||
window_id: SuperWindowId(WindowId(drop_handler.window)),
|
||||
event: DroppedFile(filename),
|
||||
});
|
||||
});
|
||||
if let Some(hdrop) = hdrop {
|
||||
shellapi::DragFinish(hdrop);
|
||||
}
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData {
|
||||
&mut *(this as *mut _)
|
||||
}
|
||||
|
||||
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<shellapi::HDROP>
|
||||
where
|
||||
F: Fn(PathBuf),
|
||||
{
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::winerror::{SUCCEEDED, DV_E_FORMATETC};
|
||||
use winapi::shared::wtypes::{CLIPFORMAT, DVASPECT_CONTENT};
|
||||
use winapi::um::objidl::{FORMATETC, TYMED_HGLOBAL};
|
||||
use winapi::um::shellapi::DragQueryFileW;
|
||||
use winapi::um::winuser::CF_HDROP;
|
||||
|
||||
let mut drop_format = FORMATETC {
|
||||
cfFormat: CF_HDROP as CLIPFORMAT,
|
||||
ptd: ptr::null(),
|
||||
dwAspect: DVASPECT_CONTENT,
|
||||
lindex: -1,
|
||||
tymed: TYMED_HGLOBAL,
|
||||
};
|
||||
|
||||
let mut medium = mem::uninitialized();
|
||||
let get_data_result = (*data_obj).GetData(&mut drop_format, &mut medium);
|
||||
if SUCCEEDED(get_data_result) {
|
||||
let hglobal = (*medium.u).hGlobal();
|
||||
let hdrop = (*hglobal) as shellapi::HDROP;
|
||||
|
||||
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
|
||||
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
|
||||
|
||||
let mut pathbuf: [wchar_t; MAX_PATH] = mem::uninitialized();
|
||||
|
||||
for i in 0..item_count {
|
||||
let character_count =
|
||||
DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), MAX_PATH as UINT) as usize;
|
||||
|
||||
if character_count > 0 {
|
||||
callback(OsString::from_wide(&pathbuf[0..character_count]).into());
|
||||
}
|
||||
}
|
||||
|
||||
return Some(hdrop);
|
||||
} else if get_data_result == DV_E_FORMATETC {
|
||||
// If the dropped item is not a file this error will occur.
|
||||
// In this case it is OK to return without taking further action.
|
||||
debug!("Error occured while processing dropped/hovered item: item is not a file.");
|
||||
return None;
|
||||
} else {
|
||||
debug!("Unexpected error occured while processing dropped/hovered item.");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileDropHandler {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
FileDropHandler::Release(self.data as *mut unknwnbase::IUnknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
|
||||
parent: unknwnbase::IUnknownVtbl {
|
||||
QueryInterface: FileDropHandler::QueryInterface,
|
||||
AddRef: FileDropHandler::AddRef,
|
||||
Release: FileDropHandler::Release,
|
||||
},
|
||||
DragEnter: FileDropHandler::DragEnter,
|
||||
DragOver: FileDropHandler::DragOver,
|
||||
DragLeave: FileDropHandler::DragLeave,
|
||||
Drop: FileDropHandler::Drop,
|
||||
};
|
||||
@@ -1,46 +1,90 @@
|
||||
use std::{char, ptr};
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
||||
|
||||
use events::VirtualKeyCode;
|
||||
use events::ModifiersState;
|
||||
|
||||
use winapi::shared::minwindef::{WPARAM, LPARAM};
|
||||
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT, HKL, HKL__};
|
||||
use winapi::um::winuser;
|
||||
|
||||
use ScanCode;
|
||||
use std::char;
|
||||
|
||||
const MAPVK_VK_TO_CHAR: u32 = 2;
|
||||
const MAPVK_VSC_TO_VK_EX: u32 = 3;
|
||||
fn key_pressed(vkey: c_int) -> bool {
|
||||
unsafe {
|
||||
(winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key_mods() -> ModifiersState {
|
||||
let mut mods = ModifiersState::default();
|
||||
unsafe {
|
||||
if winuser::GetKeyState(winuser::VK_SHIFT) & (1 << 15) == (1 << 15) {
|
||||
mods.shift = true;
|
||||
}
|
||||
if winuser::GetKeyState(winuser::VK_CONTROL) & (1 << 15) == (1 << 15) {
|
||||
mods.ctrl = true;
|
||||
}
|
||||
if winuser::GetKeyState(winuser::VK_MENU) & (1 << 15) == (1 << 15) {
|
||||
mods.alt = true;
|
||||
}
|
||||
if (winuser::GetKeyState(winuser::VK_LWIN) | winuser::GetKeyState(winuser::VK_RWIN)) & (1 << 15) == (1 << 15) {
|
||||
mods.logo = true;
|
||||
}
|
||||
}
|
||||
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);
|
||||
mods
|
||||
}
|
||||
|
||||
pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<VirtualKeyCode>) {
|
||||
let scancode = ((lparam >> 16) & 0xff) as u32;
|
||||
let extended = (lparam & 0x01000000) != 0;
|
||||
let vk = match wparam as i32 {
|
||||
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) as i32 },
|
||||
winuser::VK_CONTROL => if extended { winuser::VK_RCONTROL } else { winuser::VK_LCONTROL },
|
||||
winuser::VK_MENU => if extended { winuser::VK_RMENU } else { winuser::VK_LMENU },
|
||||
other => other
|
||||
};
|
||||
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(v_key, 0, keyboard_state.as_ptr(), unicode_bytes.as_mut_ptr(), unicode_bytes.len() as _, 0, hkl);
|
||||
if len >= 1 {
|
||||
char::decode_utf16(unicode_bytes.into_iter().cloned()).next().and_then(|c| c.ok())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Figures out if the keyboard layout has an AltGr key instead of an Alt key.
|
||||
///
|
||||
/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So,
|
||||
/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every
|
||||
/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If
|
||||
/// pressing AltGr outputs characters that are different from the standard characters, the layout
|
||||
/// uses AltGr. Otherwise, it doesn't.
|
||||
///
|
||||
/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416
|
||||
fn layout_uses_altgr() -> bool {
|
||||
unsafe {
|
||||
static ACTIVE_LAYOUT: AtomicPtr<HKL__> = AtomicPtr::new(ptr::null_mut());
|
||||
static USES_ALTGR: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
let hkl = winuser::GetKeyboardLayout(0);
|
||||
let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst);
|
||||
|
||||
if hkl == old_hkl {
|
||||
return USES_ALTGR.load(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let mut keyboard_state_altgr = [0u8; 256];
|
||||
// AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses
|
||||
// we have to emulate to do an AltGr test.
|
||||
keyboard_state_altgr[winuser::VK_MENU as usize] = 0x80;
|
||||
keyboard_state_altgr[winuser::VK_CONTROL as usize] = 0x80;
|
||||
|
||||
let keyboard_state_empty = [0u8; 256];
|
||||
|
||||
for v_key in 0..=255 {
|
||||
let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl);
|
||||
let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl);
|
||||
if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) {
|
||||
if noaltgr != altgr {
|
||||
USES_ALTGR.store(true, Ordering::SeqCst);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
USES_ALTGR.store(false, Ordering::SeqCst);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
|
||||
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
(scancode, match vk {
|
||||
match vkey {
|
||||
//winuser::VK_LBUTTON => Some(VirtualKeyCode::Lbutton),
|
||||
//winuser::VK_RBUTTON => Some(VirtualKeyCode::Rbutton),
|
||||
//winuser::VK_CANCEL => Some(VirtualKeyCode::Cancel),
|
||||
@@ -55,8 +99,8 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
|
||||
winuser::VK_RSHIFT => Some(VirtualKeyCode::RShift),
|
||||
winuser::VK_LCONTROL => Some(VirtualKeyCode::LControl),
|
||||
winuser::VK_RCONTROL => Some(VirtualKeyCode::RControl),
|
||||
winuser::VK_LMENU => Some(VirtualKeyCode::LMenu),
|
||||
winuser::VK_RMENU => Some(VirtualKeyCode::RMenu),
|
||||
winuser::VK_LMENU => Some(VirtualKeyCode::LAlt),
|
||||
winuser::VK_RMENU => Some(VirtualKeyCode::RAlt),
|
||||
winuser::VK_PAUSE => Some(VirtualKeyCode::Pause),
|
||||
winuser::VK_CAPITAL => Some(VirtualKeyCode::Capital),
|
||||
winuser::VK_KANA => Some(VirtualKeyCode::Kana),
|
||||
@@ -158,7 +202,7 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
|
||||
winuser::VK_F13 => Some(VirtualKeyCode::F13),
|
||||
winuser::VK_F14 => Some(VirtualKeyCode::F14),
|
||||
winuser::VK_F15 => Some(VirtualKeyCode::F15),
|
||||
/*winuser::VK_F16 => Some(VirtualKeyCode::F16),
|
||||
winuser::VK_F16 => Some(VirtualKeyCode::F16),
|
||||
winuser::VK_F17 => Some(VirtualKeyCode::F17),
|
||||
winuser::VK_F18 => Some(VirtualKeyCode::F18),
|
||||
winuser::VK_F19 => Some(VirtualKeyCode::F19),
|
||||
@@ -166,7 +210,7 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
|
||||
winuser::VK_F21 => Some(VirtualKeyCode::F21),
|
||||
winuser::VK_F22 => Some(VirtualKeyCode::F22),
|
||||
winuser::VK_F23 => Some(VirtualKeyCode::F23),
|
||||
winuser::VK_F24 => Some(VirtualKeyCode::F24),*/
|
||||
winuser::VK_F24 => Some(VirtualKeyCode::F24),
|
||||
winuser::VK_NUMLOCK => Some(VirtualKeyCode::Numlock),
|
||||
winuser::VK_SCROLL => Some(VirtualKeyCode::Scroll),
|
||||
winuser::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward),
|
||||
@@ -191,13 +235,13 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
|
||||
winuser::VK_OEM_COMMA => Some(VirtualKeyCode::Comma),
|
||||
winuser::VK_OEM_MINUS => Some(VirtualKeyCode::Minus),
|
||||
winuser::VK_OEM_PERIOD => Some(VirtualKeyCode::Period),
|
||||
winuser::VK_OEM_1 => map_text_keys(vk),
|
||||
winuser::VK_OEM_2 => map_text_keys(vk),
|
||||
winuser::VK_OEM_3 => map_text_keys(vk),
|
||||
winuser::VK_OEM_4 => map_text_keys(vk),
|
||||
winuser::VK_OEM_5 => map_text_keys(vk),
|
||||
winuser::VK_OEM_6 => map_text_keys(vk),
|
||||
winuser::VK_OEM_7 => map_text_keys(vk),
|
||||
winuser::VK_OEM_1 => map_text_keys(vkey),
|
||||
winuser::VK_OEM_2 => map_text_keys(vkey),
|
||||
winuser::VK_OEM_3 => map_text_keys(vkey),
|
||||
winuser::VK_OEM_4 => map_text_keys(vkey),
|
||||
winuser::VK_OEM_5 => map_text_keys(vkey),
|
||||
winuser::VK_OEM_6 => map_text_keys(vkey),
|
||||
winuser::VK_OEM_7 => map_text_keys(vkey),
|
||||
/*winuser::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */
|
||||
winuser::VK_OEM_102 => Some(VirtualKeyCode::OEM102),
|
||||
/*winuser::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey),
|
||||
@@ -212,13 +256,58 @@ pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<
|
||||
winuser::VK_PA1 => Some(VirtualKeyCode::Pa1),
|
||||
winuser::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/
|
||||
_ => None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_extended_keys(vkey: c_int, mut scancode: UINT, extended: bool) -> Option<(c_int, UINT)> {
|
||||
// Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/
|
||||
let vkey = match vkey {
|
||||
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(
|
||||
scancode,
|
||||
winuser::MAPVK_VSC_TO_VK_EX,
|
||||
) as _ },
|
||||
winuser::VK_CONTROL => if extended {
|
||||
winuser::VK_RCONTROL
|
||||
} else {
|
||||
winuser::VK_LCONTROL
|
||||
},
|
||||
winuser::VK_MENU => if extended {
|
||||
winuser::VK_RMENU
|
||||
} else {
|
||||
winuser::VK_LMENU
|
||||
},
|
||||
_ => match scancode {
|
||||
// This is only triggered when using raw input. Without this check, we get two events whenever VK_PAUSE is
|
||||
// pressed, the first one having scancode 0x1D but vkey VK_PAUSE...
|
||||
0x1D if vkey == winuser::VK_PAUSE => return None,
|
||||
// ...and the second having scancode 0x45 but an unmatched vkey!
|
||||
0x45 => winuser::VK_PAUSE,
|
||||
// VK_PAUSE and VK_SCROLL have the same scancode when using modifiers, alongside incorrect vkey values.
|
||||
0x46 => {
|
||||
if extended {
|
||||
scancode = 0x45;
|
||||
winuser::VK_PAUSE
|
||||
} else {
|
||||
winuser::VK_SCROLL
|
||||
}
|
||||
},
|
||||
_ => vkey,
|
||||
},
|
||||
};
|
||||
Some((vkey, scancode))
|
||||
}
|
||||
|
||||
pub fn process_key_params(wparam: WPARAM, lparam: LPARAM) -> Option<(ScanCode, Option<VirtualKeyCode>)> {
|
||||
let scancode = ((lparam >> 16) & 0xff) as UINT;
|
||||
let extended = (lparam & 0x01000000) != 0;
|
||||
handle_extended_keys(wparam as _, scancode, extended)
|
||||
.map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey)))
|
||||
}
|
||||
|
||||
// This is needed as windows doesn't properly distinguish
|
||||
// some virtual key codes for different keyboard layouts
|
||||
fn map_text_keys(win_virtual_key: i32) -> Option<VirtualKeyCode> {
|
||||
let char_key = unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, MAPVK_VK_TO_CHAR) } & 0x7FFF;
|
||||
let char_key = unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, winuser::MAPVK_VK_TO_CHAR) } & 0x7FFF;
|
||||
match char::from_u32(char_key) {
|
||||
Some(';') => Some(VirtualKeyCode::Semicolon),
|
||||
Some('/') => Some(VirtualKeyCode::Slash),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
114
src/platform/windows/icon.rs
Normal file
114
src/platform/windows/icon.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::{self, mem, ptr};
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use winapi::ctypes::{c_int, wchar_t};
|
||||
use winapi::shared::minwindef::{BYTE, LPARAM, WPARAM};
|
||||
use winapi::shared::windef::{HICON, HWND};
|
||||
use winapi::um::winuser;
|
||||
|
||||
use {Pixel, PIXEL_SIZE, Icon};
|
||||
use platform::platform::util;
|
||||
|
||||
impl Pixel {
|
||||
fn to_bgra(&mut self) {
|
||||
mem::swap(&mut self.r, &mut self.b);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IconType {
|
||||
Small = winuser::ICON_SMALL as isize,
|
||||
Big = winuser::ICON_BIG as isize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WinIcon {
|
||||
pub handle: HICON,
|
||||
}
|
||||
|
||||
unsafe impl Send for WinIcon {}
|
||||
|
||||
impl WinIcon {
|
||||
#[allow(dead_code)]
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, util::WinError> {
|
||||
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
|
||||
let handle = unsafe {
|
||||
winuser::LoadImageW(
|
||||
ptr::null_mut(),
|
||||
wide_path.as_ptr() as *const wchar_t,
|
||||
winuser::IMAGE_ICON,
|
||||
0, // 0 indicates that we want to use the actual width
|
||||
0, // and height
|
||||
winuser::LR_LOADFROMFILE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon { handle })
|
||||
} else {
|
||||
Err(util::WinError::from_last_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_icon(icon: Icon) -> Result<Self, util::WinError> {
|
||||
Self::from_rgba(icon.rgba, icon.width, icon.height)
|
||||
}
|
||||
|
||||
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, util::WinError> {
|
||||
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (width * height) as usize);
|
||||
let mut and_mask = Vec::with_capacity(pixel_count);
|
||||
let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) };
|
||||
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
|
||||
pixel.to_bgra();
|
||||
}
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
winuser::CreateIcon(
|
||||
ptr::null_mut(),
|
||||
width as c_int,
|
||||
height as c_int,
|
||||
1,
|
||||
(PIXEL_SIZE * 8) as BYTE,
|
||||
and_mask.as_ptr() as *const BYTE,
|
||||
rgba.as_ptr() as *const BYTE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon { handle })
|
||||
} else {
|
||||
Err(util::WinError::from_last_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
winuser::SendMessageW(
|
||||
hwnd,
|
||||
winuser::WM_SETICON,
|
||||
icon_type as WPARAM,
|
||||
self.handle as LPARAM,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinIcon {
|
||||
fn drop(&mut self) {
|
||||
unsafe { winuser::DestroyIcon(self.handle) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
winuser::SendMessageW(
|
||||
hwnd,
|
||||
winuser::WM_SETICON,
|
||||
icon_type as WPARAM,
|
||||
0 as LPARAM,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,65 @@ pub use self::window::Window;
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub parent: Option<HWND>,
|
||||
pub taskbar_icon: Option<::Icon>,
|
||||
pub no_redirection_bitmap: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
|
||||
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
|
||||
|
||||
// TODO: document what this means
|
||||
pub type Cursor = *const winapi::ctypes::wchar_t;
|
||||
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cursor(pub *const winapi::ctypes::wchar_t);
|
||||
unsafe impl Send for Cursor {}
|
||||
unsafe impl Sync for Cursor {}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(u32);
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub fn get_persistent_identifier(&self) -> Option<String> {
|
||||
if self.0 != 0 {
|
||||
raw_input::get_raw_input_device_name(self.0 as _)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId(0));
|
||||
|
||||
fn wrap_device_id(id: u32) -> ::DeviceId {
|
||||
::DeviceId(DeviceId(id))
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(HWND);
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
use std::ptr::null_mut;
|
||||
|
||||
WindowId(null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
mod dpi;
|
||||
mod drop_handler;
|
||||
mod event;
|
||||
mod events_loop;
|
||||
mod icon;
|
||||
mod monitor;
|
||||
mod raw_input;
|
||||
mod util;
|
||||
mod window;
|
||||
mod window_state;
|
||||
|
||||
@@ -1,178 +1,173 @@
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{DWORD, LPARAM, BOOL, TRUE};
|
||||
use winapi::shared::windef::{HMONITOR, HDC, LPRECT, HWND};
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
|
||||
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
|
||||
use winapi::um::winnt::LONG;
|
||||
use winapi::um::winuser;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::{mem, ptr};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::EventsLoop;
|
||||
use super::{EventsLoop, util};
|
||||
use dpi::{PhysicalPosition, PhysicalSize};
|
||||
use platform::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
|
||||
use platform::platform::window::Window;
|
||||
|
||||
/// Win32 implementation of the main `MonitorId` object.
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorId {
|
||||
/// The system name of the adapter.
|
||||
adapter_name: [wchar_t; 32],
|
||||
|
||||
/// Monitor handle.
|
||||
hmonitor: HMonitor,
|
||||
|
||||
/// The system name of the monitor.
|
||||
monitor_name: String,
|
||||
|
||||
/// True if this is the primary monitor.
|
||||
primary: bool,
|
||||
|
||||
/// The position of the monitor in pixels on the desktop.
|
||||
///
|
||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
||||
position: (i32, i32),
|
||||
|
||||
/// The current resolution in pixels on the monitor.
|
||||
dimensions: (u32, u32),
|
||||
|
||||
/// DPI scaling factor.
|
||||
hidpi_factor: f32,
|
||||
/// DPI scale factor.
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct HMonitor(HMONITOR);
|
||||
|
||||
unsafe impl Send for HMonitor {}
|
||||
|
||||
fn wchar_as_string(wchar: &[wchar_t]) -> String {
|
||||
String::from_utf16_lossy(wchar)
|
||||
.trim_right_matches(0 as char)
|
||||
.to_string()
|
||||
unsafe extern "system" fn monitor_enum_proc(
|
||||
hmonitor: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_place: LPRECT,
|
||||
data: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorId>;
|
||||
(*monitors).push_back(MonitorId::from_hmonitor(hmonitor));
|
||||
TRUE // continue enumeration
|
||||
}
|
||||
|
||||
unsafe extern "system" fn monitor_enum_proc(hmonitor: HMONITOR, _: HDC, place: LPRECT, data: LPARAM) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorId>;
|
||||
|
||||
let place = *place;
|
||||
let position = (place.left as i32, place.top as i32);
|
||||
let dimensions = ((place.right - place.left) as u32, (place.bottom - place.top) as u32);
|
||||
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed();
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
if winuser::GetMonitorInfoW(hmonitor, &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO) == 0 {
|
||||
// Some error occurred, just skip this monitor and go on.
|
||||
return TRUE;
|
||||
pub fn get_available_monitors() -> VecDeque<MonitorId> {
|
||||
let mut monitors: VecDeque<MonitorId> = VecDeque::new();
|
||||
unsafe {
|
||||
winuser::EnumDisplayMonitors(
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(monitor_enum_proc),
|
||||
&mut monitors as *mut _ as LPARAM,
|
||||
);
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
(*monitors).push_back(MonitorId {
|
||||
adapter_name: monitor_info.szDevice,
|
||||
hmonitor: HMonitor(hmonitor),
|
||||
monitor_name: wchar_as_string(&monitor_info.szDevice),
|
||||
primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0,
|
||||
position,
|
||||
dimensions,
|
||||
hidpi_factor: 1.0,
|
||||
});
|
||||
|
||||
// TRUE means continue enumeration.
|
||||
TRUE
|
||||
pub fn get_primary_monitor() -> MonitorId {
|
||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||
let hmonitor = unsafe {
|
||||
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
|
||||
};
|
||||
MonitorId::from_hmonitor(hmonitor)
|
||||
}
|
||||
|
||||
impl EventsLoop {
|
||||
// TODO: Investigate opportunities for caching
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
unsafe {
|
||||
let mut result: VecDeque<MonitorId> = VecDeque::new();
|
||||
winuser::EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(monitor_enum_proc), &mut result as *mut _ as LPARAM);
|
||||
result
|
||||
}
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
pub fn get_current_monitor(handle: HWND) -> MonitorId {
|
||||
unsafe {
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed();
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
|
||||
let hmonitor = winuser::MonitorFromWindow(handle, winuser::MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
winuser::GetMonitorInfoW(
|
||||
hmonitor,
|
||||
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
|
||||
);
|
||||
|
||||
let place = monitor_info.rcMonitor;
|
||||
let position = (place.left as i32, place.top as i32);
|
||||
let dimensions = (
|
||||
(place.right - place.left) as u32,
|
||||
(place.bottom - place.top) as u32,
|
||||
);
|
||||
|
||||
MonitorId {
|
||||
adapter_name: monitor_info.szDevice,
|
||||
hmonitor: super::monitor::HMonitor(hmonitor),
|
||||
monitor_name: wchar_as_string(&monitor_info.szDevice),
|
||||
primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0,
|
||||
position,
|
||||
dimensions,
|
||||
hidpi_factor: 1.0,
|
||||
}
|
||||
}
|
||||
pub fn get_current_monitor(hwnd: HWND) -> MonitorId {
|
||||
let hmonitor = unsafe {
|
||||
winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST)
|
||||
};
|
||||
MonitorId::from_hmonitor(hmonitor)
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
// we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag
|
||||
// TODO: it is possible to query the win32 API for the primary monitor, this should be done
|
||||
// instead
|
||||
for monitor in self.get_available_monitors().into_iter() {
|
||||
if monitor.primary {
|
||||
return monitor;
|
||||
}
|
||||
}
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Failed to find the primary monitor")
|
||||
impl Window {
|
||||
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
|
||||
get_available_monitors()
|
||||
}
|
||||
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
get_primary_monitor()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, util::WinError> {
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
let status = unsafe {
|
||||
winuser::GetMonitorInfoW(
|
||||
hmonitor,
|
||||
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
|
||||
)
|
||||
};
|
||||
if status == 0 {
|
||||
Err(util::WinError::from_last_error())
|
||||
} else {
|
||||
Ok(monitor_info)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
/// See the docs if the crate root file.
|
||||
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
|
||||
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
|
||||
let place = monitor_info.rcMonitor;
|
||||
let dimensions = (
|
||||
(place.right - place.left) as u32,
|
||||
(place.bottom - place.top) as u32,
|
||||
);
|
||||
MonitorId {
|
||||
hmonitor: HMonitor(hmonitor),
|
||||
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
|
||||
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
|
||||
position: (place.left as i32, place.top as i32),
|
||||
dimensions,
|
||||
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
||||
let left = self.position.0 as LONG;
|
||||
let right = left + self.dimensions.0 as LONG;
|
||||
let top = self.position.1 as LONG;
|
||||
let bottom = top + self.dimensions.1 as LONG;
|
||||
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
Some(self.monitor_name.clone())
|
||||
}
|
||||
|
||||
/// See the docs of the crate root file.
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> String {
|
||||
self.monitor_name.clone()
|
||||
}
|
||||
|
||||
/// See the docs of the crate root file.
|
||||
#[inline]
|
||||
pub fn get_hmonitor(&self) -> HMONITOR {
|
||||
self.hmonitor.0
|
||||
}
|
||||
|
||||
/// See the docs of the crate root file.
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
// TODO: retrieve the dimensions every time this is called
|
||||
self.dimensions
|
||||
}
|
||||
|
||||
/// This is a Win32-only function for `MonitorId` that returns the system name of the adapter
|
||||
/// device.
|
||||
#[inline]
|
||||
pub fn get_adapter_name(&self) -> &[wchar_t] {
|
||||
&self.adapter_name
|
||||
}
|
||||
|
||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
self.position
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
self.dimensions.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.position.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
}
|
||||
|
||||
235
src/platform/windows/raw_input.rs
Normal file
235
src/platform/windows/raw_input.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use std::mem::{self, size_of};
|
||||
use std::ptr;
|
||||
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{UINT, USHORT, TRUE};
|
||||
use winapi::shared::hidusage::{
|
||||
HID_USAGE_PAGE_GENERIC,
|
||||
HID_USAGE_GENERIC_MOUSE,
|
||||
HID_USAGE_GENERIC_KEYBOARD,
|
||||
};
|
||||
use winapi::shared::windef::HWND;
|
||||
use winapi::um::winnt::HANDLE;
|
||||
use winapi::um::winuser::{
|
||||
self,
|
||||
RAWINPUTDEVICELIST,
|
||||
RID_DEVICE_INFO,
|
||||
RID_DEVICE_INFO_MOUSE,
|
||||
RID_DEVICE_INFO_KEYBOARD,
|
||||
RID_DEVICE_INFO_HID,
|
||||
RIM_TYPEMOUSE,
|
||||
RIM_TYPEKEYBOARD,
|
||||
RIM_TYPEHID,
|
||||
RIDI_DEVICEINFO,
|
||||
RIDI_DEVICENAME,
|
||||
RAWINPUTDEVICE,
|
||||
RIDEV_DEVNOTIFY,
|
||||
RIDEV_INPUTSINK,
|
||||
HRAWINPUT,
|
||||
RAWINPUT,
|
||||
RAWINPUTHEADER,
|
||||
RID_INPUT,
|
||||
};
|
||||
|
||||
use platform::platform::util;
|
||||
use events::ElementState;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||
let list_size = size_of::<RAWINPUTDEVICELIST>() as UINT;
|
||||
|
||||
let mut num_devices = 0;
|
||||
let status = unsafe { winuser::GetRawInputDeviceList(
|
||||
ptr::null_mut(),
|
||||
&mut num_devices,
|
||||
list_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer = Vec::with_capacity(num_devices as _);
|
||||
|
||||
let num_stored = unsafe { winuser::GetRawInputDeviceList(
|
||||
buffer.as_ptr() as _,
|
||||
&mut num_devices,
|
||||
list_size,
|
||||
) };
|
||||
|
||||
if num_stored == UINT::max_value() {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(num_devices, num_stored);
|
||||
|
||||
unsafe { buffer.set_len(num_devices as _) };
|
||||
|
||||
Some(buffer)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum RawDeviceInfo {
|
||||
Mouse(RID_DEVICE_INFO_MOUSE),
|
||||
Keyboard(RID_DEVICE_INFO_KEYBOARD),
|
||||
Hid(RID_DEVICE_INFO_HID),
|
||||
}
|
||||
|
||||
impl From<RID_DEVICE_INFO> for RawDeviceInfo {
|
||||
fn from(info: RID_DEVICE_INFO) -> Self {
|
||||
unsafe {
|
||||
match info.dwType {
|
||||
RIM_TYPEMOUSE => RawDeviceInfo::Mouse(*info.u.mouse()),
|
||||
RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(*info.u.keyboard()),
|
||||
RIM_TYPEHID => RawDeviceInfo::Hid(*info.u.hid()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
|
||||
let mut info: RID_DEVICE_INFO = unsafe { mem::uninitialized() };
|
||||
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
|
||||
|
||||
info.cbSize = info_size;
|
||||
|
||||
let mut minimum_size = 0;
|
||||
let status = unsafe { winuser::GetRawInputDeviceInfoW(
|
||||
handle,
|
||||
RIDI_DEVICEINFO,
|
||||
&mut info as *mut _ as _,
|
||||
&mut minimum_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(info_size, status);
|
||||
|
||||
Some(info.into())
|
||||
}
|
||||
|
||||
pub fn get_raw_input_device_name(handle: HANDLE) -> Option<String> {
|
||||
let mut minimum_size = 0;
|
||||
let status = unsafe { winuser::GetRawInputDeviceInfoW(
|
||||
handle,
|
||||
RIDI_DEVICENAME,
|
||||
ptr::null_mut(),
|
||||
&mut minimum_size,
|
||||
) };
|
||||
|
||||
if status != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut name: Vec<wchar_t> = Vec::with_capacity(minimum_size as _);
|
||||
|
||||
let status = unsafe { winuser::GetRawInputDeviceInfoW(
|
||||
handle,
|
||||
RIDI_DEVICENAME,
|
||||
name.as_ptr() as _,
|
||||
&mut minimum_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(minimum_size, status);
|
||||
|
||||
unsafe { name.set_len(minimum_size as _) };
|
||||
|
||||
Some(util::wchar_to_string(&name))
|
||||
}
|
||||
|
||||
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
|
||||
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
|
||||
|
||||
let success = unsafe { winuser::RegisterRawInputDevices(
|
||||
devices.as_ptr() as _,
|
||||
devices.len() as _,
|
||||
device_size,
|
||||
) };
|
||||
|
||||
success == TRUE
|
||||
}
|
||||
|
||||
pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool {
|
||||
// RIDEV_DEVNOTIFY: receive hotplug events
|
||||
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
|
||||
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
|
||||
|
||||
let devices: [RAWINPUTDEVICE; 2] = [
|
||||
RAWINPUTDEVICE {
|
||||
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
||||
usUsage: HID_USAGE_GENERIC_MOUSE,
|
||||
dwFlags: flags,
|
||||
hwndTarget: window_handle,
|
||||
},
|
||||
RAWINPUTDEVICE {
|
||||
usUsagePage: HID_USAGE_PAGE_GENERIC,
|
||||
usUsage: HID_USAGE_GENERIC_KEYBOARD,
|
||||
dwFlags: flags,
|
||||
hwndTarget: window_handle,
|
||||
},
|
||||
];
|
||||
|
||||
register_raw_input_devices(&devices)
|
||||
}
|
||||
|
||||
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
|
||||
let mut data: RAWINPUT = unsafe { mem::uninitialized() };
|
||||
let mut data_size = size_of::<RAWINPUT>() as UINT;
|
||||
let header_size = size_of::<RAWINPUTHEADER>() as UINT;
|
||||
|
||||
let status = unsafe { winuser::GetRawInputData(
|
||||
handle,
|
||||
RID_INPUT,
|
||||
&mut data as *mut _ as _,
|
||||
&mut data_size,
|
||||
header_size,
|
||||
) };
|
||||
|
||||
if status == UINT::max_value() || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(data)
|
||||
}
|
||||
|
||||
|
||||
fn button_flags_to_element_state(button_flags: USHORT, down_flag: USHORT, up_flag: USHORT)
|
||||
-> Option<ElementState>
|
||||
{
|
||||
// We assume the same button won't be simultaneously pressed and released.
|
||||
if util::has_flag(button_flags, down_flag) {
|
||||
Some(ElementState::Pressed)
|
||||
} else if util::has_flag(button_flags, up_flag) {
|
||||
Some(ElementState::Released)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 3] {
|
||||
[
|
||||
button_flags_to_element_state(
|
||||
button_flags,
|
||||
winuser::RI_MOUSE_LEFT_BUTTON_DOWN,
|
||||
winuser::RI_MOUSE_LEFT_BUTTON_UP,
|
||||
),
|
||||
button_flags_to_element_state(
|
||||
button_flags,
|
||||
winuser::RI_MOUSE_MIDDLE_BUTTON_DOWN,
|
||||
winuser::RI_MOUSE_MIDDLE_BUTTON_UP,
|
||||
),
|
||||
button_flags_to_element_state(
|
||||
button_flags,
|
||||
winuser::RI_MOUSE_RIGHT_BUTTON_DOWN,
|
||||
winuser::RI_MOUSE_RIGHT_BUTTON_UP,
|
||||
),
|
||||
]
|
||||
}
|
||||
183
src/platform/windows/util.rs
Normal file
183
src/platform/windows/util.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use std::{self, mem, ptr, slice, io};
|
||||
use std::ops::BitAnd;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use MouseCursor;
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::winbase::{
|
||||
FormatMessageW,
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
||||
FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
lstrlenW,
|
||||
LocalFree,
|
||||
};
|
||||
use winapi::um::winnt::{
|
||||
LPCWSTR,
|
||||
MAKELANGID,
|
||||
LANG_NEUTRAL,
|
||||
SUBLANG_DEFAULT,
|
||||
};
|
||||
use winapi::um::winuser;
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where T:
|
||||
Copy + PartialEq + BitAnd<T, Output = T>
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
|
||||
String::from_utf16_lossy(wchar).to_string()
|
||||
}
|
||||
|
||||
pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
|
||||
let len = unsafe { lstrlenW(wchar) } as usize;
|
||||
let wchar_slice = unsafe { slice::from_raw_parts(wchar, len) };
|
||||
wchar_to_string(wchar_slice)
|
||||
}
|
||||
|
||||
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
|
||||
let mut data: T = mem::uninitialized();
|
||||
if fun(&mut data) != 0 {
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn win_to_err<F: FnOnce() -> BOOL>(f: F) -> Result<(), io::Error> {
|
||||
if f() != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor_pos() -> Option<POINT> {
|
||||
unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) }
|
||||
}
|
||||
|
||||
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
|
||||
unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) }
|
||||
}
|
||||
|
||||
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
|
||||
unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
let mut top_left = mem::zeroed();
|
||||
|
||||
win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?;
|
||||
win_to_err(|| winuser::GetClientRect(hwnd, &mut rect))?;
|
||||
rect.left += top_left.x;
|
||||
rect.top += top_left.y;
|
||||
rect.right += top_left.x;
|
||||
rect.bottom += top_left.y;
|
||||
|
||||
Ok(rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
|
||||
unsafe {
|
||||
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
|
||||
let style_ex = winuser::GetWindowLongW(hwnd, winuser::GWL_EXSTYLE);
|
||||
adjust_window_rect_with_styles(hwnd, style as _, style_ex as _, rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect_with_styles(hwnd: HWND, style: DWORD, style_ex: DWORD, rect: RECT) -> Option<RECT> {
|
||||
unsafe { status_map(|r| {
|
||||
*r = rect;
|
||||
|
||||
let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL;
|
||||
winuser::AdjustWindowRectEx(r, style as _ , b_menu, style_ex as _)
|
||||
}) }
|
||||
}
|
||||
|
||||
pub fn set_cursor_hidden(hidden: bool) {
|
||||
static HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
|
||||
if changed {
|
||||
unsafe{ winuser::ShowCursor(!hidden as BOOL) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
|
||||
unsafe {
|
||||
let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null());
|
||||
win_to_err(|| winuser::ClipCursor(rect_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_focused(window: HWND) -> bool {
|
||||
window == unsafe{ winuser::GetActiveWindow() }
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct WinError(Option<String>);
|
||||
|
||||
impl WinError {
|
||||
pub fn from_last_error() -> Self {
|
||||
WinError(unsafe { get_last_error() })
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_last_error() -> Option<String> {
|
||||
let err = GetLastError();
|
||||
if err != 0 {
|
||||
let buf_addr: LPCWSTR = {
|
||||
let mut buf_addr: LPCWSTR = mem::uninitialized();
|
||||
FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER
|
||||
| FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
ptr::null(),
|
||||
err,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as DWORD,
|
||||
// This is a pointer to a pointer
|
||||
&mut buf_addr as *mut LPCWSTR as *mut _,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
buf_addr
|
||||
};
|
||||
if !buf_addr.is_null() {
|
||||
let buf_len = lstrlenW(buf_addr) as usize;
|
||||
let buf_slice = std::slice::from_raw_parts(buf_addr, buf_len);
|
||||
let string = wchar_to_string(buf_slice);
|
||||
LocalFree(buf_addr as *mut _);
|
||||
return Some(string);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl MouseCursor {
|
||||
pub(crate) fn to_windows_cursor(self) -> *const wchar_t {
|
||||
match self {
|
||||
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
|
||||
MouseCursor::Hand => winuser::IDC_HAND,
|
||||
MouseCursor::Crosshair => winuser::IDC_CROSS,
|
||||
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
|
||||
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
|
||||
MouseCursor::Grab | MouseCursor::Grabbing |
|
||||
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
|
||||
MouseCursor::EResize | MouseCursor::WResize |
|
||||
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
|
||||
MouseCursor::NResize | MouseCursor::SResize |
|
||||
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
|
||||
MouseCursor::NeResize | MouseCursor::SwResize |
|
||||
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
|
||||
MouseCursor::NwResize | MouseCursor::SeResize |
|
||||
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
|
||||
MouseCursor::Wait => winuser::IDC_WAIT,
|
||||
MouseCursor::Progress => winuser::IDC_APPSTARTING,
|
||||
MouseCursor::Help => winuser::IDC_HELP,
|
||||
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
329
src/platform/windows/window_state.rs
Normal file
329
src/platform/windows/window_state.rs
Normal file
@@ -0,0 +1,329 @@
|
||||
use {MouseCursor, WindowAttributes};
|
||||
use std::{io, ptr};
|
||||
use std::sync::MutexGuard;
|
||||
use dpi::LogicalSize;
|
||||
use platform::platform::{util, events_loop};
|
||||
use platform::platform::icon::WinIcon;
|
||||
use winapi::shared::windef::{RECT, HWND};
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::um::winuser;
|
||||
|
||||
/// Contains information about states and the window that the callback is going to use.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowState {
|
||||
pub mouse: MouseProperties,
|
||||
|
||||
/// Used by `WM_GETMINMAXINFO`.
|
||||
pub min_size: Option<LogicalSize>,
|
||||
pub max_size: Option<LogicalSize>,
|
||||
|
||||
pub window_icon: Option<WinIcon>,
|
||||
pub taskbar_icon: Option<WinIcon>,
|
||||
|
||||
pub saved_window: Option<SavedWindow>,
|
||||
pub dpi_factor: f64,
|
||||
|
||||
pub fullscreen: Option<::MonitorId>,
|
||||
window_flags: WindowFlags,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedWindow {
|
||||
pub client_rect: RECT,
|
||||
pub dpi_factor: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MouseProperties {
|
||||
pub cursor: MouseCursor,
|
||||
cursor_flags: CursorFlags,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct CursorFlags: u8 {
|
||||
const GRABBED = 1 << 0;
|
||||
const HIDDEN = 1 << 1;
|
||||
const IN_WINDOW = 1 << 2;
|
||||
}
|
||||
}
|
||||
bitflags! {
|
||||
pub struct WindowFlags: u32 {
|
||||
const RESIZABLE = 1 << 0;
|
||||
const DECORATIONS = 1 << 1;
|
||||
const VISIBLE = 1 << 2;
|
||||
const ON_TASKBAR = 1 << 3;
|
||||
const ALWAYS_ON_TOP = 1 << 4;
|
||||
const NO_BACK_BUFFER = 1 << 5;
|
||||
const TRANSPARENT = 1 << 6;
|
||||
const CHILD = 1 << 7;
|
||||
const MAXIMIZED = 1 << 8;
|
||||
|
||||
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
|
||||
/// included here to make masking easier.
|
||||
const MARKER_FULLSCREEN = 1 << 9;
|
||||
|
||||
/// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`.
|
||||
/// In most cases, it's okay to let those parameters change the state. However, when we're
|
||||
/// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to
|
||||
/// effect our stored state, because the purpose of `apply_diff` is to update the actual
|
||||
/// window's state to match our stored state. This controls whether to accept those changes.
|
||||
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10;
|
||||
|
||||
const FULLSCREEN_AND_MASK = !(
|
||||
WindowFlags::DECORATIONS.bits |
|
||||
WindowFlags::RESIZABLE.bits |
|
||||
WindowFlags::MAXIMIZED.bits
|
||||
);
|
||||
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
||||
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
pub fn new(
|
||||
attributes: &WindowAttributes,
|
||||
window_icon: Option<WinIcon>,
|
||||
taskbar_icon: Option<WinIcon>,
|
||||
dpi_factor: f64
|
||||
) -> WindowState {
|
||||
WindowState {
|
||||
mouse: MouseProperties {
|
||||
cursor: MouseCursor::default(),
|
||||
cursor_flags: CursorFlags::empty(),
|
||||
},
|
||||
|
||||
min_size: attributes.min_dimensions,
|
||||
max_size: attributes.max_dimensions,
|
||||
|
||||
window_icon,
|
||||
taskbar_icon,
|
||||
|
||||
saved_window: None,
|
||||
dpi_factor,
|
||||
|
||||
fullscreen: None,
|
||||
window_flags: WindowFlags::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_flags(&self) -> WindowFlags {
|
||||
self.window_flags
|
||||
}
|
||||
|
||||
pub fn set_window_flags<F>(mut this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>, f: F)
|
||||
where F: FnOnce(&mut WindowFlags)
|
||||
{
|
||||
let old_flags = this.window_flags;
|
||||
f(&mut this.window_flags);
|
||||
|
||||
let is_fullscreen = this.fullscreen.is_some();
|
||||
this.window_flags.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
|
||||
let new_flags = this.window_flags;
|
||||
|
||||
drop(this);
|
||||
old_flags.apply_diff(window, new_flags, set_client_rect);
|
||||
}
|
||||
|
||||
pub fn refresh_window_state(this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>) {
|
||||
Self::set_window_flags(this, window, set_client_rect, |_| ());
|
||||
}
|
||||
|
||||
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
||||
where F: FnOnce(&mut WindowFlags)
|
||||
{
|
||||
f(&mut self.window_flags);
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseProperties {
|
||||
pub fn cursor_flags(&self) -> CursorFlags {
|
||||
self.cursor_flags
|
||||
}
|
||||
|
||||
pub fn set_cursor_flags<F>(&mut self, window: HWND, f: F) -> Result<(), io::Error>
|
||||
where F: FnOnce(&mut CursorFlags)
|
||||
{
|
||||
let old_flags = self.cursor_flags;
|
||||
f(&mut self.cursor_flags);
|
||||
match self.cursor_flags.refresh_os_cursor(window) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
self.cursor_flags = old_flags;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowFlags {
|
||||
fn mask(mut self) -> WindowFlags {
|
||||
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||
}
|
||||
if !self.contains(WindowFlags::VISIBLE) {
|
||||
self &= WindowFlags::INVISIBLE_AND_MASK;
|
||||
}
|
||||
if !self.contains(WindowFlags::DECORATIONS) {
|
||||
self &= WindowFlags::NO_DECORATIONS_AND_MASK;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_window_styles(self) -> (DWORD, DWORD) {
|
||||
use winapi::um::winuser::*;
|
||||
|
||||
let (mut style, mut style_ex) = (0, 0);
|
||||
|
||||
if self.contains(WindowFlags::RESIZABLE) {
|
||||
style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
|
||||
}
|
||||
if self.contains(WindowFlags::DECORATIONS) {
|
||||
style |= WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER;
|
||||
style_ex = WS_EX_WINDOWEDGE;
|
||||
}
|
||||
if self.contains(WindowFlags::VISIBLE) {
|
||||
style |= WS_VISIBLE;
|
||||
}
|
||||
if self.contains(WindowFlags::ON_TASKBAR) {
|
||||
style_ex |= WS_EX_APPWINDOW;
|
||||
}
|
||||
if self.contains(WindowFlags::ALWAYS_ON_TOP) {
|
||||
style_ex |= WS_EX_TOPMOST;
|
||||
}
|
||||
if self.contains(WindowFlags::NO_BACK_BUFFER) {
|
||||
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
|
||||
}
|
||||
if self.contains(WindowFlags::TRANSPARENT) {
|
||||
// Is this necessary? The docs say that WS_EX_LAYERED requires a windows class without
|
||||
// CS_OWNDC, and Winit windows have that flag set.
|
||||
style_ex |= WS_EX_LAYERED;
|
||||
}
|
||||
if self.contains(WindowFlags::CHILD) {
|
||||
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
|
||||
}
|
||||
if self.contains(WindowFlags::MAXIMIZED) {
|
||||
style |= WS_MAXIMIZE;
|
||||
}
|
||||
|
||||
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
|
||||
style_ex |= WS_EX_ACCEPTFILES;
|
||||
|
||||
(style, style_ex)
|
||||
}
|
||||
|
||||
/// Adjust the window client rectangle to the return value, if present.
|
||||
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
|
||||
self = self.mask();
|
||||
new = new.mask();
|
||||
|
||||
let diff = self ^ new;
|
||||
if diff == WindowFlags::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if diff.contains(WindowFlags::VISIBLE) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(
|
||||
window,
|
||||
match new.contains(WindowFlags::VISIBLE) {
|
||||
true => winuser::SW_SHOW,
|
||||
false => winuser::SW_HIDE
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
if diff.contains(WindowFlags::ALWAYS_ON_TOP) {
|
||||
unsafe {
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
match new.contains(WindowFlags::ALWAYS_ON_TOP) {
|
||||
true => winuser::HWND_TOPMOST,
|
||||
false => winuser::HWND_NOTOPMOST,
|
||||
},
|
||||
0, 0, 0, 0,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
|
||||
);
|
||||
winuser::UpdateWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) {
|
||||
unsafe {
|
||||
winuser::ShowWindow(
|
||||
window,
|
||||
match new.contains(WindowFlags::MAXIMIZED) {
|
||||
true => winuser::SW_MAXIMIZE,
|
||||
false => winuser::SW_RESTORE
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if diff != WindowFlags::empty() {
|
||||
let (style, style_ex) = new.to_window_styles();
|
||||
|
||||
unsafe {
|
||||
winuser::SendMessageW(window, *events_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
|
||||
|
||||
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
||||
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
||||
|
||||
match set_client_rect.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) {
|
||||
Some(client_rect) => {
|
||||
let (x, y, w, h) = (
|
||||
client_rect.left,
|
||||
client_rect.top,
|
||||
client_rect.right - client_rect.left,
|
||||
client_rect.bottom - client_rect.top,
|
||||
);
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
x, y, w, h,
|
||||
winuser::SWP_NOZORDER
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
},
|
||||
None => {
|
||||
// Refresh the window frame.
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
0, 0, 0, 0,
|
||||
winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOSIZE
|
||||
| winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
}
|
||||
}
|
||||
winuser::SendMessageW(window, *events_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorFlags {
|
||||
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> {
|
||||
let client_rect = util::get_client_rect(window)?;
|
||||
|
||||
if util::is_focused(window) {
|
||||
if self.contains(CursorFlags::GRABBED) {
|
||||
util::set_cursor_clip(Some(client_rect))?;
|
||||
} else {
|
||||
util::set_cursor_clip(None)?;
|
||||
}
|
||||
}
|
||||
|
||||
let cursor_in_client = self.contains(CursorFlags::IN_WINDOW);
|
||||
if cursor_in_client {
|
||||
util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN));
|
||||
} else {
|
||||
util::set_cursor_hidden(false);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
328
src/window.rs
328
src/window.rs
@@ -1,15 +1,19 @@
|
||||
use std::collections::vec_deque::IntoIter as VecDequeIter;
|
||||
|
||||
use CreationError;
|
||||
use CursorState;
|
||||
use EventsLoop;
|
||||
use MouseCursor;
|
||||
use Window;
|
||||
use WindowBuilder;
|
||||
use WindowId;
|
||||
|
||||
use libc;
|
||||
use platform;
|
||||
use {
|
||||
CreationError,
|
||||
EventsLoop,
|
||||
Icon,
|
||||
LogicalPosition,
|
||||
LogicalSize,
|
||||
MouseCursor,
|
||||
PhysicalPosition,
|
||||
PhysicalSize,
|
||||
platform,
|
||||
Window,
|
||||
WindowBuilder,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
impl WindowBuilder {
|
||||
/// Initializes a new `WindowBuilder` with default values.
|
||||
@@ -22,29 +26,39 @@ impl WindowBuilder {
|
||||
}
|
||||
|
||||
/// Requests the window to be of specific dimensions.
|
||||
///
|
||||
/// Width and height are in pixels.
|
||||
#[inline]
|
||||
pub fn with_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
|
||||
self.window.dimensions = Some((width, height));
|
||||
pub fn with_dimensions(mut self, size: LogicalSize) -> WindowBuilder {
|
||||
self.window.dimensions = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a minimum dimension size for the window
|
||||
///
|
||||
/// Width and height are in pixels.
|
||||
#[inline]
|
||||
pub fn with_min_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
|
||||
self.window.min_dimensions = Some((width, height));
|
||||
pub fn with_min_dimensions(mut self, min_size: LogicalSize) -> WindowBuilder {
|
||||
self.window.min_dimensions = Some(min_size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a maximum dimension size for the window
|
||||
///
|
||||
/// Width and height are in pixels.
|
||||
#[inline]
|
||||
pub fn with_max_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
|
||||
self.window.max_dimensions = Some((width, height));
|
||||
pub fn with_max_dimensions(mut self, max_size: LogicalSize) -> WindowBuilder {
|
||||
self.window.max_dimensions = Some(max_size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the window is resizable or not
|
||||
///
|
||||
/// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be
|
||||
/// triggered by DPI scaling, entering fullscreen mode, etc.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// This only has an effect on desktop platforms.
|
||||
///
|
||||
/// Due to a bug in XFCE, this has no effect on Xfwm.
|
||||
#[inline]
|
||||
pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder {
|
||||
self.window.resizable = resizable;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -91,6 +105,31 @@ impl WindowBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether or not the window will always be on top of other windows.
|
||||
#[inline]
|
||||
pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder {
|
||||
self.window.always_on_top = always_on_top;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
|
||||
/// corner of the titlebar.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// This only has an effect on Windows and X11.
|
||||
///
|
||||
/// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
|
||||
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
||||
///
|
||||
/// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That
|
||||
/// said, it's usually in the same ballpark as on Windows.
|
||||
#[inline]
|
||||
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> WindowBuilder {
|
||||
self.window.window_icon = window_icon;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables multitouch.
|
||||
#[inline]
|
||||
pub fn with_multitouch(mut self) -> WindowBuilder {
|
||||
@@ -102,23 +141,24 @@ impl WindowBuilder {
|
||||
///
|
||||
/// Error should be very rare and only occur in case of permission denied, incompatible system,
|
||||
/// out of memory, etc.
|
||||
#[inline]
|
||||
pub fn build(mut self, events_loop: &EventsLoop) -> Result<Window, CreationError> {
|
||||
// resizing the window to the dimensions of the monitor when fullscreen
|
||||
if self.window.dimensions.is_none() {
|
||||
self.window.dimensions = Some(self.window.dimensions.unwrap_or_else(|| {
|
||||
if let Some(ref monitor) = self.window.fullscreen {
|
||||
self.window.dimensions = Some(monitor.get_dimensions());
|
||||
// resizing the window to the dimensions of the monitor when fullscreen
|
||||
LogicalSize::from_physical(monitor.get_dimensions(), 1.0)
|
||||
} else {
|
||||
// default dimensions
|
||||
(1024, 768).into()
|
||||
}
|
||||
}
|
||||
|
||||
// default dimensions
|
||||
if self.window.dimensions.is_none() {
|
||||
self.window.dimensions = Some((1024, 768));
|
||||
}
|
||||
}));
|
||||
|
||||
// building
|
||||
let w = try!(platform::Window::new(&events_loop.events_loop, &self.window, &self.platform_specific));
|
||||
|
||||
Ok(Window { window: w })
|
||||
platform::Window::new(
|
||||
&events_loop.events_loop,
|
||||
self.window,
|
||||
self.platform_specific,
|
||||
).map(|window| Window { window })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +217,7 @@ impl Window {
|
||||
///
|
||||
/// Returns `None` if the window no longer exists.
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
self.window.get_position()
|
||||
}
|
||||
|
||||
@@ -186,7 +226,7 @@ impl Window {
|
||||
///
|
||||
/// The same conditions that apply to `get_position` apply to this method.
|
||||
#[inline]
|
||||
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
self.window.get_inner_position()
|
||||
}
|
||||
|
||||
@@ -196,61 +236,30 @@ impl Window {
|
||||
///
|
||||
/// This is a no-op if the window has already been closed.
|
||||
#[inline]
|
||||
pub fn set_position(&self, x: i32, y: i32) {
|
||||
self.window.set_position(x, y)
|
||||
pub fn set_position(&self, position: LogicalPosition) {
|
||||
self.window.set_position(position)
|
||||
}
|
||||
|
||||
/// Returns the size in pixels of the client area of the window.
|
||||
/// Returns the logical size of the window's client area.
|
||||
///
|
||||
/// The client area is the content of the window, excluding the title bar and borders.
|
||||
/// These are the dimensions that need to be supplied to `glViewport`.
|
||||
///
|
||||
/// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be.
|
||||
///
|
||||
/// Returns `None` if the window no longer exists.
|
||||
#[inline]
|
||||
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
self.window.get_inner_size()
|
||||
}
|
||||
|
||||
/// Returns the size in points of the client area of the window.
|
||||
/// Returns the logical size of the entire window.
|
||||
///
|
||||
/// The client area is the content of the window, excluding the title bar and borders.
|
||||
/// To get the dimensions of the frame buffer when calling `glViewport`, multiply with hidpi factor.
|
||||
///
|
||||
/// Returns `None` if the window no longer exists.
|
||||
///
|
||||
/// DEPRECATED
|
||||
#[inline]
|
||||
#[deprecated]
|
||||
pub fn get_inner_size_points(&self) -> Option<(u32, u32)> {
|
||||
self.window.get_inner_size().map(|(x, y)| {
|
||||
let hidpi = self.hidpi_factor();
|
||||
((x as f32 / hidpi) as u32, (y as f32 / hidpi) as u32)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the size in pixels of the client area of the window.
|
||||
///
|
||||
/// The client area is the content of the window, excluding the title bar and borders.
|
||||
/// These are the dimensions of the frame buffer, and the dimensions that you should use
|
||||
/// when you call `glViewport`.
|
||||
///
|
||||
/// Returns `None` if the window no longer exists.
|
||||
///
|
||||
/// DEPRECATED
|
||||
#[inline]
|
||||
#[deprecated]
|
||||
pub fn get_inner_size_pixels(&self) -> Option<(u32, u32)> {
|
||||
self.window.get_inner_size()
|
||||
}
|
||||
|
||||
/// Returns the size in pixels of the window.
|
||||
///
|
||||
/// These dimensions include title bar and borders. If you don't want these, you should use
|
||||
/// use `get_inner_size` instead.
|
||||
/// These dimensions include the title bar and borders. If you don't want that (and you usually don't),
|
||||
/// use `get_inner_size` instead.
|
||||
///
|
||||
/// Returns `None` if the window no longer exists.
|
||||
#[inline]
|
||||
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.window.get_outer_size()
|
||||
}
|
||||
|
||||
@@ -260,70 +269,92 @@ impl Window {
|
||||
///
|
||||
/// This is a no-op if the window has already been closed.
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, x: u32, y: u32) {
|
||||
self.window.set_inner_size(x, y)
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
self.window.set_inner_size(size)
|
||||
}
|
||||
|
||||
/// Sets a minimum dimension size for the window.
|
||||
///
|
||||
/// Width and height are in pixels.
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.window.set_min_dimensions(dimensions)
|
||||
}
|
||||
|
||||
/// Sets a maximum dimension size for the window.
|
||||
///
|
||||
/// Width and height are in pixels.
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.window.set_max_dimensions(dimensions)
|
||||
}
|
||||
|
||||
/// DEPRECATED. Gets the native platform specific display for this window.
|
||||
/// This is typically only required when integrating with
|
||||
/// other libraries that need this information.
|
||||
#[deprecated]
|
||||
/// Sets whether the window is resizable or not.
|
||||
///
|
||||
/// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be
|
||||
/// triggered by DPI scaling, entering fullscreen mode, etc.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// This only has an effect on desktop platforms.
|
||||
///
|
||||
/// Due to a bug in XFCE, this has no effect on Xfwm.
|
||||
#[inline]
|
||||
pub unsafe fn platform_display(&self) -> *mut libc::c_void {
|
||||
self.window.platform_display()
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.window.set_resizable(resizable)
|
||||
}
|
||||
|
||||
/// DEPRECATED. Gets the native platform specific window handle. This is
|
||||
/// typically only required when integrating with other libraries
|
||||
/// that need this information.
|
||||
#[deprecated]
|
||||
/// 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.
|
||||
///
|
||||
/// Note that this value can change depending on user action (for example if the window is
|
||||
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
|
||||
/// the most robust way to track the DPI you need to use to draw.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
|
||||
/// - **Android:** Always returns 1.0.
|
||||
#[inline]
|
||||
pub unsafe fn platform_window(&self) -> *mut libc::c_void {
|
||||
self.window.platform_window()
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.window.get_hidpi_factor()
|
||||
}
|
||||
|
||||
/// Modifies the mouse cursor of the window.
|
||||
/// Has no effect on Android.
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
self.window.set_cursor(cursor);
|
||||
}
|
||||
|
||||
/// Returns the ratio between the backing framebuffer resolution and the
|
||||
/// window size in screen pixels. This is typically one for a normal display
|
||||
/// and two for a retina display.
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> f32 {
|
||||
self.window.hidpi_factor()
|
||||
}
|
||||
|
||||
/// Changes the position of the cursor in window coordinates.
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
|
||||
self.window.set_cursor_position(x, y)
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
|
||||
self.window.set_cursor_position(position)
|
||||
}
|
||||
|
||||
/// Sets how winit handles the cursor. See the documentation of `CursorState` for details.
|
||||
/// Grabs the cursor, preventing it from leaving the window.
|
||||
///
|
||||
/// Has no effect on Android.
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward.
|
||||
///
|
||||
/// This has no effect on Android or iOS.
|
||||
#[inline]
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
self.window.set_cursor_state(state)
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
self.window.grab_cursor(grab)
|
||||
}
|
||||
|
||||
/// Hides the cursor, making it invisible but still usable.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// On Windows and X11, the cursor is only hidden within the confines of the window.
|
||||
///
|
||||
/// On macOS, the cursor is hidden as long as the window has input focus, even if the cursor is outside of the
|
||||
/// window.
|
||||
///
|
||||
/// This has no effect on Android or iOS.
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
self.window.hide_cursor(hide)
|
||||
}
|
||||
|
||||
/// Sets the window to maximized or back
|
||||
@@ -338,26 +369,82 @@ impl Window {
|
||||
self.window.set_fullscreen(monitor)
|
||||
}
|
||||
|
||||
/// Gets the window's current fullscreen state.
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<MonitorId> {
|
||||
self.window.get_fullscreen()
|
||||
}
|
||||
|
||||
/// Turn window decorations on or off.
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
self.window.set_decorations(decorations)
|
||||
}
|
||||
|
||||
/// Change whether or not the window will always be on top of other windows.
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
self.window.set_always_on_top(always_on_top)
|
||||
}
|
||||
|
||||
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
|
||||
/// corner of the titlebar.
|
||||
///
|
||||
/// For more usage notes, see `WindowBuilder::with_window_icon`.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// This only has an effect on Windows and X11.
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
self.window.set_window_icon(window_icon)
|
||||
}
|
||||
|
||||
/// Sets location of IME candidate box in client area coordinates relative to the top left.
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, position: LogicalPosition) {
|
||||
self.window.set_ime_spot(position)
|
||||
}
|
||||
|
||||
/// Returns the monitor on which the window currently resides
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> MonitorId {
|
||||
self.window.get_current_monitor()
|
||||
}
|
||||
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
///
|
||||
/// This is the same as `EventsLoop::get_available_monitors`, and is provided for convenience.
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
|
||||
let data = self.window.get_available_monitors();
|
||||
AvailableMonitorsIter { data: data.into_iter() }
|
||||
}
|
||||
|
||||
/// Returns the primary monitor of the system.
|
||||
///
|
||||
/// This is the same as `EventsLoop::get_primary_monitor`, and is provided for convenience.
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(&self) -> MonitorId {
|
||||
MonitorId { inner: self.window.get_primary_monitor() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId(self.window.id())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
|
||||
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
|
||||
self.window.raw_window_handle()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator for the list of 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::MonitorId>,
|
||||
}
|
||||
@@ -377,7 +464,7 @@ impl Iterator for AvailableMonitorsIter {
|
||||
}
|
||||
|
||||
/// Identifier for a monitor.
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorId {
|
||||
pub(crate) inner: platform::MonitorId
|
||||
}
|
||||
@@ -391,22 +478,29 @@ impl MonitorId {
|
||||
self.inner.get_name()
|
||||
}
|
||||
|
||||
/// Returns the number of pixels currently displayed on the monitor.
|
||||
/// Returns the monitor's resolution.
|
||||
#[inline]
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
pub fn get_dimensions(&self) -> PhysicalSize {
|
||||
self.inner.get_dimensions()
|
||||
}
|
||||
|
||||
/// Returns the top-left corner position of the monitor relative to the larger full
|
||||
/// screen area.
|
||||
#[inline]
|
||||
pub fn get_position(&self) -> (i32, i32) {
|
||||
pub fn get_position(&self) -> PhysicalPosition {
|
||||
self.inner.get_position()
|
||||
}
|
||||
|
||||
/// Returns the ratio between the monitor's physical pixels and logical pixels.
|
||||
/// 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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** This respects Xft.dpi XResource, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
|
||||
/// - **Android:** Always returns 1.0.
|
||||
#[inline]
|
||||
pub fn get_hidpi_factor(&self) -> f32 {
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.inner.get_hidpi_factor()
|
||||
}
|
||||
}
|
||||
|
||||
39
tests/serde_objects.rs
Normal file
39
tests/serde_objects.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
#![cfg(feature = "serde")]
|
||||
|
||||
extern crate serde;
|
||||
extern crate winit;
|
||||
|
||||
use winit::{ControlFlow, MouseCursor};
|
||||
use winit::{
|
||||
KeyboardInput, TouchPhase, ElementState, MouseButton, MouseScrollDelta, VirtualKeyCode,
|
||||
ModifiersState
|
||||
};
|
||||
use winit::dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
fn needs_serde<S: Serialize + Deserialize<'static>>() {}
|
||||
|
||||
#[test]
|
||||
fn root_serde() {
|
||||
needs_serde::<ControlFlow>();
|
||||
needs_serde::<MouseCursor>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn events_serde() {
|
||||
needs_serde::<KeyboardInput>();
|
||||
needs_serde::<TouchPhase>();
|
||||
needs_serde::<ElementState>();
|
||||
needs_serde::<MouseButton>();
|
||||
needs_serde::<MouseScrollDelta>();
|
||||
needs_serde::<VirtualKeyCode>();
|
||||
needs_serde::<ModifiersState>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dpi_serde() {
|
||||
needs_serde::<LogicalPosition>();
|
||||
needs_serde::<PhysicalPosition>();
|
||||
needs_serde::<LogicalSize>();
|
||||
needs_serde::<PhysicalSize>();
|
||||
}
|
||||
Reference in New Issue
Block a user