diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 08cd768b..e1b4afe8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,3 +2,4 @@ - [ ] 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/tomaka/winit/blob/master/FEATURES.md), if new features were added or implemented diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5c282b..6d48e850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased + - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. - Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules. @@ -36,13 +37,36 @@ - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. +- Rename several functions to improve both internal consistency and compliance with Rust API guidelines. +- Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now. + +# 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:** Remove the `icon_loading` feature and the associated `image` dependency. +- 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d32ec8a4..a8a0c783 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,28 +1,10 @@ # 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. -Winit aims to provide a generic platform abstracting the main graphic platforms (Windows, macOS, X11, -Wayland, Android, iOS and the web platform via Emscripten). - -Most platforms expose capabilities that cannot be meaningfully transposed to the others. Winit does not -aim to support every single functionality of every platform, but rather to abstract the set of -capabilities that is common to all platforms. In this context, APIs exposed in winit can be split into -different "support levels": - -- Tier 1: features which are in the main scope of winit. They are part of the common API of winit, and - are taken care of by the maintainers. Any part of these features that is not working correctly is - considered a bug in winit. -- Tier 2: some platform-specific features can be sufficiently fundamental to the platform that winit can - integrate support for them in the platform-specific part of the API. These features are not considered - directly handled by the maintainers of winit. If you have a strong incentive to have such a feature - integrated in winit, consider implementing it and proposing yourself to maintain it in the future. -- Tier 3: these features are not directly exposed by winit, but rather can be implemented using the - raw handles to the underlying platform that winit exposes. If your feature of interest is rather - niche, this is probably where it belongs. - -The exact list of supported Tier 1 features is tracked in this issue: -[#252](https://github.com/tomaka/winit/issues/252). ## Reporting an issue @@ -44,6 +26,9 @@ When making a code contribution to winit, before opening your pull request, plea - 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 @@ -56,15 +41,14 @@ backends of winit. As such, depending on your platform of interest, your contact This table summarizes who can be contacted in which case, with the following legend: -- `M`: is a main maintainer for this platform -- `R`: can review code for this platform -- `T`: has the ability of testing the platform +- `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 | -| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| @francesca64 | R | M | M | | M | R | | -| @mitchmindtree | T | | T | T | | | | -| @Osspial | M | | T | T | T | | T | -| @vberger | | | T | M | | | | -| @mtak- | | T | | | T | M | | +| 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 | | diff --git a/Cargo.toml b/Cargo.toml index e5362812..0d7e5e0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.18.1" +version = "0.19.1" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." keywords = ["windowing"] @@ -21,6 +21,7 @@ serde = { version = "1", optional = true, features = ["serde_derive"] } [dev-dependencies] image = "0.21" +env_logger = "0.5" [target.'cfg(target_os = "android")'.dependencies.android_glue] version = "0.2" @@ -29,13 +30,13 @@ version = "0.2" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -objc = "0.2.3" cocoa = "0.18.4" core-foundation = "0.6" core-graphics = "0.17.3" +dispatch = "0.1.4" +objc = "0.2.3" [target.'cfg(target_os = "windows")'.dependencies] -backtrace = "0.3" bitflags = "1" [target.'cfg(target_os = "windows")'.dependencies.winapi] @@ -67,12 +68,11 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", " calloop = "0.4.2" smithay-client-toolkit = "0.6" x11-dl = "2.18.3" -parking_lot = "0.7" percent-encoding = "1.0" -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))'.dependencies.parking_lot] -version = "0.7" +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] +version = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies.stdweb] -version = "0.4.13" +version = "0.4.17" optional = true diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 00000000..515ff597 --- /dev/null +++ b/FEATURES.md @@ -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/tomaka/winit/issues/165 +[#219]: https://github.com/tomaka/winit/issues/219 +[#242]: https://github.com/tomaka/winit/issues/242 +[#306]: https://github.com/tomaka/winit/issues/306 +[#315]: https://github.com/tomaka/winit/issues/315 +[#319]: https://github.com/tomaka/winit/issues/319 +[#33]: https://github.com/tomaka/winit/issues/33 +[#459]: https://github.com/tomaka/winit/issues/459 +[#5]: https://github.com/tomaka/winit/issues/5 +[#63]: https://github.com/tomaka/winit/issues/63 +[#720]: https://github.com/tomaka/winit/issues/720 +[#721]: https://github.com/tomaka/winit/issues/721 +[#750]: https://github.com/tomaka/winit/issues/750 +[#804]: https://github.com/tomaka/winit/issues/804 +[#812]: https://github.com/tomaka/winit/issues/812 diff --git a/HALL_OF_CHAMPIONS.md b/HALL_OF_CHAMPIONS.md new file mode 100644 index 00000000..c3427514 --- /dev/null +++ b/HALL_OF_CHAMPIONS.md @@ -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 diff --git a/README.md b/README.md index 19661019..bc4b6971 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,24 @@ [![](http://meritbadge.herokuapp.com/winit)](https://crates.io/crates/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) -[![Build Status](https://travis-ci.org/tomaka/winit.svg?branch=master)](https://travis-ci.org/tomaka/winit) -[![Build status](https://ci.appveyor.com/api/projects/status/5h87hj0g4q2xe3j9/branch/master?svg=true)](https://ci.appveyor.com/project/tomaka/winit/branch/master) +[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit) +[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master) ```toml [dependencies] -winit = "0.18.1" +winit = "0.19.1" ``` ## [Documentation](https://docs.rs/winit) +## Contact Us + +Join us in any of these: + +[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a) +[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](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 diff --git a/examples/cursor.rs b/examples/cursor.rs index 27c003ac..59441c22 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,6 +1,6 @@ extern crate winit; -use winit::window::{WindowBuilder, MouseCursor}; +use winit::window::{WindowBuilder, CursorIcon}; use winit::event::{Event, WindowEvent, ElementState, KeyboardInput}; use winit::event_loop::{EventLoop, ControlFlow}; @@ -16,7 +16,7 @@ fn main() { match event { Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => { println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); - window.set_cursor(CURSORS[cursor_idx]); + window.set_cursor_icon(CURSORS[cursor_idx]); if cursor_idx < CURSORS.len() - 1 { cursor_idx += 1; } else { @@ -32,17 +32,17 @@ fn main() { }); } -const CURSORS: &[MouseCursor] = &[ - 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 +const CURSORS: &[CursorIcon] = &[ + CursorIcon::Default, CursorIcon::Crosshair, CursorIcon::Hand, + CursorIcon::Arrow, CursorIcon::Move, CursorIcon::Text, + CursorIcon::Wait, CursorIcon::Help, CursorIcon::Progress, + CursorIcon::NotAllowed, CursorIcon::ContextMenu, CursorIcon::Cell, + CursorIcon::VerticalText, CursorIcon::Alias, CursorIcon::Copy, + CursorIcon::NoDrop, CursorIcon::Grab, CursorIcon::Grabbing, + CursorIcon::AllScroll, CursorIcon::ZoomIn, CursorIcon::ZoomOut, + CursorIcon::EResize, CursorIcon::NResize, CursorIcon::NeResize, + CursorIcon::NwResize, CursorIcon::SResize, CursorIcon::SeResize, + CursorIcon::SwResize, CursorIcon::WResize, CursorIcon::EwResize, + CursorIcon::NsResize, CursorIcon::NeswResize, CursorIcon::NwseResize, + CursorIcon::ColResize, CursorIcon::RowResize ]; diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 741d0d74..8e7b1f7e 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -29,8 +29,8 @@ fn main() { use winit::event::VirtualKeyCode::*; match key { Escape => *control_flow = ControlFlow::Exit, - G => window.grab_cursor(!modifiers.shift).unwrap(), - H => window.hide_cursor(!modifiers.shift), + G => window.set_cursor_grab(!modifiers.shift).unwrap(), + H => window.set_cursor_visible(modifiers.shift), _ => (), } } diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 788a608c..8827751f 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -70,12 +70,11 @@ fn main() { #[cfg(target_os = "macos")] { if macos_use_simple_fullscreen { - use winit::os::macos::WindowExt; - if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) { + use winit::platform::macos::WindowExtMacOS; + if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) { is_fullscreen = !is_fullscreen; } - - return ControlFlow::Continue; + return; } } @@ -83,7 +82,16 @@ fn main() { if !is_fullscreen { window.set_fullscreen(None); } else { - window.set_fullscreen(Some(window.get_current_monitor())); + window.set_fullscreen(Some(window.current_monitor())); + } + } + (VirtualKeyCode::S, ElementState::Pressed) => { + println!("window.fullscreen {:?}", window.fullscreen()); + + #[cfg(target_os = "macos")] + { + use winit::platform::macos::WindowExtMacOS; + println!("window.simple_fullscreen {:?}", WindowExtMacOS::simple_fullscreen(&window)); } } (VirtualKeyCode::M, ElementState::Pressed) => { @@ -105,8 +113,8 @@ fn main() { // Enumerate monitors and prompt user to choose one fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { - for (num, monitor) in event_loop.get_available_monitors().enumerate() { - println!("Monitor #{}: {:?}", num, monitor.get_name()); + for (num, monitor) in event_loop.available_monitors().enumerate() { + println!("Monitor #{}: {:?}", num, monitor.name()); } print!("Please write the number of the monitor to use: "); @@ -115,9 +123,9 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { 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 = event_loop.get_available_monitors().nth(num).expect("Please enter a valid ID"); + let monitor = event_loop.available_monitors().nth(num).expect("Please enter a valid ID"); - println!("Using {:?}", monitor.get_name()); + println!("Using {:?}", monitor.name()); monitor } diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index dea43763..37c0fe7f 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -12,8 +12,8 @@ fn main() { .build(&event_loop) .unwrap(); - window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0))); - window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0))); + window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0))); + window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0))); event_loop.run(move |event, _, control_flow| { println!("{:?}", event); diff --git a/examples/monitor_list.rs b/examples/monitor_list.rs index 335aa022..42e683bf 100644 --- a/examples/monitor_list.rs +++ b/examples/monitor_list.rs @@ -5,5 +5,5 @@ use winit::window::WindowBuilder; fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - println!("{:#?}\nPrimary: {:#?}", window.get_available_monitors(), window.get_primary_monitor()); + println!("{:#?}\nPrimary: {:#?}", window.available_monitors(), window.primary_monitor()); } diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs new file mode 100644 index 00000000..05408799 --- /dev/null +++ b/examples/multithreaded.rs @@ -0,0 +1,115 @@ +extern crate env_logger; +extern crate winit; + +use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; + +use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, WindowBuilder}, +}; + +const WINDOW_COUNT: usize = 3; +const WINDOW_SIZE: (u32, u32) = (600, 400); + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); + for _ in 0..WINDOW_COUNT { + let window = WindowBuilder::new() + .with_inner_size(WINDOW_SIZE.into()) + .build(&event_loop) + .unwrap(); + let (tx, rx) = mpsc::channel(); + window_senders.insert(window.id(), tx); + thread::spawn(move || { + while let Ok(event) = rx.recv() { + match event { + WindowEvent::KeyboardInput { input: KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + modifiers, + .. + }, .. } => { + window.set_title(&format!("{:?}", key)); + let state = !modifiers.shift; + use self::VirtualKeyCode::*; + match key { + A => window.set_always_on_top(state), + C => window.set_cursor_icon(match state { + true => CursorIcon::Progress, + false => CursorIcon::Default, + }), + D => window.set_decorations(!state), + F => window.set_fullscreen(match state { + true => Some(window.current_monitor()), + false => None, + }), + G => window.set_cursor_grab(state).unwrap(), + H => window.set_cursor_visible(!state), + I => { + println!("Info:"); + println!("-> outer_position : {:?}", window.outer_position()); + println!("-> inner_position : {:?}", window.inner_position()); + println!("-> outer_size : {:?}", window.outer_size()); + println!("-> inner_size : {:?}", window.inner_size()); + }, + L => window.set_min_inner_size(match state { + true => Some(WINDOW_SIZE.into()), + false => None, + }), + M => window.set_maximized(state), + P => window.set_outer_position({ + let mut position = window.outer_position().unwrap(); + let sign = if state { 1.0 } else { -1.0 }; + position.x += 10.0 * sign; + position.y += 10.0 * sign; + position + }), + Q => window.request_redraw(), + R => window.set_resizable(state), + S => window.set_inner_size(match state { + true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100), + false => WINDOW_SIZE, + }.into()), + W => window.set_cursor_position(( + WINDOW_SIZE.0 as i32 / 2, + WINDOW_SIZE.1 as i32 / 2, + ).into()).unwrap(), + Z => { + window.set_visible(false); + thread::sleep(Duration::from_secs(1)); + window.set_visible(true); + }, + _ => (), + } + }, + _ => (), + } + } + }); + } + event_loop.run(move |event, _event_loop, control_flow| { + *control_flow = match !window_senders.is_empty() { + true => ControlFlow::Wait, + false => ControlFlow::Exit, + }; + match event { + Event::WindowEvent { event, window_id } => { + match event { + WindowEvent::CloseRequested + | WindowEvent::Destroyed + | WindowEvent::KeyboardInput { input: KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + .. }, .. } => { + window_senders.remove(&window_id); + }, + _ => if let Some(tx) = window_senders.get(&window_id) { + tx.send(event).unwrap(); + }, + } + } + _ => (), + } + }) +} diff --git a/examples/resizable.rs b/examples/resizable.rs index f5690b86..969ce04b 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -10,7 +10,7 @@ fn main() { let window = WindowBuilder::new() .with_title("Hit space to toggle resizability.") - .with_dimensions((400, 200).into()) + .with_inner_size((400, 200).into()) .with_resizable(resizable) .build(&event_loop) .unwrap(); diff --git a/examples/timer.rs b/examples/timer.rs index 3692d327..97b61b84 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -21,6 +21,7 @@ fn main() { Event::NewEvents(StartCause::ResumeTimeReached{..}) => { *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0)); println!("\nTimer\n"); + _window.set_inner_size(winit::dpi::LogicalSize::new(300.0, 300.0)); }, Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/src/dpi.rs b/src/dpi.rs index cae9b401..afd1cbfd 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -33,10 +33,10 @@ //! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor, //! or because the user changed the configuration of their screen. //! - You can also retrieve the DPI factor of a monitor by calling -//! [`MonitorHandle::get_hidpi_factor`](../monitor/struct.MonitorHandle.html#method.get_hidpi_factor), or the +//! [`MonitorHandle::hidpi_factor`](../monitor/struct.MonitorHandle.html#method.hidpi_factor), or the //! current DPI factor applied to a window by calling -//! [`Window::get_hidpi_factor`](../window/struct.Window.html#method.get_hidpi_factor), which is roughly equivalent -//! to `window.get_current_monitor().get_hidpi_factor()`. +//! [`Window::hidpi_factor`](../window/struct.Window.html#method.hidpi_factor), which is roughly equivalent +//! to `window.current_monitor().hidpi_factor()`. //! //! Depending on the platform, the window's actual DPI factor may only be known after //! the event loop has started and your window has been drawn once. To properly handle these cases, diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..83c37210 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,86 @@ +use std::fmt; +use std::error; + +use platform_impl; + +/// An error whose cause it outside Winit's control. +#[derive(Debug)] +pub enum ExternalError { + /// The operation is not supported by the backend. + NotSupported(NotSupportedError), + /// The OS cannot perform the operation. + Os(OsError), +} + +/// The error type for when the requested operation is not supported by the backend. +#[derive(Clone)] +pub struct NotSupportedError { + _marker: (), +} + +/// The error type for when the OS cannot perform the requested operation. +#[derive(Debug)] +pub struct OsError { + line: u32, + file: &'static str, + error: platform_impl::OsError, +} + +impl NotSupportedError { + #[inline] + #[allow(dead_code)] + pub(crate) fn new() -> NotSupportedError { + NotSupportedError { + _marker: () + } + } +} + +impl OsError { + #[allow(dead_code)] + pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError { + OsError { + line, + file, + error, + } + } +} + +#[allow(unused_macros)] +macro_rules! os_error { + ($error:expr) => {{ + crate::error::OsError::new(line!(), file!(), $error) + }} +} + +impl fmt::Display for OsError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + formatter.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error)) + } +} + +impl fmt::Display for ExternalError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + ExternalError::NotSupported(e) => e.fmt(formatter), + ExternalError::Os(e) => e.fmt(formatter), + } + } +} + +impl fmt::Debug for NotSupportedError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + formatter.debug_struct("NotSupportedError").finish() + } +} + +impl fmt::Display for NotSupportedError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + formatter.pad("the requested operation is not supported by Winit") + } +} + +impl error::Error for OsError {} +impl error::Error for ExternalError {} +impl error::Error for NotSupportedError {} diff --git a/src/event_loop.rs b/src/event_loop.rs index ab6839cd..476e7ed5 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -94,6 +94,10 @@ impl Default for ControlFlow { impl EventLoop<()> { /// Builds a new event loop with a `()` as the user event type. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. pub fn new() -> EventLoop<()> { EventLoop::<()>::new_user_event() } @@ -106,6 +110,10 @@ impl EventLoop { /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. /// If it is not set, winit will try to connect to a wayland connection, and if it fails will /// fallback on x11. If this variable is set with any other value, winit will panic. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. pub fn new_user_event() -> EventLoop { EventLoop { event_loop: platform_impl::EventLoop::new(), @@ -113,21 +121,6 @@ impl EventLoop { } } - /// Returns the list of all the monitors available on the system. - /// - // Note: should be replaced with `-> impl Iterator` once stable. - #[inline] - pub fn get_available_monitors(&self) -> AvailableMonitorsIter { - let data = self.event_loop.get_available_monitors(); - AvailableMonitorsIter{ data: data.into_iter() } - } - - /// Returns the primary monitor of the system. - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle { inner: self.event_loop.get_primary_monitor() } - } - /// Hijacks the calling thread and initializes the `winit` event loop with the provided /// closure. Since the closure is `'static`, it must be a `move` closure if it needs to /// access any data from the calling context. @@ -145,13 +138,27 @@ impl EventLoop { self.event_loop.run(event_handler) } - /// Creates an `EventLoopProxy` that can be used to wake up the `EventLoop` from another - /// thread. + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy(), } } + + /// Returns the list of all the monitors available on the system. + /// + // Note: should be replaced with `-> impl Iterator` once stable. + #[inline] + pub fn available_monitors(&self) -> AvailableMonitorsIter { + let data = self.event_loop.available_monitors(); + AvailableMonitorsIter{ data: data.into_iter() } + } + + /// Returns the primary monitor of the system. + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle { inner: self.event_loop.primary_monitor() } + } } impl Deref for EventLoop { diff --git a/src/lib.rs b/src/lib.rs index 8cdf77b3..7846803a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,8 +87,6 @@ extern crate serde; #[cfg(target_os = "windows")] extern crate winapi; -#[cfg(target_os = "windows")] -extern crate backtrace; #[macro_use] #[cfg(target_os = "windows")] extern crate bitflags; @@ -98,6 +96,8 @@ extern crate objc; #[cfg(target_os = "macos")] extern crate cocoa; #[cfg(target_os = "macos")] +extern crate dispatch; +#[cfg(target_os = "macos")] extern crate core_foundation; #[cfg(target_os = "macos")] extern crate core_graphics; @@ -116,6 +116,8 @@ extern crate stdweb; extern crate calloop; pub mod dpi; +#[macro_use] +pub mod error; pub mod event; pub mod event_loop; mod icon; diff --git a/src/monitor.rs b/src/monitor.rs index 7533cb78..8fab7ca4 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -3,13 +3,13 @@ //! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_id] //! type. This is retreived from an [`AvailableMonitorsIter`][monitor_iter], which can be acquired //! with: -//! - [`EventLoop::get_available_monitors`][loop_get] -//! - [`Window::get_available_monitors`][window_get]. +//! - [`EventLoop::available_monitors`][loop_get] +//! - [`Window::available_monitors`][window_get]. //! //! [monitor_id]: ./struct.MonitorHandle.html //! [monitor_iter]: ./struct.AvailableMonitorsIter.html -//! [loop_get]: ../event_loop/struct.EventLoop.html#method.get_available_monitors -//! [window_get]: ../window/struct.Window.html#method.get_available_monitors +//! [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors +//! [window_get]: ../window/struct.Window.html#method.available_monitors use std::collections::vec_deque::IntoIter as VecDequeIter; use platform_impl; @@ -18,11 +18,11 @@ use dpi::{PhysicalPosition, PhysicalSize}; /// An iterator over all available monitors. /// /// Can be acquired with: -/// - [`EventLoop::get_available_monitors`][loop_get] -/// - [`Window::get_available_monitors`][window_get]. +/// - [`EventLoop::available_monitors`][loop_get] +/// - [`Window::available_monitors`][window_get]. /// -/// [loop_get]: ../event_loop/struct.EventLoop.html#method.get_available_monitors -/// [window_get]: ../window/struct.Window.html#method.get_available_monitors +/// [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors +/// [window_get]: ../window/struct.Window.html#method.available_monitors // Implementation note: we retrieve the list once, then serve each element by one by one. // This may change in the future. #[derive(Debug)] @@ -59,21 +59,21 @@ impl MonitorHandle { /// /// Returns `None` if the monitor doesn't exist anymore. #[inline] - pub fn get_name(&self) -> Option { - self.inner.get_name() + pub fn name(&self) -> Option { + self.inner.name() } /// Returns the monitor's resolution. #[inline] - pub fn get_dimensions(&self) -> PhysicalSize { - self.inner.get_dimensions() + pub fn dimensions(&self) -> PhysicalSize { + self.inner.dimensions() } /// Returns the top-left corner position of the monitor relative to the larger full /// screen area. #[inline] - pub fn get_position(&self) -> PhysicalPosition { - self.inner.get_position() + pub fn position(&self) -> PhysicalPosition { + self.inner.position() } /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. @@ -85,7 +85,7 @@ impl MonitorHandle { /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. /// - **Android:** Always returns 1.0. #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - self.inner.get_hidpi_factor() + pub fn hidpi_factor(&self) -> f64 { + self.inner.hidpi_factor() } } diff --git a/src/platform/android.rs b/src/platform/android.rs index 37ee0e1d..82a73453 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -19,13 +19,13 @@ impl EventLoopExtAndroid for EventLoop { /// Additional methods on `Window` that are specific to Android. pub trait WindowExtAndroid { - fn get_native_window(&self) -> *const c_void; + fn native_window(&self) -> *const c_void; } impl WindowExtAndroid for Window { #[inline] - fn get_native_window(&self) -> *const c_void { - self.window.get_native_window() + fn native_window(&self) -> *const c_void { + self.window.native_window() } } diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 7a2345b0..85fcdff6 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -2,30 +2,74 @@ use std::os::raw::c_void; -use {MonitorHandle, Window, WindowBuilder}; +use event_loop::EventLoop; +use monitor::MonitorHandle; +use window::{Window, WindowBuilder}; + +/// Additional methods on `EventLoop` that are specific to iOS. +pub trait EventLoopExtIOS { + /// Returns the idiom (phone/tablet/tv/etc) for the current device. + fn idiom(&self) -> Idiom; +} + +impl EventLoopExtIOS for EventLoop { + fn idiom(&self) -> Idiom { + self.event_loop.idiom() + } +} /// Additional methods on `Window` that are specific to iOS. pub trait WindowExtIOS { /// 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; + fn ui_window(&self) -> *mut c_void; + + /// Returns a pointer to the `UIViewController` that is used by this window. + /// + /// The pointer will become invalid when the `Window` is destroyed. + fn ui_view_controller(&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; + fn ui_view(&self) -> *mut c_void; + + /// Sets the HiDpi factor used by this window. + /// + /// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`. + fn set_hidpi_factor(&self, hidpi_factor: f64); + + /// Sets the valid orientations for screens showing this `Window`. + /// + /// On iPhones and iPods upside down portrait is never enabled. + fn set_valid_orientations(&self, valid_orientations: ValidOrientations); } impl WindowExtIOS for Window { #[inline] - fn get_uiwindow(&self) -> *mut c_void { - self.window.get_uiwindow() as _ + fn ui_window(&self) -> *mut c_void { + self.window.ui_window() as _ } #[inline] - fn get_uiview(&self) -> *mut c_void { - self.window.get_uiview() as _ + fn ui_view_controller(&self) -> *mut c_void { + self.window.ui_view_controller() as _ + } + + #[inline] + fn ui_view(&self) -> *mut c_void { + self.window.ui_view() as _ + } + + #[inline] + fn set_hidpi_factor(&self, hidpi_factor: f64) { + self.window.set_hidpi_factor(hidpi_factor) + } + + #[inline] + fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { + self.window.set_valid_orientations(valid_orientations) } } @@ -35,6 +79,15 @@ pub trait WindowBuilderExtIOS { /// /// The class will be initialized by calling `[root_view initWithFrame:CGRect]` fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; + + /// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`. + /// + /// The default value is device dependent, and it's recommended GLES or Metal applications set + /// this to `MonitorHandle::hidpi_factor()`. + fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder; + + /// Sets the valid orientations for the `Window`. + fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder; } impl WindowBuilderExtIOS for WindowBuilder { @@ -43,17 +96,66 @@ impl WindowBuilderExtIOS for WindowBuilder { self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) }; self } + + #[inline] + fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder { + self.platform_specific.hidpi_factor = Some(hidpi_factor); + self + } + + #[inline] + fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder { + self.platform_specific.valid_orientations = valid_orientations; + self + } } /// Additional methods on `MonitorHandle` that are specific to iOS. pub trait MonitorHandleExtIOS { /// Returns a pointer to the `UIScreen` that is used by this monitor. - fn get_uiscreen(&self) -> *mut c_void; + fn ui_screen(&self) -> *mut c_void; } impl MonitorHandleExtIOS for MonitorHandle { #[inline] - fn get_uiscreen(&self) -> *mut c_void { - self.inner.get_uiscreen() as _ + fn ui_screen(&self) -> *mut c_void { + self.inner.ui_screen() as _ } } + +/// Valid orientations for a particular `Window`. +#[derive(Clone, Copy, Debug)] +pub enum ValidOrientations { + /// Excludes `PortraitUpsideDown` on iphone + LandscapeAndPortrait, + + Landscape, + + /// Excludes `PortraitUpsideDown` on iphone + Portrait, +} + +impl Default for ValidOrientations { + #[inline] + fn default() -> ValidOrientations { + ValidOrientations::LandscapeAndPortrait + } +} + +/// The device [idiom]. +/// +/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Idiom { + Unspecified, + + /// iPhone and iPod touch. + Phone, + + /// iPad. + Pad, + + /// tvOS and Apple TV. + TV, + CarPlay, +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 8997e3b2..3162b6ef 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,19 +1,22 @@ #![cfg(target_os = "macos")] use std::os::raw::c_void; -use {LogicalSize, MonitorHandle, Window, WindowBuilder}; + +use crate::dpi::LogicalSize; +use crate::monitor::MonitorHandle; +use crate::window::{Window, WindowBuilder}; /// Additional methods on `Window` that are specific to MacOS. pub trait WindowExtMacOS { /// Returns a pointer to the cocoa `NSWindow` that is used by this window. /// /// The pointer will become invalid when the `Window` is destroyed. - fn get_nswindow(&self) -> *mut c_void; + fn nswindow(&self) -> *mut c_void; /// Returns a pointer to the cocoa `NSView` that is used by this window. /// /// The pointer will become invalid when the `Window` is destroyed. - fn get_nsview(&self) -> *mut c_void; + fn 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. @@ -23,6 +26,9 @@ pub trait WindowExtMacOS { /// - `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 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). @@ -35,13 +41,13 @@ pub trait WindowExtMacOS { impl WindowExtMacOS for Window { #[inline] - fn get_nswindow(&self) -> *mut c_void { - self.window.get_nswindow() + fn nswindow(&self) -> *mut c_void { + self.window.nswindow() } #[inline] - fn get_nsview(&self) -> *mut c_void { - self.window.get_nsview() + fn nsview(&self) -> *mut c_void { + self.window.nsview() } #[inline] @@ -49,6 +55,11 @@ impl WindowExtMacOS for Window { self.window.request_user_attention(is_critical) } + #[inline] + fn simple_fullscreen(&self) -> bool { + self.window.simple_fullscreen() + } + #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window.set_simple_fullscreen(fullscreen) @@ -156,16 +167,16 @@ pub trait MonitorHandleExtMacOS { /// Returns the identifier of the monitor for Cocoa. fn native_id(&self) -> u32; /// Returns a pointer to the NSScreen representing this monitor. - fn get_nsscreen(&self) -> Option<*mut c_void>; + fn nsscreen(&self) -> Option<*mut c_void>; } impl MonitorHandleExtMacOS for MonitorHandle { #[inline] fn native_id(&self) -> u32 { - self.inner.get_native_identifier() + self.inner.native_identifier() } - fn get_nsscreen(&self) -> Option<*mut c_void> { - self.inner.get_nsscreen().map(|s| s as *mut c_void) + fn nsscreen(&self) -> Option<*mut c_void> { + self.inner.nsscreen().map(|s| s as *mut c_void) } } diff --git a/src/platform/macos/util/cursor.rs b/src/platform/macos/util/cursor.rs deleted file mode 100644 index e7815d78..00000000 --- a/src/platform/macos/util/cursor.rs +++ /dev/null @@ -1,149 +0,0 @@ -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 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 { - 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) - ) -} diff --git a/src/platform/macos/util/mod.rs b/src/platform/macos/util/mod.rs deleted file mode 100644 index baa0e6e0..00000000 --- a/src/platform/macos/util/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -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]; -} diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 5bde480d..5f212fdf 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -15,15 +15,15 @@ use platform_impl::{ EventLoop as LinuxEventLoop, Window as LinuxWindow, }; -//use platform_impl::x11::XConnection; -//use platform_impl::x11::ffi::XVisualInfo; -// +use platform_impl::x11::XConnection; +use platform_impl::x11::ffi::XVisualInfo; + // TODO: stupid hack so that glutin can do its work -//#[doc(hidden)] -//pub use platform_impl::x11; -// -//pub use platform_impl::XNotSupported; -//pub use platform_impl::x11::util::WindowType as XWindowType; +#[doc(hidden)] +pub use platform_impl::x11; + +pub use platform_impl::XNotSupported; +pub use platform_impl::x11::util::WindowType as XWindowType; /// Theme for wayland client side decorations /// @@ -96,8 +96,8 @@ impl Theme for WaylandThemeObject { /// Additional methods on `EventLoop` that are specific to Unix. pub trait EventLoopExtUnix { /// Builds a new `EventLoops` that is forced to use X11. - //fn new_x11() -> Result - // where Self: Sized; + fn new_x11() -> Result + where Self: Sized; /// Builds a new `EventLoop` that is forced to use Wayland. fn new_wayland() -> Self @@ -109,20 +109,27 @@ pub trait EventLoopExtUnix { /// True if the `EventLoop` uses X11. fn is_x11(&self) -> bool; - //#[doc(hidden)] - //fn get_xlib_xconnection(&self) -> Option>; + #[doc(hidden)] + fn xlib_xconnection(&self) -> Option>; + + /// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`. + /// + /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). + /// + /// The pointer will become invalid when the glutin `EventLoop` is destroyed. + fn wayland_display(&self) -> Option<*mut raw::c_void>; } impl EventLoopExtUnix for EventLoop { - //#[inline] - //fn new_x11() -> Result { - // LinuxEventLoop::new_x11().map(|ev| - // EventLoop { - // event_loop: ev, - // _marker: ::std::marker::PhantomData, - // } - // ) - //} + #[inline] + fn new_x11() -> Result { + LinuxEventLoop::new_x11().map(|ev| + EventLoop { + event_loop: ev, + _marker: ::std::marker::PhantomData, + } + ) + } #[inline] fn new_wayland() -> Self { @@ -145,11 +152,22 @@ impl EventLoopExtUnix for EventLoop { !self.event_loop.is_wayland() } - //#[inline] - //#[doc(hidden)] - //fn get_xlib_xconnection(&self) -> Option> { - // self.event_loop.x_connection().cloned() - //} + #[inline] + #[doc(hidden)] + fn xlib_xconnection(&self) -> Option> { + match self.event_loop { + LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()), + _ => None + } + } + + #[inline] + fn wayland_display(&self) -> Option<*mut raw::c_void> { + match self.event_loop { + LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _), + _ => None + } + } } /// Additional methods on `Window` that are specific to Unix. @@ -157,19 +175,19 @@ pub trait WindowExtUnix { /// Returns the ID of the `Window` xlib object that is used by this window. /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). - fn get_xlib_window(&self) -> Option; + fn xlib_window(&self) -> Option; /// Returns a pointer to the `Display` object of xlib that is used by this window. /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. - fn get_xlib_display(&self) -> Option<*mut raw::c_void>; + fn xlib_display(&self) -> Option<*mut raw::c_void>; - fn get_xlib_screen_id(&self) -> Option; + fn xlib_screen_id(&self) -> Option; - //#[doc(hidden)] - //fn get_xlib_xconnection(&self) -> Option>; + #[doc(hidden)] + fn xlib_xconnection(&self) -> Option>; /// Set window urgency hint (`XUrgencyHint`). Only relevant on X. fn set_urgent(&self, is_urgent: bool); @@ -179,21 +197,21 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. - fn get_xcb_connection(&self) -> Option<*mut raw::c_void>; + fn xcb_connection(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_surface` object of wayland that is used by this window. /// /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. - fn get_wayland_surface(&self) -> Option<*mut raw::c_void>; + fn wayland_surface(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_display` object of wayland that is used by this window. /// /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. - fn get_wayland_display(&self) -> Option<*mut raw::c_void>; + fn 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); @@ -210,65 +228,65 @@ pub trait WindowExtUnix { impl WindowExtUnix for Window { #[inline] - fn get_xlib_window(&self) -> Option { + fn xlib_window(&self) -> Option { match self.window { - //LinuxWindow::X(ref w) => Some(w.get_xlib_window()), + LinuxWindow::X(ref w) => Some(w.xlib_window()), _ => None } } #[inline] - fn get_xlib_display(&self) -> Option<*mut raw::c_void> { + fn xlib_display(&self) -> Option<*mut raw::c_void> { match self.window { - //LinuxWindow::X(ref w) => Some(w.get_xlib_display()), + LinuxWindow::X(ref w) => Some(w.xlib_display()), _ => None } } #[inline] - fn get_xlib_screen_id(&self) -> Option { + fn xlib_screen_id(&self) -> Option { match self.window { - //LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()), + LinuxWindow::X(ref w) => Some(w.xlib_screen_id()), _ => None } } - //#[inline] - //#[doc(hidden)] - //fn get_xlib_xconnection(&self) -> Option> { - // match self.window { - // //LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()), - // _ => None - // } - //} + #[inline] + #[doc(hidden)] + fn xlib_xconnection(&self) -> Option> { + match self.window { + LinuxWindow::X(ref w) => Some(w.xlib_xconnection()), + _ => None + } + } #[inline] - fn get_xcb_connection(&self) -> Option<*mut raw::c_void> { + fn xcb_connection(&self) -> Option<*mut raw::c_void> { match self.window { - //LinuxWindow::X(ref w) => Some(w.get_xcb_connection()), + LinuxWindow::X(ref w) => Some(w.xcb_connection()), _ => None } } #[inline] fn set_urgent(&self, is_urgent: bool) { - //if let LinuxWindow::X(ref w) = self.window { - // w.set_urgent(is_urgent); - //} + if let LinuxWindow::X(ref w) = self.window { + w.set_urgent(is_urgent); + } } #[inline] - fn get_wayland_surface(&self) -> Option<*mut raw::c_void> { + fn wayland_surface(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::Wayland(ref w) => Some(w.get_surface().as_ref().c_ptr() as *mut _), + LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _), _ => None } } #[inline] - fn get_wayland_display(&self) -> Option<*mut raw::c_void> { + fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::Wayland(ref w) => Some(w.get_display().as_ref().c_ptr() as *mut _), + LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _), _ => None } } @@ -297,7 +315,7 @@ pub trait WindowBuilderExtUnix { /// Build window with override-redirect flag; defaults to false. Only relevant on X11. fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder; /// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11. - //fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder; + fn with_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. @@ -316,9 +334,9 @@ pub trait WindowBuilderExtUnix { impl WindowBuilderExtUnix for WindowBuilder { #[inline] fn with_x11_visual(mut self, visual_infos: *const T) -> WindowBuilder { - //self.platform_specific.visual_infos = Some( - // unsafe { ptr::read(visual_infos as *const XVisualInfo) } - //); + self.platform_specific.visual_infos = Some( + unsafe { ptr::read(visual_infos as *const XVisualInfo) } + ); self } @@ -340,11 +358,11 @@ impl WindowBuilderExtUnix for WindowBuilder { 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_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 { @@ -380,6 +398,6 @@ pub trait MonitorHandleExtUnix { impl MonitorHandleExtUnix for MonitorHandle { #[inline] fn native_id(&self) -> u32 { - self.inner.get_native_identifier() + self.inner.native_identifier() } } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 7a9de688..9f47365c 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -33,7 +33,7 @@ pub trait WindowExtWindows { /// 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; + fn hwnd(&self) -> *mut libc::c_void; /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn set_taskbar_icon(&self, taskbar_icon: Option); @@ -41,7 +41,7 @@ pub trait WindowExtWindows { impl WindowExtWindows for Window { #[inline] - fn get_hwnd(&self) -> *mut libc::c_void { + fn hwnd(&self) -> *mut libc::c_void { self.window.hwnd() as *mut _ } @@ -95,12 +95,12 @@ pub trait MonitorHandleExtWindows { impl MonitorHandleExtWindows for MonitorHandle { #[inline] fn native_id(&self) -> String { - self.inner.get_native_identifier() + self.inner.native_identifier() } #[inline] fn hmonitor(&self) -> *mut c_void { - self.inner.get_hmonitor() as *mut _ + self.inner.hmonitor() as *mut _ } } @@ -109,12 +109,12 @@ pub trait DeviceIdExtWindows { /// 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; + fn persistent_identifier(&self) -> Option; } impl DeviceIdExtWindows for DeviceId { #[inline] - fn get_persistent_identifier(&self) -> Option { - self.0.get_persistent_identifier() + fn persistent_identifier(&self) -> Option { + self.0.persistent_identifier() } } diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs deleted file mode 100644 index a8882339..00000000 --- a/src/platform/windows/events_loop.rs +++ /dev/null @@ -1,1258 +0,0 @@ -//! An events loop on Win32 is a background thread. -//! -//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop. -//! Destroying the events loop stops the thread. -//! -//! You can use the `execute_in_thread` method to execute some code in the background thread. -//! Since Win32 requires you to create a window in the right thread, you must use this method -//! to create a window. -//! -//! If you create a window whose class is set to `callback`, the window's events will be -//! propagated with `run_forever` and `poll_events`. -//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to -//! add a `WindowState` entry to a list of window to be used by the callback. - -use std::{mem, panic, ptr, thread}; -use std::any::Any; -use std::cell::RefCell; -use std::collections::HashMap; -use std::os::windows::io::AsRawHandle; -use std::sync::{Arc, mpsc, Mutex}; - -use backtrace::Backtrace; -use winapi::ctypes::c_int; -use winapi::shared::minwindef::{ - BOOL, - DWORD, - HIWORD, - INT, - LOWORD, - LPARAM, - LRESULT, - UINT, - WPARAM, -}; -use winapi::shared::windef::{HWND, POINT, RECT}; -use winapi::shared::windowsx; -use winapi::shared::winerror::S_OK; -use winapi::um::{libloaderapi, processthreadsapi, ole2, winuser}; -use winapi::um::oleidl::LPDROPTARGET; -use winapi::um::winnt::{LONG, LPCSTR, SHORT}; - -use { - ControlFlow, - Event, - EventsLoopClosed, - KeyboardInput, - LogicalPosition, - LogicalSize, - PhysicalSize, - WindowEvent, - WindowId as SuperWindowId, -}; -use events::{DeviceEvent, Touch, TouchPhase}; -use platform::platform::{event, WindowId, DEVICE_ID, wrap_device_id, util}; -use platform::platform::dpi::{ - become_dpi_aware, - dpi_to_scale_factor, - enable_non_client_dpi_scaling, - get_hwnd_scale_factor, -}; -use platform::platform::drop_handler::FileDropHandler; -use platform::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; -use platform::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state}; -use platform::platform::window::adjust_size; -use platform::platform::window_state::{CursorFlags, WindowFlags, WindowState}; - -/// Dummy object that allows inserting a window's state. -// We store a pointer in order to !impl Send and Sync. -pub struct Inserter(*mut u8); - -impl Inserter { - /// Inserts a window's state for the callback to use. The state is removed automatically if the - /// callback receives a `WM_CLOSE` message for the window. - pub fn insert(&self, window: HWND, state: Arc>) { - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - let was_in = context_stash.as_mut().unwrap().windows.insert(window, state); - assert!(was_in.is_none()); - }); - } -} - -pub struct EventsLoop { - thread_msg_target: HWND, - // Id of the background thread from the Win32 API. - thread_id: DWORD, - // Receiver for the events. The sender is in the background thread. - receiver: mpsc::Receiver, - // Sender instance that's paired with the receiver. Used to construct an `EventsLoopProxy`. - sender: mpsc::Sender, -} - -enum EventsLoopEvent { - WinitEvent(Event), - Panic(PanicError), -} - -impl EventsLoop { - pub fn new() -> EventsLoop { - Self::with_dpi_awareness(true) - } - - pub fn with_dpi_awareness(dpi_aware: bool) -> EventsLoop { - struct InitData { - thread_msg_target: HWND, - } - unsafe impl Send for InitData {} - - become_dpi_aware(dpi_aware); - - // The main events transfer channel. - let (tx, rx) = mpsc::channel(); - - // Channel to send initialization data created on the event loop thread back to the main - // thread. - let (init_tx, init_rx) = mpsc::sync_channel(0); - - let thread_sender = tx.clone(); - let panic_sender = tx.clone(); - let thread = thread::spawn(move || { - let tx = thread_sender; - let thread_msg_target = thread_event_target_window(); - - CONTEXT_STASH.with(|context_stash| { - *context_stash.borrow_mut() = Some(ThreadLocalData { - sender: tx, - windows: HashMap::with_capacity(4), - file_drop_handlers: HashMap::with_capacity(4), - mouse_buttons_down: 0, - panic_error: None, - }); - }); - - unsafe { - // Calling `PostThreadMessageA` on a thread that does not have an events queue yet - // will fail. In order to avoid this situation, we call `IsGuiThread` to initialize - // it. - winuser::IsGUIThread(1); - // Then only we unblock the `new()` function. We are sure that we don't call - // `PostThreadMessageA()` before `new()` returns. - init_tx.send(InitData{ thread_msg_target }).ok(); - drop(init_tx); - - let mut msg = mem::uninitialized(); - - loop { - if winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 { - // If a panic occurred in the child callback, forward the panic information - // to the parent thread. - let panic_payload_opt = CONTEXT_STASH.with(|stash| - stash.borrow_mut().as_mut() - .and_then(|s| s.panic_error.take()) - ); - if let Some(panic_payload) = panic_payload_opt { - panic_sender.send(EventsLoopEvent::Panic(panic_payload)).unwrap(); - }; - - // Only happens if the message is `WM_QUIT`. - debug_assert_eq!(msg.message, winuser::WM_QUIT); - break; - } - - // Calls `callback` below. - winuser::TranslateMessage(&msg); - winuser::DispatchMessageW(&msg); - } - } - }); - - // Blocks this function until the background thread has an events loop. See other comments. - let InitData { thread_msg_target } = init_rx.recv().unwrap(); - - let thread_id = unsafe { - let handle = mem::transmute(thread.as_raw_handle()); - processthreadsapi::GetThreadId(handle) - }; - - EventsLoop { - thread_msg_target, - thread_id, - receiver: rx, - sender: tx, - } - } - - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(Event) - { - loop { - let event = match self.receiver.try_recv() { - Ok(EventsLoopEvent::WinitEvent(e)) => e, - Ok(EventsLoopEvent::Panic(panic)) => { - eprintln!("resuming child thread unwind at: {:?}", Backtrace::new()); - panic::resume_unwind(panic) - }, - Err(_) => break, - }; - - callback(event); - } - } - - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(Event) -> ControlFlow - { - loop { - let event = match self.receiver.recv() { - Ok(EventsLoopEvent::WinitEvent(e)) => e, - Ok(EventsLoopEvent::Panic(panic)) => { - eprintln!("resuming child thread unwind at: {:?}", Backtrace::new()); - panic::resume_unwind(panic) - }, - Err(_) => break, - }; - - let flow = callback(event); - match flow { - ControlFlow::Continue => continue, - ControlFlow::Break => break, - } - } - } - - pub fn create_proxy(&self) -> EventsLoopProxy { - EventsLoopProxy { - thread_id: self.thread_id, - thread_msg_target: self.thread_msg_target, - sender: self.sender.clone(), - } - } - - /// Executes a function in the background thread. - /// - /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent - /// to the unstable FnBox. - /// - /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is - /// removed automatically if the callback receives a `WM_CLOSE` message for the window. - pub(super) fn execute_in_thread(&self, function: F) - where F: FnMut(Inserter) + Send + 'static - { - self.create_proxy().execute_in_thread(function) - } -} - -impl Drop for EventsLoop { - fn drop(&mut self) { - unsafe { - // Posting `WM_QUIT` will cause `GetMessage` to stop. - winuser::PostThreadMessageA(self.thread_id, winuser::WM_QUIT, 0, 0); - } - } -} - -#[derive(Clone)] -pub struct EventsLoopProxy { - thread_id: DWORD, - thread_msg_target: HWND, - sender: mpsc::Sender, -} - -unsafe impl Send for EventsLoopProxy {} -unsafe impl Sync for EventsLoopProxy {} - -impl EventsLoopProxy { - pub fn wakeup(&self) -> Result<(), EventsLoopClosed> { - self.sender.send(EventsLoopEvent::WinitEvent(Event::Awakened)).map_err(|_| EventsLoopClosed) - } - - /// Executes a function in the background thread. - /// - /// Note that we use FnMut instead of FnOnce because boxing FnOnce won't work on stable Rust - /// until 2030 when the design of Box is finally complete. - /// https://github.com/rust-lang/rust/issues/28796 - /// - /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is - /// removed automatically if the callback receives a `WM_CLOSE` message for the window. - /// - /// Note that if you are using this to change some property of a window and updating - /// `WindowState` then you should call this within the lock of `WindowState`. Otherwise the - /// events may be sent to the other thread in different order to the one in which you set - /// `WindowState`, leaving them out of sync. - pub fn execute_in_thread(&self, mut function: F) - where - F: FnMut(Inserter) + Send + 'static, - { - if unsafe{ processthreadsapi::GetCurrentThreadId() } == self.thread_id { - function(Inserter(ptr::null_mut())); - } else { - // We are using double-boxing here because it make casting back much easier - let double_box: ThreadExecFn = Box::new(Box::new(function) as Box); - let raw = Box::into_raw(double_box); - - let res = unsafe { - winuser::PostMessageW( - self.thread_msg_target, - *EXEC_MSG_ID, - raw as *mut () as usize as WPARAM, - 0, - ) - }; - assert!(res != 0, "PostMessage failed; is the messages queue full?"); - } - } -} - -type ThreadExecFn = Box>; - -lazy_static! { - // Message sent when we want to execute a closure in the thread. - // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, - // and LPARAM is unused. - static ref EXEC_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as LPCSTR) - } - }; - // Message sent by a `Window` when it wants to be destroyed by the main thread. - // WPARAM and LPARAM are unused. - pub static ref DESTROY_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as LPCSTR) - } - }; - // Message sent by a `Window` after creation if it has a DPI != 96. - // WPARAM is the the DPI (u32). LOWORD of LPARAM is width, and HIWORD is height. - pub static ref INITIAL_DPI_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR) - } - }; - // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the - // documentation in the `window_state` module for more information. - pub static ref SET_RETAIN_STATE_ON_SIZE_MSG_ID: u32 = unsafe { - winuser::RegisterWindowMessageA("Winit::SetRetainMaximized\0".as_ptr() as LPCSTR) - }; - static ref THREAD_EVENT_TARGET_WINDOW_CLASS: Vec = unsafe { - use std::ffi::OsStr; - use std::os::windows::ffi::OsStrExt; - - let class_name: Vec<_> = OsStr::new("Winit Thread Event Target") - .encode_wide() - .chain(Some(0).into_iter()) - .collect(); - - let class = winuser::WNDCLASSEXW { - cbSize: mem::size_of::() as UINT, - style: 0, - lpfnWndProc: Some(thread_event_target_callback), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: libloaderapi::GetModuleHandleW(ptr::null()), - hIcon: ptr::null_mut(), - hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly - hbrBackground: ptr::null_mut(), - lpszMenuName: ptr::null(), - lpszClassName: class_name.as_ptr(), - hIconSm: ptr::null_mut(), - }; - - winuser::RegisterClassExW(&class); - - class_name - }; -} - -fn thread_event_target_window() -> HWND { - unsafe { - let window = winuser::CreateWindowExW( - winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, - THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(), - ptr::null_mut(), - 0, - 0, 0, - 0, 0, - ptr::null_mut(), - ptr::null_mut(), - libloaderapi::GetModuleHandleW(ptr::null()), - ptr::null_mut(), - ); - winuser::SetWindowLongPtrW( - window, - winuser::GWL_STYLE, - (winuser::WS_VISIBLE | winuser::WS_POPUP) as _ - ); - - window - } -} - -// There's no parameters passed to the callback function, so it needs to get its context stashed -// in a thread-local variable. -thread_local!(static CONTEXT_STASH: RefCell> = RefCell::new(None)); -struct ThreadLocalData { - sender: mpsc::Sender, - windows: HashMap>>, - file_drop_handlers: HashMap, // Each window has its own drop handler. - mouse_buttons_down: u32, - panic_error: Option, -} -type PanicError = Box; - -// Utility function that dispatches an event on the current thread. -pub fn send_event(event: Event) { - CONTEXT_STASH.with(|context_stash| { - let context_stash = context_stash.borrow(); - - let _ = context_stash.as_ref().unwrap().sender.send(EventsLoopEvent::WinitEvent(event)); // Ignoring if closed - }); -} - -/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of -/// the window. -unsafe fn capture_mouse(window: HWND) { - let set_capture = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - context_stash.mouse_buttons_down += 1; - true - } else { - false - } - }); - if set_capture { - winuser::SetCapture(window); - } -} - -/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor -/// is outside the window. -unsafe fn release_mouse() { - let release_capture = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - context_stash.mouse_buttons_down = context_stash.mouse_buttons_down.saturating_sub(1); - if context_stash.mouse_buttons_down == 0 { - return true; - } - } - false - }); - if release_capture { - winuser::ReleaseCapture(); - } -} - -pub unsafe fn run_catch_panic(error: R, f: F) -> R - where F: panic::UnwindSafe + FnOnce() -> R -{ - // If a panic has been triggered, cancel all future operations in the function. - if CONTEXT_STASH.with(|stash| stash.borrow().as_ref().map(|s| s.panic_error.is_some()).unwrap_or(false)) { - return error; - } - - let callback_result = panic::catch_unwind(f); - match callback_result { - Ok(lresult) => lresult, - Err(err) => CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - context_stash.panic_error = Some(err); - winuser::PostQuitMessage(-1); - } - error - }) - } -} - -/// Any window whose callback is configured to this function will have its events propagated -/// through the events loop of the thread the window was created in. -// -// This is the callback that is called by `DispatchMessage` in the events loop. -// -// Returning 0 tells the Win32 API that the message has been processed. -// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -pub unsafe extern "system" fn callback( - window: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // Unwinding into foreign code is undefined behavior. So we catch any panics that occur in our - // code, and if a panic happens we cancel any future operations. - run_catch_panic(-1, || callback_inner(window, msg, wparam, lparam)) -} - -unsafe fn callback_inner( - window: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - match msg { - winuser::WM_CREATE => { - use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE}; - let ole_init_result = ole2::OleInitialize(ptr::null_mut()); - // It is ok if the initialize result is `S_FALSE` because it might happen that - // multiple windows are created on the same thread. - if ole_init_result == OLE_E_WRONGCOMPOBJ { - panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); - } else if ole_init_result == RPC_E_CHANGED_MODE { - panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`"); - } - - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - - let drop_handlers = &mut context_stash.as_mut().unwrap().file_drop_handlers; - let new_handler = FileDropHandler::new(window); - let handler_interface_ptr = &mut (*new_handler.data).interface as LPDROPTARGET; - drop_handlers.insert(window, new_handler); - - assert_eq!(ole2::RegisterDragDrop(window, handler_interface_ptr), S_OK); - }); - 0 - }, - - winuser::WM_NCCREATE => { - enable_non_client_dpi_scaling(window); - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_CLOSE => { - use events::WindowEvent::CloseRequested; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CloseRequested - }); - 0 - }, - - winuser::WM_DESTROY => { - use events::WindowEvent::Destroyed; - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - ole2::RevokeDragDrop(window); - let context_stash_mut = context_stash.as_mut().unwrap(); - context_stash_mut.file_drop_handlers.remove(&window); - context_stash_mut.windows.remove(&window); - }); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Destroyed - }); - 0 - }, - - winuser::WM_PAINT => { - use events::WindowEvent::Refresh; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Refresh, - }); - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - // WM_MOVE supplies client area positions, so we send Moved here instead. - winuser::WM_WINDOWPOSCHANGED => { - use events::WindowEvent::Moved; - - let windowpos = lparam as *const winuser::WINDOWPOS; - if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { - let dpi_factor = get_hwnd_scale_factor(window); - let logical_position = LogicalPosition::from_physical( - ((*windowpos).x, (*windowpos).y), - dpi_factor, - ); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Moved(logical_position), - }); - } - - // This is necessary for us to still get sent WM_SIZE. - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_SIZE => { - use events::WindowEvent::Resized; - let w = LOWORD(lparam as DWORD) as u32; - let h = HIWORD(lparam as DWORD) as u32; - - let dpi_factor = get_hwnd_scale_factor(window); - let logical_size = LogicalSize::from_physical((w, h), dpi_factor); - let event = Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Resized(logical_size), - }; - - // Wait for the parent thread to process the resize event before returning from the - // callback. - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - let cstash = context_stash.as_mut().unwrap(); - - if let Some(w) = cstash.windows.get_mut(&window) { - let mut w = w.lock().unwrap(); - - // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. - if !w.window_flags().contains(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE) { - let maximized = wparam == winuser::SIZE_MAXIMIZED; - w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); - } - } - - cstash.sender.send(EventsLoopEvent::WinitEvent(event)).ok(); - }); - 0 - }, - - winuser::WM_CHAR => { - use std::mem; - use events::WindowEvent::ReceivedCharacter; - let chr: char = mem::transmute(wparam as u32); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); - 0 - }, - - // Prevents default windows menu hotkeys playing unwanted - // "ding" sounds. Alternatively could check for WM_SYSCOMMAND - // with wparam being SC_KEYMENU, but this may prevent some - // other unwanted default hotkeys as well. - winuser::WM_SYSCHAR => { - 0 - } - - winuser::WM_MOUSEMOVE => { - use events::WindowEvent::{CursorEntered, CursorMoved}; - let x = windowsx::GET_X_LPARAM(lparam); - let y = windowsx::GET_Y_LPARAM(lparam); - - let mouse_was_outside_window = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - if let Some(w) = context_stash.windows.get_mut(&window) { - let mut w = w.lock().unwrap(); - - let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); - w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)).ok(); - return was_outside_window; - } - } - - false - }); - - - if mouse_was_outside_window { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorEntered { device_id: DEVICE_ID }, - }); - - // Calling TrackMouseEvent in order to receive mouse leave events. - winuser::TrackMouseEvent(&mut winuser::TRACKMOUSEEVENT { - cbSize: mem::size_of::() as DWORD, - dwFlags: winuser::TME_LEAVE, - hwndTrack: window, - dwHoverTime: winuser::HOVER_DEFAULT, - }); - } - - let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x as f64, y as f64), dpi_factor); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, - }); - - 0 - }, - - winuser::WM_MOUSELEAVE => { - use events::WindowEvent::CursorLeft; - - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - if let Some(w) = context_stash.windows.get_mut(&window) { - let mut w = w.lock().unwrap(); - w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)).ok(); - } - } - }); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorLeft { device_id: DEVICE_ID } - }); - - 0 - }, - - winuser::WM_MOUSEWHEEL => { - use events::MouseScrollDelta::LineDelta; - use events::TouchPhase; - - let value = (wparam >> 16) as i16; - let value = value as i32; - let value = value as f32 / winuser::WHEEL_DELTA as f32; - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods() }, - }); - - 0 - }, - - winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { - use events::ElementState::Pressed; - use events::VirtualKeyCode; - if msg == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 { - winuser::DefWindowProcW(window, msg, wparam, lparam) - } else { - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - } - } - }); - // Windows doesn't emit a delete character by default, but in order to make it - // consistent with the other platforms we'll emit a delete character here. - if vkey == Some(VirtualKeyCode::Delete) { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::ReceivedCharacter('\u{7F}'), - }); - } - } - 0 - } - }, - - winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { - use events::ElementState::Released; - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - } - }); - } - 0 - }, - - winuser::WM_LBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Left; - use events::ElementState::Pressed; - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_LBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Left; - use events::ElementState::Released; - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_RBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Right; - use events::ElementState::Pressed; - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_RBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Right; - use events::ElementState::Released; - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_MBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Middle; - use events::ElementState::Pressed; - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_MBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Middle; - use events::ElementState::Released; - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_XBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Other; - use events::ElementState::Pressed; - let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8), modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_XBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Other; - use events::ElementState::Released; - let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8), modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); - - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_INPUT => { - use events::DeviceEvent::{Motion, MouseMotion, MouseWheel, Button, Key}; - use events::MouseScrollDelta::LineDelta; - use events::ElementState::{Pressed, Released}; - - if let Some(data) = get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); - - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x } - }); - } - - if y != 0.0 { - send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y } - }); - } - - if x != 0.0 || y != 0.0 { - send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) } - }); - } - } - - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; - send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { delta: LineDelta(0.0, delta as f32) } - }); - } - - let button_state = get_raw_mouse_button_state(mouse.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - send_event(Event::DeviceEvent { - device_id, - event: Button { - button, - state, - } - }); - } - } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); - - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { - Pressed - } else { - Released - }; - - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - if let Some((vkey, scancode)) = handle_extended_keys( - keyboard.VKey as _, - scancode, - extended, - ) { - let virtual_keycode = vkey_to_winit_vkey(vkey); - - send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } - } - - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_TOUCH => { - let pcount = LOWORD( wparam as DWORD ) as usize; - let mut inputs = Vec::with_capacity( pcount ); - inputs.set_len( pcount ); - let htouch = lparam as winuser::HTOUCHINPUT; - if winuser::GetTouchInputInfo( - htouch, - pcount as UINT, - inputs.as_mut_ptr(), - mem::size_of::() as INT, - ) > 0 { - let dpi_factor = get_hwnd_scale_factor(window); - for input in &inputs { - let x = (input.x as f64) / 100f64; - let y = (input.y as f64) / 100f64; - let location = LogicalPosition::from_physical((x, y), dpi_factor); - send_event( Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::Touch(Touch { - phase: - if input.dwFlags & winuser::TOUCHEVENTF_DOWN != 0 { - TouchPhase::Started - } else if input.dwFlags & winuser::TOUCHEVENTF_UP != 0 { - TouchPhase::Ended - } else if input.dwFlags & winuser::TOUCHEVENTF_MOVE != 0 { - TouchPhase::Moved - } else { - continue; - }, - location, - id: input.dwID as u64, - device_id: DEVICE_ID, - }) - }); - } - } - winuser::CloseTouchInputHandle( htouch ); - 0 - } - - winuser::WM_SETFOCUS => { - use events::WindowEvent::{Focused, CursorMoved}; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Focused(true) - }); - - let x = windowsx::GET_X_LPARAM(lparam) as f64; - let y = windowsx::GET_Y_LPARAM(lparam) as f64; - let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x, y), dpi_factor); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, - }); - - 0 - }, - - winuser::WM_KILLFOCUS => { - use events::WindowEvent::Focused; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Focused(false) - }); - 0 - }, - - winuser::WM_SETCURSOR => { - let set_cursor_to = CONTEXT_STASH.with(|context_stash| { - context_stash - .borrow() - .as_ref() - .and_then(|cstash| cstash.windows.get(&window)) - .and_then(|window_state_mutex| { - let window_state = window_state_mutex.lock().unwrap(); - if window_state.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW) { - Some(window_state.mouse.cursor) - } else { - None - } - }) - }); - - match set_cursor_to { - Some(cursor) => { - let cursor = winuser::LoadCursorW( - ptr::null_mut(), - cursor.to_windows_cursor(), - ); - winuser::SetCursor(cursor); - 0 - }, - None => winuser::DefWindowProcW(window, msg, wparam, lparam) - } - }, - - winuser::WM_DROPFILES => { - // See `FileDropHandler` for implementation. - 0 - }, - - winuser::WM_GETMINMAXINFO => { - let mmi = lparam as *mut winuser::MINMAXINFO; - //(*mmi).max_position = winapi::shared::windef::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor. - //(*mmi).max_size = winapi::shared::windef::POINT { x: .., y: .. }; // The dimensions of the primary monitor. - - CONTEXT_STASH.with(|context_stash| { - if let Some(cstash) = context_stash.borrow().as_ref() { - if let Some(wstash) = cstash.windows.get(&window) { - let window_state = wstash.lock().unwrap(); - - if window_state.min_size.is_some() || window_state.max_size.is_some() { - let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; - let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; - if let Some(min_size) = window_state.min_size { - let min_size = min_size.to_physical(window_state.dpi_factor); - let (width, height) = adjust_size(min_size, style, ex_style); - (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32 }; - } - if let Some(max_size) = window_state.max_size { - let max_size = max_size.to_physical(window_state.dpi_factor); - let (width, height) = adjust_size(max_size, style, ex_style); - (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32 }; - } - } - } - } - }); - - 0 - }, - - // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change - // DPI, therefore all applications are closed while DPI is changing. - winuser::WM_DPICHANGED => { - use events::WindowEvent::HiDpiFactorChanged; - - // This message actually provides two DPI values - x and y. However MSDN says that - // "you only need to use either the X-axis or the Y-axis value when scaling your - // application since they are the same". - // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx - let new_dpi_x = u32::from(LOWORD(wparam as DWORD)); - let new_dpi_factor = dpi_to_scale_factor(new_dpi_x); - - let allow_resize = CONTEXT_STASH.with(|context_stash| { - if let Some(wstash) = context_stash.borrow().as_ref().and_then(|cstash| cstash.windows.get(&window)) { - let mut window_state = wstash.lock().unwrap(); - let old_dpi_factor = window_state.dpi_factor; - window_state.dpi_factor = new_dpi_factor; - - new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none() - } else { - true - } - }); - - // This prevents us from re-applying DPI adjustment to the restored size after exiting - // fullscreen (the restored size is already DPI adjusted). - if allow_resize { - // Resize window to the size suggested by Windows. - let rect = &*(lparam as *const RECT); - winuser::SetWindowPos( - window, - ptr::null_mut(), - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, - ); - } - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: HiDpiFactorChanged(new_dpi_factor), - }); - - 0 - }, - - _ => { - if msg == *DESTROY_MSG_ID { - winuser::DestroyWindow(window); - 0 - } else if msg == *INITIAL_DPI_MSG_ID { - use events::WindowEvent::HiDpiFactorChanged; - let scale_factor = dpi_to_scale_factor(wparam as u32); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: HiDpiFactorChanged(scale_factor), - }); - // Automatically resize for actual DPI - let width = LOWORD(lparam as DWORD) as u32; - let height = HIWORD(lparam as DWORD) as u32; - let (adjusted_width, adjusted_height): (u32, u32) = PhysicalSize::from_logical( - (width, height), - scale_factor, - ).into(); - // We're not done yet! `SetWindowPos` needs the window size, not the client area size. - let mut rect = RECT { - top: 0, - left: 0, - bottom: adjusted_height as LONG, - right: adjusted_width as LONG, - }; - let dw_style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; - let b_menu = !winuser::GetMenu(window).is_null() as BOOL; - let dw_style_ex = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; - winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as c_int; - let outer_y = (rect.top - rect.bottom).abs() as c_int; - winuser::SetWindowPos( - window, - ptr::null_mut(), - 0, - 0, - outer_x, - outer_y, - winuser::SWP_NOMOVE - | winuser::SWP_NOREPOSITION - | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE, - ); - 0 - } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { - CONTEXT_STASH.with(|context_stash| { - if let Some(cstash) = context_stash.borrow().as_ref() { - if let Some(wstash) = cstash.windows.get(&window) { - let mut window_state = wstash.lock().unwrap(); - window_state.set_window_flags_in_place(|f| f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0)); - } - } - }); - 0 - } else { - winuser::DefWindowProcW(window, msg, wparam, lparam) - } - } - } -} - -pub unsafe extern "system" fn thread_event_target_callback( - window: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // See `callback` comment. - run_catch_panic(-1, || { - match msg { - _ if msg == *EXEC_MSG_ID => { - let mut function: ThreadExecFn = Box::from_raw(wparam as usize as *mut _); - function(Inserter(ptr::null_mut())); - 0 - }, - _ => winuser::DefWindowProcW(window, msg, wparam, lparam) - } - }) -} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index a6c69250..ce58c0e7 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -15,7 +15,7 @@ use { Event, LogicalPosition, LogicalSize, - MouseCursor, + CursorIcon, PhysicalPosition, PhysicalSize, WindowAttributes, @@ -23,9 +23,12 @@ use { WindowId as RootWindowId, }; use CreationError::OsError; +use error::{ExternalError, NotSupportedError}; use events::{Touch, TouchPhase}; use window::MonitorHandle as RootMonitorHandle; +pub type OsError = std::io::Error; + pub struct EventLoop { event_rx: Receiver, suspend_callback: RefCell ()>>>, @@ -45,14 +48,14 @@ impl EventLoop { } #[inline] - pub fn get_available_monitors(&self) -> VecDeque { + pub fn available_monitors(&self) -> VecDeque { let mut rb = VecDeque::with_capacity(1); rb.push_back(MonitorHandle); rb } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } @@ -62,7 +65,7 @@ impl EventLoop { while let Ok(event) = self.event_rx.try_recv() { let e = match event{ android_glue::Event::EventMotion(motion) => { - let dpi_factor = MonitorHandle.get_hidpi_factor(); + let dpi_factor = MonitorHandle.hidpi_factor(); let location = LogicalPosition::from_physical( (motion.x as f64, motion.y as f64), dpi_factor, @@ -99,12 +102,12 @@ impl EventLoop { android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => { // Activity Orientation changed or resized. - let native_window = unsafe { android_glue::get_native_window() }; + let native_window = unsafe { android_glue::native_window() }; if native_window.is_null() { None } else { - let dpi_factor = MonitorHandle.get_hidpi_factor(); - let physical_size = MonitorHandle.get_dimensions(); + let dpi_factor = MonitorHandle.hidpi_factor(); + let physical_size = MonitorHandle.dimensions(); let size = LogicalSize::from_physical(physical_size, dpi_factor); Some(Event::WindowEvent { window_id: RootWindowId(WindowId), @@ -203,10 +206,10 @@ impl fmt::Debug for MonitorHandle { } let monitor_id_proxy = MonitorHandle { - name: self.get_name(), - dimensions: self.get_dimensions(), - position: self.get_position(), - hidpi_factor: self.get_hidpi_factor(), + name: self.name(), + dimensions: self.dimensions(), + position: self.outer_position(), + hidpi_factor: self.hidpi_factor(), }; monitor_id_proxy.fmt(f) @@ -215,14 +218,14 @@ impl fmt::Debug for MonitorHandle { impl MonitorHandle { #[inline] - pub fn get_name(&self) -> Option { + pub fn name(&self) -> Option { Some("Primary".to_string()) } #[inline] - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { unsafe { - let window = android_glue::get_native_window(); + let window = android_glue::native_window(); ( ffi::ANativeWindow_getWidth(window) as f64, ffi::ANativeWindow_getHeight(window) as f64, @@ -231,13 +234,13 @@ impl MonitorHandle { } #[inline] - pub fn get_position(&self) -> PhysicalPosition { + pub fn outer_position(&self) -> PhysicalPosition { // Android assumes single screen (0, 0).into() } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { 1.0 } } @@ -252,12 +255,12 @@ impl Window { _: PlatformSpecificWindowBuilderAttributes) -> Result { - let native_window = unsafe { android_glue::get_native_window() }; + let native_window = unsafe { android_glue::native_window() }; if native_window.is_null() { return Err(OsError(format!("Android's native window is null"))); } - android_glue::set_multitouch(win_attribs.multitouch); + android_glue::set_multitouch(true); Ok(Window { native_window: native_window as *const _, @@ -265,7 +268,7 @@ impl Window { } #[inline] - pub fn get_native_window(&self) -> *const c_void { + pub fn native_window(&self) -> *const c_void { self.native_window } @@ -285,29 +288,29 @@ impl Window { } #[inline] - pub fn get_position(&self) -> Option { + pub fn outer_position(&self) -> Option { // N/A None } #[inline] - pub fn get_inner_position(&self) -> Option { + pub fn inner_position(&self) -> Option { // N/A None } #[inline] - pub fn set_position(&self, _position: LogicalPosition) { + pub fn set_outer_position(&self, _position: LogicalPosition) { // N/A } #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _dimensions: Option) { // N/A } #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _dimensions: Option) { // N/A } @@ -317,19 +320,19 @@ impl Window { } #[inline] - pub fn get_inner_size(&self) -> Option { + pub fn inner_size(&self) -> Option { if self.native_window.is_null() { None } else { - let dpi_factor = self.get_hidpi_factor(); - let physical_size = self.get_current_monitor().get_dimensions(); + let dpi_factor = self.hidpi_factor(); + let physical_size = self.current_monitor().dimensions(); Some(LogicalSize::from_physical(physical_size, dpi_factor)) } } #[inline] - pub fn get_outer_size(&self) -> Option { - self.get_inner_size() + pub fn outer_size(&self) -> Option { + self.inner_size() } #[inline] @@ -338,18 +341,18 @@ impl Window { } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - self.get_current_monitor().get_hidpi_factor() + pub fn hidpi_factor(&self) -> f64 { + self.current_monitor().hidpi_factor() } #[inline] - pub fn set_cursor(&self, _: MouseCursor) { + pub fn set_cursor_icon(&self, _: CursorIcon) { // N/A } #[inline] - pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> { - Err("Cursor grabbing is not possible on Android.".to_owned()) + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] @@ -358,8 +361,8 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> { - Err("Setting cursor position is not possible on Android.".to_owned()) + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] @@ -368,6 +371,13 @@ impl Window { // Android has single screen maximized apps so nothing to do } + #[inline] + pub fn fullscreen(&self) -> Option { + // N/A + // Android has single screen maximized apps so nothing to do + None + } + #[inline] pub fn set_fullscreen(&self, _monitor: Option) { // N/A @@ -390,24 +400,24 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _spot: LogicalPosition) { + pub fn set_ime_position(&self, _spot: LogicalPosition) { // N/A } #[inline] - pub fn get_current_monitor(&self) -> RootMonitorHandle { + pub fn current_monitor(&self) -> RootMonitorHandle { RootMonitorHandle { inner: MonitorHandle } } #[inline] - pub fn get_available_monitors(&self) -> VecDeque { + pub fn available_monitors(&self) -> VecDeque { let mut rb = VecDeque::with_capacity(1); rb.push_back(MonitorHandle); rb } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } diff --git a/src/platform_impl/emscripten/mod.rs b/src/platform_impl/emscripten/mod.rs index 571d6b45..140ba53d 100644 --- a/src/platform_impl/emscripten/mod.rs +++ b/src/platform_impl/emscripten/mod.rs @@ -10,11 +10,12 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, Arc}; use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use error::{ExternalError, NotSupportedError}; use window::MonitorHandle as RootMonitorHandle; const DOCUMENT_NAME: &'static str = "#document\0"; -fn get_hidpi_factor() -> f64 { +fn hidpi_factor() -> f64 { unsafe { ffi::emscripten_get_device_pixel_ratio() as f64 } } @@ -24,6 +25,8 @@ pub struct PlatformSpecificWindowBuilderAttributes; unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} +pub type OsError = std::io::Error; + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; @@ -41,23 +44,23 @@ pub struct MonitorHandle; impl MonitorHandle { #[inline] - pub fn get_name(&self) -> Option { + pub fn name(&self) -> Option { Some("Canvas".to_owned()) } #[inline] - pub fn get_position(&self) -> PhysicalPosition { + pub fn outer_position(&self) -> PhysicalPosition { unimplemented!() } #[inline] - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { (0, 0).into() } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - get_hidpi_factor() + pub fn hidpi_factor(&self) -> f64 { + hidpi_factor() } } @@ -113,14 +116,14 @@ impl EventLoop { } #[inline] - pub fn get_available_monitors(&self) -> VecDeque { + pub fn available_monitors(&self) -> VecDeque { let mut list = VecDeque::with_capacity(1); list.push_back(MonitorHandle); list } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } @@ -163,7 +166,7 @@ impl WindowId { pub struct Window2 { cursor_grabbed: Mutex, - cursor_hidden: Mutex, + cursor_visible: Mutex, is_fullscreen: bool, events: Box>>, } @@ -208,7 +211,7 @@ extern "C" fn mouse_callback( match event_type { ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => { - let dpi_factor = get_hidpi_factor(); + let dpi_factor = hidpi_factor(); let position = LogicalPosition::from_physical( ((*event).canvasX as f64, (*event).canvasY as f64), dpi_factor, @@ -328,7 +331,7 @@ extern fn touch_callback( for touch in 0..(*event).numTouches as usize { let touch = (*event).touches[touch]; if touch.isChanged == ffi::EM_TRUE { - let dpi_factor = get_hidpi_factor(); + let dpi_factor = hidpi_factor(); let location = LogicalPosition::from_physical( (touch.canvasX as f64, touch.canvasY as f64), dpi_factor, @@ -387,8 +390,8 @@ impl Window { } let w = Window2 { - cursor_grabbed: Default::default(), - cursor_hidden: Default::default(), + cursor_grabbed: Mutex::new(false), + cursor_visible: Mutex::new(true), events: Default::default(), is_fullscreen: attribs.fullscreen.is_some(), }; @@ -427,7 +430,7 @@ 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(size) = attribs.dimensions { + } else if let Some(size) = attribs.inner_size { window.set_inner_size(size); } @@ -445,21 +448,21 @@ impl Window { } #[inline] - pub fn get_position(&self) -> Option { + pub fn outer_position(&self) -> Option { Some((0, 0).into()) } #[inline] - pub fn get_inner_position(&self) -> Option { + pub fn inner_position(&self) -> Option { Some((0, 0).into()) } #[inline] - pub fn set_position(&self, _: LogicalPosition) { + pub fn set_outer_position(&self, _: LogicalPosition) { } #[inline] - pub fn get_inner_size(&self) -> Option { + pub fn inner_size(&self) -> Option { unsafe { let mut width = 0; let mut height = 0; @@ -470,7 +473,7 @@ impl Window { { None } else { - let dpi_factor = self.get_hidpi_factor(); + let dpi_factor = self.hidpi_factor(); let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor); Some(logical) } @@ -478,14 +481,14 @@ impl Window { } #[inline] - pub fn get_outer_size(&self) -> Option { - self.get_inner_size() + pub fn outer_size(&self) -> Option { + self.inner_size() } #[inline] pub fn set_inner_size(&self, size: LogicalSize) { unsafe { - let dpi_factor = self.get_hidpi_factor(); + let dpi_factor = self.hidpi_factor(); let physical = PhysicalSize::from_logical(size, dpi_factor); let (width, height): (u32, u32) = physical.into(); ffi::emscripten_set_element_css_size( @@ -497,12 +500,12 @@ impl Window { } #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _dimensions: Option) { // N/A } #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _dimensions: Option) { // N/A } @@ -522,12 +525,12 @@ impl Window { } #[inline] - pub fn set_cursor(&self, _cursor: ::MouseCursor) { + pub fn set_cursor_icon(&self, _cursor: ::CursorIcon) { // N/A } #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap(); if grab == *grabbed_lock { return Ok(()); } unsafe { @@ -554,24 +557,24 @@ impl Window { } #[inline] - 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 { + pub fn set_cursor_visible(&self, visible: bool) { + let mut visible_lock = self.window.cursor_visible.lock().unwrap(); + if visible == *visible_lock { return; } + if visible { show_mouse(); + } else { + unsafe { ffi::emscripten_hide_mouse() }; } - *hidden_lock = hide; + *visible_lock = visible; } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - get_hidpi_factor() + pub fn hidpi_factor(&self) -> f64 { + hidpi_factor() } #[inline] - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> { + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { Err("Setting cursor position is not possible on Emscripten.".to_owned()) } @@ -580,6 +583,11 @@ impl Window { // iOS has single screen maximized apps so nothing to do } + #[inline] + pub fn fullscreen(&self) -> Option<::MonitorHandle> { + None + } + #[inline] pub fn set_fullscreen(&self, _monitor: Option<::MonitorHandle>) { // iOS has single screen maximized apps so nothing to do @@ -601,24 +609,24 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) { + pub fn set_ime_position(&self, _logical_spot: LogicalPosition) { // N/A } #[inline] - pub fn get_current_monitor(&self) -> RootMonitorHandle { + pub fn current_monitor(&self) -> RootMonitorHandle { RootMonitorHandle { inner: MonitorHandle } } #[inline] - pub fn get_available_monitors(&self) -> VecDeque { + pub fn available_monitors(&self) -> VecDeque { let mut list = VecDeque::with_capacity(1); list.push_back(MonitorHandle); list } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } } @@ -634,7 +642,7 @@ impl Drop for Window { unsafe { // Return back to normal cursor state self.hide_cursor(false); - self.grab_cursor(false); + self.set_cursor_grab(false); // Exit fullscreen if on if self.window.is_fullscreen { diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs new file mode 100644 index 00000000..295891d5 --- /dev/null +++ b/src/platform_impl/ios/app_state.rs @@ -0,0 +1,580 @@ +use std::{mem, ptr}; +use std::cell::{RefCell, RefMut}; +use std::mem::ManuallyDrop; +use std::os::raw::c_void; +use std::time::Instant; + +use event::{Event, StartCause}; +use event_loop::ControlFlow; + +use platform_impl::platform::event_loop::{EventHandler, Never}; +use platform_impl::platform::ffi::{ + id, + CFAbsoluteTimeGetCurrent, + CFRelease, + CFRunLoopAddTimer, + CFRunLoopGetMain, + CFRunLoopRef, + CFRunLoopTimerCreate, + CFRunLoopTimerInvalidate, + CFRunLoopTimerRef, + CFRunLoopTimerSetNextFireDate, + kCFRunLoopCommonModes, + NSUInteger, +}; + +macro_rules! bug { + ($msg:expr) => { + panic!("winit iOS bug, file an issue: {}", $msg) + }; +} + +// this is the state machine for the app lifecycle +#[derive(Debug)] +enum AppStateImpl { + NotLaunched { + queued_windows: Vec, + queued_events: Vec>, + }, + Launching { + queued_windows: Vec, + queued_events: Vec>, + queued_event_handler: Box, + }, + ProcessingEvents { + event_handler: Box, + active_control_flow: ControlFlow, + }, + // special state to deal with reentrancy and prevent mutable aliasing. + InUserCallback { + queued_events: Vec>, + }, + Waiting { + waiting_event_handler: Box, + start: Instant, + }, + PollFinished { + waiting_event_handler: Box, + }, + Terminated, +} + +impl Drop for AppStateImpl { + fn drop(&mut self) { + match self { + &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } | + &mut AppStateImpl::Launching { ref mut queued_windows, .. } => unsafe { + for &mut window in queued_windows { + let () = msg_send![window, release]; + } + } + _ => {} + } + } +} + +pub struct AppState { + app_state: AppStateImpl, + control_flow: ControlFlow, + waker: EventLoopWaker, +} + +impl AppState { + // requires main thread + unsafe fn get_mut() -> RefMut<'static, AppState> { + // basically everything in UIKit requires the main thread, so it's pointless to use the + // std::sync APIs. + // must be mut because plain `static` requires `Sync` + static mut APP_STATE: RefCell> = RefCell::new(None); + + if cfg!(debug_assertions) { + assert_main_thread!("bug in winit: `AppState::get_mut()` can only be called on the main thread"); + } + + let mut guard = APP_STATE.borrow_mut(); + if guard.is_none() { + #[inline(never)] + #[cold] + unsafe fn init_guard(guard: &mut RefMut<'static, Option>) { + let waker = EventLoopWaker::new(CFRunLoopGetMain()); + **guard = Some(AppState { + app_state: AppStateImpl::NotLaunched { + queued_windows: Vec::new(), + queued_events: Vec::new(), + }, + control_flow: ControlFlow::default(), + waker, + }); + } + init_guard(&mut guard) + } + RefMut::map(guard, |state| { + state.as_mut().unwrap() + }) + } + + // requires main thread and window is a UIWindow + // retains window + pub unsafe fn set_key_window(window: id) { + let mut this = AppState::get_mut(); + match &mut this.app_state { + &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => { + queued_windows.push(window); + msg_send![window, retain]; + return; + } + &mut AppStateImpl::ProcessingEvents { .. } => {}, + &mut AppStateImpl::InUserCallback { .. } => {}, + &mut AppStateImpl::Terminated => panic!("Attempt to create a `Window` \ + after the app has terminated"), + app_state => unreachable!("unexpected state: {:#?}", app_state), // all other cases should be impossible + } + drop(this); + msg_send![window, makeKeyAndVisible] + } + + // requires main thread + pub unsafe fn will_launch(queued_event_handler: Box) { + let mut this = AppState::get_mut(); + let (queued_windows, queued_events) = match &mut this.app_state { + &mut AppStateImpl::NotLaunched { + ref mut queued_windows, + ref mut queued_events, + } => { + let windows = ptr::read(queued_windows); + let events = ptr::read(queued_events); + (windows, events) + } + _ => panic!("winit iOS expected the app to be in a `NotLaunched` \ + state, but was not - please file an issue"), + }; + ptr::write(&mut this.app_state, AppStateImpl::Launching { + queued_windows, + queued_events, + queued_event_handler, + }); + } + + // requires main thread + pub unsafe fn did_finish_launching() { + let mut this = AppState::get_mut(); + let windows = match &mut this.app_state { + &mut AppStateImpl::Launching { + ref mut queued_windows, + .. + } => mem::replace(queued_windows, Vec::new()), + _ => panic!( + "winit iOS expected the app to be in a `Launching` \ + state, but was not - please file an issue" + ), + }; + // have to drop RefMut because the window setup code below can trigger new events + drop(this); + + for window in windows { + let count: NSUInteger = msg_send![window, retainCount]; + // make sure the window is still referenced + if count > 1 { + // Do a little screen dance here to account for windows being created before + // `UIApplicationMain` is called. This fixes visual issues such as being + // offcenter and sized incorrectly. Additionally, to fix orientation issues, we + // gotta reset the `rootViewController`. + // + // relevant iOS log: + // ``` + // [ApplicationLifecycle] Windows were created before application initialzation + // completed. This may result in incorrect visual appearance. + // ``` + let screen: id = msg_send![window, screen]; + let () = msg_send![screen, retain]; + let () = msg_send![window, setScreen:0 as id]; + let () = msg_send![window, setScreen:screen]; + let () = msg_send![screen, release]; + let controller: id = msg_send![window, rootViewController]; + let () = msg_send![window, setRootViewController:ptr::null::<()>()]; + let () = msg_send![window, setRootViewController:controller]; + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } + + let mut this = AppState::get_mut(); + let (windows, events, event_handler) = match &mut this.app_state { + &mut AppStateImpl::Launching { + ref mut queued_windows, + ref mut queued_events, + ref mut queued_event_handler, + } => { + let windows = ptr::read(queued_windows); + let events = ptr::read(queued_events); + let event_handler = ptr::read(queued_event_handler); + (windows, events, event_handler) + } + _ => panic!("winit iOS expected the app to be in a `Launching` \ + state, but was not - please file an issue"), + }; + ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::Poll, + }); + drop(this); + + let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); + AppState::handle_nonuser_events(events); + + // the above window dance hack, could possibly trigger new windows to be created. + // we can just set those windows up normally, as they were created after didFinishLaunching + for window in windows { + let count: NSUInteger = msg_send![window, retainCount]; + // make sure the window is still referenced + if count > 1 { + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } + } + + // requires main thread + // AppState::did_finish_launching handles the special transition `Init` + pub unsafe fn handle_wakeup_transition() { + let mut this = AppState::get_mut(); + let event = match this.control_flow { + ControlFlow::Poll => { + let event_handler = match &mut this.app_state { + &mut AppStateImpl::NotLaunched { .. } | + &mut AppStateImpl::Launching { .. } => return, + &mut AppStateImpl::PollFinished { + ref mut waiting_event_handler, + } => ptr::read(waiting_event_handler), + _ => bug!("`EventHandler` unexpectedly started polling"), + }; + ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::Poll, + }); + Event::NewEvents(StartCause::Poll) + } + ControlFlow::Wait => { + let (event_handler, start) = match &mut this.app_state { + &mut AppStateImpl::NotLaunched { .. } | + &mut AppStateImpl::Launching { .. } => return, + &mut AppStateImpl::Waiting { + ref mut waiting_event_handler, + ref mut start, + } => (ptr::read(waiting_event_handler), *start), + _ => bug!("`EventHandler` unexpectedly woke up"), + }; + ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::Wait, + }); + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: None, + }) + } + ControlFlow::WaitUntil(requested_resume) => { + let (event_handler, start) = match &mut this.app_state { + &mut AppStateImpl::NotLaunched { .. } | + &mut AppStateImpl::Launching { .. } => return, + &mut AppStateImpl::Waiting { + ref mut waiting_event_handler, + ref mut start, + } => (ptr::read(waiting_event_handler), *start), + _ => bug!("`EventHandler` unexpectedly woke up"), + }; + ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::WaitUntil(requested_resume), + }); + if Instant::now() >= requested_resume { + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + }) + } else { + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + }) + } + } + ControlFlow::Exit => bug!("unexpected controlflow `Exit`"), + }; + drop(this); + AppState::handle_nonuser_event(event) + } + + // requires main thread + pub unsafe fn handle_nonuser_event(event: Event) { + AppState::handle_nonuser_events(std::iter::once(event)) + } + + // requires main thread + pub unsafe fn handle_nonuser_events>>(events: I) { + let mut this = AppState::get_mut(); + let mut control_flow = this.control_flow; + let (mut event_handler, active_control_flow) = match &mut this.app_state { + &mut AppStateImpl::Launching { + ref mut queued_events, + .. + } + | &mut AppStateImpl::NotLaunched { + ref mut queued_events, + .. + } + | &mut AppStateImpl::InUserCallback { + ref mut queued_events, + .. + } => { + queued_events.extend(events); + return + } + &mut AppStateImpl::ProcessingEvents { + ref mut event_handler, + ref mut active_control_flow, + } => (ptr::read(event_handler), *active_control_flow), + &mut AppStateImpl::PollFinished { .. } + | &mut AppStateImpl::Waiting { .. } + | &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"), + }; + ptr::write(&mut this.app_state, AppStateImpl::InUserCallback { + queued_events: Vec::new(), + }); + drop(this); + + for event in events { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + loop { + let mut this = AppState::get_mut(); + let queued_events = match &mut this.app_state { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + } => mem::replace(queued_events, Vec::new()), + _ => bug!("unexpected `AppStateImpl`"), + }; + if queued_events.is_empty() { + this.app_state = AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow, + }; + this.control_flow = control_flow; + break + } + drop(this); + for event in queued_events { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + } + } + + // requires main thread + pub unsafe fn handle_user_events() { + let mut this = AppState::get_mut(); + let mut control_flow = this.control_flow; + let (mut event_handler, active_control_flow) = match &mut this.app_state { + &mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return, + &mut AppStateImpl::ProcessingEvents { + ref mut event_handler, + ref mut active_control_flow, + } => (ptr::read(event_handler), *active_control_flow), + &mut AppStateImpl::InUserCallback { .. } + | &mut AppStateImpl::PollFinished { .. } + | &mut AppStateImpl::Waiting { .. } + | &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"), + }; + ptr::write(&mut this.app_state, AppStateImpl::InUserCallback { + queued_events: Vec::new(), + }); + drop(this); + + event_handler.handle_user_events(&mut control_flow); + loop { + let mut this = AppState::get_mut(); + let queued_events = match &mut this.app_state { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + } => mem::replace(queued_events, Vec::new()), + _ => bug!("unexpected `AppStateImpl`"), + }; + if queued_events.is_empty() { + this.app_state = AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow, + }; + this.control_flow = control_flow; + break + } + drop(this); + for event in queued_events { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + event_handler.handle_user_events(&mut control_flow); + } + } + + // requires main thread + pub unsafe fn handle_events_cleared() { + let mut this = AppState::get_mut(); + match &mut this.app_state { + &mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return, + &mut AppStateImpl::ProcessingEvents { .. } => {} + _ => unreachable!(), + }; + drop(this); + + AppState::handle_user_events(); + AppState::handle_nonuser_event(Event::EventsCleared); + + let mut this = AppState::get_mut(); + let (event_handler, old) = match &mut this.app_state { + &mut AppStateImpl::ProcessingEvents { + ref mut event_handler, + ref mut active_control_flow, + } => (ManuallyDrop::new(ptr::read(event_handler)), *active_control_flow), + _ => unreachable!(), + }; + + let new = this.control_flow; + match (old, new) { + (ControlFlow::Poll, ControlFlow::Poll) => { + ptr::write( + &mut this.app_state, + AppStateImpl::PollFinished { + waiting_event_handler: ManuallyDrop::into_inner(event_handler), + }, + ) + }, + (ControlFlow::Wait, ControlFlow::Wait) => { + let start = Instant::now(); + ptr::write( + &mut this.app_state, + AppStateImpl::Waiting { + waiting_event_handler: ManuallyDrop::into_inner(event_handler), + start, + }, + ) + }, + (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) + if old_instant == new_instant => { + let start = Instant::now(); + ptr::write( + &mut this.app_state, + AppStateImpl::Waiting { + waiting_event_handler: ManuallyDrop::into_inner(event_handler), + start, + }, + ) + } + (_, ControlFlow::Wait) => { + let start = Instant::now(); + ptr::write( + &mut this.app_state, + AppStateImpl::Waiting { + waiting_event_handler: ManuallyDrop::into_inner(event_handler), + start, + }, + ); + this.waker.stop() + }, + (_, ControlFlow::WaitUntil(new_instant)) => { + let start = Instant::now(); + ptr::write( + &mut this.app_state, + AppStateImpl::Waiting { + waiting_event_handler: ManuallyDrop::into_inner(event_handler), + start, + }, + ); + this.waker.start_at(new_instant) + }, + (_, ControlFlow::Poll) => { + ptr::write( + &mut this.app_state, + AppStateImpl::PollFinished { + waiting_event_handler: ManuallyDrop::into_inner(event_handler), + }, + ); + this.waker.start() + }, + (_, ControlFlow::Exit) => { + // https://developer.apple.com/library/archive/qa/qa1561/_index.html + // it is not possible to quit an iOS app gracefully and programatically + warn!("`ControlFlow::Exit` ignored on iOS"); + this.control_flow = old + } + } + } + + pub fn terminated() { + let mut this = unsafe { AppState::get_mut() }; + let mut old = mem::replace(&mut this.app_state, AppStateImpl::Terminated); + let mut control_flow = this.control_flow; + if let AppStateImpl::ProcessingEvents { ref mut event_handler, .. } = old { + drop(this); + event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) + } else { + bug!("`LoopDestroyed` happened while not processing events") + } + } +} + +struct EventLoopWaker { + timer: CFRunLoopTimerRef, +} + +impl Drop for EventLoopWaker { + fn drop(&mut self) { + unsafe { + CFRunLoopTimerInvalidate(self.timer); + CFRelease(self.timer as _); + } + } +} + +impl EventLoopWaker { + fn new(rl: CFRunLoopRef) -> EventLoopWaker { + extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + unsafe { + // create a timer with a 1microsec interval (1ns does not work) to mimic polling. + // it is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediatley in did_finish_launching + let timer = CFRunLoopTimerCreate( + ptr::null_mut(), + std::f64::MAX, + 0.000_000_1, + 0, + 0, + wakeup_main_loop, + ptr::null_mut(), + ); + CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); + + EventLoopWaker { timer } + } + } + + fn stop(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + } + + fn start(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + } + + fn start_at(&mut self, instant: Instant) { + let now = Instant::now(); + if now >= instant { + self.start(); + } else { + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = + duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } +} \ No newline at end of file diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs new file mode 100644 index 00000000..589348c7 --- /dev/null +++ b/src/platform_impl/ios/event_loop.rs @@ -0,0 +1,334 @@ +use std::{mem, ptr}; +use std::collections::VecDeque; +use std::ffi::c_void; +use std::fmt::{self, Debug, Formatter}; +use std::marker::PhantomData; +use std::sync::mpsc::{self, Sender, Receiver}; + +use event::Event; +use event_loop::{ + ControlFlow, + EventLoopWindowTarget as RootEventLoopWindowTarget, + EventLoopClosed, +}; +use platform::ios::Idiom; + +use platform_impl::platform::app_state::AppState; +use platform_impl::platform::ffi::{ + id, + nil, + CFIndex, + CFRelease, + CFRunLoopActivity, + CFRunLoopAddObserver, + CFRunLoopAddSource, + CFRunLoopGetMain, + CFRunLoopObserverCreate, + CFRunLoopObserverRef, + CFRunLoopSourceContext, + CFRunLoopSourceCreate, + CFRunLoopSourceInvalidate, + CFRunLoopSourceRef, + CFRunLoopSourceSignal, + CFRunLoopWakeUp, + kCFRunLoopCommonModes, + kCFRunLoopDefaultMode, + kCFRunLoopEntry, + kCFRunLoopBeforeWaiting, + kCFRunLoopAfterWaiting, + kCFRunLoopExit, + NSOperatingSystemVersion, + NSString, + UIApplicationMain, + UIUserInterfaceIdiom, +}; +use platform_impl::platform::monitor; +use platform_impl::platform::MonitorHandle; +use platform_impl::platform::view; + +pub struct EventLoopWindowTarget { + receiver: Receiver, + sender_to_clone: Sender, + capabilities: Capabilities, +} + +impl EventLoopWindowTarget { + pub fn capabilities(&self) -> &Capabilities { + &self.capabilities + } +} + +pub struct EventLoop { + window_target: RootEventLoopWindowTarget, +} + +impl EventLoop { + pub fn new() -> EventLoop { + static mut SINGLETON_INIT: bool = false; + unsafe { + assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); + assert!(!SINGLETON_INIT, "Only one `EventLoop` is supported on iOS. \ + `EventLoopProxy` might be helpful"); + SINGLETON_INIT = true; + view::create_delegate_class(); + } + + let (sender_to_clone, receiver) = mpsc::channel(); + + // this line sets up the main run loop before `UIApplicationMain` + setup_control_flow_observers(); + + let version: NSOperatingSystemVersion = unsafe { + let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; + msg_send![process_info, operatingSystemVersion] + }; + let capabilities = version.into(); + + EventLoop { + window_target: RootEventLoopWindowTarget { + p: EventLoopWindowTarget { + receiver, + sender_to_clone, + capabilities, + }, + _marker: PhantomData, + } + } + } + + pub fn run(self, event_handler: F) -> ! + where + F: 'static + FnMut(Event, &RootEventLoopWindowTarget, &mut ControlFlow) + { + unsafe { + let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication]; + assert_eq!(application, ptr::null_mut(), "\ + `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ + Note: `EventLoop::run` calls `UIApplicationMain` on iOS"); + AppState::will_launch(Box::new(EventLoopHandler { + f: event_handler, + event_loop: self.window_target, + })); + + UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate")); + unreachable!() + } + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) + } + + pub fn available_monitors(&self) -> VecDeque { + // guaranteed to be on main thread + unsafe { + monitor::uiscreens() + } + } + + pub fn primary_monitor(&self) -> MonitorHandle { + // guaranteed to be on main thread + unsafe { + monitor::main_uiscreen() + } + } + + pub fn window_target(&self) -> &RootEventLoopWindowTarget { + &self.window_target + } +} + +// EventLoopExtIOS +impl EventLoop { + pub fn idiom(&self) -> Idiom { + // guaranteed to be on main thread + unsafe { + self::get_idiom() + } + } +} + +pub struct EventLoopProxy { + sender: Sender, + source: CFRunLoopSourceRef, +} + +unsafe impl Send for EventLoopProxy {} +unsafe impl Sync for EventLoopProxy {} + +impl Clone for EventLoopProxy { + fn clone(&self) -> EventLoopProxy { + EventLoopProxy::new(self.sender.clone()) + } +} + +impl Drop for EventLoopProxy { + fn drop(&mut self) { + unsafe { + CFRunLoopSourceInvalidate(self.source); + CFRelease(self.source as _); + } + } +} + +impl EventLoopProxy { + fn new(sender: Sender) -> EventLoopProxy { + unsafe { + // just wakeup the eventloop + extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} + + // adding a Source to the main CFRunLoop lets us wake it up and + // process user events through the normal OS EventLoop mechanisms. + let rl = CFRunLoopGetMain(); + // we want all the members of context to be zero/null, except one + let mut context: CFRunLoopSourceContext = mem::zeroed(); + context.perform = event_loop_proxy_handler; + let source = CFRunLoopSourceCreate( + ptr::null_mut(), + CFIndex::max_value() - 1, + &mut context, + ); + CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); + CFRunLoopWakeUp(rl); + + EventLoopProxy { + sender, + source, + } + } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.sender.send(event).map_err(|_| EventLoopClosed)?; + unsafe { + // let the main thread know there's a new event + CFRunLoopSourceSignal(self.source); + let rl = CFRunLoopGetMain(); + CFRunLoopWakeUp(rl); + } + Ok(()) + } +} + +fn setup_control_flow_observers() { + unsafe { + // begin is queued with the highest priority to ensure it is processed before other observers + extern fn control_flow_begin_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(), + kCFRunLoopEntry => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } + } + + // end is queued with the lowest priority to ensure it is processed after other observers + // without that, LoopDestroyed will get sent after EventsCleared + extern fn control_flow_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(), + kCFRunLoopExit => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } + } + + let main_loop = CFRunLoopGetMain(); + let begin_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopEntry | kCFRunLoopAfterWaiting, + 1, // repeat = true + CFIndex::min_value(), + control_flow_begin_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); + let end_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + 1, // repeat = true + CFIndex::max_value(), + control_flow_end_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); + } +} + +#[derive(Debug)] +pub enum Never {} + +pub trait EventHandler: Debug { + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow); + fn handle_user_events(&mut self, control_flow: &mut ControlFlow); +} + +struct EventLoopHandler { + f: F, + event_loop: RootEventLoopWindowTarget, +} + +impl Debug for EventLoopHandler { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.debug_struct("EventLoopHandler") + .field("event_loop", &self.event_loop) + .finish() + } +} + +impl EventHandler for EventLoopHandler +where + F: 'static + FnMut(Event, &RootEventLoopWindowTarget, &mut ControlFlow), + T: 'static, +{ + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { + (self.f)( + event.map_nonuser_event().unwrap(), + &self.event_loop, + control_flow, + ); + } + + fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { + for event in self.event_loop.p.receiver.try_iter() { + (self.f)( + Event::UserEvent(event), + &self.event_loop, + control_flow, + ); + } + } +} + +// must be called on main thread +pub unsafe fn get_idiom() -> Idiom { + let device: id = msg_send![class!(UIDevice), currentDevice]; + let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom]; + raw_idiom.into() +} + +pub struct Capabilities { + pub supports_safe_area: bool, +} + +impl From for Capabilities { + fn from(os_version: NSOperatingSystemVersion) -> Capabilities { + assert!(os_version.major >= 8, "`winit` current requires iOS version 8 or greater"); + + let supports_safe_area = os_version.major >= 11; + + Capabilities { supports_safe_area } + } +} diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 6fd1a7ca..8583dbd7 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -1,24 +1,33 @@ #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] use std::ffi::CString; +use std::ops::BitOr; use std::os::raw::*; +use objc::{Encode, Encoding}; use objc::runtime::Object; +use platform::ios::{Idiom, ValidOrientations}; + pub type id = *mut Object; pub const nil: id = 0 as id; -pub type CFStringRef = *const c_void; -pub type CFTimeInterval = f64; -pub type Boolean = u32; - -pub const kCFRunLoopRunHandledSource: i32 = 4; - #[cfg(target_pointer_width = "32")] pub type CGFloat = f32; #[cfg(target_pointer_width = "64")] pub type CGFloat = f64; +pub type NSInteger = isize; +pub type NSUInteger = usize; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct NSOperatingSystemVersion { + pub major: NSInteger, + pub minor: NSInteger, + pub patch: NSInteger, +} + #[repr(C)] #[derive(Debug, Clone)] pub struct CGPoint { @@ -26,13 +35,6 @@ pub struct CGPoint { pub y: CGFloat, } -#[repr(C)] -#[derive(Debug, Clone)] -pub struct CGRect { - pub origin: CGPoint, - pub size: CGSize, -} - #[repr(C)] #[derive(Debug, Clone)] pub struct CGSize { @@ -40,13 +42,134 @@ pub struct CGSize { pub height: CGFloat, } +#[repr(C)] +#[derive(Debug, Clone)] +pub struct CGRect { + pub origin: CGPoint, + pub size: CGSize, +} + +unsafe impl Encode for CGRect { + fn encode() -> Encoding { + unsafe { + if cfg!(target_pointer_width = "32") { + Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}") + } else if cfg!(target_pointer_width = "64") { + Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}") + } else { + unimplemented!() + } + } + } +} +#[derive(Debug)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UITouchPhase { + Began = 0, + Moved, + Stationary, + Ended, + Cancelled, +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct UIEdgeInsets { + pub top: CGFloat, + pub left: CGFloat, + pub bottom: CGFloat, + pub right: CGFloat, +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UIUserInterfaceIdiom(NSInteger); + +unsafe impl Encode for UIUserInterfaceIdiom { + fn encode() -> Encoding { NSInteger::encode() } +} + +impl UIUserInterfaceIdiom { + pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1); + pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0); + pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1); + pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2); + pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3); +} + +impl From for UIUserInterfaceIdiom { + fn from(idiom: Idiom) -> UIUserInterfaceIdiom { + match idiom { + Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified, + Idiom::Phone => UIUserInterfaceIdiom::Phone, + Idiom::Pad => UIUserInterfaceIdiom::Pad, + Idiom::TV => UIUserInterfaceIdiom::TV, + Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay, + } + } +} + +impl Into for UIUserInterfaceIdiom { + fn into(self) -> Idiom { + match self { + UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified, + UIUserInterfaceIdiom::Phone => Idiom::Phone, + UIUserInterfaceIdiom::Pad => Idiom::Pad, + UIUserInterfaceIdiom::TV => Idiom::TV, + UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay, + _ => unreachable!(), + } + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct UIInterfaceOrientationMask(NSUInteger); + +unsafe impl Encode for UIInterfaceOrientationMask { + fn encode() -> Encoding { NSUInteger::encode() } +} + +impl UIInterfaceOrientationMask { + pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1); + pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2); + pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4); + pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3); + pub const Landscape: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0); + pub const AllButUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0); + pub const All: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0); +} + +impl BitOr for UIInterfaceOrientationMask { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self { + UIInterfaceOrientationMask(self.0 | rhs.0) + } +} + +impl UIInterfaceOrientationMask { + pub fn from_valid_orientations_idiom( + valid_orientations: ValidOrientations, + idiom: Idiom, + ) -> UIInterfaceOrientationMask { + match (valid_orientations, idiom) { + (ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => UIInterfaceOrientationMask::AllButUpsideDown, + (ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All, + (ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape, + (ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait, + (ValidOrientations::Portrait, _) => UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown, + } + } +} + #[link(name = "UIKit", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")] -#[link(name = "GlKit", kind = "framework")] extern { - pub static kCFRunLoopDefaultMode: CFStringRef; + pub static kCFRunLoopDefaultMode: CFRunLoopMode; + pub static kCFRunLoopCommonModes: CFRunLoopMode; - // int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName ); pub fn UIApplicationMain( argc: c_int, argv: *const c_char, @@ -54,31 +177,115 @@ extern { delegateClassName: id, ) -> c_int; - // SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled ); - pub fn CFRunLoopRunInMode( - mode: CFStringRef, - seconds: CFTimeInterval, - returnAfterSourceHandled: Boolean, - ) -> i32; + pub fn CFRunLoopGetMain() -> CFRunLoopRef; + pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); + + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + pub fn CFRunLoopAddObserver( + rl: CFRunLoopRef, + observer: CFRunLoopObserverRef, + mode: CFRunLoopMode, + ); + + pub fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: CFAbsoluteTime, + interval: CFTimeInterval, + flags: CFOptionFlags, + order: CFIndex, + callout: CFRunLoopTimerCallBack, + context: *mut CFRunLoopTimerContext, + ) -> CFRunLoopTimerRef; + pub fn CFRunLoopAddTimer( + rl: CFRunLoopRef, + timer: CFRunLoopTimerRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopTimerSetNextFireDate( + timer: CFRunLoopTimerRef, + fireDate: CFAbsoluteTime, + ); + pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); + + pub fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: CFIndex, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + pub fn CFRunLoopAddSource( + rl: CFRunLoopRef, + source: CFRunLoopSourceRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); + pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + pub fn CFRelease(cftype: *const c_void); } -extern { - pub fn setjmp(env: *mut c_void) -> c_int; - pub fn longjmp(env: *mut c_void, val: c_int) -> !; +pub type Boolean = u8; +pub enum CFAllocator {} +pub type CFAllocatorRef = *mut CFAllocator; +pub enum CFRunLoop {} +pub type CFRunLoopRef = *mut CFRunLoop; +pub type CFRunLoopMode = CFStringRef; +pub enum CFRunLoopObserver {} +pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; +pub enum CFRunLoopTimer {} +pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; +pub enum CFRunLoopSource {} +pub type CFRunLoopSourceRef = *mut CFRunLoopSource; +pub enum CFString {} +pub type CFStringRef = *const CFString; + +pub type CFHashCode = c_ulong; +pub type CFIndex = c_long; +pub type CFOptionFlags = c_ulong; +pub type CFRunLoopActivity = CFOptionFlags; + +pub type CFAbsoluteTime = CFTimeInterval; +pub type CFTimeInterval = f64; + +pub const kCFRunLoopEntry: CFRunLoopActivity = 0; +pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; +pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; +pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; + +pub type CFRunLoopObserverCallBack = extern "C" fn( + observer: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + info: *mut c_void, +); +pub type CFRunLoopTimerCallBack = extern "C" fn( + timer: CFRunLoopTimerRef, + info: *mut c_void, +); + +pub enum CFRunLoopObserverContext {} +pub enum CFRunLoopTimerContext {} + +#[repr(C)] +pub struct CFRunLoopSourceContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: extern "C" fn(*const c_void) -> *const c_void, + pub release: extern "C" fn(*const c_void), + pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, + pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, + pub hash: extern "C" fn(*const c_void) -> CFHashCode, + pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub perform: extern "C" fn(*mut c_void), } -// 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] diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index da41d15d..73dd2b2e 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -47,656 +47,68 @@ //! //! This is how those event are represented in winit: //! -//! - applicationDidBecomeActive is Focused(true) -//! - applicationWillResignActive is Focused(false) -//! - applicationDidEnterBackground is Suspended(true) -//! - applicationWillEnterForeground is Suspended(false) -//! - applicationWillTerminate is Destroyed +//! - applicationDidBecomeActive is Suspended(false) +//! - applicationWillResignActive is Suspended(true) +//! - applicationWillTerminate is LoopDestroyed //! -//! Keep in mind that after Destroyed event is received every attempt to draw with +//! Keep in mind that after LoopDestroyed event is received every attempt to draw with //! opengl will result in segfault. //! -//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed +//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed. #![cfg(target_os = "ios")] -use std::{fmt, mem, ptr}; -use std::cell::RefCell; -use std::collections::VecDeque; -use std::os::raw::*; -use std::sync::Arc; - -use objc::declare::ClassDecl; -use objc::runtime::{BOOL, Class, Object, Sel, YES}; - -use { - CreationError, - Event, - LogicalPosition, - LogicalSize, - MouseCursor, - PhysicalPosition, - PhysicalSize, - WindowAttributes, - WindowEvent, - WindowId as RootEventId, -}; -use events::{Touch, TouchPhase}; -use window::MonitorHandle as RootMonitorHandle; +// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be +// worked around in the future by using GCD (grand central dispatch) and/or caching of values like +// window size/position. +macro_rules! assert_main_thread { + ($($t:tt)*) => { + if !msg_send![class!(NSThread), isMainThread] { + panic!($($t)*); + } + }; +} +mod app_state; +mod event_loop; mod ffi; -use self::ffi::{ - CFTimeInterval, - CFRunLoopRunInMode, - CGFloat, - CGPoint, - CGRect, - id, - JBLEN, - JmpBuf, - kCFRunLoopDefaultMode, - kCFRunLoopRunHandledSource, - longjmp, - nil, - NSString, - setjmp, - UIApplicationMain, - }; +mod monitor; +mod view; +mod window; -static mut JMPBUF: Option> = None; +use std::fmt; -pub struct Window { - _events_queue: Arc>>, - delegate_state: Box, -} - -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -#[derive(Debug)] -struct DelegateState { - window: id, - controller: id, - view: id, - size: LogicalSize, - scale: f64, -} - -impl DelegateState { - fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState { - DelegateState { - 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 MonitorHandle; - -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[derive(Debug)] - struct MonitorHandle { - name: Option, - dimensions: PhysicalSize, - position: PhysicalPosition, - hidpi_factor: f64, - } - - let monitor_id_proxy = MonitorHandle { - name: self.get_name(), - dimensions: self.get_dimensions(), - position: self.get_position(), - hidpi_factor: self.get_hidpi_factor(), - }; - - monitor_id_proxy.fmt(f) - } -} - -impl MonitorHandle { - #[inline] - pub fn get_uiscreen(&self) -> id { - let class = class!(UIScreen); - unsafe { msg_send![class, mainScreen] } - } - - #[inline] - pub fn get_name(&self) -> Option { - Some("Primary".to_string()) - } - - #[inline] - 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) -> PhysicalPosition { - // iOS assumes single screen - (0, 0).into() - } - - #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] }; - scale as f64 - } -} - -pub struct EventLoop { - events_queue: Arc>>, -} - -#[derive(Clone)] -pub struct EventLoopProxy; - -impl EventLoop { - pub fn new() -> EventLoop { - unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("`EventLoop` can only be created on the main thread on iOS"); - } - } - EventLoop { events_queue: Default::default() } - } - - #[inline] - pub fn get_available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(::Event) - { - if let Some(event) = self.events_queue.borrow_mut().pop_front() { - callback(event); - return; - } - - unsafe { - // jump hack, so we won't quit on willTerminate event before processing it - assert!(JMPBUF.is_some(), "`EventLoop::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) = self.events_queue.borrow_mut().pop_front() { - callback(event) - } - } - - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(::Event) -> ::ControlFlow, - { - // Yeah that's a very bad implementation. - loop { - let mut control_flow = ::ControlFlow::Continue; - self.poll_events(|e| { - if let ::ControlFlow::Break = callback(e) { - control_flow = ::ControlFlow::Break; - } - }); - if let ::ControlFlow::Break = control_flow { - break; - } - ::std::thread::sleep(::std::time::Duration::from_millis(5)); - } - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy - } -} - -impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> { - unimplemented!() - } -} +pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +pub use self::monitor::MonitorHandle; +pub use self::window::{ + PlatformSpecificWindowBuilderAttributes, + Window, + WindowId, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId; - -impl WindowId { - pub unsafe fn dummy() -> Self { - WindowId - } +pub struct DeviceId { + uiscreen: ffi::id, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId; - 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), + DeviceId { + uiscreen: std::ptr::null_mut(), } } } -// 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: &EventLoop, - _attributes: WindowAttributes, - pl_attributes: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - unsafe { - debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::>()); - assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS"); +unsafe impl Send for DeviceId {} +unsafe impl Sync for DeviceId {} + +#[derive(Debug)] +pub enum OsError {} + +impl fmt::Display for OsError { + fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { + match self { + _ => unreachable!() } - - 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![MonitorHandle.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 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 { - // N/A - None - } - - #[inline] - pub fn get_inner_position(&self) -> Option { - // N/A - None - } - - #[inline] - pub fn set_position(&self, _position: LogicalPosition) { - // N/A - } - - #[inline] - pub fn get_inner_size(&self) -> Option { - Some(self.delegate_state.size) - } - - #[inline] - pub fn get_outer_size(&self) -> Option { - self.get_inner_size() - } - - #[inline] - pub fn set_inner_size(&self, _size: LogicalSize) { - // N/A - } - - #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option) { - // N/A - } - - #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option) { - // N/A - } - - #[inline] - pub fn set_resizable(&self, _resizable: bool) { - // N/A - } - - #[inline] - pub fn set_cursor(&self, _cursor: MouseCursor) { - // N/A - } - - #[inline] - pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> { - Err("Cursor grabbing is not possible on iOS.".to_owned()) - } - - #[inline] - pub fn hide_cursor(&self, _hide: bool) { - // N/A - } - - #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - self.delegate_state.scale - } - - #[inline] - 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 set_fullscreen(&self, _monitor: Option) { - // N/A - // iOS has single screen maximized apps so nothing to do - } - - #[inline] - pub fn set_decorations(&self, _decorations: bool) { - // 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) -> RootMonitorHandle { - RootMonitorHandle { inner: MonitorHandle } - } - - #[inline] - pub fn get_available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - #[inline] - pub fn id(&self) -> WindowId { - WindowId } } - -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![screen_class, mainScreen]; - let bounds: CGRect = msg_send![main_screen, bounds]; - let scale: CGFloat = msg_send![main_screen, nativeScale]; - - let window: id = msg_send![window_class, alloc]; - let window: id = msg_send![window, initWithFrame:bounds.clone()]; - - let size = (bounds.size.width as f64, bounds.size.height as f64).into(); - - 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 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("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_copy(&mut JMPBUF), 1); } - } - - extern fn did_become_active(this: &Object, _: Sel, _: id) { - unsafe { - let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); - let events_queue = &*(events_queue as *const RefCell>); - events_queue.borrow_mut().push_back(Event::WindowEvent { - window_id: RootEventId(WindowId), - event: WindowEvent::Focused(true), - }); - } - } - - extern fn will_resign_active(this: &Object, _: Sel, _: id) { - unsafe { - let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); - let events_queue = &*(events_queue as *const RefCell>); - events_queue.borrow_mut().push_back(Event::WindowEvent { - window_id: RootEventId(WindowId), - event: WindowEvent::Focused(false), - }); - } - } - - extern fn will_enter_foreground(this: &Object, _: Sel, _: id) { - unsafe { - let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); - let events_queue = &*(events_queue as *const RefCell>); - events_queue.borrow_mut().push_back(Event::Suspended(false)); - } - } - - extern fn did_enter_background(this: &Object, _: Sel, _: id) { - unsafe { - let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); - let events_queue = &*(events_queue as *const RefCell>); - events_queue.borrow_mut().push_back(Event::Suspended(true)); - } - } - - extern fn will_terminate(this: &Object, _: Sel, _: id) { - unsafe { - let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); - let events_queue = &*(events_queue as *const RefCell>); - // push event to the front to garantee that we'll process it - // immidiatly after jump - events_queue.borrow_mut().push_front(Event::WindowEvent { - window_id: RootEventId(WindowId), - event: WindowEvent::Destroyed, - }); - longjmp(mem::transmute_copy(&mut JMPBUF), 1); - } - } - - extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) { - unsafe { - let events_queue: *mut c_void = *this.get_ivar("eventsQueue"); - let events_queue = &*(events_queue as *const RefCell>); - - let touches_enum: id = msg_send![touches, objectEnumerator]; - - loop { - let touch: id = msg_send![touches_enum, nextObject]; - if touch == nil { - break - } - let location: CGPoint = msg_send![touch, locationInView:nil]; - let touch_id = touch as u64; - let phase: i32 = msg_send![touch, phase]; - - 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).into(), - phase: match phase { - 0 => TouchPhase::Started, - 1 => TouchPhase::Moved, - // 2 is UITouchPhaseStationary and is not expected here - 3 => TouchPhase::Ended, - 4 => TouchPhase::Cancelled, - _ => panic!("unexpected touch phase: {:?}", phase) - } - }), - }); - } - } - } - - 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:), - did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL); - - decl.add_method(sel!(applicationDidBecomeActive:), - did_become_active as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationWillResignActive:), - will_resign_active as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationWillEnterForeground:), - will_enter_foreground as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationDidEnterBackground:), - did_enter_background as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationWillTerminate:), - will_terminate as extern fn(&Object, Sel, id)); - - - decl.add_method(sel!(touchesBegan:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - decl.add_method(sel!(touchesMoved:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - decl.add_method(sel!(touchesEnded:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - decl.add_method(sel!(touchesCancelled:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - - decl.add_method(sel!(postLaunch:), - post_launch as extern fn(&Object, Sel, id)); - - decl.add_ivar::<*mut c_void>("winitState"); - decl.add_ivar::<*mut c_void>("eventsQueue"); - - decl.register(); - } -} - -#[inline] -fn start_app() { - unsafe { - UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate")); - } -} - -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs new file mode 100644 index 00000000..14e7d379 --- /dev/null +++ b/src/platform_impl/ios/monitor.rs @@ -0,0 +1,171 @@ +use std::{ + collections::VecDeque, + fmt, + ops::{Deref, DerefMut}, +}; + +use dpi::{PhysicalPosition, PhysicalSize}; + +use platform_impl::platform::ffi::{ + id, + nil, + CGFloat, + CGRect, + NSUInteger, +}; + +pub struct Inner { + uiscreen: id, +} + +impl Drop for Inner { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.uiscreen, release]; + } + } +} + +pub struct MonitorHandle { + inner: Inner, +} + +impl Deref for MonitorHandle { + type Target = Inner; + + fn deref(&self) -> &Inner { + unsafe { + assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); + } + &self.inner + } +} + +impl DerefMut for MonitorHandle { + fn deref_mut(&mut self) -> &mut Inner { + unsafe { + assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); + } + &mut self.inner + } +} + +unsafe impl Send for MonitorHandle {} +unsafe impl Sync for MonitorHandle {} + +impl Clone for MonitorHandle { + fn clone(&self) -> MonitorHandle { + MonitorHandle::retained_new(self.uiscreen) + } +} + +impl Drop for MonitorHandle { + fn drop(&mut self) { + unsafe { + assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS"); + } + } +} + +impl fmt::Debug for MonitorHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[derive(Debug)] + struct MonitorHandle { + name: Option, + dimensions: PhysicalSize, + position: PhysicalPosition, + hidpi_factor: f64, + } + + let monitor_id_proxy = MonitorHandle { + name: self.name(), + dimensions: self.dimensions(), + position: self.position(), + hidpi_factor: self.hidpi_factor(), + }; + + monitor_id_proxy.fmt(f) + } +} + +impl MonitorHandle { + pub fn retained_new(uiscreen: id) -> MonitorHandle { + unsafe { + assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS"); + let () = msg_send![uiscreen, retain]; + } + MonitorHandle { inner: Inner { uiscreen } } + } +} + +impl Inner { + pub fn name(&self) -> Option { + unsafe { + if self.uiscreen == main_uiscreen().uiscreen { + Some("Primary".to_string()) + } else if self.uiscreen == mirrored_uiscreen().uiscreen { + Some("Mirrored".to_string()) + } else { + uiscreens() + .iter() + .position(|rhs| rhs.uiscreen == self.uiscreen) + .map(|idx| idx.to_string()) + } + } + } + + pub fn dimensions(&self) -> PhysicalSize { + unsafe { + let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; + (bounds.size.width as f64, bounds.size.height as f64).into() + } + } + + pub fn position(&self) -> PhysicalPosition { + unsafe { + let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; + (bounds.origin.x as f64, bounds.origin.y as f64).into() + } + } + + pub fn hidpi_factor(&self) -> f64 { + unsafe { + let scale: CGFloat = msg_send![self.ui_screen(), nativeScale]; + scale as f64 + } + } +} + +// MonitorHandleExtIOS +impl Inner { + pub fn ui_screen(&self) -> id { + self.uiscreen + } +} + +// requires being run on main thread +pub unsafe fn main_uiscreen() -> MonitorHandle { + let uiscreen: id = msg_send![class!(UIScreen), mainScreen]; + MonitorHandle::retained_new(uiscreen) +} + +// requires being run on main thread +unsafe fn mirrored_uiscreen() -> MonitorHandle { + let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen]; + MonitorHandle::retained_new(uiscreen) +} + +// requires being run on main thread +pub unsafe fn uiscreens() -> VecDeque { + let screens: id = msg_send![class!(UIScreen), screens]; + let count: NSUInteger = msg_send![screens, count]; + let mut result = VecDeque::with_capacity(count as _); + let screens_enum: id = msg_send![screens, objectEnumerator]; + loop { + let screen: id = msg_send![screens_enum, nextObject]; + if screen == nil { + break result + } + result.push_back(MonitorHandle::retained_new(screen)); + } +} diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs new file mode 100644 index 00000000..9205df43 --- /dev/null +++ b/src/platform_impl/ios/view.rs @@ -0,0 +1,393 @@ +use std::collections::HashMap; + +use objc::declare::ClassDecl; +use objc::runtime::{BOOL, Class, NO, Object, Sel, YES}; + +use event::{ + DeviceId as RootDeviceId, + Event, + Touch, + TouchPhase, + WindowEvent +}; +use platform::ios::MonitorHandleExtIOS; +use window::{WindowAttributes, WindowId as RootWindowId}; + +use platform_impl::platform::app_state::AppState; +use platform_impl::platform::DeviceId; +use platform_impl::platform::event_loop; +use platform_impl::platform::ffi::{ + id, + nil, + CGFloat, + CGPoint, + CGRect, + UIInterfaceOrientationMask, + UITouchPhase, +}; +use platform_impl::platform::window::{PlatformSpecificWindowBuilderAttributes}; + +// requires main thread +unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { + static mut CLASSES: Option> = None; + static mut ID: usize = 0; + + if CLASSES.is_none() { + CLASSES = Some(HashMap::default()); + } + + let classes = CLASSES.as_mut().unwrap(); + + classes.entry(root_view_class).or_insert_with(move || { + let uiview_class = class!(UIView); + let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class]; + assert_eq!(is_uiview, YES, "`root_view_class` must inherit from `UIView`"); + + extern fn draw_rect(object: &Object, _: Sel, rect: CGRect) { + unsafe { + let window: id = msg_send![object, window]; + AppState::handle_nonuser_event(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::RedrawRequested, + }); + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![super(object, superclass), drawRect: rect]; + } + } + + extern fn layout_subviews(object: &Object, _: Sel) { + unsafe { + let window: id = msg_send![object, window]; + let bounds: CGRect = msg_send![window, bounds]; + let screen: id = msg_send![window, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width, + height: screen_frame.size.height, + }; + AppState::handle_nonuser_event(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size), + }); + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![super(object, superclass), layoutSubviews]; + } + } + + let mut decl = ClassDecl::new(&format!("WinitUIView{}", ID), root_view_class) + .expect("Failed to declare class `WinitUIView`"); + ID += 1; + decl.add_method(sel!(drawRect:), + draw_rect as extern fn(&Object, Sel, CGRect)); + decl.add_method(sel!(layoutSubviews), + layout_subviews as extern fn(&Object, Sel)); + decl.register() + }) +} + +// requires main thread +unsafe fn get_view_controller_class() -> &'static Class { + static mut CLASS: Option<&'static Class> = None; + if CLASS.is_none() { + let uiviewcontroller_class = class!(UIViewController); + + extern fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) { + unsafe { + object.set_ivar::("_prefers_status_bar_hidden", hidden); + let () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; + } + } + + extern fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL { + unsafe { + *object.get_ivar::("_prefers_status_bar_hidden") + } + } + + extern fn set_supported_orientations(object: &mut Object, _: Sel, orientations: UIInterfaceOrientationMask) { + unsafe { + object.set_ivar::("_supported_orientations", orientations); + let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; + } + } + + extern fn supported_orientations(object: &Object, _: Sel) -> UIInterfaceOrientationMask { + unsafe { + *object.get_ivar::("_supported_orientations") + } + } + + extern fn should_autorotate(_: &Object, _: Sel) -> BOOL { + YES + } + + let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class) + .expect("Failed to declare class `WinitUIViewController`"); + decl.add_ivar::("_prefers_status_bar_hidden"); + decl.add_ivar::("_supported_orientations"); + decl.add_method(sel!(setPrefersStatusBarHidden:), + set_prefers_status_bar_hidden as extern fn(&mut Object, Sel, BOOL)); + decl.add_method(sel!(prefersStatusBarHidden), + prefers_status_bar_hidden as extern fn(&Object, Sel) -> BOOL); + decl.add_method(sel!(setSupportedInterfaceOrientations:), + set_supported_orientations as extern fn(&mut Object, Sel, UIInterfaceOrientationMask)); + decl.add_method(sel!(supportedInterfaceOrientations), + supported_orientations as extern fn(&Object, Sel) -> UIInterfaceOrientationMask); + decl.add_method(sel!(shouldAutorotate), + should_autorotate as extern fn(&Object, Sel) -> BOOL); + CLASS = Some(decl.register()); + } + CLASS.unwrap() +} + +// requires main thread +unsafe fn get_window_class() -> &'static Class { + static mut CLASS: Option<&'static Class> = None; + if CLASS.is_none() { + let uiwindow_class = class!(UIWindow); + + extern fn become_key_window(object: &Object, _: Sel) { + unsafe { + AppState::handle_nonuser_event(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::Focused(true), + }); + let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow]; + } + } + + extern fn resign_key_window(object: &Object, _: Sel) { + unsafe { + AppState::handle_nonuser_event(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::Focused(false), + }); + let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow]; + } + } + + extern fn handle_touches(object: &Object, _: Sel, touches: id, _:id) { + unsafe { + let uiscreen = msg_send![object, screen]; + let touches_enum: id = msg_send![touches, objectEnumerator]; + let mut touch_events = Vec::new(); + loop { + let touch: id = msg_send![touches_enum, nextObject]; + if touch == nil { + break + } + let location: CGPoint = msg_send![touch, locationInView:nil]; + let touch_id = touch as u64; + let phase: UITouchPhase = msg_send![touch, phase]; + let phase = match phase { + UITouchPhase::Began => TouchPhase::Started, + UITouchPhase::Moved => TouchPhase::Moved, + // 2 is UITouchPhase::Stationary and is not expected here + UITouchPhase::Ended => TouchPhase::Ended, + UITouchPhase::Cancelled => TouchPhase::Cancelled, + _ => panic!("unexpected touch phase: {:?}", phase as i32), + }; + + touch_events.push(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::Touch(Touch { + device_id: RootDeviceId(DeviceId { uiscreen }), + id: touch_id, + location: (location.x as f64, location.y as f64).into(), + phase, + }), + }); + } + AppState::handle_nonuser_events(touch_events); + } + } + + extern fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) { + unsafe { + let () = msg_send![super(object, class!(UIWindow)), setContentScaleFactor:hidpi_factor]; + let view_controller: id = msg_send![object, rootViewController]; + let view: id = msg_send![view_controller, view]; + let () = msg_send![view, setContentScaleFactor:hidpi_factor]; + let bounds: CGRect = msg_send![object, bounds]; + let screen: id = msg_send![object, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width, + height: screen_frame.size.height, + }; + AppState::handle_nonuser_events( + std::iter::once(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _), + }).chain(std::iter::once(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::Resized(size), + })) + ); + } + } + + let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class) + .expect("Failed to declare class `WinitUIWindow`"); + decl.add_method(sel!(becomeKeyWindow), + become_key_window as extern fn(&Object, Sel)); + decl.add_method(sel!(resignKeyWindow), + resign_key_window as extern fn(&Object, Sel)); + + decl.add_method(sel!(touchesBegan:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + decl.add_method(sel!(touchesMoved:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + decl.add_method(sel!(touchesEnded:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + decl.add_method(sel!(touchesCancelled:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + + decl.add_method(sel!(setContentScaleFactor:), + set_content_scale_factor as extern fn(&mut Object, Sel, CGFloat)); + + CLASS = Some(decl.register()); + } + CLASS.unwrap() +} + +// requires main thread +pub unsafe fn create_view( + _window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + frame: CGRect, +) -> id { + let class = get_view_class(platform_attributes.root_view_class); + + let view: id = msg_send![class, alloc]; + assert!(!view.is_null(), "Failed to create `UIView` instance"); + let view: id = msg_send![view, initWithFrame:frame]; + assert!(!view.is_null(), "Failed to initialize `UIView` instance"); + let () = msg_send![view, setMultipleTouchEnabled:YES]; + + view +} + +// requires main thread +pub unsafe fn create_view_controller( + window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + view: id, +) -> id { + let class = get_view_controller_class(); + + let view_controller: id = msg_send![class, alloc]; + assert!(!view_controller.is_null(), "Failed to create `UIViewController` instance"); + let view_controller: id = msg_send![view_controller, init]; + assert!(!view_controller.is_null(), "Failed to initialize `UIViewController` instance"); + let status_bar_hidden = if window_attributes.decorations { + NO + } else { + YES + }; + let idiom = event_loop::get_idiom(); + let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom( + platform_attributes.valid_orientations, + idiom, + ); + let () = msg_send![view_controller, setPrefersStatusBarHidden:status_bar_hidden]; + let () = msg_send![view_controller, setSupportedInterfaceOrientations:supported_orientations]; + let () = msg_send![view_controller, setView:view]; + view_controller +} + +// requires main thread +pub unsafe fn create_window( + window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + frame: CGRect, + view_controller: id, +) -> id { + let class = get_window_class(); + + let window: id = msg_send![class, alloc]; + assert!(!window.is_null(), "Failed to create `UIWindow` instance"); + let window: id = msg_send![window, initWithFrame:frame]; + assert!(!window.is_null(), "Failed to initialize `UIWindow` instance"); + let () = msg_send![window, setRootViewController:view_controller]; + if let Some(hidpi_factor) = platform_attributes.hidpi_factor { + let () = msg_send![window, setContentScaleFactor:hidpi_factor as CGFloat]; + } + if let &Some(ref monitor) = &window_attributes.fullscreen { + let () = msg_send![window, setScreen:monitor.ui_screen()]; + } + + window +} + +pub fn create_delegate_class() { + extern fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL { + unsafe { + AppState::did_finish_launching(); + } + YES + } + + extern fn did_become_active(_: &Object, _: Sel, _: id) { + unsafe { + AppState::handle_nonuser_event(Event::Suspended(false)) + } + } + + extern fn will_resign_active(_: &Object, _: Sel, _: id) { + unsafe { + AppState::handle_nonuser_event(Event::Suspended(true)) + } + } + + extern fn will_enter_foreground(_: &Object, _: Sel, _: id) {} + extern fn did_enter_background(_: &Object, _: Sel, _: id) {} + + extern fn will_terminate(_: &Object, _: Sel, _: id) { + unsafe { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break + } + let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)]; + if is_winit_window == YES { + events.push(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Destroyed, + }); + } + } + AppState::handle_nonuser_events(events); + AppState::terminated(); + } + } + + 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:), + did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL); + + decl.add_method(sel!(applicationDidBecomeActive:), + did_become_active as extern fn(&Object, Sel, id)); + decl.add_method(sel!(applicationWillResignActive:), + will_resign_active as extern fn(&Object, Sel, id)); + decl.add_method(sel!(applicationWillEnterForeground:), + will_enter_foreground as extern fn(&Object, Sel, id)); + decl.add_method(sel!(applicationDidEnterBackground:), + did_enter_background as extern fn(&Object, Sel, id)); + + decl.add_method(sel!(applicationWillTerminate:), + will_terminate as extern fn(&Object, Sel, id)); + + decl.register(); + } +} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs new file mode 100644 index 00000000..2ebbb469 --- /dev/null +++ b/src/platform_impl/ios/window.rs @@ -0,0 +1,487 @@ +use std::{ + collections::VecDeque, + ops::{Deref, DerefMut}, +}; + +use objc::runtime::{Class, NO, Object, YES}; + +use dpi::{self, LogicalPosition, LogicalSize}; +use error::{ExternalError, NotSupportedError, OsError as RootOsError}; +use icon::Icon; +use monitor::MonitorHandle as RootMonitorHandle; +use platform::ios::{MonitorHandleExtIOS, ValidOrientations}; +use window::{ + CursorIcon, + WindowAttributes, +}; +use platform_impl::{ + platform::{ + app_state::AppState, + event_loop, + ffi::{ + id, + CGFloat, + CGPoint, + CGRect, + CGSize, + UIEdgeInsets, + UIInterfaceOrientationMask, + }, + monitor, + view, + EventLoopWindowTarget, + MonitorHandle + }, +}; + +pub struct Inner { + pub window: id, + pub view_controller: id, + pub view: id, + supports_safe_area: bool, +} + +impl Drop for Inner { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.view, release]; + let () = msg_send![self.view_controller, release]; + let () = msg_send![self.window, release]; + } + } +} + +impl Inner { + pub fn set_title(&self, _title: &str) { + debug!("`Window::set_title` is ignored on iOS") + } + + pub fn set_visible(&self, visible: bool) { + match visible { + true => unsafe { + let () = msg_send![self.window, setHidden:NO]; + }, + false => unsafe { + let () = msg_send![self.window, setHidden:YES]; + }, + } + } + + pub fn request_redraw(&self) { + unsafe { + let () = msg_send![self.view, setNeedsDisplay]; + } + } + + pub fn inner_position(&self) -> Result { + unsafe { + let safe_area = self.safe_area_screen_space(); + Ok(LogicalPosition { + x: safe_area.origin.x, + y: safe_area.origin.y, + }) + } + } + + pub fn outer_position(&self) -> Result { + unsafe { + let screen_frame = self.screen_frame(); + Ok(LogicalPosition { + x: screen_frame.origin.x, + y: screen_frame.origin.y, + }) + } + } + + pub fn set_outer_position(&self, position: LogicalPosition) { + unsafe { + let screen_frame = self.screen_frame(); + let new_screen_frame = CGRect { + origin: CGPoint { + x: position.x as _, + y: position.y as _, + }, + size: screen_frame.size, + }; + let bounds = self.from_screen_space(new_screen_frame); + let () = msg_send![self.window, setBounds:bounds]; + } + } + + pub fn inner_size(&self) -> LogicalSize { + unsafe { + let safe_area = self.safe_area_screen_space(); + LogicalSize { + width: safe_area.size.width, + height: safe_area.size.height, + } + } + } + + pub fn outer_size(&self) -> LogicalSize { + unsafe { + let screen_frame = self.screen_frame(); + LogicalSize { + width: screen_frame.size.width, + height: screen_frame.size.height, + } + } + } + + pub fn set_inner_size(&self, _size: LogicalSize) { + unimplemented!("not clear what `Window::set_inner_size` means on iOS"); + } + + pub fn set_min_inner_size(&self, _dimensions: Option) { + warn!("`Window::set_min_inner_size` is ignored on iOS") + } + + pub fn set_max_inner_size(&self, _dimensions: Option) { + warn!("`Window::set_max_inner_size` is ignored on iOS") + } + + pub fn set_resizable(&self, _resizable: bool) { + warn!("`Window::set_resizable` is ignored on iOS") + } + + pub fn hidpi_factor(&self) -> f64 { + unsafe { + let hidpi: CGFloat = msg_send![self.view, contentScaleFactor]; + hidpi as _ + } + } + + pub fn set_cursor_icon(&self, _cursor: CursorIcon) { + debug!("`Window::set_cursor_icon` ignored on iOS") + } + + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn set_cursor_visible(&self, _visible: bool) { + debug!("`Window::set_cursor_visible` is ignored on iOS") + } + + pub fn set_maximized(&self, _maximized: bool) { + warn!("`Window::set_maximized` is ignored on iOS") + } + + pub fn set_fullscreen(&self, monitor: Option) { + unsafe { + match monitor { + Some(monitor) => { + let uiscreen = monitor.ui_screen() as id; + let current: id = msg_send![self.window, screen]; + let bounds: CGRect = msg_send![uiscreen, bounds]; + + // this is pretty slow on iOS, so avoid doing it if we can + if uiscreen != current { + let () = msg_send![self.window, setScreen:uiscreen]; + } + let () = msg_send![self.window, setFrame:bounds]; + } + None => warn!("`Window::set_fullscreen(None)` ignored on iOS"), + } + } + } + + pub fn fullscreen(&self) -> Option { + unsafe { + let monitor = self.current_monitor(); + let uiscreen = monitor.inner.ui_screen(); + let screen_space_bounds = self.screen_frame(); + let screen_bounds: CGRect = msg_send![uiscreen, bounds]; + + // TODO: track fullscreen instead of relying on brittle float comparisons + if screen_space_bounds.origin.x == screen_bounds.origin.x + && screen_space_bounds.origin.y == screen_bounds.origin.y + && screen_space_bounds.size.width == screen_bounds.size.width + && screen_space_bounds.size.height == screen_bounds.size.height + { + Some(monitor) + } else { + None + } + } + } + + pub fn set_decorations(&self, decorations: bool) { + unsafe { + let status_bar_hidden = if decorations { NO } else { YES }; + let () = msg_send![self.view_controller, setPrefersStatusBarHidden:status_bar_hidden]; + } + } + + pub fn set_always_on_top(&self, _always_on_top: bool) { + warn!("`Window::set_always_on_top` is ignored on iOS") + } + + pub fn set_window_icon(&self, _icon: Option) { + warn!("`Window::set_window_icon` is ignored on iOS") + } + + pub fn set_ime_position(&self, _position: LogicalPosition) { + warn!("`Window::set_ime_position` is ignored on iOS") + } + + pub fn current_monitor(&self) -> RootMonitorHandle { + unsafe { + let uiscreen: id = msg_send![self.window, screen]; + RootMonitorHandle { inner: MonitorHandle::retained_new(uiscreen) } + } + } + + pub fn available_monitors(&self) -> VecDeque { + unsafe { + monitor::uiscreens() + } + } + + pub fn primary_monitor(&self) -> MonitorHandle { + unsafe { + monitor::main_uiscreen() + } + } + + pub fn id(&self) -> WindowId { + self.window.into() + } +} + +pub struct Window { + pub inner: Inner, +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { + assert_main_thread!("`Window::drop` can only be run on the main thread on iOS"); + } + } +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Deref for Window { + type Target = Inner; + + fn deref(&self) -> &Inner { + unsafe { + assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); + } + &self.inner + } +} + +impl DerefMut for Window { + fn deref_mut(&mut self) -> &mut Inner { + unsafe { + assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); + } + &mut self.inner + } +} + +impl Window { + pub fn new( + event_loop: &EventLoopWindowTarget, + window_attributes: WindowAttributes, + platform_attributes: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + if let Some(_) = window_attributes.min_inner_size { + warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); + } + if let Some(_) = window_attributes.max_inner_size { + warn!("`WindowAttributes::max_inner_size` is ignored on iOS"); + } + if window_attributes.always_on_top { + warn!("`WindowAttributes::always_on_top` is unsupported on iOS"); + } + // TODO: transparency, visible + + unsafe { + let screen = window_attributes.fullscreen + .as_ref() + .map(|screen| screen.ui_screen() as _) + .unwrap_or_else(|| monitor::main_uiscreen().ui_screen()); + let screen_bounds: CGRect = msg_send![screen, bounds]; + + let frame = match window_attributes.inner_size { + Some(dim) => CGRect { + origin: screen_bounds.origin, + size: CGSize { width: dim.width, height: dim.height }, + }, + None => screen_bounds, + }; + + let view = view::create_view(&window_attributes, &platform_attributes, frame.clone()); + let view_controller = view::create_view_controller(&window_attributes, &platform_attributes, view); + let window = view::create_window(&window_attributes, &platform_attributes, frame, view_controller); + + let supports_safe_area = event_loop.capabilities().supports_safe_area; + + let result = Window { + inner: Inner { + window, + view_controller, + view, + supports_safe_area, + }, + }; + AppState::set_key_window(window); + Ok(result) + } + } +} + +// WindowExtIOS +impl Inner { + pub fn ui_window(&self) -> id { self.window } + pub fn ui_view_controller(&self) -> id { self.view_controller } + pub fn ui_view(&self) -> id { self.view } + + pub fn set_hidpi_factor(&self, hidpi_factor: f64) { + unsafe { + assert!(dpi::validate_hidpi_factor(hidpi_factor), "`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor"); + let hidpi_factor = hidpi_factor as CGFloat; + let () = msg_send![self.view, setContentScaleFactor:hidpi_factor]; + } + } + + pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { + unsafe { + let idiom = event_loop::get_idiom(); + let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(valid_orientations, idiom); + msg_send![self.view_controller, setSupportedInterfaceOrientations:supported_orientations]; + } + } +} + +impl Inner { + // requires main thread + unsafe fn screen_frame(&self) -> CGRect { + self.to_screen_space(msg_send![self.window, bounds]) + } + + // requires main thread + unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect { + let screen: id = msg_send![self.window, screen]; + if !screen.is_null() { + let screen_space: id = msg_send![screen, coordinateSpace]; + msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space] + } else { + rect + } + } + + // requires main thread + unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect { + let screen: id = msg_send![self.window, screen]; + if !screen.is_null() { + let screen_space: id = msg_send![screen, coordinateSpace]; + msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space] + } else { + rect + } + } + + // requires main thread + unsafe fn safe_area_screen_space(&self) -> CGRect { + let bounds: CGRect = msg_send![self.window, bounds]; + if self.supports_safe_area { + let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets]; + let safe_bounds = CGRect { + origin: CGPoint { + x: bounds.origin.x + safe_area.left, + y: bounds.origin.y + safe_area.top, + }, + size: CGSize { + width: bounds.size.width - safe_area.left - safe_area.right, + height: bounds.size.height - safe_area.top - safe_area.bottom, + }, + }; + self.to_screen_space(safe_bounds) + } else { + let screen_frame = self.to_screen_space(bounds); + let status_bar_frame: CGRect = { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + assert!(!app.is_null(), "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"); + msg_send![app, statusBarFrame] + }; + let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height { + (screen_frame.origin.y, screen_frame.size.height) + } else { + let y = status_bar_frame.size.height; + let height = screen_frame.size.height - (status_bar_frame.size.height - screen_frame.origin.y); + (y, height) + }; + CGRect { + origin: CGPoint { + x: screen_frame.origin.x, + y, + }, + size: CGSize { + width: screen_frame.size.width, + height, + } + } + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId { + window: id, +} + +impl WindowId { + pub unsafe fn dummy() -> Self { + WindowId { + window: std::ptr::null_mut(), + } + } +} + +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} + +impl From<&Object> for WindowId { + fn from(window: &Object) -> WindowId { + WindowId { window: window as *const _ as _ } + } +} + +impl From<&mut Object> for WindowId { + fn from(window: &mut Object) -> WindowId { + WindowId { window: window as _ } + } +} + +impl From for WindowId { + fn from(window: id) -> WindowId { + WindowId { window } + } +} + +#[derive(Clone)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub root_view_class: &'static Class, + pub hidpi_factor: Option, + pub valid_orientations: ValidOrientations, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> PlatformSpecificWindowBuilderAttributes { + PlatformSpecificWindowBuilderAttributes { + root_view_class: class!(UIView), + hidpi_factor: None, + valid_orientations: Default::default(), + } + } +} diff --git a/src/platform_impl/linux/dlopen.rs b/src/platform_impl/linux/dlopen.rs index d9ad3fb2..96547481 100644 --- a/src/platform_impl/linux/dlopen.rs +++ b/src/platform_impl/linux/dlopen.rs @@ -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; diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 94f324ed..3b316a25 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -1,7 +1,7 @@ #![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::{env, mem, fmt}; use std::ffi::CStr; use std::os::raw::*; use std::sync::Arc; @@ -11,16 +11,18 @@ use sctk::reexports::client::ConnectError; use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use icon::Icon; +use error::{ExternalError, NotSupportedError, OsError as RootOsError}; +use event::Event; use event_loop::{EventLoopClosed, ControlFlow, EventLoopWindowTarget as RootELW}; use monitor::MonitorHandle as RootMonitorHandle; -use window::{WindowAttributes, CreationError, MouseCursor}; -//use self::x11::{XConnection, XError}; -//use self::x11::ffi::XVisualInfo; -//pub use self::x11::XNotSupported; +use window::{WindowAttributes, CursorIcon}; +use self::x11::{XConnection, XError}; +use self::x11::ffi::XVisualInfo; +pub use self::x11::XNotSupported; mod dlopen; pub mod wayland; -//pub mod x11; +pub mod x11; /// Environment variable specifying which backend should be used on unix platform. /// @@ -33,31 +35,46 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { - //pub visual_infos: Option, + pub visual_infos: Option, pub screen_id: Option, 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 x11_window_type: x11::util::WindowType, pub gtk_theme_variant: Option, pub app_id: Option } -//lazy_static!( -// pub static ref X11_BACKEND: Mutex, XNotSupported>> = { -// Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) -// }; -//); +lazy_static!( + pub static ref X11_BACKEND: Mutex, XNotSupported>> = { + Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) + }; +); + +#[derive(Debug, Clone)] +pub enum OsError { + XError(XError), + XMisc(&'static str), +} + +impl fmt::Display for OsError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + OsError::XError(e) => formatter.pad(&e.description), + OsError::XMisc(e) => formatter.pad(e), + } + } +} pub enum Window { - //X(x11::Window), + X(x11::Window), Wayland(wayland::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum WindowId { - //X(x11::WindowId), + X(x11::WindowId), Wayland(wayland::WindowId), } @@ -69,7 +86,7 @@ impl WindowId { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { - //X(x11::DeviceId), + X(x11::DeviceId), Wayland(wayland::DeviceId), } @@ -81,48 +98,48 @@ impl DeviceId { #[derive(Debug, Clone)] pub enum MonitorHandle { - //X(x11::MonitorHandle), + X(x11::MonitorHandle), Wayland(wayland::MonitorHandle), } impl MonitorHandle { #[inline] - pub fn get_name(&self) -> Option { + pub fn name(&self) -> Option { match self { - //&MonitorHandle::X(ref m) => m.get_name(), - &MonitorHandle::Wayland(ref m) => m.get_name(), + &MonitorHandle::X(ref m) => m.name(), + &MonitorHandle::Wayland(ref m) => m.name(), } } #[inline] - pub fn get_native_identifier(&self) -> u32 { + pub fn native_identifier(&self) -> u32 { match self { - //&MonitorHandle::X(ref m) => m.get_native_identifier(), - &MonitorHandle::Wayland(ref m) => m.get_native_identifier(), + &MonitorHandle::X(ref m) => m.native_identifier(), + &MonitorHandle::Wayland(ref m) => m.native_identifier(), } } #[inline] - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { match self { - //&MonitorHandle::X(ref m) => m.get_dimensions(), - &MonitorHandle::Wayland(ref m) => m.get_dimensions(), + &MonitorHandle::X(ref m) => m.dimensions(), + &MonitorHandle::Wayland(ref m) => m.dimensions(), } } #[inline] - pub fn get_position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { match self { - //&MonitorHandle::X(ref m) => m.get_position(), - &MonitorHandle::Wayland(ref m) => m.get_position(), + &MonitorHandle::X(ref m) => m.position(), + &MonitorHandle::Wayland(ref m) => m.position(), } } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { match self { - //&MonitorHandle::X(ref m) => m.get_hidpi_factor(), - &MonitorHandle::Wayland(ref m) => m.get_hidpi_factor() as f64, + &MonitorHandle::X(ref m) => m.hidpi_factor(), + &MonitorHandle::Wayland(ref m) => m.hidpi_factor() as f64, } } } @@ -133,21 +150,21 @@ impl Window { window_target: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { + ) -> Result { match *window_target { EventLoopWindowTarget::Wayland(ref window_target) => { wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland) }, - //EventLoop::X(ref event_loop) => { - // x11::Window::new(event_loop, attribs, pl_attribs).map(Window::X) - //}, + EventLoopWindowTarget::X(ref window_target) => { + x11::Window::new(window_target, attribs, pl_attribs).map(Window::X) + }, } } #[inline] pub fn id(&self) -> WindowId { match self { - //&Window::X(ref w) => WindowId::X(w.id()), + &Window::X(ref w) => WindowId::X(w.id()), &Window::Wayland(ref w) => WindowId::Wayland(w.id()), } } @@ -155,135 +172,127 @@ impl Window { #[inline] pub fn set_title(&self, title: &str) { match self { - //&Window::X(ref w) => w.set_title(title), + &Window::X(ref w) => w.set_title(title), &Window::Wayland(ref w) => w.set_title(title), } } #[inline] - pub fn show(&self) { + pub fn set_visible(&self, visible: bool) { match self { - //&Window::X(ref w) => w.show(), - &Window::Wayland(ref w) => w.show(), + &Window::X(ref w) => w.set_visible(visible), + &Window::Wayland(ref w) => w.set_visible(visible), } } #[inline] - pub fn hide(&self) { + pub fn outer_position(&self) -> Result { match self { - //&Window::X(ref w) => w.hide(), - &Window::Wayland(ref w) => w.hide(), + &Window::X(ref w) => w.outer_position(), + &Window::Wayland(ref w) => w.outer_position(), } } #[inline] - pub fn get_position(&self) -> Option { + pub fn inner_position(&self) -> Result { match self { - //&Window::X(ref w) => w.get_position(), - &Window::Wayland(ref w) => w.get_position(), + &Window::X(ref m) => m.inner_position(), + &Window::Wayland(ref m) => m.inner_position(), } } #[inline] - pub fn get_inner_position(&self) -> Option { + pub fn set_outer_position(&self, position: LogicalPosition) { match self { - //&Window::X(ref m) => m.get_inner_position(), - &Window::Wayland(ref m) => m.get_inner_position(), + &Window::X(ref w) => w.set_outer_position(position), + &Window::Wayland(ref w) => w.set_outer_position(position), } } #[inline] - pub fn set_position(&self, position: LogicalPosition) { + pub fn inner_size(&self) -> LogicalSize { match self { - //&Window::X(ref w) => w.set_position(position), - &Window::Wayland(ref w) => w.set_position(position), + &Window::X(ref w) => w.inner_size(), + &Window::Wayland(ref w) => w.inner_size(), } } #[inline] - pub fn get_inner_size(&self) -> Option { + pub fn outer_size(&self) -> LogicalSize { match self { - //&Window::X(ref w) => w.get_inner_size(), - &Window::Wayland(ref w) => w.get_inner_size(), - } - } - - #[inline] - pub fn get_outer_size(&self) -> Option { - match self { - //&Window::X(ref w) => w.get_outer_size(), - &Window::Wayland(ref w) => w.get_outer_size(), + &Window::X(ref w) => w.outer_size(), + &Window::Wayland(ref w) => w.outer_size(), } } #[inline] pub fn set_inner_size(&self, size: LogicalSize) { match self { - //&Window::X(ref w) => w.set_inner_size(size), + &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) { + pub fn set_min_inner_size(&self, dimensions: Option) { match self { - //&Window::X(ref w) => w.set_min_dimensions(dimensions), - &Window::Wayland(ref w) => w.set_min_dimensions(dimensions), + &Window::X(ref w) => w.set_min_inner_size(dimensions), + &Window::Wayland(ref w) => w.set_min_inner_size(dimensions), } } #[inline] - pub fn set_max_dimensions(&self, dimensions: Option) { + pub fn set_max_inner_size(&self, dimensions: Option) { match self { - //&Window::X(ref w) => w.set_max_dimensions(dimensions), - &Window::Wayland(ref w) => w.set_max_dimensions(dimensions), + &Window::X(ref w) => w.set_max_inner_size(dimensions), + &Window::Wayland(ref w) => w.set_max_inner_size(dimensions), } } #[inline] pub fn set_resizable(&self, resizable: bool) { match self { - //&Window::X(ref w) => w.set_resizable(resizable), + &Window::X(ref w) => w.set_resizable(resizable), &Window::Wayland(ref w) => w.set_resizable(resizable), } } #[inline] - pub fn set_cursor(&self, cursor: MouseCursor) { + pub fn set_cursor_icon(&self, cursor: CursorIcon) { match self { - //&Window::X(ref w) => w.set_cursor(cursor), - &Window::Wayland(ref w) => w.set_cursor(cursor) + &Window::X(ref w) => w.set_cursor_icon(cursor), + &Window::Wayland(ref w) => w.set_cursor_icon(cursor) } } #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { match self { - //&Window::X(ref window) => window.grab_cursor(grab), - &Window::Wayland(ref window) => window.grab_cursor(grab), + &Window::X(ref window) => window.set_cursor_grab(grab), + &Window::Wayland(ref window) => window.set_cursor_grab(grab), } } #[inline] - pub fn hide_cursor(&self, hide: bool) { + pub fn set_cursor_visible(&self, visible: bool) { match self { - //&Window::X(ref window) => window.hide_cursor(hide), - &Window::Wayland(ref window) => window.hide_cursor(hide), + &Window::X(ref window) => window.set_cursor_visible(visible), + &Window::Wayland(ref window) => window.set_cursor_visible(visible), } } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { match self { - //&Window::X(ref w) => w.get_hidpi_factor(), + &Window::X(ref w) => w.hidpi_factor(), &Window::Wayland(ref w) => w.hidpi_factor() as f64, } } #[inline] - pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { match self { - //&Window::X(ref w) => w.set_cursor_position(position), + &Window::X(ref w) => w.set_cursor_position(position), &Window::Wayland(ref w) => w.set_cursor_position(position), } } @@ -291,15 +300,24 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { match self { - //&Window::X(ref w) => w.set_maximized(maximized), + &Window::X(ref w) => w.set_maximized(maximized), &Window::Wayland(ref w) => w.set_maximized(maximized), } } + #[inline] + pub fn fullscreen(&self) -> Option { + match self { + &Window::X(ref w) => w.fullscreen(), + &Window::Wayland(ref w) => w.fullscreen() + .map(|monitor_id| RootMonitorHandle { inner: MonitorHandle::Wayland(monitor_id) }) + } + } + #[inline] pub fn set_fullscreen(&self, monitor: Option) { match self { - //&Window::X(ref w) => w.set_fullscreen(monitor), + &Window::X(ref w) => w.set_fullscreen(monitor), &Window::Wayland(ref w) => w.set_fullscreen(monitor) } } @@ -307,7 +325,7 @@ impl Window { #[inline] pub fn set_decorations(&self, decorations: bool) { match self { - //&Window::X(ref w) => w.set_decorations(decorations), + &Window::X(ref w) => w.set_decorations(decorations), &Window::Wayland(ref w) => w.set_decorations(decorations) } } @@ -315,7 +333,7 @@ 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::X(ref w) => w.set_always_on_top(always_on_top), &Window::Wayland(_) => (), } } @@ -323,15 +341,15 @@ impl Window { #[inline] pub fn set_window_icon(&self, window_icon: Option) { match self { - //&Window::X(ref w) => w.set_window_icon(window_icon), + &Window::X(ref w) => w.set_window_icon(window_icon), &Window::Wayland(_) => (), } } #[inline] - pub fn set_ime_spot(&self, position: LogicalPosition) { + pub fn set_ime_position(&self, position: LogicalPosition) { match self { - //&Window::X(ref w) => w.set_ime_spot(position), + &Window::X(ref w) => w.set_ime_position(position), &Window::Wayland(_) => (), } } @@ -339,27 +357,27 @@ impl Window { #[inline] pub fn request_redraw(&self) { match self { - //&Window::X(ref w) => w.request_redraw(), + &Window::X(ref w) => w.request_redraw(), &Window::Wayland(ref w) => w.request_redraw(), } } #[inline] - pub fn get_current_monitor(&self) -> RootMonitorHandle { + pub fn current_monitor(&self) -> RootMonitorHandle { match self { - //&Window::X(ref window) => RootMonitorHandle { inner: MonitorHandle::X(window.get_current_monitor()) }, - &Window::Wayland(ref window) => RootMonitorHandle { inner: MonitorHandle::Wayland(window.get_current_monitor()) }, + &Window::X(ref window) => RootMonitorHandle { inner: MonitorHandle::X(window.current_monitor()) }, + &Window::Wayland(ref window) => RootMonitorHandle { inner: MonitorHandle::Wayland(window.current_monitor()) }, } } #[inline] - pub fn get_available_monitors(&self) -> VecDeque { + pub fn available_monitors(&self) -> VecDeque { match self { - //&Window::X(ref window) => window.get_available_monitors() - // .into_iter() - // .map(MonitorHandle::X) - // .collect(), - &Window::Wayland(ref window) => window.get_available_monitors() + &Window::X(ref window) => window.available_monitors() + .into_iter() + .map(MonitorHandle::X) + .collect(), + &Window::Wayland(ref window) => window.available_monitors() .into_iter() .map(MonitorHandle::Wayland) .collect(), @@ -367,15 +385,15 @@ impl Window { } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { match self { - //&Window::X(ref window) => MonitorHandle::X(window.get_primary_monitor()), - &Window::Wayland(ref window) => MonitorHandle::Wayland(window.get_primary_monitor()), + &Window::X(ref window) => MonitorHandle::X(window.primary_monitor()), + &Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()), } } } -/* + unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent, @@ -405,16 +423,16 @@ unsafe extern "C" fn x_error_callback( // Fun fact: this return value is completely ignored. 0 } -*/ + pub enum EventLoop { Wayland(wayland::EventLoop), - //X(x11::EventLoop) + X(x11::EventLoop) } #[derive(Clone)] pub enum EventLoopProxy { - //X(x11::EventLoopProxy), + X(x11::EventLoopProxy), Wayland(wayland::EventLoopProxy), } @@ -460,46 +478,45 @@ impl EventLoop { .map(EventLoop::Wayland) } - pub fn new_x11() -> Result, () /*XNotSupported*/> { - //X11_BACKEND - // .lock() - // .as_ref() - // .map(Arc::clone) - // .map(x11::EventLoop::new) - // .map(EventLoop::X) - // .map_err(|err| err.clone()) - unimplemented!() + pub fn new_x11() -> Result, XNotSupported> { + X11_BACKEND + .lock() + .as_ref() + .map(Arc::clone) + .map(x11::EventLoop::new) + .map(EventLoop::X) + .map_err(|err| err.clone()) } #[inline] - pub fn get_available_monitors(&self) -> VecDeque { + pub fn available_monitors(&self) -> VecDeque { match *self { EventLoop::Wayland(ref evlp) => evlp - .get_available_monitors() + .available_monitors() .into_iter() .map(MonitorHandle::Wayland) .collect(), - //EventLoop::X(ref evlp) => evlp - // .x_connection() - // .get_available_monitors() - // .into_iter() - // .map(MonitorHandle::X) - // .collect(), + EventLoop::X(ref evlp) => evlp + .x_connection() + .available_monitors() + .into_iter() + .map(MonitorHandle::X) + .collect(), } } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { match *self { - EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.get_primary_monitor()), - //EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().get_primary_monitor()), + EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), + EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), } } pub fn create_proxy(&self) -> EventLoopProxy { match *self { EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()), - //EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()), + EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()), } } @@ -508,7 +525,7 @@ impl EventLoop { { match *self { EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback), - //EventLoop::X(ref mut evlp) => evlp.run_return(callback) + EventLoop::X(ref mut evlp) => evlp.run_return(callback) } } @@ -517,7 +534,7 @@ impl EventLoop { { match self { EventLoop::Wayland(evlp) => evlp.run(callback), - //EventLoop::X(ref mut evlp) => evlp.run(callback) + EventLoop::X(evlp) => evlp.run(callback) } } @@ -525,36 +542,44 @@ impl EventLoop { pub fn is_wayland(&self) -> bool { match *self { EventLoop::Wayland(_) => true, - //EventLoop::X(_) => false, + EventLoop::X(_) => false, } } pub fn window_target(&self) -> &::event_loop::EventLoopWindowTarget { match *self { EventLoop::Wayland(ref evl) => evl.window_target(), - //EventLoop::X(ref evl) => evl.window_target() + EventLoop::X(ref evl) => evl.window_target() } } - - //#[inline] - //pub fn x_connection(&self) -> Option<&Arc> { - // match *self { - // EventLoop::Wayland(_) => None, - // EventLoop::X(ref ev) => Some(ev.x_connection()), - // } - //} } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { match *self { EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event), - //EventLoopProxy::X(ref proxy) => proxy.wakeup(), + EventLoopProxy::X(ref proxy) => proxy.send_event(event), } } } pub enum EventLoopWindowTarget { Wayland(wayland::EventLoopWindowTarget), - //X(x11::EventLoopWIndowTarget) + X(x11::EventLoopWindowTarget) +} + +fn sticky_exit_callback( + evt: Event, target: &RootELW, control_flow: &mut ControlFlow, callback: &mut F +) where F: FnMut(Event, &RootELW, &mut ControlFlow) +{ + // make ControlFlow::Exit sticky by providing a dummy + // control flow reference if it is already Exit. + let mut dummy = ControlFlow::Exit; + let cf = if *control_flow == ControlFlow::Exit { + &mut dummy + } else { + control_flow + }; + // user callback + callback(evt, target, cf) } diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 346d3536..c73a225f 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -8,6 +8,7 @@ use std::time::Instant; use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}; use event::ModifiersState; use dpi::{PhysicalPosition, PhysicalSize}; +use platform_impl::platform::sticky_exit_callback; use super::window::WindowStore; use super::WindowId; @@ -204,23 +205,39 @@ impl EventLoop { // empty buffer of events { let mut guard = sink.lock().unwrap(); - guard.empty_with(|evt| callback(evt, &self.window_target, &mut control_flow)); + guard.empty_with(|evt| { + sticky_exit_callback(evt, &self.window_target, &mut control_flow, &mut callback); + }); } // empty user events { let mut guard = user_events.borrow_mut(); for evt in guard.drain(..) { - callback(::event::Event::UserEvent(evt), &self.window_target, &mut control_flow); + sticky_exit_callback( + ::event::Event::UserEvent(evt), + &self.window_target, + &mut control_flow, + &mut callback + ); } } - - callback(::event::Event::EventsCleared, &self.window_target, &mut control_flow); - - // fo a second run of post-dispatch-triggers, to handle user-generated "request-redraw" + // do a second run of post-dispatch-triggers, to handle user-generated "request-redraw" + // in response of resize & friends self.post_dispatch_triggers(); { let mut guard = sink.lock().unwrap(); - guard.empty_with(|evt| callback(evt, &self.window_target, &mut control_flow)); + guard.empty_with(|evt| { + sticky_exit_callback(evt, &self.window_target, &mut control_flow, &mut callback); + }); + } + // send Events cleared + { + sticky_exit_callback( + ::event::Event::EventsCleared, + &self.window_target, + &mut control_flow, + &mut callback + ); } // send pending events to the server @@ -249,7 +266,11 @@ impl EventLoop { ControlFlow::WaitUntil(deadline) => { let start = Instant::now(); // compute the blocking duration - let duration = deadline.duration_since(::std::cmp::max(deadline, start)); + let duration = if deadline > start { + deadline - start + } else { + ::std::time::Duration::from_millis(0) + }; self.inner_loop.dispatch(Some(duration), &mut ()).unwrap(); control_flow = ControlFlow::default(); let now = Instant::now(); @@ -279,12 +300,16 @@ impl EventLoop { callback(::event::Event::LoopDestroyed, &self.window_target, &mut control_flow); } - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor(&self.outputs) + pub fn primary_monitor(&self) -> MonitorHandle { + primary_monitor(&self.outputs) } - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors(&self.outputs) + pub fn available_monitors(&self) -> VecDeque { + available_monitors(&self.outputs) + } + + pub fn display(&self) -> &Display { + &*self.display } pub fn window_target(&self) -> &RootELW { @@ -511,11 +536,11 @@ impl fmt::Debug for MonitorHandle { } let monitor_id_proxy = MonitorHandle { - name: self.get_name(), - native_identifier: self.get_native_identifier(), - dimensions: self.get_dimensions(), - position: self.get_position(), - hidpi_factor: self.get_hidpi_factor(), + name: self.name(), + native_identifier: self.native_identifier(), + dimensions: self.dimensions(), + position: self.position(), + hidpi_factor: self.hidpi_factor(), }; monitor_id_proxy.fmt(f) @@ -523,18 +548,18 @@ impl fmt::Debug for MonitorHandle { } impl MonitorHandle { - pub fn get_name(&self) -> Option { + pub fn name(&self) -> Option { self.mgr.with_info(&self.proxy, |_, info| { format!("{} ({})", info.model, info.make) }) } #[inline] - pub fn get_native_identifier(&self) -> u32 { + pub fn native_identifier(&self) -> u32 { self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0) } - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { match self.mgr.with_info(&self.proxy, |_, info| { info.modes .iter() @@ -546,7 +571,7 @@ impl MonitorHandle { }.into() } - pub fn get_position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { self.mgr .with_info(&self.proxy, |_, info| info.location) .unwrap_or((0, 0)) @@ -554,14 +579,14 @@ impl MonitorHandle { } #[inline] - pub fn get_hidpi_factor(&self) -> i32 { + pub fn hidpi_factor(&self) -> i32 { self.mgr .with_info(&self.proxy, |_, info| info.scale_factor) .unwrap_or(1) } } -pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorHandle { +pub fn primary_monitor(outputs: &OutputMgr) -> MonitorHandle { outputs.with_all(|list| { if let Some(&(_, ref proxy, _)) = list.first() { MonitorHandle { @@ -574,7 +599,7 @@ pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorHandle { }) } -pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque { +pub fn available_monitors(outputs: &OutputMgr) -> VecDeque { outputs.with_all(|list| { list.iter() .map(|&(_, ref proxy, _)| MonitorHandle { diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 420d0ab6..d0930949 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -295,6 +295,13 @@ fn keysym_to_vkey(keysym: u32) -> Option { 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), diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 622b3f8d..976caf75 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -2,18 +2,19 @@ use std::collections::VecDeque; use std::sync::{Arc, Mutex, Weak}; use dpi::{LogicalPosition, LogicalSize}; +use error::{ExternalError, NotSupportedError, OsError as RootOsError}; use platform_impl::{MonitorHandle as PlatformMonitorHandle, PlatformSpecificWindowBuilderAttributes as PlAttributes}; use monitor::MonitorHandle as RootMonitorHandle; -use window::{CreationError, WindowAttributes, MouseCursor}; +use window::{WindowAttributes, CursorIcon}; use sctk::surface::{get_dpi_factor, get_outputs}; -use sctk::window::{ConceptFrame, Event as WEvent, Window as SWindow, Theme}; +use sctk::window::{ConceptFrame, Event as WEvent, State as WState, Window as SWindow, Theme}; use sctk::reexports::client::Display; use sctk::reexports::client::protocol::{wl_seat, wl_surface}; use sctk::output::OutputMgr; use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; -use platform_impl::platform::wayland::event_loop::{get_available_monitors, get_primary_monitor}; +use platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor}; pub struct Window { surface: wl_surface::WlSurface, @@ -23,14 +24,16 @@ pub struct Window { kill_switch: (Arc>, Arc>), display: Arc, need_frame_refresh: Arc>, - need_refresh: Arc> + need_refresh: Arc>, + fullscreen: Arc>, } impl Window { - pub fn new(evlp: &EventLoopWindowTarget, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result { - let (width, height) = attributes.dimensions.map(Into::into).unwrap_or((800, 600)); + pub fn new(evlp: &EventLoopWindowTarget, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result { + let (width, height) = attributes.inner_size.map(Into::into).unwrap_or((800, 600)); // Create the window let size = Arc::new(Mutex::new((width, height))); + let fullscreen = Arc::new(Mutex::new(false)); let window_store = evlp.store.clone(); let surface = evlp.env.create_surface(move |dpi, surface| { @@ -45,12 +48,15 @@ impl Window { surface.clone(), (width, height), move |event| match event { - WEvent::Configure { new_size, .. } => { + 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.as_ref().equals(&my_surface.as_ref()) { window.newsize = new_size; *(window.need_refresh.lock().unwrap()) = true; + *(window.fullscreen.lock().unwrap()) = is_fullscreen; *(window.need_frame_refresh.lock().unwrap()) = true; return; } @@ -104,8 +110,8 @@ impl Window { frame.set_title(attributes.title); // min-max dimensions - frame.set_min_size(attributes.min_dimensions.map(Into::into)); - frame.set_max_size(attributes.max_dimensions.map(Into::into)); + frame.set_min_size(attributes.min_inner_size.map(Into::into)); + frame.set_max_size(attributes.max_inner_size.map(Into::into)); let kill_switch = Arc::new(Mutex::new(false)); let need_frame_refresh = Arc::new(Mutex::new(true)); @@ -117,6 +123,7 @@ impl Window { newsize: None, size: size.clone(), need_refresh: need_refresh.clone(), + fullscreen: fullscreen.clone(), need_frame_refresh: need_frame_refresh.clone(), surface: surface.clone(), kill_switch: kill_switch.clone(), @@ -135,6 +142,7 @@ impl Window { kill_switch: (kill_switch, evlp.cleanup_needed.clone()), need_frame_refresh, need_refresh, + fullscreen, }) } @@ -147,35 +155,27 @@ impl Window { self.frame.lock().unwrap().set_title(title.into()); } - #[inline] - pub fn show(&self) { + pub fn set_visible(&self, _visible: bool) { // TODO } #[inline] - pub fn hide(&self) { - // TODO + pub fn outer_position(&self) -> Result { + Err(NotSupportedError::new()) } #[inline] - pub fn get_position(&self) -> Option { - // Not possible with wayland - None + pub fn inner_position(&self) -> Result { + Err(NotSupportedError::new()) } #[inline] - pub fn get_inner_position(&self) -> Option { - // Not possible with wayland - None - } - - #[inline] - pub fn set_position(&self, _pos: LogicalPosition) { + pub fn set_outer_position(&self, _pos: LogicalPosition) { // Not possible with wayland } - pub fn get_inner_size(&self) -> Option { - Some(self.size.lock().unwrap().clone().into()) + pub fn inner_size(&self) -> LogicalSize { + self.size.lock().unwrap().clone().into() } pub fn request_redraw(&self) { @@ -183,10 +183,10 @@ impl Window { } #[inline] - pub fn get_outer_size(&self) -> Option { + pub fn outer_size(&self) -> 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, h).into()) + (w, h).into() } #[inline] @@ -198,12 +198,12 @@ impl Window { } #[inline] - pub fn set_min_dimensions(&self, dimensions: Option) { + pub fn set_min_inner_size(&self, dimensions: Option) { self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into)); } #[inline] - pub fn set_max_dimensions(&self, dimensions: Option) { + pub fn set_max_inner_size(&self, dimensions: Option) { self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into)); } @@ -230,6 +230,14 @@ impl Window { } } + pub fn fullscreen(&self) -> Option { + if *(self.fullscreen.lock().unwrap()) { + Some(self.current_monitor()) + } else { + None + } + } + pub fn set_fullscreen(&self, monitor: Option) { if let Some(RootMonitorHandle { inner: PlatformMonitorHandle::Wayland(ref monitor_id), @@ -250,34 +258,34 @@ impl Window { } #[inline] - pub fn set_cursor(&self, _cursor: MouseCursor) { + pub fn set_cursor_icon(&self, _cursor: CursorIcon) { // TODO } #[inline] - pub fn hide_cursor(&self, _hide: bool) { + pub fn set_cursor_visible(&self, _visible: 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()) + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[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 set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) } - pub fn get_display(&self) -> &Display { + pub fn display(&self) -> &Display { &*self.display } - pub fn get_surface(&self) -> &wl_surface::WlSurface { + pub fn surface(&self) -> &wl_surface::WlSurface { &self.surface } - pub fn get_current_monitor(&self) -> MonitorHandle { + pub fn current_monitor(&self) -> MonitorHandle { let output = get_outputs(&self.surface).last().unwrap().clone(); MonitorHandle { proxy: output, @@ -285,12 +293,12 @@ impl Window { } } - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors(&self.outputs) + pub fn available_monitors(&self) -> VecDeque { + available_monitors(&self.outputs) } - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor(&self.outputs) + pub fn primary_monitor(&self) -> MonitorHandle { + primary_monitor(&self.outputs) } } @@ -310,6 +318,7 @@ struct InternalWindow { newsize: Option<(u32, u32)>, size: Arc>, need_refresh: Arc>, + fullscreen: Arc>, need_frame_refresh: Arc>, closed: bool, kill_switch: Arc>, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs new file mode 100644 index 00000000..19332dc9 --- /dev/null +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -0,0 +1,1004 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::ptr; +use std::rc::Rc; +use std::slice; + +use libc::{c_int, c_uint, c_ulong, c_char, c_long}; + +use super::{ + mkdid, mkwid, get_xtarget, DeviceId, WindowId, Device, ImeReceiver, XExtension, + monitor, ffi, UnownedWindow, ScrollOrientation, GenericEventCookie, + events, util, DndState, Dnd, DeviceInfo +}; + +use event_loop::EventLoopWindowTarget as RootELW; +use event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}; +use dpi::{LogicalPosition,LogicalSize}; + +pub(super) struct EventProcessor { + pub(super) dnd: Dnd, + pub(super) ime_receiver: ImeReceiver, + pub(super) randr_event_offset: c_int, + pub(super) devices: RefCell>, + pub(super) xi2ext: XExtension, + pub(super) target: Rc> +} + +impl EventProcessor { + pub(super) fn init_device(&self, device: c_int) { + let wt = get_xtarget(&self.target); + let mut devices = self.devices.borrow_mut(); + if let Some(info) = DeviceInfo::get(&wt.xconn, device) { + for info in info.iter() { + devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); + } + } + } + + fn with_window(&self, window_id: ffi::Window, callback: F) -> Option + where F: Fn(&UnownedWindow) -> Ret + { + let mut deleted = false; + let window_id = WindowId(window_id); + let wt = get_xtarget(&self.target); + let result = wt.windows + .borrow() + .get(&window_id) + .and_then(|window| { + let arc = window.upgrade(); + deleted = arc.is_none(); + arc + }) + .map(|window| callback(&*window)); + if deleted { + // Garbage collection + wt.windows.borrow_mut().remove(&window_id); + } + result + } + + fn window_exists(&self, window_id: ffi::Window) -> bool { + self.with_window(window_id, |_| ()).is_some() + } + + pub(super) unsafe fn poll_one_event(&mut self, event_ptr : *mut ffi::XEvent) -> bool { + let wt = get_xtarget(&self.target); + // This function is used to poll and remove a single event + // from the Xlib event queue in a non-blocking, atomic way. + // XCheckIfEvent is non-blocking and removes events from queue. + // XNextEvent can't be used because it blocks while holding the + // global Xlib mutex. + // XPeekEvent does not remove events from the queue. + unsafe extern "C" fn predicate( + _display: *mut ffi::Display, + _event: *mut ffi::XEvent, + _arg : *mut c_char) -> c_int { + // This predicate always returns "true" (1) to accept all events + 1 + } + + let result = (wt.xconn.xlib.XCheckIfEvent)( + wt.xconn.display, + event_ptr, + Some(predicate), + std::ptr::null_mut()); + + result != 0 + } + + pub(super) fn process_event(&mut self, xev: &mut ffi::XEvent, mut callback: F) + where F: FnMut(Event) + { + let wt = get_xtarget(&self.target); + // XFilterEvent tells us when an event has been discarded by the input method. + // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, + // along with an extra copy of the KeyRelease events. This also prevents backspace and + // arrow keys from being detected twice. + if ffi::True == unsafe { (wt.xconn.xlib.XFilterEvent)( + xev, + { let xev: &ffi::XAnyEvent = xev.as_ref(); xev.window } + ) } { + return; + } + + let event_type = xev.get_type(); + match event_type { + ffi::MappingNotify => { + unsafe { (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); } + wt.xconn.check_errors().expect("Failed to call XRefreshKeyboardMapping"); + } + + ffi::ClientMessage => { + let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); + + let window = client_msg.window; + let window_id = mkwid(window); + + if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window { + callback(Event::WindowEvent { window_id, event: WindowEvent::CloseRequested }); + } else if client_msg.message_type == self.dnd.atoms.enter { + let source_window = client_msg.data.get_long(0) as c_ulong; + let flags = client_msg.data.get_long(1); + let version = flags >> 24; + self.dnd.version = Some(version); + let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; + if !has_more_types { + let type_list = vec![ + client_msg.data.get_long(2) as c_ulong, + client_msg.data.get_long(3) as c_ulong, + client_msg.data.get_long(4) as c_ulong + ]; + self.dnd.type_list = Some(type_list); + } else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } { + self.dnd.type_list = Some(more_types); + } + } else if client_msg.message_type == self.dnd.atoms.position { + // This event occurs every time the mouse moves while a file's being dragged + // over our window. We emit HoveredFile in response; while the macOS backend + // does that upon a drag entering, XDND doesn't have access to the actual drop + // data until this event. For parity with other platforms, we only emit + // `HoveredFile` the first time, though if winit's API is later extended to + // supply position updates with `HoveredFile` or another event, implementing + // that here would be trivial. + + let source_window = client_msg.data.get_long(0) as c_ulong; + + // Equivalent to `(x << shift) | y` + // where `shift = mem::size_of::() * 8` + // Note that coordinates are in "desktop space", not "window space" + // (in X11 parlance, they're root window coordinates) + //let packed_coordinates = client_msg.data.get_long(2); + //let shift = mem::size_of::() * 8; + //let x = packed_coordinates >> shift; + //let y = packed_coordinates & !(x << shift); + + // By our own state flow, `version` should never be `None` at this point. + let version = self.dnd.version.unwrap_or(5); + + // Action is specified in versions 2 and up, though we don't need it anyway. + //let action = client_msg.data.get_long(4); + + let accepted = if let Some(ref type_list) = self.dnd.type_list { + type_list.contains(&self.dnd.atoms.uri_list) + } else { + false + }; + + if accepted { + self.dnd.source_window = Some(source_window); + unsafe { + if self.dnd.result.is_none() { + let time = if version >= 1 { + client_msg.data.get_long(3) as c_ulong + } else { + // In version 0, time isn't specified + ffi::CurrentTime + }; + // This results in the `SelectionNotify` event below + self.dnd.convert_selection(window, time); + } + self.dnd.send_status(window, source_window, DndState::Accepted) + .expect("Failed to send `XdndStatus` message."); + } + } else { + unsafe { + self.dnd.send_status(window, source_window, DndState::Rejected) + .expect("Failed to send `XdndStatus` message."); + } + self.dnd.reset(); + } + } else if client_msg.message_type == self.dnd.atoms.drop { + let (source_window, state) = if let Some(source_window) = self.dnd.source_window { + if let Some(Ok(ref path_list)) = self.dnd.result { + for path in path_list { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::DroppedFile(path.clone()), + }); + } + } + (source_window, DndState::Accepted) + } else { + // `source_window` won't be part of our DND state if we already rejected the drop in our + // `XdndPosition` handler. + let source_window = client_msg.data.get_long(0) as c_ulong; + (source_window, DndState::Rejected) + }; + unsafe { + self.dnd.send_finished(window, source_window, state) + .expect("Failed to send `XdndFinished` message."); + } + self.dnd.reset(); + } else if client_msg.message_type == self.dnd.atoms.leave { + self.dnd.reset(); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::HoveredFileCancelled, + }); + } + } + + ffi::SelectionNotify => { + let xsel: &ffi::XSelectionEvent = xev.as_ref(); + + let window = xsel.requestor; + let window_id = mkwid(window); + + if xsel.property == self.dnd.atoms.selection { + let mut result = None; + + // This is where we receive data from drag and drop + if let Ok(mut data) = unsafe { self.dnd.read_data(window) } { + let parse_result = self.dnd.parse_data(&mut data); + if let Ok(ref path_list) = parse_result { + for path in path_list { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::HoveredFile(path.clone()), + }); + } + } + result = Some(parse_result); + } + + self.dnd.result = result; + } + } + + ffi::ConfigureNotify => { + #[derive(Debug, Default)] + struct Events { + resized: Option, + moved: Option, + dpi_changed: Option, + } + + let xev: &ffi::XConfigureEvent = xev.as_ref(); + let xwindow = xev.window; + let events = self.with_window(xwindow, |window| { + // So apparently... + // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root + // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent + // https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5 + // We don't want to send `Moved` when this is false, since then every `Resized` + // (whether the window moved or not) is accompanied by an extraneous `Moved` event + // that has a position relative to the parent window. + let is_synthetic = xev.send_event == ffi::True; + + // These are both in physical space. + let new_inner_size = (xev.width as u32, xev.height as u32); + let new_inner_position = (xev.x as i32, xev.y as i32); + + let mut monitor = window.current_monitor(); // This must be done *before* locking! + let mut shared_state_lock = window.shared_state.lock(); + + let (mut resized, moved) = { + let resized = util::maybe_change(&mut shared_state_lock.size, new_inner_size); + let moved = if is_synthetic { + util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position) + } else { + // Detect when frame extents change. + // Since this isn't synthetic, as per the notes above, this position is relative to the + // parent window. + let rel_parent = new_inner_position; + if util::maybe_change(&mut shared_state_lock.inner_position_rel_parent, rel_parent) { + // This ensures we process the next `Moved`. + shared_state_lock.inner_position = None; + // Extra insurance against stale frame extents. + shared_state_lock.frame_extents = None; + } + false + }; + (resized, moved) + }; + + let mut events = Events::default(); + + let new_outer_position = if moved || shared_state_lock.position.is_none() { + // We need to convert client area position to window position. + let frame_extents = shared_state_lock.frame_extents + .as_ref() + .cloned() + .unwrap_or_else(|| { + let frame_extents = wt.xconn.get_frame_extents_heuristic(xwindow, wt.root); + shared_state_lock.frame_extents = Some(frame_extents.clone()); + frame_extents + }); + let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1); + shared_state_lock.position = Some(outer); + if moved { + let logical_position = LogicalPosition::from_physical(outer, monitor.hidpi_factor); + events.moved = Some(WindowEvent::Moved(logical_position)); + } + outer + } else { + shared_state_lock.position.unwrap() + }; + + if is_synthetic { + // If we don't use the existing adjusted value when available, then the user can screw up the + // resizing by dragging across monitors *without* dropping the window. + let (width, height) = shared_state_lock.dpi_adjusted + .unwrap_or_else(|| (xev.width as f64, xev.height as f64)); + let last_hidpi_factor = shared_state_lock.guessed_dpi + .take() + .unwrap_or_else(|| { + shared_state_lock.last_monitor + .as_ref() + .map(|last_monitor| last_monitor.hidpi_factor) + .unwrap_or(1.0) + }); + let new_hidpi_factor = { + let window_rect = util::AaRect::new(new_outer_position, new_inner_size); + monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); + let new_hidpi_factor = monitor.hidpi_factor; + shared_state_lock.last_monitor = Some(monitor.clone()); + new_hidpi_factor + }; + if last_hidpi_factor != new_hidpi_factor { + events.dpi_changed = Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor)); + let (new_width, new_height, flusher) = window.adjust_for_dpi( + last_hidpi_factor, + new_hidpi_factor, + width, + height, + ); + flusher.queue(); + shared_state_lock.dpi_adjusted = Some((new_width, new_height)); + // if the DPI factor changed, force a resize event to ensure the logical + // size is computed with the right DPI factor + resized = true; + } + } + + // This is a hack to ensure that the DPI adjusted resize is actually applied on all WMs. KWin + // doesn't need this, but Xfwm does. The hack should not be run on other WMs, since tiling + // WMs constrain the window size, making the resize fail. This would cause an endless stream of + // XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU. + if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { + let rounded_size = (adjusted_size.0.round() as u32, adjusted_size.1.round() as u32); + if new_inner_size == rounded_size || !util::wm_name_is_one_of(&["Xfwm4"]) { + // When this finally happens, the event will not be synthetic. + shared_state_lock.dpi_adjusted = None; + } else { + unsafe { + (wt.xconn.xlib.XResizeWindow)( + wt.xconn.display, + xwindow, + rounded_size.0 as c_uint, + rounded_size.1 as c_uint, + ); + } + } + } + + if resized { + let logical_size = LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor); + events.resized = Some(WindowEvent::Resized(logical_size)); + } + + events + }); + + if let Some(events) = events { + let window_id = mkwid(xwindow); + if let Some(event) = events.dpi_changed { + callback(Event::WindowEvent { window_id, event }); + } + if let Some(event) = events.resized { + callback(Event::WindowEvent { window_id, event }); + } + if let Some(event) = events.moved { + callback(Event::WindowEvent { window_id, event }); + } + } + } + + ffi::ReparentNotify => { + let xev: &ffi::XReparentEvent = xev.as_ref(); + + // This is generally a reliable way to detect when the window manager's been + // replaced, though this event is only fired by reparenting window managers + // (which is almost all of them). Failing to correctly update WM info doesn't + // really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only + // effect is that we waste some time trying to query unsupported properties. + wt.xconn.update_cached_wm_info(wt.root); + + self.with_window(xev.window, |window| { + window.invalidate_cached_frame_extents(); + }); + } + + ffi::DestroyNotify => { + let xev: &ffi::XDestroyWindowEvent = xev.as_ref(); + + let window = xev.window; + let window_id = mkwid(window); + + // In the event that the window's been destroyed without being dropped first, we + // cleanup again here. + wt.windows.borrow_mut().remove(&WindowId(window)); + + // Since all XIM stuff needs to happen from the same thread, we destroy the input + // context here instead of when dropping the window. + wt.ime + .borrow_mut() + .remove_context(window) + .expect("Failed to destroy input context"); + + callback(Event::WindowEvent { window_id, event: WindowEvent::Destroyed }); + } + + ffi::Expose => { + let xev: &ffi::XExposeEvent = xev.as_ref(); + + let window = xev.window; + let window_id = mkwid(window); + + callback(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested }); + } + + ffi::KeyPress | ffi::KeyRelease => { + use event::ElementState::{Pressed, Released}; + + // Note that in compose/pre-edit sequences, this will always be Released. + let state = if xev.get_type() == ffi::KeyPress { + Pressed + } else { + Released + }; + + let xkev: &mut ffi::XKeyEvent = xev.as_mut(); + + let window = xkev.window; + let window_id = mkwid(window); + + // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable + // value, though this should only be an issue under multiseat configurations. + let device = util::VIRTUAL_CORE_KEYBOARD; + let device_id = mkdid(device); + + // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with + // a keycode of 0. + if xkev.keycode != 0 { + let modifiers = ModifiersState { + alt: xkev.state & ffi::Mod1Mask != 0, + shift: xkev.state & ffi::ShiftMask != 0, + ctrl: xkev.state & ffi::ControlMask != 0, + logo: xkev.state & ffi::Mod4Mask != 0, + }; + + let keysym = unsafe { + let mut keysym = 0; + (wt.xconn.xlib.XLookupString)( + xkev, + ptr::null_mut(), + 0, + &mut keysym, + ptr::null_mut(), + ); + wt.xconn.check_errors().expect("Failed to lookup keysym"); + keysym + }; + let virtual_keycode = events::keysym_to_element(keysym as c_uint); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id, + input: KeyboardInput { + state, + scancode: xkev.keycode - 8, + virtual_keycode, + modifiers, + }, + } + }); + } + + if state == Pressed { + let written = if let Some(ic) = wt.ime.borrow().get_context(window) { + wt.xconn.lookup_utf8(ic, xkev) + } else { + return; + }; + + for chr in written.chars() { + let event = Event::WindowEvent { + window_id, + event: WindowEvent::ReceivedCharacter(chr), + }; + callback(event); + } + } + } + + ffi::GenericEvent => { + let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) { e } else { return }; + let xev = &guard.cookie; + if self.xi2ext.opcode != xev.extension { + return; + } + + use event::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion}; + use event::ElementState::{Pressed, Released}; + use event::MouseButton::{Left, Right, Middle, Other}; + use event::MouseScrollDelta::LineDelta; + use event::{Touch, TouchPhase}; + + match xev.evtype { + ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { + let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + let window_id = mkwid(xev.event); + let device_id = mkdid(xev.deviceid); + if (xev.flags & ffi::XIPointerEmulated) != 0 { + // Deliver multi-touch events instead of emulated mouse events. + return; + } + + let modifiers = ModifiersState::from(xev.mods); + + let state = if xev.evtype == ffi::XI_ButtonPress { + Pressed + } else { + Released + }; + match xev.detail as u32 { + ffi::Button1 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Left, + modifiers, + }, + }), + ffi::Button2 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Middle, + modifiers, + }, + }), + ffi::Button3 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Right, + modifiers, + }, + }), + + // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. + // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in + // turn) as axis motion, so we don't otherwise special-case these button presses. + 4 | 5 | 6 | 7 => if xev.flags & ffi::XIPointerEmulated == 0 { + callback(Event::WindowEvent { + window_id, + event: MouseWheel { + device_id, + delta: match xev.detail { + 4 => LineDelta(0.0, 1.0), + 5 => LineDelta(0.0, -1.0), + 6 => LineDelta(-1.0, 0.0), + 7 => LineDelta(1.0, 0.0), + _ => unreachable!(), + }, + phase: TouchPhase::Moved, + modifiers, + }, + }); + }, + + x => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Other(x as u8), + modifiers, + }, + }), + } + } + ffi::XI_Motion => { + let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + let device_id = mkdid(xev.deviceid); + let window_id = mkwid(xev.event); + let new_cursor_pos = (xev.event_x, xev.event_y); + + let modifiers = ModifiersState::from(xev.mods); + + let cursor_moved = self.with_window(xev.event, |window| { + let mut shared_state_lock = window.shared_state.lock(); + util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) + }); + if cursor_moved == Some(true) { + let dpi_factor = self.with_window(xev.event, |window| { + window.hidpi_factor() + }); + if let Some(dpi_factor) = dpi_factor { + let position = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } else { + return; + } + } else if cursor_moved.is_none() { + return; + } + + // More gymnastics, for self.devices + let mut events = Vec::new(); + { + let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; + let mut devices = self.devices.borrow_mut(); + let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { + Some(device) => device, + None => return, + }; + + let mut value = xev.valuators.values; + for i in 0..xev.valuators.mask_len*8 { + if ffi::XIMaskIsSet(mask, i) { + let x = unsafe { *value }; + if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) { + let delta = (x - info.position) / info.increment; + info.position = x; + events.push(Event::WindowEvent { + window_id, + event: MouseWheel { + device_id, + delta: match info.orientation { + ScrollOrientation::Horizontal => LineDelta(delta as f32, 0.0), + // X11 vertical scroll coordinates are opposite to winit's + ScrollOrientation::Vertical => LineDelta(0.0, -delta as f32), + }, + phase: TouchPhase::Moved, + modifiers, + }, + }); + } else { + events.push(Event::WindowEvent { + window_id, + event: AxisMotion { + device_id, + axis: i as u32, + value: unsafe { *value }, + }, + }); + } + value = unsafe { value.offset(1) }; + } + } + } + for event in events { + callback(event); + } + } + + ffi::XI_Enter => { + let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) }; + + let window_id = mkwid(xev.event); + let device_id = mkdid(xev.deviceid); + + if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) { + let mut devices = self.devices.borrow_mut(); + for device_info in all_info.iter() { + if device_info.deviceid == xev.sourceid + // This is needed for resetting to work correctly on i3, and + // presumably some other WMs. On those, `XI_Enter` doesn't include + // the physical device ID, so both `sourceid` and `deviceid` are + // the virtual device. + || device_info.attachment == xev.sourceid { + let device_id = DeviceId(device_info.deviceid); + if let Some(device) = devices.get_mut(&device_id) { + device.reset_scroll_position(device_info); + } + } + } + } + callback(Event::WindowEvent { + window_id, + event: CursorEntered { device_id }, + }); + + if let Some(dpi_factor) = self.with_window(xev.event, |window| { + window.hidpi_factor() + }) { + let position = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + + // The mods field on this event isn't actually populated, so query the + // pointer device. In the future, we can likely remove this round-trip by + // relying on `Xkb` for modifier values. + // + // This needs to only be done after confirming the window still exists, + // since otherwise we risk getting a `BadWindow` error if the window was + // dropped with queued events. + let modifiers = wt.xconn + .query_pointer(xev.event, xev.deviceid) + .expect("Failed to query pointer device") + .get_modifier_state(); + + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } + } + ffi::XI_Leave => { + let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; + + // Leave, FocusIn, and FocusOut can be received by a window that's already + // been destroyed, which the user presumably doesn't want to deal with. + let window_closed = !self.window_exists(xev.event); + if !window_closed { + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: CursorLeft { device_id: mkdid(xev.deviceid) }, + }); + } + } + ffi::XI_FocusIn => { + let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; + + let dpi_factor = match self.with_window(xev.event, |window| { + window.hidpi_factor() + }) { + Some(dpi_factor) => dpi_factor, + None => return, + }; + let window_id = mkwid(xev.event); + + wt.ime + .borrow_mut() + .focus(xev.event) + .expect("Failed to focus input context"); + + callback(Event::WindowEvent { window_id, event: Focused(true) }); + + // The deviceid for this event is for a keyboard instead of a pointer, + // so we have to do a little extra work. + let pointer_id = self.devices + .borrow() + .get(&DeviceId(xev.deviceid)) + .map(|device| device.attachment) + .unwrap_or(2); + + let position = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id), + position, + modifiers: ModifiersState::from(xev.mods), + } + }); + } + ffi::XI_FocusOut => { + let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; + if !self.window_exists(xev.event) { return; } + wt.ime + .borrow_mut() + .unfocus(xev.event) + .expect("Failed to unfocus input context"); + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: Focused(false), + }) + } + + ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { + let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + let window_id = mkwid(xev.event); + let phase = match xev.evtype { + ffi::XI_TouchBegin => TouchPhase::Started, + ffi::XI_TouchUpdate => TouchPhase::Moved, + ffi::XI_TouchEnd => TouchPhase::Ended, + _ => unreachable!() + }; + let dpi_factor = self.with_window(xev.event, |window| { + window.hidpi_factor() + }); + if let Some(dpi_factor) = dpi_factor { + let location = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Touch(Touch { + device_id: mkdid(xev.deviceid), + phase, + location, + id: xev.detail as u64, + }), + }) + } + } + + ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { + let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + if xev.flags & ffi::XIPointerEmulated == 0 { + callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Button { + button: xev.detail as u32, + state: match xev.evtype { + ffi::XI_RawButtonPress => Pressed, + ffi::XI_RawButtonRelease => Released, + _ => unreachable!(), + }, + }}); + } + } + + ffi::XI_RawMotion => { + let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + let did = mkdid(xev.deviceid); + + let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; + let mut value = xev.raw_values; + let mut mouse_delta = (0.0, 0.0); + let mut scroll_delta = (0.0, 0.0); + for i in 0..xev.valuators.mask_len*8 { + if ffi::XIMaskIsSet(mask, i) { + let x = unsafe { *value }; + // We assume that every XInput2 device with analog axes is a pointing device emitting + // relative coordinates. + match i { + 0 => mouse_delta.0 = x, + 1 => mouse_delta.1 = x, + 2 => scroll_delta.0 = x as f32, + 3 => scroll_delta.1 = x as f32, + _ => {}, + } + callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion { + axis: i as u32, + value: x, + }}); + value = unsafe { value.offset(1) }; + } + } + if mouse_delta != (0.0, 0.0) { + callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseMotion { + delta: mouse_delta, + }}); + } + if scroll_delta != (0.0, 0.0) { + callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseWheel { + delta: LineDelta(scroll_delta.0, scroll_delta.1), + }}); + } + } + + ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { + let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + + let state = match xev.evtype { + ffi::XI_RawKeyPress => Pressed, + ffi::XI_RawKeyRelease => Released, + _ => unreachable!(), + }; + + let device_id = xev.sourceid; + let keycode = xev.detail; + if keycode < 8 { return; } + let scancode = (keycode - 8) as u32; + + let keysym = unsafe { + (wt.xconn.xlib.XKeycodeToKeysym)( + wt.xconn.display, + xev.detail as ffi::KeyCode, + 0, + ) + }; + wt.xconn.check_errors().expect("Failed to lookup raw keysym"); + + let virtual_keycode = events::keysym_to_element(keysym as c_uint); + + callback(Event::DeviceEvent { + device_id: mkdid(device_id), + event: DeviceEvent::Key(KeyboardInput { + scancode, + virtual_keycode, + state, + // So, in an ideal world we can use libxkbcommon to get modifiers. + // However, libxkbcommon-x11 isn't as commonly installed as one + // would hope. We can still use the Xkb extension to get + // comprehensive keyboard state updates, but interpreting that + // info manually is going to be involved. + modifiers: ModifiersState::default(), + }), + }); + } + + ffi::XI_HierarchyChanged => { + let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) }; + for info in unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } { + if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { + self.init_device(info.deviceid); + callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added }); + } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) { + callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Removed }); + let mut devices = self.devices.borrow_mut(); + devices.remove(&DeviceId(info.deviceid)); + } + } + } + + _ => {} + } + }, + _ => { + if event_type == self.randr_event_offset { + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = monitor::invalidate_cached_monitor_list(); + if let Some(prev_list) = prev_list { + let new_list = wt.xconn.available_monitors(); + for new_monitor in new_list { + prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| { + if new_monitor.hidpi_factor != prev_monitor.hidpi_factor { + for (window_id, window) in wt.windows.borrow().iter() { + if let Some(window) = window.upgrade() { + // Check if the window is on this monitor + let monitor = window.current_monitor(); + if monitor.name == new_monitor.name { + callback(Event::WindowEvent { + window_id: mkwid(window_id.0), + event: WindowEvent::HiDpiFactorChanged( + new_monitor.hidpi_factor + ), + }); + let (width, height) = window.inner_size_physical(); + let (_, _, flusher) = window.adjust_for_dpi( + prev_monitor.hidpi_factor, + new_monitor.hidpi_factor, + width as f64, + height as f64, + ); + flusher.queue(); + } + } + } + } + }); + } + } + } + }, + } + + match self.ime_receiver.try_recv() { + Ok((window_id, x, y)) => { + wt.ime.borrow_mut().send_xim_spot(window_id, x, y); + }, + Err(_) => (), + } + } +} diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs index 037ee4c4..254f04e1 100644 --- a/src/platform_impl/linux/x11/events.rs +++ b/src/platform_impl/linux/x11/events.rs @@ -1,1003 +1,1003 @@ -use {events, libc}; +use libc; use super::ffi; -use VirtualKeyCode; +use event::VirtualKeyCode; pub fn keysym_to_element(keysym: libc::c_uint) -> Option { Some(match keysym { - ffi::XK_BackSpace => events::VirtualKeyCode::Back, - ffi::XK_Tab => events::VirtualKeyCode::Tab, - //ffi::XK_Linefeed => events::VirtualKeyCode::Linefeed, - //ffi::XK_Clear => events::VirtualKeyCode::Clear, - ffi::XK_Return => events::VirtualKeyCode::Return, - //ffi::XK_Pause => events::VirtualKeyCode::Pause, - //ffi::XK_Scroll_Lock => events::VirtualKeyCode::Scroll_lock, - //ffi::XK_Sys_Req => events::VirtualKeyCode::Sys_req, - ffi::XK_Escape => events::VirtualKeyCode::Escape, - ffi::XK_Delete => events::VirtualKeyCode::Delete, - ffi::XK_Multi_key => events::VirtualKeyCode::Compose, - //ffi::XK_Kanji => events::VirtualKeyCode::Kanji, - //ffi::XK_Muhenkan => events::VirtualKeyCode::Muhenkan, - //ffi::XK_Henkan_Mode => events::VirtualKeyCode::Henkan_mode, - //ffi::XK_Henkan => events::VirtualKeyCode::Henkan, - //ffi::XK_Romaji => events::VirtualKeyCode::Romaji, - //ffi::XK_Hiragana => events::VirtualKeyCode::Hiragana, - //ffi::XK_Katakana => events::VirtualKeyCode::Katakana, - //ffi::XK_Hiragana_Katakana => events::VirtualKeyCode::Hiragana_katakana, - //ffi::XK_Zenkaku => events::VirtualKeyCode::Zenkaku, - //ffi::XK_Hankaku => events::VirtualKeyCode::Hankaku, - //ffi::XK_Zenkaku_Hankaku => events::VirtualKeyCode::Zenkaku_hankaku, - //ffi::XK_Touroku => events::VirtualKeyCode::Touroku, - //ffi::XK_Massyo => events::VirtualKeyCode::Massyo, - //ffi::XK_Kana_Lock => events::VirtualKeyCode::Kana_lock, - //ffi::XK_Kana_Shift => events::VirtualKeyCode::Kana_shift, - //ffi::XK_Eisu_Shift => events::VirtualKeyCode::Eisu_shift, - //ffi::XK_Eisu_toggle => events::VirtualKeyCode::Eisu_toggle, - ffi::XK_Home => events::VirtualKeyCode::Home, - ffi::XK_Left => events::VirtualKeyCode::Left, - ffi::XK_Up => events::VirtualKeyCode::Up, - ffi::XK_Right => events::VirtualKeyCode::Right, - ffi::XK_Down => events::VirtualKeyCode::Down, - //ffi::XK_Prior => events::VirtualKeyCode::Prior, - ffi::XK_Page_Up => events::VirtualKeyCode::PageUp, - //ffi::XK_Next => events::VirtualKeyCode::Next, - ffi::XK_Page_Down => events::VirtualKeyCode::PageDown, - ffi::XK_End => events::VirtualKeyCode::End, - //ffi::XK_Begin => events::VirtualKeyCode::Begin, - //ffi::XK_Win_L => events::VirtualKeyCode::Win_l, - //ffi::XK_Win_R => events::VirtualKeyCode::Win_r, - //ffi::XK_App => events::VirtualKeyCode::App, - //ffi::XK_Select => events::VirtualKeyCode::Select, - //ffi::XK_Print => events::VirtualKeyCode::Print, - //ffi::XK_Execute => events::VirtualKeyCode::Execute, - ffi::XK_Insert => events::VirtualKeyCode::Insert, - //ffi::XK_Undo => events::VirtualKeyCode::Undo, - //ffi::XK_Redo => events::VirtualKeyCode::Redo, - //ffi::XK_Menu => events::VirtualKeyCode::Menu, - //ffi::XK_Find => events::VirtualKeyCode::Find, - //ffi::XK_Cancel => events::VirtualKeyCode::Cancel, - //ffi::XK_Help => events::VirtualKeyCode::Help, - //ffi::XK_Break => events::VirtualKeyCode::Break, - //ffi::XK_Mode_switch => events::VirtualKeyCode::Mode_switch, - //ffi::XK_script_switch => events::VirtualKeyCode::Script_switch, - //ffi::XK_Num_Lock => events::VirtualKeyCode::Num_lock, - //ffi::XK_KP_Space => events::VirtualKeyCode::Kp_space, - //ffi::XK_KP_Tab => events::VirtualKeyCode::Kp_tab, - //ffi::XK_KP_Enter => events::VirtualKeyCode::Kp_enter, - //ffi::XK_KP_F1 => events::VirtualKeyCode::Kp_f1, - //ffi::XK_KP_F2 => events::VirtualKeyCode::Kp_f2, - //ffi::XK_KP_F3 => events::VirtualKeyCode::Kp_f3, - //ffi::XK_KP_F4 => events::VirtualKeyCode::Kp_f4, - ffi::XK_KP_Home => events::VirtualKeyCode::Home, - ffi::XK_KP_Left => events::VirtualKeyCode::Left, - ffi::XK_KP_Up => events::VirtualKeyCode::Up, - ffi::XK_KP_Right => events::VirtualKeyCode::Right, - ffi::XK_KP_Down => events::VirtualKeyCode::Down, - //ffi::XK_KP_Prior => events::VirtualKeyCode::Kp_prior, - ffi::XK_KP_Page_Up => events::VirtualKeyCode::PageUp, - //ffi::XK_KP_Next => events::VirtualKeyCode::Kp_next, - ffi::XK_KP_Page_Down => events::VirtualKeyCode::PageDown, - ffi::XK_KP_End => events::VirtualKeyCode::End, - //ffi::XK_KP_Begin => events::VirtualKeyCode::Kp_begin, - ffi::XK_KP_Insert => events::VirtualKeyCode::Insert, - 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_Separator => events::VirtualKeyCode::Kp_separator, - //ffi::XK_KP_Subtract => events::VirtualKeyCode::NumpadSubtract, - //ffi::XK_KP_Decimal => events::VirtualKeyCode::Kp_decimal, - //ffi::XK_KP_Divide => events::VirtualKeyCode::NumpadDivide, - ffi::XK_KP_0 => events::VirtualKeyCode::Numpad0, - ffi::XK_KP_1 => events::VirtualKeyCode::Numpad1, - ffi::XK_KP_2 => events::VirtualKeyCode::Numpad2, - ffi::XK_KP_3 => events::VirtualKeyCode::Numpad3, - ffi::XK_KP_4 => events::VirtualKeyCode::Numpad4, - ffi::XK_KP_5 => events::VirtualKeyCode::Numpad5, - ffi::XK_KP_6 => events::VirtualKeyCode::Numpad6, - ffi::XK_KP_7 => events::VirtualKeyCode::Numpad7, - ffi::XK_KP_8 => events::VirtualKeyCode::Numpad8, - ffi::XK_KP_9 => events::VirtualKeyCode::Numpad9, - ffi::XK_F1 => events::VirtualKeyCode::F1, - ffi::XK_F2 => events::VirtualKeyCode::F2, - ffi::XK_F3 => events::VirtualKeyCode::F3, - ffi::XK_F4 => events::VirtualKeyCode::F4, - ffi::XK_F5 => events::VirtualKeyCode::F5, - ffi::XK_F6 => events::VirtualKeyCode::F6, - ffi::XK_F7 => events::VirtualKeyCode::F7, - ffi::XK_F8 => events::VirtualKeyCode::F8, - ffi::XK_F9 => events::VirtualKeyCode::F9, - ffi::XK_F10 => events::VirtualKeyCode::F10, - ffi::XK_F11 => events::VirtualKeyCode::F11, - //ffi::XK_L1 => events::VirtualKeyCode::L1, - ffi::XK_F12 => events::VirtualKeyCode::F12, - //ffi::XK_L2 => events::VirtualKeyCode::L2, - ffi::XK_F13 => events::VirtualKeyCode::F13, - //ffi::XK_L3 => events::VirtualKeyCode::L3, - ffi::XK_F14 => events::VirtualKeyCode::F14, - //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_L6 => events::VirtualKeyCode::L6, - ffi::XK_F17 => events::VirtualKeyCode::F17, - //ffi::XK_L7 => events::VirtualKeyCode::L7, - ffi::XK_F18 => events::VirtualKeyCode::F18, - //ffi::XK_L8 => events::VirtualKeyCode::L8, - ffi::XK_F19 => events::VirtualKeyCode::F19, - //ffi::XK_L9 => events::VirtualKeyCode::L9, - ffi::XK_F20 => events::VirtualKeyCode::F20, - //ffi::XK_L10 => events::VirtualKeyCode::L10, - ffi::XK_F21 => events::VirtualKeyCode::F21, - //ffi::XK_R1 => events::VirtualKeyCode::R1, - ffi::XK_F22 => events::VirtualKeyCode::F22, - //ffi::XK_R2 => events::VirtualKeyCode::R2, - ffi::XK_F23 => events::VirtualKeyCode::F23, - //ffi::XK_R3 => events::VirtualKeyCode::R3, - ffi::XK_F24 => events::VirtualKeyCode::F24, - //ffi::XK_R4 => events::VirtualKeyCode::R4, - //ffi::XK_F25 => events::VirtualKeyCode::F25, - //ffi::XK_R5 => events::VirtualKeyCode::R5, - //ffi::XK_F26 => events::VirtualKeyCode::F26, - //ffi::XK_R6 => events::VirtualKeyCode::R6, - //ffi::XK_F27 => events::VirtualKeyCode::F27, - //ffi::XK_R7 => events::VirtualKeyCode::R7, - //ffi::XK_F28 => events::VirtualKeyCode::F28, - //ffi::XK_R8 => events::VirtualKeyCode::R8, - //ffi::XK_F29 => events::VirtualKeyCode::F29, - //ffi::XK_R9 => events::VirtualKeyCode::R9, - //ffi::XK_F30 => events::VirtualKeyCode::F30, - //ffi::XK_R10 => events::VirtualKeyCode::R10, - //ffi::XK_F31 => events::VirtualKeyCode::F31, - //ffi::XK_R11 => events::VirtualKeyCode::R11, - //ffi::XK_F32 => events::VirtualKeyCode::F32, - //ffi::XK_R12 => events::VirtualKeyCode::R12, - //ffi::XK_F33 => events::VirtualKeyCode::F33, - //ffi::XK_R13 => events::VirtualKeyCode::R13, - //ffi::XK_F34 => events::VirtualKeyCode::F34, - //ffi::XK_R14 => events::VirtualKeyCode::R14, - //ffi::XK_F35 => events::VirtualKeyCode::F35, - //ffi::XK_R15 => events::VirtualKeyCode::R15, - ffi::XK_Shift_L => events::VirtualKeyCode::LShift, - ffi::XK_Shift_R => events::VirtualKeyCode::RShift, - ffi::XK_Control_L => events::VirtualKeyCode::LControl, - ffi::XK_Control_R => events::VirtualKeyCode::RControl, - //ffi::XK_Caps_Lock => events::VirtualKeyCode::Caps_lock, - //ffi::XK_Shift_Lock => events::VirtualKeyCode::Shift_lock, - //ffi::XK_Meta_L => events::VirtualKeyCode::Meta_l, - //ffi::XK_Meta_R => events::VirtualKeyCode::Meta_r, - ffi::XK_Alt_L => events::VirtualKeyCode::LAlt, - ffi::XK_Alt_R => events::VirtualKeyCode::RAlt, - //ffi::XK_Super_L => events::VirtualKeyCode::Super_l, - //ffi::XK_Super_R => events::VirtualKeyCode::Super_r, - //ffi::XK_Hyper_L => events::VirtualKeyCode::Hyper_l, - //ffi::XK_Hyper_R => events::VirtualKeyCode::Hyper_r, - ffi::XK_ISO_Left_Tab => events::VirtualKeyCode::Tab, - ffi::XK_space => events::VirtualKeyCode::Space, - //ffi::XK_exclam => events::VirtualKeyCode::Exclam, - //ffi::XK_quotedbl => events::VirtualKeyCode::Quotedbl, - //ffi::XK_numbersign => events::VirtualKeyCode::Numbersign, - //ffi::XK_dollar => events::VirtualKeyCode::Dollar, - //ffi::XK_percent => events::VirtualKeyCode::Percent, - //ffi::XK_ampersand => events::VirtualKeyCode::Ampersand, - ffi::XK_apostrophe => events::VirtualKeyCode::Apostrophe, - //ffi::XK_quoteright => events::VirtualKeyCode::Quoteright, - //ffi::XK_parenleft => events::VirtualKeyCode::Parenleft, - //ffi::XK_parenright => events::VirtualKeyCode::Parenright, - //ffi::XK_asterisk => events::VirtualKeyCode::Asterisk, - ffi::XK_plus => events::VirtualKeyCode::Add, - ffi::XK_comma => events::VirtualKeyCode::Comma, - ffi::XK_minus => events::VirtualKeyCode::Subtract, - ffi::XK_period => events::VirtualKeyCode::Period, - ffi::XK_slash => events::VirtualKeyCode::Slash, - ffi::XK_0 => events::VirtualKeyCode::Key0, - ffi::XK_1 => events::VirtualKeyCode::Key1, - ffi::XK_2 => events::VirtualKeyCode::Key2, - ffi::XK_3 => events::VirtualKeyCode::Key3, - ffi::XK_4 => events::VirtualKeyCode::Key4, - ffi::XK_5 => events::VirtualKeyCode::Key5, - ffi::XK_6 => events::VirtualKeyCode::Key6, - ffi::XK_7 => events::VirtualKeyCode::Key7, - ffi::XK_8 => events::VirtualKeyCode::Key8, - ffi::XK_9 => events::VirtualKeyCode::Key9, - ffi::XK_colon => events::VirtualKeyCode::Colon, - ffi::XK_semicolon => events::VirtualKeyCode::Semicolon, - //ffi::XK_less => events::VirtualKeyCode::Less, - ffi::XK_equal => events::VirtualKeyCode::Equals, - //ffi::XK_greater => events::VirtualKeyCode::Greater, - //ffi::XK_question => events::VirtualKeyCode::Question, - ffi::XK_at => events::VirtualKeyCode::At, - ffi::XK_A => events::VirtualKeyCode::A, - ffi::XK_B => events::VirtualKeyCode::B, - ffi::XK_C => events::VirtualKeyCode::C, - ffi::XK_D => events::VirtualKeyCode::D, - ffi::XK_E => events::VirtualKeyCode::E, - ffi::XK_F => events::VirtualKeyCode::F, - ffi::XK_G => events::VirtualKeyCode::G, - ffi::XK_H => events::VirtualKeyCode::H, - ffi::XK_I => events::VirtualKeyCode::I, - ffi::XK_J => events::VirtualKeyCode::J, - ffi::XK_K => events::VirtualKeyCode::K, - ffi::XK_L => events::VirtualKeyCode::L, - ffi::XK_M => events::VirtualKeyCode::M, - ffi::XK_N => events::VirtualKeyCode::N, - ffi::XK_O => events::VirtualKeyCode::O, - ffi::XK_P => events::VirtualKeyCode::P, - ffi::XK_Q => events::VirtualKeyCode::Q, - ffi::XK_R => events::VirtualKeyCode::R, - ffi::XK_S => events::VirtualKeyCode::S, - ffi::XK_T => events::VirtualKeyCode::T, - ffi::XK_U => events::VirtualKeyCode::U, - ffi::XK_V => events::VirtualKeyCode::V, - ffi::XK_W => events::VirtualKeyCode::W, - ffi::XK_X => events::VirtualKeyCode::X, - ffi::XK_Y => events::VirtualKeyCode::Y, - ffi::XK_Z => events::VirtualKeyCode::Z, - ffi::XK_bracketleft => events::VirtualKeyCode::LBracket, - ffi::XK_backslash => events::VirtualKeyCode::Backslash, - ffi::XK_bracketright => events::VirtualKeyCode::RBracket, - //ffi::XK_asciicircum => events::VirtualKeyCode::Asciicircum, - //ffi::XK_underscore => events::VirtualKeyCode::Underscore, - ffi::XK_grave => events::VirtualKeyCode::Grave, - //ffi::XK_quoteleft => events::VirtualKeyCode::Quoteleft, - ffi::XK_a => events::VirtualKeyCode::A, - ffi::XK_b => events::VirtualKeyCode::B, - ffi::XK_c => events::VirtualKeyCode::C, - ffi::XK_d => events::VirtualKeyCode::D, - ffi::XK_e => events::VirtualKeyCode::E, - ffi::XK_f => events::VirtualKeyCode::F, - ffi::XK_g => events::VirtualKeyCode::G, - ffi::XK_h => events::VirtualKeyCode::H, - ffi::XK_i => events::VirtualKeyCode::I, - ffi::XK_j => events::VirtualKeyCode::J, - ffi::XK_k => events::VirtualKeyCode::K, - ffi::XK_l => events::VirtualKeyCode::L, - ffi::XK_m => events::VirtualKeyCode::M, - ffi::XK_n => events::VirtualKeyCode::N, - ffi::XK_o => events::VirtualKeyCode::O, - ffi::XK_p => events::VirtualKeyCode::P, - ffi::XK_q => events::VirtualKeyCode::Q, - ffi::XK_r => events::VirtualKeyCode::R, - ffi::XK_s => events::VirtualKeyCode::S, - ffi::XK_t => events::VirtualKeyCode::T, - ffi::XK_u => events::VirtualKeyCode::U, - ffi::XK_v => events::VirtualKeyCode::V, - ffi::XK_w => events::VirtualKeyCode::W, - ffi::XK_x => events::VirtualKeyCode::X, - ffi::XK_y => events::VirtualKeyCode::Y, - ffi::XK_z => events::VirtualKeyCode::Z, - //ffi::XK_braceleft => events::VirtualKeyCode::Braceleft, - //ffi::XK_bar => events::VirtualKeyCode::Bar, - //ffi::XK_braceright => events::VirtualKeyCode::Braceright, - //ffi::XK_asciitilde => events::VirtualKeyCode::Asciitilde, - //ffi::XK_nobreakspace => events::VirtualKeyCode::Nobreakspace, - //ffi::XK_exclamdown => events::VirtualKeyCode::Exclamdown, - //ffi::XK_cent => events::VirtualKeyCode::Cent, - //ffi::XK_sterling => events::VirtualKeyCode::Sterling, - //ffi::XK_currency => events::VirtualKeyCode::Currency, - //ffi::XK_yen => events::VirtualKeyCode::Yen, - //ffi::XK_brokenbar => events::VirtualKeyCode::Brokenbar, - //ffi::XK_section => events::VirtualKeyCode::Section, - //ffi::XK_diaeresis => events::VirtualKeyCode::Diaeresis, - //ffi::XK_copyright => events::VirtualKeyCode::Copyright, - //ffi::XK_ordfeminine => events::VirtualKeyCode::Ordfeminine, - //ffi::XK_guillemotleft => events::VirtualKeyCode::Guillemotleft, - //ffi::XK_notsign => events::VirtualKeyCode::Notsign, - //ffi::XK_hyphen => events::VirtualKeyCode::Hyphen, - //ffi::XK_registered => events::VirtualKeyCode::Registered, - //ffi::XK_macron => events::VirtualKeyCode::Macron, - //ffi::XK_degree => events::VirtualKeyCode::Degree, - //ffi::XK_plusminus => events::VirtualKeyCode::Plusminus, - //ffi::XK_twosuperior => events::VirtualKeyCode::Twosuperior, - //ffi::XK_threesuperior => events::VirtualKeyCode::Threesuperior, - //ffi::XK_acute => events::VirtualKeyCode::Acute, - //ffi::XK_mu => events::VirtualKeyCode::Mu, - //ffi::XK_paragraph => events::VirtualKeyCode::Paragraph, - //ffi::XK_periodcentered => events::VirtualKeyCode::Periodcentered, - //ffi::XK_cedilla => events::VirtualKeyCode::Cedilla, - //ffi::XK_onesuperior => events::VirtualKeyCode::Onesuperior, - //ffi::XK_masculine => events::VirtualKeyCode::Masculine, - //ffi::XK_guillemotright => events::VirtualKeyCode::Guillemotright, - //ffi::XK_onequarter => events::VirtualKeyCode::Onequarter, - //ffi::XK_onehalf => events::VirtualKeyCode::Onehalf, - //ffi::XK_threequarters => events::VirtualKeyCode::Threequarters, - //ffi::XK_questiondown => events::VirtualKeyCode::Questiondown, - //ffi::XK_Agrave => events::VirtualKeyCode::Agrave, - //ffi::XK_Aacute => events::VirtualKeyCode::Aacute, - //ffi::XK_Acircumflex => events::VirtualKeyCode::Acircumflex, - //ffi::XK_Atilde => events::VirtualKeyCode::Atilde, - //ffi::XK_Adiaeresis => events::VirtualKeyCode::Adiaeresis, - //ffi::XK_Aring => events::VirtualKeyCode::Aring, - //ffi::XK_AE => events::VirtualKeyCode::Ae, - //ffi::XK_Ccedilla => events::VirtualKeyCode::Ccedilla, - //ffi::XK_Egrave => events::VirtualKeyCode::Egrave, - //ffi::XK_Eacute => events::VirtualKeyCode::Eacute, - //ffi::XK_Ecircumflex => events::VirtualKeyCode::Ecircumflex, - //ffi::XK_Ediaeresis => events::VirtualKeyCode::Ediaeresis, - //ffi::XK_Igrave => events::VirtualKeyCode::Igrave, - //ffi::XK_Iacute => events::VirtualKeyCode::Iacute, - //ffi::XK_Icircumflex => events::VirtualKeyCode::Icircumflex, - //ffi::XK_Idiaeresis => events::VirtualKeyCode::Idiaeresis, - //ffi::XK_ETH => events::VirtualKeyCode::Eth, - //ffi::XK_Eth => events::VirtualKeyCode::Eth, - //ffi::XK_Ntilde => events::VirtualKeyCode::Ntilde, - //ffi::XK_Ograve => events::VirtualKeyCode::Ograve, - //ffi::XK_Oacute => events::VirtualKeyCode::Oacute, - //ffi::XK_Ocircumflex => events::VirtualKeyCode::Ocircumflex, - //ffi::XK_Otilde => events::VirtualKeyCode::Otilde, - //ffi::XK_Odiaeresis => events::VirtualKeyCode::Odiaeresis, - //ffi::XK_multiply => events::VirtualKeyCode::Multiply, - //ffi::XK_Ooblique => events::VirtualKeyCode::Ooblique, - //ffi::XK_Ugrave => events::VirtualKeyCode::Ugrave, - //ffi::XK_Uacute => events::VirtualKeyCode::Uacute, - //ffi::XK_Ucircumflex => events::VirtualKeyCode::Ucircumflex, - //ffi::XK_Udiaeresis => events::VirtualKeyCode::Udiaeresis, - //ffi::XK_Yacute => events::VirtualKeyCode::Yacute, - //ffi::XK_THORN => events::VirtualKeyCode::Thorn, - //ffi::XK_Thorn => events::VirtualKeyCode::Thorn, - //ffi::XK_ssharp => events::VirtualKeyCode::Ssharp, - //ffi::XK_agrave => events::VirtualKeyCode::Agrave, - //ffi::XK_aacute => events::VirtualKeyCode::Aacute, - //ffi::XK_acircumflex => events::VirtualKeyCode::Acircumflex, - //ffi::XK_atilde => events::VirtualKeyCode::Atilde, - //ffi::XK_adiaeresis => events::VirtualKeyCode::Adiaeresis, - //ffi::XK_aring => events::VirtualKeyCode::Aring, - //ffi::XK_ae => events::VirtualKeyCode::Ae, - //ffi::XK_ccedilla => events::VirtualKeyCode::Ccedilla, - //ffi::XK_egrave => events::VirtualKeyCode::Egrave, - //ffi::XK_eacute => events::VirtualKeyCode::Eacute, - //ffi::XK_ecircumflex => events::VirtualKeyCode::Ecircumflex, - //ffi::XK_ediaeresis => events::VirtualKeyCode::Ediaeresis, - //ffi::XK_igrave => events::VirtualKeyCode::Igrave, - //ffi::XK_iacute => events::VirtualKeyCode::Iacute, - //ffi::XK_icircumflex => events::VirtualKeyCode::Icircumflex, - //ffi::XK_idiaeresis => events::VirtualKeyCode::Idiaeresis, - //ffi::XK_eth => events::VirtualKeyCode::Eth, - //ffi::XK_ntilde => events::VirtualKeyCode::Ntilde, - //ffi::XK_ograve => events::VirtualKeyCode::Ograve, - //ffi::XK_oacute => events::VirtualKeyCode::Oacute, - //ffi::XK_ocircumflex => events::VirtualKeyCode::Ocircumflex, - //ffi::XK_otilde => events::VirtualKeyCode::Otilde, - //ffi::XK_odiaeresis => events::VirtualKeyCode::Odiaeresis, - //ffi::XK_division => events::VirtualKeyCode::Division, - //ffi::XK_oslash => events::VirtualKeyCode::Oslash, - //ffi::XK_ugrave => events::VirtualKeyCode::Ugrave, - //ffi::XK_uacute => events::VirtualKeyCode::Uacute, - //ffi::XK_ucircumflex => events::VirtualKeyCode::Ucircumflex, - //ffi::XK_udiaeresis => events::VirtualKeyCode::Udiaeresis, - //ffi::XK_yacute => events::VirtualKeyCode::Yacute, - //ffi::XK_thorn => events::VirtualKeyCode::Thorn, - //ffi::XK_ydiaeresis => events::VirtualKeyCode::Ydiaeresis, - //ffi::XK_Aogonek => events::VirtualKeyCode::Aogonek, - //ffi::XK_breve => events::VirtualKeyCode::Breve, - //ffi::XK_Lstroke => events::VirtualKeyCode::Lstroke, - //ffi::XK_Lcaron => events::VirtualKeyCode::Lcaron, - //ffi::XK_Sacute => events::VirtualKeyCode::Sacute, - //ffi::XK_Scaron => events::VirtualKeyCode::Scaron, - //ffi::XK_Scedilla => events::VirtualKeyCode::Scedilla, - //ffi::XK_Tcaron => events::VirtualKeyCode::Tcaron, - //ffi::XK_Zacute => events::VirtualKeyCode::Zacute, - //ffi::XK_Zcaron => events::VirtualKeyCode::Zcaron, - //ffi::XK_Zabovedot => events::VirtualKeyCode::Zabovedot, - //ffi::XK_aogonek => events::VirtualKeyCode::Aogonek, - //ffi::XK_ogonek => events::VirtualKeyCode::Ogonek, - //ffi::XK_lstroke => events::VirtualKeyCode::Lstroke, - //ffi::XK_lcaron => events::VirtualKeyCode::Lcaron, - //ffi::XK_sacute => events::VirtualKeyCode::Sacute, - //ffi::XK_caron => events::VirtualKeyCode::Caron, - //ffi::XK_scaron => events::VirtualKeyCode::Scaron, - //ffi::XK_scedilla => events::VirtualKeyCode::Scedilla, - //ffi::XK_tcaron => events::VirtualKeyCode::Tcaron, - //ffi::XK_zacute => events::VirtualKeyCode::Zacute, - //ffi::XK_doubleacute => events::VirtualKeyCode::Doubleacute, - //ffi::XK_zcaron => events::VirtualKeyCode::Zcaron, - //ffi::XK_zabovedot => events::VirtualKeyCode::Zabovedot, - //ffi::XK_Racute => events::VirtualKeyCode::Racute, - //ffi::XK_Abreve => events::VirtualKeyCode::Abreve, - //ffi::XK_Lacute => events::VirtualKeyCode::Lacute, - //ffi::XK_Cacute => events::VirtualKeyCode::Cacute, - //ffi::XK_Ccaron => events::VirtualKeyCode::Ccaron, - //ffi::XK_Eogonek => events::VirtualKeyCode::Eogonek, - //ffi::XK_Ecaron => events::VirtualKeyCode::Ecaron, - //ffi::XK_Dcaron => events::VirtualKeyCode::Dcaron, - //ffi::XK_Dstroke => events::VirtualKeyCode::Dstroke, - //ffi::XK_Nacute => events::VirtualKeyCode::Nacute, - //ffi::XK_Ncaron => events::VirtualKeyCode::Ncaron, - //ffi::XK_Odoubleacute => events::VirtualKeyCode::Odoubleacute, - //ffi::XK_Rcaron => events::VirtualKeyCode::Rcaron, - //ffi::XK_Uring => events::VirtualKeyCode::Uring, - //ffi::XK_Udoubleacute => events::VirtualKeyCode::Udoubleacute, - //ffi::XK_Tcedilla => events::VirtualKeyCode::Tcedilla, - //ffi::XK_racute => events::VirtualKeyCode::Racute, - //ffi::XK_abreve => events::VirtualKeyCode::Abreve, - //ffi::XK_lacute => events::VirtualKeyCode::Lacute, - //ffi::XK_cacute => events::VirtualKeyCode::Cacute, - //ffi::XK_ccaron => events::VirtualKeyCode::Ccaron, - //ffi::XK_eogonek => events::VirtualKeyCode::Eogonek, - //ffi::XK_ecaron => events::VirtualKeyCode::Ecaron, - //ffi::XK_dcaron => events::VirtualKeyCode::Dcaron, - //ffi::XK_dstroke => events::VirtualKeyCode::Dstroke, - //ffi::XK_nacute => events::VirtualKeyCode::Nacute, - //ffi::XK_ncaron => events::VirtualKeyCode::Ncaron, - //ffi::XK_odoubleacute => events::VirtualKeyCode::Odoubleacute, - //ffi::XK_udoubleacute => events::VirtualKeyCode::Udoubleacute, - //ffi::XK_rcaron => events::VirtualKeyCode::Rcaron, - //ffi::XK_uring => events::VirtualKeyCode::Uring, - //ffi::XK_tcedilla => events::VirtualKeyCode::Tcedilla, - //ffi::XK_abovedot => events::VirtualKeyCode::Abovedot, - //ffi::XK_Hstroke => events::VirtualKeyCode::Hstroke, - //ffi::XK_Hcircumflex => events::VirtualKeyCode::Hcircumflex, - //ffi::XK_Iabovedot => events::VirtualKeyCode::Iabovedot, - //ffi::XK_Gbreve => events::VirtualKeyCode::Gbreve, - //ffi::XK_Jcircumflex => events::VirtualKeyCode::Jcircumflex, - //ffi::XK_hstroke => events::VirtualKeyCode::Hstroke, - //ffi::XK_hcircumflex => events::VirtualKeyCode::Hcircumflex, - //ffi::XK_idotless => events::VirtualKeyCode::Idotless, - //ffi::XK_gbreve => events::VirtualKeyCode::Gbreve, - //ffi::XK_jcircumflex => events::VirtualKeyCode::Jcircumflex, - //ffi::XK_Cabovedot => events::VirtualKeyCode::Cabovedot, - //ffi::XK_Ccircumflex => events::VirtualKeyCode::Ccircumflex, - //ffi::XK_Gabovedot => events::VirtualKeyCode::Gabovedot, - //ffi::XK_Gcircumflex => events::VirtualKeyCode::Gcircumflex, - //ffi::XK_Ubreve => events::VirtualKeyCode::Ubreve, - //ffi::XK_Scircumflex => events::VirtualKeyCode::Scircumflex, - //ffi::XK_cabovedot => events::VirtualKeyCode::Cabovedot, - //ffi::XK_ccircumflex => events::VirtualKeyCode::Ccircumflex, - //ffi::XK_gabovedot => events::VirtualKeyCode::Gabovedot, - //ffi::XK_gcircumflex => events::VirtualKeyCode::Gcircumflex, - //ffi::XK_ubreve => events::VirtualKeyCode::Ubreve, - //ffi::XK_scircumflex => events::VirtualKeyCode::Scircumflex, - //ffi::XK_kra => events::VirtualKeyCode::Kra, - //ffi::XK_kappa => events::VirtualKeyCode::Kappa, - //ffi::XK_Rcedilla => events::VirtualKeyCode::Rcedilla, - //ffi::XK_Itilde => events::VirtualKeyCode::Itilde, - //ffi::XK_Lcedilla => events::VirtualKeyCode::Lcedilla, - //ffi::XK_Emacron => events::VirtualKeyCode::Emacron, - //ffi::XK_Gcedilla => events::VirtualKeyCode::Gcedilla, - //ffi::XK_Tslash => events::VirtualKeyCode::Tslash, - //ffi::XK_rcedilla => events::VirtualKeyCode::Rcedilla, - //ffi::XK_itilde => events::VirtualKeyCode::Itilde, - //ffi::XK_lcedilla => events::VirtualKeyCode::Lcedilla, - //ffi::XK_emacron => events::VirtualKeyCode::Emacron, - //ffi::XK_gcedilla => events::VirtualKeyCode::Gcedilla, - //ffi::XK_tslash => events::VirtualKeyCode::Tslash, - //ffi::XK_ENG => events::VirtualKeyCode::Eng, - //ffi::XK_eng => events::VirtualKeyCode::Eng, - //ffi::XK_Amacron => events::VirtualKeyCode::Amacron, - //ffi::XK_Iogonek => events::VirtualKeyCode::Iogonek, - //ffi::XK_Eabovedot => events::VirtualKeyCode::Eabovedot, - //ffi::XK_Imacron => events::VirtualKeyCode::Imacron, - //ffi::XK_Ncedilla => events::VirtualKeyCode::Ncedilla, - //ffi::XK_Omacron => events::VirtualKeyCode::Omacron, - //ffi::XK_Kcedilla => events::VirtualKeyCode::Kcedilla, - //ffi::XK_Uogonek => events::VirtualKeyCode::Uogonek, - //ffi::XK_Utilde => events::VirtualKeyCode::Utilde, - //ffi::XK_Umacron => events::VirtualKeyCode::Umacron, - //ffi::XK_amacron => events::VirtualKeyCode::Amacron, - //ffi::XK_iogonek => events::VirtualKeyCode::Iogonek, - //ffi::XK_eabovedot => events::VirtualKeyCode::Eabovedot, - //ffi::XK_imacron => events::VirtualKeyCode::Imacron, - //ffi::XK_ncedilla => events::VirtualKeyCode::Ncedilla, - //ffi::XK_omacron => events::VirtualKeyCode::Omacron, - //ffi::XK_kcedilla => events::VirtualKeyCode::Kcedilla, - //ffi::XK_uogonek => events::VirtualKeyCode::Uogonek, - //ffi::XK_utilde => events::VirtualKeyCode::Utilde, - //ffi::XK_umacron => events::VirtualKeyCode::Umacron, - //ffi::XK_overline => events::VirtualKeyCode::Overline, - //ffi::XK_kana_fullstop => events::VirtualKeyCode::Kana_fullstop, - //ffi::XK_kana_openingbracket => events::VirtualKeyCode::Kana_openingbracket, - //ffi::XK_kana_closingbracket => events::VirtualKeyCode::Kana_closingbracket, - //ffi::XK_kana_comma => events::VirtualKeyCode::Kana_comma, - //ffi::XK_kana_conjunctive => events::VirtualKeyCode::Kana_conjunctive, - //ffi::XK_kana_middledot => events::VirtualKeyCode::Kana_middledot, - //ffi::XK_kana_WO => events::VirtualKeyCode::Kana_wo, - //ffi::XK_kana_a => events::VirtualKeyCode::Kana_a, - //ffi::XK_kana_i => events::VirtualKeyCode::Kana_i, - //ffi::XK_kana_u => events::VirtualKeyCode::Kana_u, - //ffi::XK_kana_e => events::VirtualKeyCode::Kana_e, - //ffi::XK_kana_o => events::VirtualKeyCode::Kana_o, - //ffi::XK_kana_ya => events::VirtualKeyCode::Kana_ya, - //ffi::XK_kana_yu => events::VirtualKeyCode::Kana_yu, - //ffi::XK_kana_yo => events::VirtualKeyCode::Kana_yo, - //ffi::XK_kana_tsu => events::VirtualKeyCode::Kana_tsu, - //ffi::XK_kana_tu => events::VirtualKeyCode::Kana_tu, - //ffi::XK_prolongedsound => events::VirtualKeyCode::Prolongedsound, - //ffi::XK_kana_A => events::VirtualKeyCode::Kana_a, - //ffi::XK_kana_I => events::VirtualKeyCode::Kana_i, - //ffi::XK_kana_U => events::VirtualKeyCode::Kana_u, - //ffi::XK_kana_E => events::VirtualKeyCode::Kana_e, - //ffi::XK_kana_O => events::VirtualKeyCode::Kana_o, - //ffi::XK_kana_KA => events::VirtualKeyCode::Kana_ka, - //ffi::XK_kana_KI => events::VirtualKeyCode::Kana_ki, - //ffi::XK_kana_KU => events::VirtualKeyCode::Kana_ku, - //ffi::XK_kana_KE => events::VirtualKeyCode::Kana_ke, - //ffi::XK_kana_KO => events::VirtualKeyCode::Kana_ko, - //ffi::XK_kana_SA => events::VirtualKeyCode::Kana_sa, - //ffi::XK_kana_SHI => events::VirtualKeyCode::Kana_shi, - //ffi::XK_kana_SU => events::VirtualKeyCode::Kana_su, - //ffi::XK_kana_SE => events::VirtualKeyCode::Kana_se, - //ffi::XK_kana_SO => events::VirtualKeyCode::Kana_so, - //ffi::XK_kana_TA => events::VirtualKeyCode::Kana_ta, - //ffi::XK_kana_CHI => events::VirtualKeyCode::Kana_chi, - //ffi::XK_kana_TI => events::VirtualKeyCode::Kana_ti, - //ffi::XK_kana_TSU => events::VirtualKeyCode::Kana_tsu, - //ffi::XK_kana_TU => events::VirtualKeyCode::Kana_tu, - //ffi::XK_kana_TE => events::VirtualKeyCode::Kana_te, - //ffi::XK_kana_TO => events::VirtualKeyCode::Kana_to, - //ffi::XK_kana_NA => events::VirtualKeyCode::Kana_na, - //ffi::XK_kana_NI => events::VirtualKeyCode::Kana_ni, - //ffi::XK_kana_NU => events::VirtualKeyCode::Kana_nu, - //ffi::XK_kana_NE => events::VirtualKeyCode::Kana_ne, - //ffi::XK_kana_NO => events::VirtualKeyCode::Kana_no, - //ffi::XK_kana_HA => events::VirtualKeyCode::Kana_ha, - //ffi::XK_kana_HI => events::VirtualKeyCode::Kana_hi, - //ffi::XK_kana_FU => events::VirtualKeyCode::Kana_fu, - //ffi::XK_kana_HU => events::VirtualKeyCode::Kana_hu, - //ffi::XK_kana_HE => events::VirtualKeyCode::Kana_he, - //ffi::XK_kana_HO => events::VirtualKeyCode::Kana_ho, - //ffi::XK_kana_MA => events::VirtualKeyCode::Kana_ma, - //ffi::XK_kana_MI => events::VirtualKeyCode::Kana_mi, - //ffi::XK_kana_MU => events::VirtualKeyCode::Kana_mu, - //ffi::XK_kana_ME => events::VirtualKeyCode::Kana_me, - //ffi::XK_kana_MO => events::VirtualKeyCode::Kana_mo, - //ffi::XK_kana_YA => events::VirtualKeyCode::Kana_ya, - //ffi::XK_kana_YU => events::VirtualKeyCode::Kana_yu, - //ffi::XK_kana_YO => events::VirtualKeyCode::Kana_yo, - //ffi::XK_kana_RA => events::VirtualKeyCode::Kana_ra, - //ffi::XK_kana_RI => events::VirtualKeyCode::Kana_ri, - //ffi::XK_kana_RU => events::VirtualKeyCode::Kana_ru, - //ffi::XK_kana_RE => events::VirtualKeyCode::Kana_re, - //ffi::XK_kana_RO => events::VirtualKeyCode::Kana_ro, - //ffi::XK_kana_WA => events::VirtualKeyCode::Kana_wa, - //ffi::XK_kana_N => events::VirtualKeyCode::Kana_n, - //ffi::XK_voicedsound => events::VirtualKeyCode::Voicedsound, - //ffi::XK_semivoicedsound => events::VirtualKeyCode::Semivoicedsound, - //ffi::XK_kana_switch => events::VirtualKeyCode::Kana_switch, - //ffi::XK_Arabic_comma => events::VirtualKeyCode::Arabic_comma, - //ffi::XK_Arabic_semicolon => events::VirtualKeyCode::Arabic_semicolon, - //ffi::XK_Arabic_question_mark => events::VirtualKeyCode::Arabic_question_mark, - //ffi::XK_Arabic_hamza => events::VirtualKeyCode::Arabic_hamza, - //ffi::XK_Arabic_maddaonalef => events::VirtualKeyCode::Arabic_maddaonalef, - //ffi::XK_Arabic_hamzaonalef => events::VirtualKeyCode::Arabic_hamzaonalef, - //ffi::XK_Arabic_hamzaonwaw => events::VirtualKeyCode::Arabic_hamzaonwaw, - //ffi::XK_Arabic_hamzaunderalef => events::VirtualKeyCode::Arabic_hamzaunderalef, - //ffi::XK_Arabic_hamzaonyeh => events::VirtualKeyCode::Arabic_hamzaonyeh, - //ffi::XK_Arabic_alef => events::VirtualKeyCode::Arabic_alef, - //ffi::XK_Arabic_beh => events::VirtualKeyCode::Arabic_beh, - //ffi::XK_Arabic_tehmarbuta => events::VirtualKeyCode::Arabic_tehmarbuta, - //ffi::XK_Arabic_teh => events::VirtualKeyCode::Arabic_teh, - //ffi::XK_Arabic_theh => events::VirtualKeyCode::Arabic_theh, - //ffi::XK_Arabic_jeem => events::VirtualKeyCode::Arabic_jeem, - //ffi::XK_Arabic_hah => events::VirtualKeyCode::Arabic_hah, - //ffi::XK_Arabic_khah => events::VirtualKeyCode::Arabic_khah, - //ffi::XK_Arabic_dal => events::VirtualKeyCode::Arabic_dal, - //ffi::XK_Arabic_thal => events::VirtualKeyCode::Arabic_thal, - //ffi::XK_Arabic_ra => events::VirtualKeyCode::Arabic_ra, - //ffi::XK_Arabic_zain => events::VirtualKeyCode::Arabic_zain, - //ffi::XK_Arabic_seen => events::VirtualKeyCode::Arabic_seen, - //ffi::XK_Arabic_sheen => events::VirtualKeyCode::Arabic_sheen, - //ffi::XK_Arabic_sad => events::VirtualKeyCode::Arabic_sad, - //ffi::XK_Arabic_dad => events::VirtualKeyCode::Arabic_dad, - //ffi::XK_Arabic_tah => events::VirtualKeyCode::Arabic_tah, - //ffi::XK_Arabic_zah => events::VirtualKeyCode::Arabic_zah, - //ffi::XK_Arabic_ain => events::VirtualKeyCode::Arabic_ain, - //ffi::XK_Arabic_ghain => events::VirtualKeyCode::Arabic_ghain, - //ffi::XK_Arabic_tatweel => events::VirtualKeyCode::Arabic_tatweel, - //ffi::XK_Arabic_feh => events::VirtualKeyCode::Arabic_feh, - //ffi::XK_Arabic_qaf => events::VirtualKeyCode::Arabic_qaf, - //ffi::XK_Arabic_kaf => events::VirtualKeyCode::Arabic_kaf, - //ffi::XK_Arabic_lam => events::VirtualKeyCode::Arabic_lam, - //ffi::XK_Arabic_meem => events::VirtualKeyCode::Arabic_meem, - //ffi::XK_Arabic_noon => events::VirtualKeyCode::Arabic_noon, - //ffi::XK_Arabic_ha => events::VirtualKeyCode::Arabic_ha, - //ffi::XK_Arabic_heh => events::VirtualKeyCode::Arabic_heh, - //ffi::XK_Arabic_waw => events::VirtualKeyCode::Arabic_waw, - //ffi::XK_Arabic_alefmaksura => events::VirtualKeyCode::Arabic_alefmaksura, - //ffi::XK_Arabic_yeh => events::VirtualKeyCode::Arabic_yeh, - //ffi::XK_Arabic_fathatan => events::VirtualKeyCode::Arabic_fathatan, - //ffi::XK_Arabic_dammatan => events::VirtualKeyCode::Arabic_dammatan, - //ffi::XK_Arabic_kasratan => events::VirtualKeyCode::Arabic_kasratan, - //ffi::XK_Arabic_fatha => events::VirtualKeyCode::Arabic_fatha, - //ffi::XK_Arabic_damma => events::VirtualKeyCode::Arabic_damma, - //ffi::XK_Arabic_kasra => events::VirtualKeyCode::Arabic_kasra, - //ffi::XK_Arabic_shadda => events::VirtualKeyCode::Arabic_shadda, - //ffi::XK_Arabic_sukun => events::VirtualKeyCode::Arabic_sukun, - //ffi::XK_Arabic_switch => events::VirtualKeyCode::Arabic_switch, - //ffi::XK_Serbian_dje => events::VirtualKeyCode::Serbian_dje, - //ffi::XK_Macedonia_gje => events::VirtualKeyCode::Macedonia_gje, - //ffi::XK_Cyrillic_io => events::VirtualKeyCode::Cyrillic_io, - //ffi::XK_Ukrainian_ie => events::VirtualKeyCode::Ukrainian_ie, - //ffi::XK_Ukranian_je => events::VirtualKeyCode::Ukranian_je, - //ffi::XK_Macedonia_dse => events::VirtualKeyCode::Macedonia_dse, - //ffi::XK_Ukrainian_i => events::VirtualKeyCode::Ukrainian_i, - //ffi::XK_Ukranian_i => events::VirtualKeyCode::Ukranian_i, - //ffi::XK_Ukrainian_yi => events::VirtualKeyCode::Ukrainian_yi, - //ffi::XK_Ukranian_yi => events::VirtualKeyCode::Ukranian_yi, - //ffi::XK_Cyrillic_je => events::VirtualKeyCode::Cyrillic_je, - //ffi::XK_Serbian_je => events::VirtualKeyCode::Serbian_je, - //ffi::XK_Cyrillic_lje => events::VirtualKeyCode::Cyrillic_lje, - //ffi::XK_Serbian_lje => events::VirtualKeyCode::Serbian_lje, - //ffi::XK_Cyrillic_nje => events::VirtualKeyCode::Cyrillic_nje, - //ffi::XK_Serbian_nje => events::VirtualKeyCode::Serbian_nje, - //ffi::XK_Serbian_tshe => events::VirtualKeyCode::Serbian_tshe, - //ffi::XK_Macedonia_kje => events::VirtualKeyCode::Macedonia_kje, - //ffi::XK_Byelorussian_shortu => events::VirtualKeyCode::Byelorussian_shortu, - //ffi::XK_Cyrillic_dzhe => events::VirtualKeyCode::Cyrillic_dzhe, - //ffi::XK_Serbian_dze => events::VirtualKeyCode::Serbian_dze, - //ffi::XK_numerosign => events::VirtualKeyCode::Numerosign, - //ffi::XK_Serbian_DJE => events::VirtualKeyCode::Serbian_dje, - //ffi::XK_Macedonia_GJE => events::VirtualKeyCode::Macedonia_gje, - //ffi::XK_Cyrillic_IO => events::VirtualKeyCode::Cyrillic_io, - //ffi::XK_Ukrainian_IE => events::VirtualKeyCode::Ukrainian_ie, - //ffi::XK_Ukranian_JE => events::VirtualKeyCode::Ukranian_je, - //ffi::XK_Macedonia_DSE => events::VirtualKeyCode::Macedonia_dse, - //ffi::XK_Ukrainian_I => events::VirtualKeyCode::Ukrainian_i, - //ffi::XK_Ukranian_I => events::VirtualKeyCode::Ukranian_i, - //ffi::XK_Ukrainian_YI => events::VirtualKeyCode::Ukrainian_yi, - //ffi::XK_Ukranian_YI => events::VirtualKeyCode::Ukranian_yi, - //ffi::XK_Cyrillic_JE => events::VirtualKeyCode::Cyrillic_je, - //ffi::XK_Serbian_JE => events::VirtualKeyCode::Serbian_je, - //ffi::XK_Cyrillic_LJE => events::VirtualKeyCode::Cyrillic_lje, - //ffi::XK_Serbian_LJE => events::VirtualKeyCode::Serbian_lje, - //ffi::XK_Cyrillic_NJE => events::VirtualKeyCode::Cyrillic_nje, - //ffi::XK_Serbian_NJE => events::VirtualKeyCode::Serbian_nje, - //ffi::XK_Serbian_TSHE => events::VirtualKeyCode::Serbian_tshe, - //ffi::XK_Macedonia_KJE => events::VirtualKeyCode::Macedonia_kje, - //ffi::XK_Byelorussian_SHORTU => events::VirtualKeyCode::Byelorussian_shortu, - //ffi::XK_Cyrillic_DZHE => events::VirtualKeyCode::Cyrillic_dzhe, - //ffi::XK_Serbian_DZE => events::VirtualKeyCode::Serbian_dze, - //ffi::XK_Cyrillic_yu => events::VirtualKeyCode::Cyrillic_yu, - //ffi::XK_Cyrillic_a => events::VirtualKeyCode::Cyrillic_a, - //ffi::XK_Cyrillic_be => events::VirtualKeyCode::Cyrillic_be, - //ffi::XK_Cyrillic_tse => events::VirtualKeyCode::Cyrillic_tse, - //ffi::XK_Cyrillic_de => events::VirtualKeyCode::Cyrillic_de, - //ffi::XK_Cyrillic_ie => events::VirtualKeyCode::Cyrillic_ie, - //ffi::XK_Cyrillic_ef => events::VirtualKeyCode::Cyrillic_ef, - //ffi::XK_Cyrillic_ghe => events::VirtualKeyCode::Cyrillic_ghe, - //ffi::XK_Cyrillic_ha => events::VirtualKeyCode::Cyrillic_ha, - //ffi::XK_Cyrillic_i => events::VirtualKeyCode::Cyrillic_i, - //ffi::XK_Cyrillic_shorti => events::VirtualKeyCode::Cyrillic_shorti, - //ffi::XK_Cyrillic_ka => events::VirtualKeyCode::Cyrillic_ka, - //ffi::XK_Cyrillic_el => events::VirtualKeyCode::Cyrillic_el, - //ffi::XK_Cyrillic_em => events::VirtualKeyCode::Cyrillic_em, - //ffi::XK_Cyrillic_en => events::VirtualKeyCode::Cyrillic_en, - //ffi::XK_Cyrillic_o => events::VirtualKeyCode::Cyrillic_o, - //ffi::XK_Cyrillic_pe => events::VirtualKeyCode::Cyrillic_pe, - //ffi::XK_Cyrillic_ya => events::VirtualKeyCode::Cyrillic_ya, - //ffi::XK_Cyrillic_er => events::VirtualKeyCode::Cyrillic_er, - //ffi::XK_Cyrillic_es => events::VirtualKeyCode::Cyrillic_es, - //ffi::XK_Cyrillic_te => events::VirtualKeyCode::Cyrillic_te, - //ffi::XK_Cyrillic_u => events::VirtualKeyCode::Cyrillic_u, - //ffi::XK_Cyrillic_zhe => events::VirtualKeyCode::Cyrillic_zhe, - //ffi::XK_Cyrillic_ve => events::VirtualKeyCode::Cyrillic_ve, - //ffi::XK_Cyrillic_softsign => events::VirtualKeyCode::Cyrillic_softsign, - //ffi::XK_Cyrillic_yeru => events::VirtualKeyCode::Cyrillic_yeru, - //ffi::XK_Cyrillic_ze => events::VirtualKeyCode::Cyrillic_ze, - //ffi::XK_Cyrillic_sha => events::VirtualKeyCode::Cyrillic_sha, - //ffi::XK_Cyrillic_e => events::VirtualKeyCode::Cyrillic_e, - //ffi::XK_Cyrillic_shcha => events::VirtualKeyCode::Cyrillic_shcha, - //ffi::XK_Cyrillic_che => events::VirtualKeyCode::Cyrillic_che, - //ffi::XK_Cyrillic_hardsign => events::VirtualKeyCode::Cyrillic_hardsign, - //ffi::XK_Cyrillic_YU => events::VirtualKeyCode::Cyrillic_yu, - //ffi::XK_Cyrillic_A => events::VirtualKeyCode::Cyrillic_a, - //ffi::XK_Cyrillic_BE => events::VirtualKeyCode::Cyrillic_be, - //ffi::XK_Cyrillic_TSE => events::VirtualKeyCode::Cyrillic_tse, - //ffi::XK_Cyrillic_DE => events::VirtualKeyCode::Cyrillic_de, - //ffi::XK_Cyrillic_IE => events::VirtualKeyCode::Cyrillic_ie, - //ffi::XK_Cyrillic_EF => events::VirtualKeyCode::Cyrillic_ef, - //ffi::XK_Cyrillic_GHE => events::VirtualKeyCode::Cyrillic_ghe, - //ffi::XK_Cyrillic_HA => events::VirtualKeyCode::Cyrillic_ha, - //ffi::XK_Cyrillic_I => events::VirtualKeyCode::Cyrillic_i, - //ffi::XK_Cyrillic_SHORTI => events::VirtualKeyCode::Cyrillic_shorti, - //ffi::XK_Cyrillic_KA => events::VirtualKeyCode::Cyrillic_ka, - //ffi::XK_Cyrillic_EL => events::VirtualKeyCode::Cyrillic_el, - //ffi::XK_Cyrillic_EM => events::VirtualKeyCode::Cyrillic_em, - //ffi::XK_Cyrillic_EN => events::VirtualKeyCode::Cyrillic_en, - //ffi::XK_Cyrillic_O => events::VirtualKeyCode::Cyrillic_o, - //ffi::XK_Cyrillic_PE => events::VirtualKeyCode::Cyrillic_pe, - //ffi::XK_Cyrillic_YA => events::VirtualKeyCode::Cyrillic_ya, - //ffi::XK_Cyrillic_ER => events::VirtualKeyCode::Cyrillic_er, - //ffi::XK_Cyrillic_ES => events::VirtualKeyCode::Cyrillic_es, - //ffi::XK_Cyrillic_TE => events::VirtualKeyCode::Cyrillic_te, - //ffi::XK_Cyrillic_U => events::VirtualKeyCode::Cyrillic_u, - //ffi::XK_Cyrillic_ZHE => events::VirtualKeyCode::Cyrillic_zhe, - //ffi::XK_Cyrillic_VE => events::VirtualKeyCode::Cyrillic_ve, - //ffi::XK_Cyrillic_SOFTSIGN => events::VirtualKeyCode::Cyrillic_softsign, - //ffi::XK_Cyrillic_YERU => events::VirtualKeyCode::Cyrillic_yeru, - //ffi::XK_Cyrillic_ZE => events::VirtualKeyCode::Cyrillic_ze, - //ffi::XK_Cyrillic_SHA => events::VirtualKeyCode::Cyrillic_sha, - //ffi::XK_Cyrillic_E => events::VirtualKeyCode::Cyrillic_e, - //ffi::XK_Cyrillic_SHCHA => events::VirtualKeyCode::Cyrillic_shcha, - //ffi::XK_Cyrillic_CHE => events::VirtualKeyCode::Cyrillic_che, - //ffi::XK_Cyrillic_HARDSIGN => events::VirtualKeyCode::Cyrillic_hardsign, - //ffi::XK_Greek_ALPHAaccent => events::VirtualKeyCode::Greek_alphaaccent, - //ffi::XK_Greek_EPSILONaccent => events::VirtualKeyCode::Greek_epsilonaccent, - //ffi::XK_Greek_ETAaccent => events::VirtualKeyCode::Greek_etaaccent, - //ffi::XK_Greek_IOTAaccent => events::VirtualKeyCode::Greek_iotaaccent, - //ffi::XK_Greek_IOTAdiaeresis => events::VirtualKeyCode::Greek_iotadiaeresis, - //ffi::XK_Greek_OMICRONaccent => events::VirtualKeyCode::Greek_omicronaccent, - //ffi::XK_Greek_UPSILONaccent => events::VirtualKeyCode::Greek_upsilonaccent, - //ffi::XK_Greek_UPSILONdieresis => events::VirtualKeyCode::Greek_upsilondieresis, - //ffi::XK_Greek_OMEGAaccent => events::VirtualKeyCode::Greek_omegaaccent, - //ffi::XK_Greek_accentdieresis => events::VirtualKeyCode::Greek_accentdieresis, - //ffi::XK_Greek_horizbar => events::VirtualKeyCode::Greek_horizbar, - //ffi::XK_Greek_alphaaccent => events::VirtualKeyCode::Greek_alphaaccent, - //ffi::XK_Greek_epsilonaccent => events::VirtualKeyCode::Greek_epsilonaccent, - //ffi::XK_Greek_etaaccent => events::VirtualKeyCode::Greek_etaaccent, - //ffi::XK_Greek_iotaaccent => events::VirtualKeyCode::Greek_iotaaccent, - //ffi::XK_Greek_iotadieresis => events::VirtualKeyCode::Greek_iotadieresis, - //ffi::XK_Greek_iotaaccentdieresis => events::VirtualKeyCode::Greek_iotaaccentdieresis, - //ffi::XK_Greek_omicronaccent => events::VirtualKeyCode::Greek_omicronaccent, - //ffi::XK_Greek_upsilonaccent => events::VirtualKeyCode::Greek_upsilonaccent, - //ffi::XK_Greek_upsilondieresis => events::VirtualKeyCode::Greek_upsilondieresis, - //ffi::XK_Greek_upsilonaccentdieresis => events::VirtualKeyCode::Greek_upsilonaccentdieresis, - //ffi::XK_Greek_omegaaccent => events::VirtualKeyCode::Greek_omegaaccent, - //ffi::XK_Greek_ALPHA => events::VirtualKeyCode::Greek_alpha, - //ffi::XK_Greek_BETA => events::VirtualKeyCode::Greek_beta, - //ffi::XK_Greek_GAMMA => events::VirtualKeyCode::Greek_gamma, - //ffi::XK_Greek_DELTA => events::VirtualKeyCode::Greek_delta, - //ffi::XK_Greek_EPSILON => events::VirtualKeyCode::Greek_epsilon, - //ffi::XK_Greek_ZETA => events::VirtualKeyCode::Greek_zeta, - //ffi::XK_Greek_ETA => events::VirtualKeyCode::Greek_eta, - //ffi::XK_Greek_THETA => events::VirtualKeyCode::Greek_theta, - //ffi::XK_Greek_IOTA => events::VirtualKeyCode::Greek_iota, - //ffi::XK_Greek_KAPPA => events::VirtualKeyCode::Greek_kappa, - //ffi::XK_Greek_LAMDA => events::VirtualKeyCode::Greek_lamda, - //ffi::XK_Greek_LAMBDA => events::VirtualKeyCode::Greek_lambda, - //ffi::XK_Greek_MU => events::VirtualKeyCode::Greek_mu, - //ffi::XK_Greek_NU => events::VirtualKeyCode::Greek_nu, - //ffi::XK_Greek_XI => events::VirtualKeyCode::Greek_xi, - //ffi::XK_Greek_OMICRON => events::VirtualKeyCode::Greek_omicron, - //ffi::XK_Greek_PI => events::VirtualKeyCode::Greek_pi, - //ffi::XK_Greek_RHO => events::VirtualKeyCode::Greek_rho, - //ffi::XK_Greek_SIGMA => events::VirtualKeyCode::Greek_sigma, - //ffi::XK_Greek_TAU => events::VirtualKeyCode::Greek_tau, - //ffi::XK_Greek_UPSILON => events::VirtualKeyCode::Greek_upsilon, - //ffi::XK_Greek_PHI => events::VirtualKeyCode::Greek_phi, - //ffi::XK_Greek_CHI => events::VirtualKeyCode::Greek_chi, - //ffi::XK_Greek_PSI => events::VirtualKeyCode::Greek_psi, - //ffi::XK_Greek_OMEGA => events::VirtualKeyCode::Greek_omega, - //ffi::XK_Greek_alpha => events::VirtualKeyCode::Greek_alpha, - //ffi::XK_Greek_beta => events::VirtualKeyCode::Greek_beta, - //ffi::XK_Greek_gamma => events::VirtualKeyCode::Greek_gamma, - //ffi::XK_Greek_delta => events::VirtualKeyCode::Greek_delta, - //ffi::XK_Greek_epsilon => events::VirtualKeyCode::Greek_epsilon, - //ffi::XK_Greek_zeta => events::VirtualKeyCode::Greek_zeta, - //ffi::XK_Greek_eta => events::VirtualKeyCode::Greek_eta, - //ffi::XK_Greek_theta => events::VirtualKeyCode::Greek_theta, - //ffi::XK_Greek_iota => events::VirtualKeyCode::Greek_iota, - //ffi::XK_Greek_kappa => events::VirtualKeyCode::Greek_kappa, - //ffi::XK_Greek_lamda => events::VirtualKeyCode::Greek_lamda, - //ffi::XK_Greek_lambda => events::VirtualKeyCode::Greek_lambda, - //ffi::XK_Greek_mu => events::VirtualKeyCode::Greek_mu, - //ffi::XK_Greek_nu => events::VirtualKeyCode::Greek_nu, - //ffi::XK_Greek_xi => events::VirtualKeyCode::Greek_xi, - //ffi::XK_Greek_omicron => events::VirtualKeyCode::Greek_omicron, - //ffi::XK_Greek_pi => events::VirtualKeyCode::Greek_pi, - //ffi::XK_Greek_rho => events::VirtualKeyCode::Greek_rho, - //ffi::XK_Greek_sigma => events::VirtualKeyCode::Greek_sigma, - //ffi::XK_Greek_finalsmallsigma => events::VirtualKeyCode::Greek_finalsmallsigma, - //ffi::XK_Greek_tau => events::VirtualKeyCode::Greek_tau, - //ffi::XK_Greek_upsilon => events::VirtualKeyCode::Greek_upsilon, - //ffi::XK_Greek_phi => events::VirtualKeyCode::Greek_phi, - //ffi::XK_Greek_chi => events::VirtualKeyCode::Greek_chi, - //ffi::XK_Greek_psi => events::VirtualKeyCode::Greek_psi, - //ffi::XK_Greek_omega => events::VirtualKeyCode::Greek_omega, - //ffi::XK_Greek_switch => events::VirtualKeyCode::Greek_switch, - //ffi::XK_leftradical => events::VirtualKeyCode::Leftradical, - //ffi::XK_topleftradical => events::VirtualKeyCode::Topleftradical, - //ffi::XK_horizconnector => events::VirtualKeyCode::Horizconnector, - //ffi::XK_topintegral => events::VirtualKeyCode::Topintegral, - //ffi::XK_botintegral => events::VirtualKeyCode::Botintegral, - //ffi::XK_vertconnector => events::VirtualKeyCode::Vertconnector, - //ffi::XK_topleftsqbracket => events::VirtualKeyCode::Topleftsqbracket, - //ffi::XK_botleftsqbracket => events::VirtualKeyCode::Botleftsqbracket, - //ffi::XK_toprightsqbracket => events::VirtualKeyCode::Toprightsqbracket, - //ffi::XK_botrightsqbracket => events::VirtualKeyCode::Botrightsqbracket, - //ffi::XK_topleftparens => events::VirtualKeyCode::Topleftparens, - //ffi::XK_botleftparens => events::VirtualKeyCode::Botleftparens, - //ffi::XK_toprightparens => events::VirtualKeyCode::Toprightparens, - //ffi::XK_botrightparens => events::VirtualKeyCode::Botrightparens, - //ffi::XK_leftmiddlecurlybrace => events::VirtualKeyCode::Leftmiddlecurlybrace, - //ffi::XK_rightmiddlecurlybrace => events::VirtualKeyCode::Rightmiddlecurlybrace, - //ffi::XK_topleftsummation => events::VirtualKeyCode::Topleftsummation, - //ffi::XK_botleftsummation => events::VirtualKeyCode::Botleftsummation, - //ffi::XK_topvertsummationconnector => events::VirtualKeyCode::Topvertsummationconnector, - //ffi::XK_botvertsummationconnector => events::VirtualKeyCode::Botvertsummationconnector, - //ffi::XK_toprightsummation => events::VirtualKeyCode::Toprightsummation, - //ffi::XK_botrightsummation => events::VirtualKeyCode::Botrightsummation, - //ffi::XK_rightmiddlesummation => events::VirtualKeyCode::Rightmiddlesummation, - //ffi::XK_lessthanequal => events::VirtualKeyCode::Lessthanequal, - //ffi::XK_notequal => events::VirtualKeyCode::Notequal, - //ffi::XK_greaterthanequal => events::VirtualKeyCode::Greaterthanequal, - //ffi::XK_integral => events::VirtualKeyCode::Integral, - //ffi::XK_therefore => events::VirtualKeyCode::Therefore, - //ffi::XK_variation => events::VirtualKeyCode::Variation, - //ffi::XK_infinity => events::VirtualKeyCode::Infinity, - //ffi::XK_nabla => events::VirtualKeyCode::Nabla, - //ffi::XK_approximate => events::VirtualKeyCode::Approximate, - //ffi::XK_similarequal => events::VirtualKeyCode::Similarequal, - //ffi::XK_ifonlyif => events::VirtualKeyCode::Ifonlyif, - //ffi::XK_implies => events::VirtualKeyCode::Implies, - //ffi::XK_identical => events::VirtualKeyCode::Identical, - //ffi::XK_radical => events::VirtualKeyCode::Radical, - //ffi::XK_includedin => events::VirtualKeyCode::Includedin, - //ffi::XK_includes => events::VirtualKeyCode::Includes, - //ffi::XK_intersection => events::VirtualKeyCode::Intersection, - //ffi::XK_union => events::VirtualKeyCode::Union, - //ffi::XK_logicaland => events::VirtualKeyCode::Logicaland, - //ffi::XK_logicalor => events::VirtualKeyCode::Logicalor, - //ffi::XK_partialderivative => events::VirtualKeyCode::Partialderivative, - //ffi::XK_function => events::VirtualKeyCode::Function, - //ffi::XK_leftarrow => events::VirtualKeyCode::Leftarrow, - //ffi::XK_uparrow => events::VirtualKeyCode::Uparrow, - //ffi::XK_rightarrow => events::VirtualKeyCode::Rightarrow, - //ffi::XK_downarrow => events::VirtualKeyCode::Downarrow, - //ffi::XK_blank => events::VirtualKeyCode::Blank, - //ffi::XK_soliddiamond => events::VirtualKeyCode::Soliddiamond, - //ffi::XK_checkerboard => events::VirtualKeyCode::Checkerboard, - //ffi::XK_ht => events::VirtualKeyCode::Ht, - //ffi::XK_ff => events::VirtualKeyCode::Ff, - //ffi::XK_cr => events::VirtualKeyCode::Cr, - //ffi::XK_lf => events::VirtualKeyCode::Lf, - //ffi::XK_nl => events::VirtualKeyCode::Nl, - //ffi::XK_vt => events::VirtualKeyCode::Vt, - //ffi::XK_lowrightcorner => events::VirtualKeyCode::Lowrightcorner, - //ffi::XK_uprightcorner => events::VirtualKeyCode::Uprightcorner, - //ffi::XK_upleftcorner => events::VirtualKeyCode::Upleftcorner, - //ffi::XK_lowleftcorner => events::VirtualKeyCode::Lowleftcorner, - //ffi::XK_crossinglines => events::VirtualKeyCode::Crossinglines, - //ffi::XK_horizlinescan1 => events::VirtualKeyCode::Horizlinescan1, - //ffi::XK_horizlinescan3 => events::VirtualKeyCode::Horizlinescan3, - //ffi::XK_horizlinescan5 => events::VirtualKeyCode::Horizlinescan5, - //ffi::XK_horizlinescan7 => events::VirtualKeyCode::Horizlinescan7, - //ffi::XK_horizlinescan9 => events::VirtualKeyCode::Horizlinescan9, - //ffi::XK_leftt => events::VirtualKeyCode::Leftt, - //ffi::XK_rightt => events::VirtualKeyCode::Rightt, - //ffi::XK_bott => events::VirtualKeyCode::Bott, - //ffi::XK_topt => events::VirtualKeyCode::Topt, - //ffi::XK_vertbar => events::VirtualKeyCode::Vertbar, - //ffi::XK_emspace => events::VirtualKeyCode::Emspace, - //ffi::XK_enspace => events::VirtualKeyCode::Enspace, - //ffi::XK_em3space => events::VirtualKeyCode::Em3space, - //ffi::XK_em4space => events::VirtualKeyCode::Em4space, - //ffi::XK_digitspace => events::VirtualKeyCode::Digitspace, - //ffi::XK_punctspace => events::VirtualKeyCode::Punctspace, - //ffi::XK_thinspace => events::VirtualKeyCode::Thinspace, - //ffi::XK_hairspace => events::VirtualKeyCode::Hairspace, - //ffi::XK_emdash => events::VirtualKeyCode::Emdash, - //ffi::XK_endash => events::VirtualKeyCode::Endash, - //ffi::XK_signifblank => events::VirtualKeyCode::Signifblank, - //ffi::XK_ellipsis => events::VirtualKeyCode::Ellipsis, - //ffi::XK_doubbaselinedot => events::VirtualKeyCode::Doubbaselinedot, - //ffi::XK_onethird => events::VirtualKeyCode::Onethird, - //ffi::XK_twothirds => events::VirtualKeyCode::Twothirds, - //ffi::XK_onefifth => events::VirtualKeyCode::Onefifth, - //ffi::XK_twofifths => events::VirtualKeyCode::Twofifths, - //ffi::XK_threefifths => events::VirtualKeyCode::Threefifths, - //ffi::XK_fourfifths => events::VirtualKeyCode::Fourfifths, - //ffi::XK_onesixth => events::VirtualKeyCode::Onesixth, - //ffi::XK_fivesixths => events::VirtualKeyCode::Fivesixths, - //ffi::XK_careof => events::VirtualKeyCode::Careof, - //ffi::XK_figdash => events::VirtualKeyCode::Figdash, - //ffi::XK_leftanglebracket => events::VirtualKeyCode::Leftanglebracket, - //ffi::XK_decimalpoint => events::VirtualKeyCode::Decimalpoint, - //ffi::XK_rightanglebracket => events::VirtualKeyCode::Rightanglebracket, - //ffi::XK_marker => events::VirtualKeyCode::Marker, - //ffi::XK_oneeighth => events::VirtualKeyCode::Oneeighth, - //ffi::XK_threeeighths => events::VirtualKeyCode::Threeeighths, - //ffi::XK_fiveeighths => events::VirtualKeyCode::Fiveeighths, - //ffi::XK_seveneighths => events::VirtualKeyCode::Seveneighths, - //ffi::XK_trademark => events::VirtualKeyCode::Trademark, - //ffi::XK_signaturemark => events::VirtualKeyCode::Signaturemark, - //ffi::XK_trademarkincircle => events::VirtualKeyCode::Trademarkincircle, - //ffi::XK_leftopentriangle => events::VirtualKeyCode::Leftopentriangle, - //ffi::XK_rightopentriangle => events::VirtualKeyCode::Rightopentriangle, - //ffi::XK_emopencircle => events::VirtualKeyCode::Emopencircle, - //ffi::XK_emopenrectangle => events::VirtualKeyCode::Emopenrectangle, - //ffi::XK_leftsinglequotemark => events::VirtualKeyCode::Leftsinglequotemark, - //ffi::XK_rightsinglequotemark => events::VirtualKeyCode::Rightsinglequotemark, - //ffi::XK_leftdoublequotemark => events::VirtualKeyCode::Leftdoublequotemark, - //ffi::XK_rightdoublequotemark => events::VirtualKeyCode::Rightdoublequotemark, - //ffi::XK_prescription => events::VirtualKeyCode::Prescription, - //ffi::XK_minutes => events::VirtualKeyCode::Minutes, - //ffi::XK_seconds => events::VirtualKeyCode::Seconds, - //ffi::XK_latincross => events::VirtualKeyCode::Latincross, - //ffi::XK_hexagram => events::VirtualKeyCode::Hexagram, - //ffi::XK_filledrectbullet => events::VirtualKeyCode::Filledrectbullet, - //ffi::XK_filledlefttribullet => events::VirtualKeyCode::Filledlefttribullet, - //ffi::XK_filledrighttribullet => events::VirtualKeyCode::Filledrighttribullet, - //ffi::XK_emfilledcircle => events::VirtualKeyCode::Emfilledcircle, - //ffi::XK_emfilledrect => events::VirtualKeyCode::Emfilledrect, - //ffi::XK_enopencircbullet => events::VirtualKeyCode::Enopencircbullet, - //ffi::XK_enopensquarebullet => events::VirtualKeyCode::Enopensquarebullet, - //ffi::XK_openrectbullet => events::VirtualKeyCode::Openrectbullet, - //ffi::XK_opentribulletup => events::VirtualKeyCode::Opentribulletup, - //ffi::XK_opentribulletdown => events::VirtualKeyCode::Opentribulletdown, - //ffi::XK_openstar => events::VirtualKeyCode::Openstar, - //ffi::XK_enfilledcircbullet => events::VirtualKeyCode::Enfilledcircbullet, - //ffi::XK_enfilledsqbullet => events::VirtualKeyCode::Enfilledsqbullet, - //ffi::XK_filledtribulletup => events::VirtualKeyCode::Filledtribulletup, - //ffi::XK_filledtribulletdown => events::VirtualKeyCode::Filledtribulletdown, - //ffi::XK_leftpointer => events::VirtualKeyCode::Leftpointer, - //ffi::XK_rightpointer => events::VirtualKeyCode::Rightpointer, - //ffi::XK_club => events::VirtualKeyCode::Club, - //ffi::XK_diamond => events::VirtualKeyCode::Diamond, - //ffi::XK_heart => events::VirtualKeyCode::Heart, - //ffi::XK_maltesecross => events::VirtualKeyCode::Maltesecross, - //ffi::XK_dagger => events::VirtualKeyCode::Dagger, - //ffi::XK_doubledagger => events::VirtualKeyCode::Doubledagger, - //ffi::XK_checkmark => events::VirtualKeyCode::Checkmark, - //ffi::XK_ballotcross => events::VirtualKeyCode::Ballotcross, - //ffi::XK_musicalsharp => events::VirtualKeyCode::Musicalsharp, - //ffi::XK_musicalflat => events::VirtualKeyCode::Musicalflat, - //ffi::XK_malesymbol => events::VirtualKeyCode::Malesymbol, - //ffi::XK_femalesymbol => events::VirtualKeyCode::Femalesymbol, - //ffi::XK_telephone => events::VirtualKeyCode::Telephone, - //ffi::XK_telephonerecorder => events::VirtualKeyCode::Telephonerecorder, - //ffi::XK_phonographcopyright => events::VirtualKeyCode::Phonographcopyright, - //ffi::XK_caret => events::VirtualKeyCode::Caret, - //ffi::XK_singlelowquotemark => events::VirtualKeyCode::Singlelowquotemark, - //ffi::XK_doublelowquotemark => events::VirtualKeyCode::Doublelowquotemark, - //ffi::XK_cursor => events::VirtualKeyCode::Cursor, - //ffi::XK_leftcaret => events::VirtualKeyCode::Leftcaret, - //ffi::XK_rightcaret => events::VirtualKeyCode::Rightcaret, - //ffi::XK_downcaret => events::VirtualKeyCode::Downcaret, - //ffi::XK_upcaret => events::VirtualKeyCode::Upcaret, - //ffi::XK_overbar => events::VirtualKeyCode::Overbar, - //ffi::XK_downtack => events::VirtualKeyCode::Downtack, - //ffi::XK_upshoe => events::VirtualKeyCode::Upshoe, - //ffi::XK_downstile => events::VirtualKeyCode::Downstile, - //ffi::XK_underbar => events::VirtualKeyCode::Underbar, - //ffi::XK_jot => events::VirtualKeyCode::Jot, - //ffi::XK_quad => events::VirtualKeyCode::Quad, - //ffi::XK_uptack => events::VirtualKeyCode::Uptack, - //ffi::XK_circle => events::VirtualKeyCode::Circle, - //ffi::XK_upstile => events::VirtualKeyCode::Upstile, - //ffi::XK_downshoe => events::VirtualKeyCode::Downshoe, - //ffi::XK_rightshoe => events::VirtualKeyCode::Rightshoe, - //ffi::XK_leftshoe => events::VirtualKeyCode::Leftshoe, - //ffi::XK_lefttack => events::VirtualKeyCode::Lefttack, - //ffi::XK_righttack => events::VirtualKeyCode::Righttack, - //ffi::XK_hebrew_doublelowline => events::VirtualKeyCode::Hebrew_doublelowline, - //ffi::XK_hebrew_aleph => events::VirtualKeyCode::Hebrew_aleph, - //ffi::XK_hebrew_bet => events::VirtualKeyCode::Hebrew_bet, - //ffi::XK_hebrew_beth => events::VirtualKeyCode::Hebrew_beth, - //ffi::XK_hebrew_gimel => events::VirtualKeyCode::Hebrew_gimel, - //ffi::XK_hebrew_gimmel => events::VirtualKeyCode::Hebrew_gimmel, - //ffi::XK_hebrew_dalet => events::VirtualKeyCode::Hebrew_dalet, - //ffi::XK_hebrew_daleth => events::VirtualKeyCode::Hebrew_daleth, - //ffi::XK_hebrew_he => events::VirtualKeyCode::Hebrew_he, - //ffi::XK_hebrew_waw => events::VirtualKeyCode::Hebrew_waw, - //ffi::XK_hebrew_zain => events::VirtualKeyCode::Hebrew_zain, - //ffi::XK_hebrew_zayin => events::VirtualKeyCode::Hebrew_zayin, - //ffi::XK_hebrew_chet => events::VirtualKeyCode::Hebrew_chet, - //ffi::XK_hebrew_het => events::VirtualKeyCode::Hebrew_het, - //ffi::XK_hebrew_tet => events::VirtualKeyCode::Hebrew_tet, - //ffi::XK_hebrew_teth => events::VirtualKeyCode::Hebrew_teth, - //ffi::XK_hebrew_yod => events::VirtualKeyCode::Hebrew_yod, - //ffi::XK_hebrew_finalkaph => events::VirtualKeyCode::Hebrew_finalkaph, - //ffi::XK_hebrew_kaph => events::VirtualKeyCode::Hebrew_kaph, - //ffi::XK_hebrew_lamed => events::VirtualKeyCode::Hebrew_lamed, - //ffi::XK_hebrew_finalmem => events::VirtualKeyCode::Hebrew_finalmem, - //ffi::XK_hebrew_mem => events::VirtualKeyCode::Hebrew_mem, - //ffi::XK_hebrew_finalnun => events::VirtualKeyCode::Hebrew_finalnun, - //ffi::XK_hebrew_nun => events::VirtualKeyCode::Hebrew_nun, - //ffi::XK_hebrew_samech => events::VirtualKeyCode::Hebrew_samech, - //ffi::XK_hebrew_samekh => events::VirtualKeyCode::Hebrew_samekh, - //ffi::XK_hebrew_ayin => events::VirtualKeyCode::Hebrew_ayin, - //ffi::XK_hebrew_finalpe => events::VirtualKeyCode::Hebrew_finalpe, - //ffi::XK_hebrew_pe => events::VirtualKeyCode::Hebrew_pe, - //ffi::XK_hebrew_finalzade => events::VirtualKeyCode::Hebrew_finalzade, - //ffi::XK_hebrew_finalzadi => events::VirtualKeyCode::Hebrew_finalzadi, - //ffi::XK_hebrew_zade => events::VirtualKeyCode::Hebrew_zade, - //ffi::XK_hebrew_zadi => events::VirtualKeyCode::Hebrew_zadi, - //ffi::XK_hebrew_qoph => events::VirtualKeyCode::Hebrew_qoph, - //ffi::XK_hebrew_kuf => events::VirtualKeyCode::Hebrew_kuf, - //ffi::XK_hebrew_resh => events::VirtualKeyCode::Hebrew_resh, - //ffi::XK_hebrew_shin => events::VirtualKeyCode::Hebrew_shin, - //ffi::XK_hebrew_taw => events::VirtualKeyCode::Hebrew_taw, - //ffi::XK_hebrew_taf => events::VirtualKeyCode::Hebrew_taf, - //ffi::XK_Hebrew_switch => events::VirtualKeyCode::Hebrew_switch, + ffi::XK_BackSpace => VirtualKeyCode::Back, + ffi::XK_Tab => VirtualKeyCode::Tab, + //ffi::XK_Linefeed => VirtualKeyCode::Linefeed, + //ffi::XK_Clear => VirtualKeyCode::Clear, + ffi::XK_Return => VirtualKeyCode::Return, + //ffi::XK_Pause => VirtualKeyCode::Pause, + //ffi::XK_Scroll_Lock => VirtualKeyCode::Scroll_lock, + //ffi::XK_Sys_Req => VirtualKeyCode::Sys_req, + ffi::XK_Escape => VirtualKeyCode::Escape, + ffi::XK_Delete => VirtualKeyCode::Delete, + ffi::XK_Multi_key => VirtualKeyCode::Compose, + //ffi::XK_Kanji => VirtualKeyCode::Kanji, + //ffi::XK_Muhenkan => VirtualKeyCode::Muhenkan, + //ffi::XK_Henkan_Mode => VirtualKeyCode::Henkan_mode, + //ffi::XK_Henkan => VirtualKeyCode::Henkan, + //ffi::XK_Romaji => VirtualKeyCode::Romaji, + //ffi::XK_Hiragana => VirtualKeyCode::Hiragana, + //ffi::XK_Katakana => VirtualKeyCode::Katakana, + //ffi::XK_Hiragana_Katakana => VirtualKeyCode::Hiragana_katakana, + //ffi::XK_Zenkaku => VirtualKeyCode::Zenkaku, + //ffi::XK_Hankaku => VirtualKeyCode::Hankaku, + //ffi::XK_Zenkaku_Hankaku => VirtualKeyCode::Zenkaku_hankaku, + //ffi::XK_Touroku => VirtualKeyCode::Touroku, + //ffi::XK_Massyo => VirtualKeyCode::Massyo, + //ffi::XK_Kana_Lock => VirtualKeyCode::Kana_lock, + //ffi::XK_Kana_Shift => VirtualKeyCode::Kana_shift, + //ffi::XK_Eisu_Shift => VirtualKeyCode::Eisu_shift, + //ffi::XK_Eisu_toggle => VirtualKeyCode::Eisu_toggle, + ffi::XK_Home => VirtualKeyCode::Home, + ffi::XK_Left => VirtualKeyCode::Left, + ffi::XK_Up => VirtualKeyCode::Up, + ffi::XK_Right => VirtualKeyCode::Right, + ffi::XK_Down => VirtualKeyCode::Down, + //ffi::XK_Prior => VirtualKeyCode::Prior, + ffi::XK_Page_Up => VirtualKeyCode::PageUp, + //ffi::XK_Next => VirtualKeyCode::Next, + ffi::XK_Page_Down => VirtualKeyCode::PageDown, + ffi::XK_End => VirtualKeyCode::End, + //ffi::XK_Begin => VirtualKeyCode::Begin, + //ffi::XK_Win_L => VirtualKeyCode::Win_l, + //ffi::XK_Win_R => VirtualKeyCode::Win_r, + //ffi::XK_App => VirtualKeyCode::App, + //ffi::XK_Select => VirtualKeyCode::Select, + //ffi::XK_Print => VirtualKeyCode::Print, + //ffi::XK_Execute => VirtualKeyCode::Execute, + ffi::XK_Insert => VirtualKeyCode::Insert, + //ffi::XK_Undo => VirtualKeyCode::Undo, + //ffi::XK_Redo => VirtualKeyCode::Redo, + //ffi::XK_Menu => VirtualKeyCode::Menu, + //ffi::XK_Find => VirtualKeyCode::Find, + //ffi::XK_Cancel => VirtualKeyCode::Cancel, + //ffi::XK_Help => VirtualKeyCode::Help, + //ffi::XK_Break => VirtualKeyCode::Break, + //ffi::XK_Mode_switch => VirtualKeyCode::Mode_switch, + //ffi::XK_script_switch => VirtualKeyCode::Script_switch, + //ffi::XK_Num_Lock => VirtualKeyCode::Num_lock, + //ffi::XK_KP_Space => VirtualKeyCode::Kp_space, + //ffi::XK_KP_Tab => VirtualKeyCode::Kp_tab, + //ffi::XK_KP_Enter => VirtualKeyCode::Kp_enter, + //ffi::XK_KP_F1 => VirtualKeyCode::Kp_f1, + //ffi::XK_KP_F2 => VirtualKeyCode::Kp_f2, + //ffi::XK_KP_F3 => VirtualKeyCode::Kp_f3, + //ffi::XK_KP_F4 => VirtualKeyCode::Kp_f4, + ffi::XK_KP_Home => VirtualKeyCode::Home, + ffi::XK_KP_Left => VirtualKeyCode::Left, + ffi::XK_KP_Up => VirtualKeyCode::Up, + ffi::XK_KP_Right => VirtualKeyCode::Right, + ffi::XK_KP_Down => VirtualKeyCode::Down, + //ffi::XK_KP_Prior => VirtualKeyCode::Kp_prior, + ffi::XK_KP_Page_Up => VirtualKeyCode::PageUp, + //ffi::XK_KP_Next => VirtualKeyCode::Kp_next, + ffi::XK_KP_Page_Down => VirtualKeyCode::PageDown, + ffi::XK_KP_End => VirtualKeyCode::End, + //ffi::XK_KP_Begin => VirtualKeyCode::Kp_begin, + ffi::XK_KP_Insert => VirtualKeyCode::Insert, + ffi::XK_KP_Delete => VirtualKeyCode::Delete, + ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals, + //ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply, + ffi::XK_KP_Add => VirtualKeyCode::Add, + //ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator, + ffi::XK_KP_Subtract => VirtualKeyCode::Subtract, + //ffi::XK_KP_Decimal => VirtualKeyCode::Kp_decimal, + ffi::XK_KP_Divide => VirtualKeyCode::Divide, + ffi::XK_KP_0 => VirtualKeyCode::Numpad0, + ffi::XK_KP_1 => VirtualKeyCode::Numpad1, + ffi::XK_KP_2 => VirtualKeyCode::Numpad2, + ffi::XK_KP_3 => VirtualKeyCode::Numpad3, + ffi::XK_KP_4 => VirtualKeyCode::Numpad4, + ffi::XK_KP_5 => VirtualKeyCode::Numpad5, + ffi::XK_KP_6 => VirtualKeyCode::Numpad6, + ffi::XK_KP_7 => VirtualKeyCode::Numpad7, + ffi::XK_KP_8 => VirtualKeyCode::Numpad8, + ffi::XK_KP_9 => VirtualKeyCode::Numpad9, + ffi::XK_F1 => VirtualKeyCode::F1, + ffi::XK_F2 => VirtualKeyCode::F2, + ffi::XK_F3 => VirtualKeyCode::F3, + ffi::XK_F4 => VirtualKeyCode::F4, + ffi::XK_F5 => VirtualKeyCode::F5, + ffi::XK_F6 => VirtualKeyCode::F6, + ffi::XK_F7 => VirtualKeyCode::F7, + ffi::XK_F8 => VirtualKeyCode::F8, + ffi::XK_F9 => VirtualKeyCode::F9, + ffi::XK_F10 => VirtualKeyCode::F10, + ffi::XK_F11 => VirtualKeyCode::F11, + //ffi::XK_L1 => VirtualKeyCode::L1, + ffi::XK_F12 => VirtualKeyCode::F12, + //ffi::XK_L2 => VirtualKeyCode::L2, + ffi::XK_F13 => VirtualKeyCode::F13, + //ffi::XK_L3 => VirtualKeyCode::L3, + ffi::XK_F14 => VirtualKeyCode::F14, + //ffi::XK_L4 => VirtualKeyCode::L4, + ffi::XK_F15 => VirtualKeyCode::F15, + //ffi::XK_L5 => VirtualKeyCode::L5, + ffi::XK_F16 => VirtualKeyCode::F16, + //ffi::XK_L6 => VirtualKeyCode::L6, + ffi::XK_F17 => VirtualKeyCode::F17, + //ffi::XK_L7 => VirtualKeyCode::L7, + ffi::XK_F18 => VirtualKeyCode::F18, + //ffi::XK_L8 => VirtualKeyCode::L8, + ffi::XK_F19 => VirtualKeyCode::F19, + //ffi::XK_L9 => VirtualKeyCode::L9, + ffi::XK_F20 => VirtualKeyCode::F20, + //ffi::XK_L10 => VirtualKeyCode::L10, + ffi::XK_F21 => VirtualKeyCode::F21, + //ffi::XK_R1 => VirtualKeyCode::R1, + ffi::XK_F22 => VirtualKeyCode::F22, + //ffi::XK_R2 => VirtualKeyCode::R2, + ffi::XK_F23 => VirtualKeyCode::F23, + //ffi::XK_R3 => VirtualKeyCode::R3, + ffi::XK_F24 => VirtualKeyCode::F24, + //ffi::XK_R4 => VirtualKeyCode::R4, + //ffi::XK_F25 => VirtualKeyCode::F25, + //ffi::XK_R5 => VirtualKeyCode::R5, + //ffi::XK_F26 => VirtualKeyCode::F26, + //ffi::XK_R6 => VirtualKeyCode::R6, + //ffi::XK_F27 => VirtualKeyCode::F27, + //ffi::XK_R7 => VirtualKeyCode::R7, + //ffi::XK_F28 => VirtualKeyCode::F28, + //ffi::XK_R8 => VirtualKeyCode::R8, + //ffi::XK_F29 => VirtualKeyCode::F29, + //ffi::XK_R9 => VirtualKeyCode::R9, + //ffi::XK_F30 => VirtualKeyCode::F30, + //ffi::XK_R10 => VirtualKeyCode::R10, + //ffi::XK_F31 => VirtualKeyCode::F31, + //ffi::XK_R11 => VirtualKeyCode::R11, + //ffi::XK_F32 => VirtualKeyCode::F32, + //ffi::XK_R12 => VirtualKeyCode::R12, + //ffi::XK_F33 => VirtualKeyCode::F33, + //ffi::XK_R13 => VirtualKeyCode::R13, + //ffi::XK_F34 => VirtualKeyCode::F34, + //ffi::XK_R14 => VirtualKeyCode::R14, + //ffi::XK_F35 => VirtualKeyCode::F35, + //ffi::XK_R15 => VirtualKeyCode::R15, + ffi::XK_Shift_L => VirtualKeyCode::LShift, + ffi::XK_Shift_R => VirtualKeyCode::RShift, + ffi::XK_Control_L => VirtualKeyCode::LControl, + ffi::XK_Control_R => VirtualKeyCode::RControl, + //ffi::XK_Caps_Lock => VirtualKeyCode::Caps_lock, + //ffi::XK_Shift_Lock => VirtualKeyCode::Shift_lock, + //ffi::XK_Meta_L => VirtualKeyCode::Meta_l, + //ffi::XK_Meta_R => VirtualKeyCode::Meta_r, + ffi::XK_Alt_L => VirtualKeyCode::LAlt, + ffi::XK_Alt_R => VirtualKeyCode::RAlt, + //ffi::XK_Super_L => VirtualKeyCode::Super_l, + //ffi::XK_Super_R => VirtualKeyCode::Super_r, + //ffi::XK_Hyper_L => VirtualKeyCode::Hyper_l, + //ffi::XK_Hyper_R => VirtualKeyCode::Hyper_r, + ffi::XK_ISO_Left_Tab => VirtualKeyCode::Tab, + ffi::XK_space => VirtualKeyCode::Space, + //ffi::XK_exclam => VirtualKeyCode::Exclam, + //ffi::XK_quotedbl => VirtualKeyCode::Quotedbl, + //ffi::XK_numbersign => VirtualKeyCode::Numbersign, + //ffi::XK_dollar => VirtualKeyCode::Dollar, + //ffi::XK_percent => VirtualKeyCode::Percent, + //ffi::XK_ampersand => VirtualKeyCode::Ampersand, + ffi::XK_apostrophe => VirtualKeyCode::Apostrophe, + //ffi::XK_quoteright => VirtualKeyCode::Quoteright, + //ffi::XK_parenleft => VirtualKeyCode::Parenleft, + //ffi::XK_parenright => VirtualKeyCode::Parenright, + //ffi::XK_asterisk => VirtualKeyCode::Asterisk, + ffi::XK_plus => VirtualKeyCode::Add, + ffi::XK_comma => VirtualKeyCode::Comma, + ffi::XK_minus => VirtualKeyCode::Subtract, + ffi::XK_period => VirtualKeyCode::Period, + ffi::XK_slash => VirtualKeyCode::Slash, + ffi::XK_0 => VirtualKeyCode::Key0, + ffi::XK_1 => VirtualKeyCode::Key1, + ffi::XK_2 => VirtualKeyCode::Key2, + ffi::XK_3 => VirtualKeyCode::Key3, + ffi::XK_4 => VirtualKeyCode::Key4, + ffi::XK_5 => VirtualKeyCode::Key5, + ffi::XK_6 => VirtualKeyCode::Key6, + ffi::XK_7 => VirtualKeyCode::Key7, + ffi::XK_8 => VirtualKeyCode::Key8, + ffi::XK_9 => VirtualKeyCode::Key9, + ffi::XK_colon => VirtualKeyCode::Colon, + ffi::XK_semicolon => VirtualKeyCode::Semicolon, + //ffi::XK_less => VirtualKeyCode::Less, + ffi::XK_equal => VirtualKeyCode::Equals, + //ffi::XK_greater => VirtualKeyCode::Greater, + //ffi::XK_question => VirtualKeyCode::Question, + ffi::XK_at => VirtualKeyCode::At, + ffi::XK_A => VirtualKeyCode::A, + ffi::XK_B => VirtualKeyCode::B, + ffi::XK_C => VirtualKeyCode::C, + ffi::XK_D => VirtualKeyCode::D, + ffi::XK_E => VirtualKeyCode::E, + ffi::XK_F => VirtualKeyCode::F, + ffi::XK_G => VirtualKeyCode::G, + ffi::XK_H => VirtualKeyCode::H, + ffi::XK_I => VirtualKeyCode::I, + ffi::XK_J => VirtualKeyCode::J, + ffi::XK_K => VirtualKeyCode::K, + ffi::XK_L => VirtualKeyCode::L, + ffi::XK_M => VirtualKeyCode::M, + ffi::XK_N => VirtualKeyCode::N, + ffi::XK_O => VirtualKeyCode::O, + ffi::XK_P => VirtualKeyCode::P, + ffi::XK_Q => VirtualKeyCode::Q, + ffi::XK_R => VirtualKeyCode::R, + ffi::XK_S => VirtualKeyCode::S, + ffi::XK_T => VirtualKeyCode::T, + ffi::XK_U => VirtualKeyCode::U, + ffi::XK_V => VirtualKeyCode::V, + ffi::XK_W => VirtualKeyCode::W, + ffi::XK_X => VirtualKeyCode::X, + ffi::XK_Y => VirtualKeyCode::Y, + ffi::XK_Z => VirtualKeyCode::Z, + ffi::XK_bracketleft => VirtualKeyCode::LBracket, + ffi::XK_backslash => VirtualKeyCode::Backslash, + ffi::XK_bracketright => VirtualKeyCode::RBracket, + //ffi::XK_asciicircum => VirtualKeyCode::Asciicircum, + //ffi::XK_underscore => VirtualKeyCode::Underscore, + ffi::XK_grave => VirtualKeyCode::Grave, + //ffi::XK_quoteleft => VirtualKeyCode::Quoteleft, + ffi::XK_a => VirtualKeyCode::A, + ffi::XK_b => VirtualKeyCode::B, + ffi::XK_c => VirtualKeyCode::C, + ffi::XK_d => VirtualKeyCode::D, + ffi::XK_e => VirtualKeyCode::E, + ffi::XK_f => VirtualKeyCode::F, + ffi::XK_g => VirtualKeyCode::G, + ffi::XK_h => VirtualKeyCode::H, + ffi::XK_i => VirtualKeyCode::I, + ffi::XK_j => VirtualKeyCode::J, + ffi::XK_k => VirtualKeyCode::K, + ffi::XK_l => VirtualKeyCode::L, + ffi::XK_m => VirtualKeyCode::M, + ffi::XK_n => VirtualKeyCode::N, + ffi::XK_o => VirtualKeyCode::O, + ffi::XK_p => VirtualKeyCode::P, + ffi::XK_q => VirtualKeyCode::Q, + ffi::XK_r => VirtualKeyCode::R, + ffi::XK_s => VirtualKeyCode::S, + ffi::XK_t => VirtualKeyCode::T, + ffi::XK_u => VirtualKeyCode::U, + ffi::XK_v => VirtualKeyCode::V, + ffi::XK_w => VirtualKeyCode::W, + ffi::XK_x => VirtualKeyCode::X, + ffi::XK_y => VirtualKeyCode::Y, + ffi::XK_z => VirtualKeyCode::Z, + //ffi::XK_braceleft => VirtualKeyCode::Braceleft, + //ffi::XK_bar => VirtualKeyCode::Bar, + //ffi::XK_braceright => VirtualKeyCode::Braceright, + //ffi::XK_asciitilde => VirtualKeyCode::Asciitilde, + //ffi::XK_nobreakspace => VirtualKeyCode::Nobreakspace, + //ffi::XK_exclamdown => VirtualKeyCode::Exclamdown, + //ffi::XK_cent => VirtualKeyCode::Cent, + //ffi::XK_sterling => VirtualKeyCode::Sterling, + //ffi::XK_currency => VirtualKeyCode::Currency, + //ffi::XK_yen => VirtualKeyCode::Yen, + //ffi::XK_brokenbar => VirtualKeyCode::Brokenbar, + //ffi::XK_section => VirtualKeyCode::Section, + //ffi::XK_diaeresis => VirtualKeyCode::Diaeresis, + //ffi::XK_copyright => VirtualKeyCode::Copyright, + //ffi::XK_ordfeminine => VirtualKeyCode::Ordfeminine, + //ffi::XK_guillemotleft => VirtualKeyCode::Guillemotleft, + //ffi::XK_notsign => VirtualKeyCode::Notsign, + //ffi::XK_hyphen => VirtualKeyCode::Hyphen, + //ffi::XK_registered => VirtualKeyCode::Registered, + //ffi::XK_macron => VirtualKeyCode::Macron, + //ffi::XK_degree => VirtualKeyCode::Degree, + //ffi::XK_plusminus => VirtualKeyCode::Plusminus, + //ffi::XK_twosuperior => VirtualKeyCode::Twosuperior, + //ffi::XK_threesuperior => VirtualKeyCode::Threesuperior, + //ffi::XK_acute => VirtualKeyCode::Acute, + //ffi::XK_mu => VirtualKeyCode::Mu, + //ffi::XK_paragraph => VirtualKeyCode::Paragraph, + //ffi::XK_periodcentered => VirtualKeyCode::Periodcentered, + //ffi::XK_cedilla => VirtualKeyCode::Cedilla, + //ffi::XK_onesuperior => VirtualKeyCode::Onesuperior, + //ffi::XK_masculine => VirtualKeyCode::Masculine, + //ffi::XK_guillemotright => VirtualKeyCode::Guillemotright, + //ffi::XK_onequarter => VirtualKeyCode::Onequarter, + //ffi::XK_onehalf => VirtualKeyCode::Onehalf, + //ffi::XK_threequarters => VirtualKeyCode::Threequarters, + //ffi::XK_questiondown => VirtualKeyCode::Questiondown, + //ffi::XK_Agrave => VirtualKeyCode::Agrave, + //ffi::XK_Aacute => VirtualKeyCode::Aacute, + //ffi::XK_Acircumflex => VirtualKeyCode::Acircumflex, + //ffi::XK_Atilde => VirtualKeyCode::Atilde, + //ffi::XK_Adiaeresis => VirtualKeyCode::Adiaeresis, + //ffi::XK_Aring => VirtualKeyCode::Aring, + //ffi::XK_AE => VirtualKeyCode::Ae, + //ffi::XK_Ccedilla => VirtualKeyCode::Ccedilla, + //ffi::XK_Egrave => VirtualKeyCode::Egrave, + //ffi::XK_Eacute => VirtualKeyCode::Eacute, + //ffi::XK_Ecircumflex => VirtualKeyCode::Ecircumflex, + //ffi::XK_Ediaeresis => VirtualKeyCode::Ediaeresis, + //ffi::XK_Igrave => VirtualKeyCode::Igrave, + //ffi::XK_Iacute => VirtualKeyCode::Iacute, + //ffi::XK_Icircumflex => VirtualKeyCode::Icircumflex, + //ffi::XK_Idiaeresis => VirtualKeyCode::Idiaeresis, + //ffi::XK_ETH => VirtualKeyCode::Eth, + //ffi::XK_Eth => VirtualKeyCode::Eth, + //ffi::XK_Ntilde => VirtualKeyCode::Ntilde, + //ffi::XK_Ograve => VirtualKeyCode::Ograve, + //ffi::XK_Oacute => VirtualKeyCode::Oacute, + //ffi::XK_Ocircumflex => VirtualKeyCode::Ocircumflex, + //ffi::XK_Otilde => VirtualKeyCode::Otilde, + //ffi::XK_Odiaeresis => VirtualKeyCode::Odiaeresis, + //ffi::XK_multiply => VirtualKeyCode::Multiply, + //ffi::XK_Ooblique => VirtualKeyCode::Ooblique, + //ffi::XK_Ugrave => VirtualKeyCode::Ugrave, + //ffi::XK_Uacute => VirtualKeyCode::Uacute, + //ffi::XK_Ucircumflex => VirtualKeyCode::Ucircumflex, + //ffi::XK_Udiaeresis => VirtualKeyCode::Udiaeresis, + //ffi::XK_Yacute => VirtualKeyCode::Yacute, + //ffi::XK_THORN => VirtualKeyCode::Thorn, + //ffi::XK_Thorn => VirtualKeyCode::Thorn, + //ffi::XK_ssharp => VirtualKeyCode::Ssharp, + //ffi::XK_agrave => VirtualKeyCode::Agrave, + //ffi::XK_aacute => VirtualKeyCode::Aacute, + //ffi::XK_acircumflex => VirtualKeyCode::Acircumflex, + //ffi::XK_atilde => VirtualKeyCode::Atilde, + //ffi::XK_adiaeresis => VirtualKeyCode::Adiaeresis, + //ffi::XK_aring => VirtualKeyCode::Aring, + //ffi::XK_ae => VirtualKeyCode::Ae, + //ffi::XK_ccedilla => VirtualKeyCode::Ccedilla, + //ffi::XK_egrave => VirtualKeyCode::Egrave, + //ffi::XK_eacute => VirtualKeyCode::Eacute, + //ffi::XK_ecircumflex => VirtualKeyCode::Ecircumflex, + //ffi::XK_ediaeresis => VirtualKeyCode::Ediaeresis, + //ffi::XK_igrave => VirtualKeyCode::Igrave, + //ffi::XK_iacute => VirtualKeyCode::Iacute, + //ffi::XK_icircumflex => VirtualKeyCode::Icircumflex, + //ffi::XK_idiaeresis => VirtualKeyCode::Idiaeresis, + //ffi::XK_eth => VirtualKeyCode::Eth, + //ffi::XK_ntilde => VirtualKeyCode::Ntilde, + //ffi::XK_ograve => VirtualKeyCode::Ograve, + //ffi::XK_oacute => VirtualKeyCode::Oacute, + //ffi::XK_ocircumflex => VirtualKeyCode::Ocircumflex, + //ffi::XK_otilde => VirtualKeyCode::Otilde, + //ffi::XK_odiaeresis => VirtualKeyCode::Odiaeresis, + //ffi::XK_division => VirtualKeyCode::Division, + //ffi::XK_oslash => VirtualKeyCode::Oslash, + //ffi::XK_ugrave => VirtualKeyCode::Ugrave, + //ffi::XK_uacute => VirtualKeyCode::Uacute, + //ffi::XK_ucircumflex => VirtualKeyCode::Ucircumflex, + //ffi::XK_udiaeresis => VirtualKeyCode::Udiaeresis, + //ffi::XK_yacute => VirtualKeyCode::Yacute, + //ffi::XK_thorn => VirtualKeyCode::Thorn, + //ffi::XK_ydiaeresis => VirtualKeyCode::Ydiaeresis, + //ffi::XK_Aogonek => VirtualKeyCode::Aogonek, + //ffi::XK_breve => VirtualKeyCode::Breve, + //ffi::XK_Lstroke => VirtualKeyCode::Lstroke, + //ffi::XK_Lcaron => VirtualKeyCode::Lcaron, + //ffi::XK_Sacute => VirtualKeyCode::Sacute, + //ffi::XK_Scaron => VirtualKeyCode::Scaron, + //ffi::XK_Scedilla => VirtualKeyCode::Scedilla, + //ffi::XK_Tcaron => VirtualKeyCode::Tcaron, + //ffi::XK_Zacute => VirtualKeyCode::Zacute, + //ffi::XK_Zcaron => VirtualKeyCode::Zcaron, + //ffi::XK_Zabovedot => VirtualKeyCode::Zabovedot, + //ffi::XK_aogonek => VirtualKeyCode::Aogonek, + //ffi::XK_ogonek => VirtualKeyCode::Ogonek, + //ffi::XK_lstroke => VirtualKeyCode::Lstroke, + //ffi::XK_lcaron => VirtualKeyCode::Lcaron, + //ffi::XK_sacute => VirtualKeyCode::Sacute, + //ffi::XK_caron => VirtualKeyCode::Caron, + //ffi::XK_scaron => VirtualKeyCode::Scaron, + //ffi::XK_scedilla => VirtualKeyCode::Scedilla, + //ffi::XK_tcaron => VirtualKeyCode::Tcaron, + //ffi::XK_zacute => VirtualKeyCode::Zacute, + //ffi::XK_doubleacute => VirtualKeyCode::Doubleacute, + //ffi::XK_zcaron => VirtualKeyCode::Zcaron, + //ffi::XK_zabovedot => VirtualKeyCode::Zabovedot, + //ffi::XK_Racute => VirtualKeyCode::Racute, + //ffi::XK_Abreve => VirtualKeyCode::Abreve, + //ffi::XK_Lacute => VirtualKeyCode::Lacute, + //ffi::XK_Cacute => VirtualKeyCode::Cacute, + //ffi::XK_Ccaron => VirtualKeyCode::Ccaron, + //ffi::XK_Eogonek => VirtualKeyCode::Eogonek, + //ffi::XK_Ecaron => VirtualKeyCode::Ecaron, + //ffi::XK_Dcaron => VirtualKeyCode::Dcaron, + //ffi::XK_Dstroke => VirtualKeyCode::Dstroke, + //ffi::XK_Nacute => VirtualKeyCode::Nacute, + //ffi::XK_Ncaron => VirtualKeyCode::Ncaron, + //ffi::XK_Odoubleacute => VirtualKeyCode::Odoubleacute, + //ffi::XK_Rcaron => VirtualKeyCode::Rcaron, + //ffi::XK_Uring => VirtualKeyCode::Uring, + //ffi::XK_Udoubleacute => VirtualKeyCode::Udoubleacute, + //ffi::XK_Tcedilla => VirtualKeyCode::Tcedilla, + //ffi::XK_racute => VirtualKeyCode::Racute, + //ffi::XK_abreve => VirtualKeyCode::Abreve, + //ffi::XK_lacute => VirtualKeyCode::Lacute, + //ffi::XK_cacute => VirtualKeyCode::Cacute, + //ffi::XK_ccaron => VirtualKeyCode::Ccaron, + //ffi::XK_eogonek => VirtualKeyCode::Eogonek, + //ffi::XK_ecaron => VirtualKeyCode::Ecaron, + //ffi::XK_dcaron => VirtualKeyCode::Dcaron, + //ffi::XK_dstroke => VirtualKeyCode::Dstroke, + //ffi::XK_nacute => VirtualKeyCode::Nacute, + //ffi::XK_ncaron => VirtualKeyCode::Ncaron, + //ffi::XK_odoubleacute => VirtualKeyCode::Odoubleacute, + //ffi::XK_udoubleacute => VirtualKeyCode::Udoubleacute, + //ffi::XK_rcaron => VirtualKeyCode::Rcaron, + //ffi::XK_uring => VirtualKeyCode::Uring, + //ffi::XK_tcedilla => VirtualKeyCode::Tcedilla, + //ffi::XK_abovedot => VirtualKeyCode::Abovedot, + //ffi::XK_Hstroke => VirtualKeyCode::Hstroke, + //ffi::XK_Hcircumflex => VirtualKeyCode::Hcircumflex, + //ffi::XK_Iabovedot => VirtualKeyCode::Iabovedot, + //ffi::XK_Gbreve => VirtualKeyCode::Gbreve, + //ffi::XK_Jcircumflex => VirtualKeyCode::Jcircumflex, + //ffi::XK_hstroke => VirtualKeyCode::Hstroke, + //ffi::XK_hcircumflex => VirtualKeyCode::Hcircumflex, + //ffi::XK_idotless => VirtualKeyCode::Idotless, + //ffi::XK_gbreve => VirtualKeyCode::Gbreve, + //ffi::XK_jcircumflex => VirtualKeyCode::Jcircumflex, + //ffi::XK_Cabovedot => VirtualKeyCode::Cabovedot, + //ffi::XK_Ccircumflex => VirtualKeyCode::Ccircumflex, + //ffi::XK_Gabovedot => VirtualKeyCode::Gabovedot, + //ffi::XK_Gcircumflex => VirtualKeyCode::Gcircumflex, + //ffi::XK_Ubreve => VirtualKeyCode::Ubreve, + //ffi::XK_Scircumflex => VirtualKeyCode::Scircumflex, + //ffi::XK_cabovedot => VirtualKeyCode::Cabovedot, + //ffi::XK_ccircumflex => VirtualKeyCode::Ccircumflex, + //ffi::XK_gabovedot => VirtualKeyCode::Gabovedot, + //ffi::XK_gcircumflex => VirtualKeyCode::Gcircumflex, + //ffi::XK_ubreve => VirtualKeyCode::Ubreve, + //ffi::XK_scircumflex => VirtualKeyCode::Scircumflex, + //ffi::XK_kra => VirtualKeyCode::Kra, + //ffi::XK_kappa => VirtualKeyCode::Kappa, + //ffi::XK_Rcedilla => VirtualKeyCode::Rcedilla, + //ffi::XK_Itilde => VirtualKeyCode::Itilde, + //ffi::XK_Lcedilla => VirtualKeyCode::Lcedilla, + //ffi::XK_Emacron => VirtualKeyCode::Emacron, + //ffi::XK_Gcedilla => VirtualKeyCode::Gcedilla, + //ffi::XK_Tslash => VirtualKeyCode::Tslash, + //ffi::XK_rcedilla => VirtualKeyCode::Rcedilla, + //ffi::XK_itilde => VirtualKeyCode::Itilde, + //ffi::XK_lcedilla => VirtualKeyCode::Lcedilla, + //ffi::XK_emacron => VirtualKeyCode::Emacron, + //ffi::XK_gcedilla => VirtualKeyCode::Gcedilla, + //ffi::XK_tslash => VirtualKeyCode::Tslash, + //ffi::XK_ENG => VirtualKeyCode::Eng, + //ffi::XK_eng => VirtualKeyCode::Eng, + //ffi::XK_Amacron => VirtualKeyCode::Amacron, + //ffi::XK_Iogonek => VirtualKeyCode::Iogonek, + //ffi::XK_Eabovedot => VirtualKeyCode::Eabovedot, + //ffi::XK_Imacron => VirtualKeyCode::Imacron, + //ffi::XK_Ncedilla => VirtualKeyCode::Ncedilla, + //ffi::XK_Omacron => VirtualKeyCode::Omacron, + //ffi::XK_Kcedilla => VirtualKeyCode::Kcedilla, + //ffi::XK_Uogonek => VirtualKeyCode::Uogonek, + //ffi::XK_Utilde => VirtualKeyCode::Utilde, + //ffi::XK_Umacron => VirtualKeyCode::Umacron, + //ffi::XK_amacron => VirtualKeyCode::Amacron, + //ffi::XK_iogonek => VirtualKeyCode::Iogonek, + //ffi::XK_eabovedot => VirtualKeyCode::Eabovedot, + //ffi::XK_imacron => VirtualKeyCode::Imacron, + //ffi::XK_ncedilla => VirtualKeyCode::Ncedilla, + //ffi::XK_omacron => VirtualKeyCode::Omacron, + //ffi::XK_kcedilla => VirtualKeyCode::Kcedilla, + //ffi::XK_uogonek => VirtualKeyCode::Uogonek, + //ffi::XK_utilde => VirtualKeyCode::Utilde, + //ffi::XK_umacron => VirtualKeyCode::Umacron, + //ffi::XK_overline => VirtualKeyCode::Overline, + //ffi::XK_kana_fullstop => VirtualKeyCode::Kana_fullstop, + //ffi::XK_kana_openingbracket => VirtualKeyCode::Kana_openingbracket, + //ffi::XK_kana_closingbracket => VirtualKeyCode::Kana_closingbracket, + //ffi::XK_kana_comma => VirtualKeyCode::Kana_comma, + //ffi::XK_kana_conjunctive => VirtualKeyCode::Kana_conjunctive, + //ffi::XK_kana_middledot => VirtualKeyCode::Kana_middledot, + //ffi::XK_kana_WO => VirtualKeyCode::Kana_wo, + //ffi::XK_kana_a => VirtualKeyCode::Kana_a, + //ffi::XK_kana_i => VirtualKeyCode::Kana_i, + //ffi::XK_kana_u => VirtualKeyCode::Kana_u, + //ffi::XK_kana_e => VirtualKeyCode::Kana_e, + //ffi::XK_kana_o => VirtualKeyCode::Kana_o, + //ffi::XK_kana_ya => VirtualKeyCode::Kana_ya, + //ffi::XK_kana_yu => VirtualKeyCode::Kana_yu, + //ffi::XK_kana_yo => VirtualKeyCode::Kana_yo, + //ffi::XK_kana_tsu => VirtualKeyCode::Kana_tsu, + //ffi::XK_kana_tu => VirtualKeyCode::Kana_tu, + //ffi::XK_prolongedsound => VirtualKeyCode::Prolongedsound, + //ffi::XK_kana_A => VirtualKeyCode::Kana_a, + //ffi::XK_kana_I => VirtualKeyCode::Kana_i, + //ffi::XK_kana_U => VirtualKeyCode::Kana_u, + //ffi::XK_kana_E => VirtualKeyCode::Kana_e, + //ffi::XK_kana_O => VirtualKeyCode::Kana_o, + //ffi::XK_kana_KA => VirtualKeyCode::Kana_ka, + //ffi::XK_kana_KI => VirtualKeyCode::Kana_ki, + //ffi::XK_kana_KU => VirtualKeyCode::Kana_ku, + //ffi::XK_kana_KE => VirtualKeyCode::Kana_ke, + //ffi::XK_kana_KO => VirtualKeyCode::Kana_ko, + //ffi::XK_kana_SA => VirtualKeyCode::Kana_sa, + //ffi::XK_kana_SHI => VirtualKeyCode::Kana_shi, + //ffi::XK_kana_SU => VirtualKeyCode::Kana_su, + //ffi::XK_kana_SE => VirtualKeyCode::Kana_se, + //ffi::XK_kana_SO => VirtualKeyCode::Kana_so, + //ffi::XK_kana_TA => VirtualKeyCode::Kana_ta, + //ffi::XK_kana_CHI => VirtualKeyCode::Kana_chi, + //ffi::XK_kana_TI => VirtualKeyCode::Kana_ti, + //ffi::XK_kana_TSU => VirtualKeyCode::Kana_tsu, + //ffi::XK_kana_TU => VirtualKeyCode::Kana_tu, + //ffi::XK_kana_TE => VirtualKeyCode::Kana_te, + //ffi::XK_kana_TO => VirtualKeyCode::Kana_to, + //ffi::XK_kana_NA => VirtualKeyCode::Kana_na, + //ffi::XK_kana_NI => VirtualKeyCode::Kana_ni, + //ffi::XK_kana_NU => VirtualKeyCode::Kana_nu, + //ffi::XK_kana_NE => VirtualKeyCode::Kana_ne, + //ffi::XK_kana_NO => VirtualKeyCode::Kana_no, + //ffi::XK_kana_HA => VirtualKeyCode::Kana_ha, + //ffi::XK_kana_HI => VirtualKeyCode::Kana_hi, + //ffi::XK_kana_FU => VirtualKeyCode::Kana_fu, + //ffi::XK_kana_HU => VirtualKeyCode::Kana_hu, + //ffi::XK_kana_HE => VirtualKeyCode::Kana_he, + //ffi::XK_kana_HO => VirtualKeyCode::Kana_ho, + //ffi::XK_kana_MA => VirtualKeyCode::Kana_ma, + //ffi::XK_kana_MI => VirtualKeyCode::Kana_mi, + //ffi::XK_kana_MU => VirtualKeyCode::Kana_mu, + //ffi::XK_kana_ME => VirtualKeyCode::Kana_me, + //ffi::XK_kana_MO => VirtualKeyCode::Kana_mo, + //ffi::XK_kana_YA => VirtualKeyCode::Kana_ya, + //ffi::XK_kana_YU => VirtualKeyCode::Kana_yu, + //ffi::XK_kana_YO => VirtualKeyCode::Kana_yo, + //ffi::XK_kana_RA => VirtualKeyCode::Kana_ra, + //ffi::XK_kana_RI => VirtualKeyCode::Kana_ri, + //ffi::XK_kana_RU => VirtualKeyCode::Kana_ru, + //ffi::XK_kana_RE => VirtualKeyCode::Kana_re, + //ffi::XK_kana_RO => VirtualKeyCode::Kana_ro, + //ffi::XK_kana_WA => VirtualKeyCode::Kana_wa, + //ffi::XK_kana_N => VirtualKeyCode::Kana_n, + //ffi::XK_voicedsound => VirtualKeyCode::Voicedsound, + //ffi::XK_semivoicedsound => VirtualKeyCode::Semivoicedsound, + //ffi::XK_kana_switch => VirtualKeyCode::Kana_switch, + //ffi::XK_Arabic_comma => VirtualKeyCode::Arabic_comma, + //ffi::XK_Arabic_semicolon => VirtualKeyCode::Arabic_semicolon, + //ffi::XK_Arabic_question_mark => VirtualKeyCode::Arabic_question_mark, + //ffi::XK_Arabic_hamza => VirtualKeyCode::Arabic_hamza, + //ffi::XK_Arabic_maddaonalef => VirtualKeyCode::Arabic_maddaonalef, + //ffi::XK_Arabic_hamzaonalef => VirtualKeyCode::Arabic_hamzaonalef, + //ffi::XK_Arabic_hamzaonwaw => VirtualKeyCode::Arabic_hamzaonwaw, + //ffi::XK_Arabic_hamzaunderalef => VirtualKeyCode::Arabic_hamzaunderalef, + //ffi::XK_Arabic_hamzaonyeh => VirtualKeyCode::Arabic_hamzaonyeh, + //ffi::XK_Arabic_alef => VirtualKeyCode::Arabic_alef, + //ffi::XK_Arabic_beh => VirtualKeyCode::Arabic_beh, + //ffi::XK_Arabic_tehmarbuta => VirtualKeyCode::Arabic_tehmarbuta, + //ffi::XK_Arabic_teh => VirtualKeyCode::Arabic_teh, + //ffi::XK_Arabic_theh => VirtualKeyCode::Arabic_theh, + //ffi::XK_Arabic_jeem => VirtualKeyCode::Arabic_jeem, + //ffi::XK_Arabic_hah => VirtualKeyCode::Arabic_hah, + //ffi::XK_Arabic_khah => VirtualKeyCode::Arabic_khah, + //ffi::XK_Arabic_dal => VirtualKeyCode::Arabic_dal, + //ffi::XK_Arabic_thal => VirtualKeyCode::Arabic_thal, + //ffi::XK_Arabic_ra => VirtualKeyCode::Arabic_ra, + //ffi::XK_Arabic_zain => VirtualKeyCode::Arabic_zain, + //ffi::XK_Arabic_seen => VirtualKeyCode::Arabic_seen, + //ffi::XK_Arabic_sheen => VirtualKeyCode::Arabic_sheen, + //ffi::XK_Arabic_sad => VirtualKeyCode::Arabic_sad, + //ffi::XK_Arabic_dad => VirtualKeyCode::Arabic_dad, + //ffi::XK_Arabic_tah => VirtualKeyCode::Arabic_tah, + //ffi::XK_Arabic_zah => VirtualKeyCode::Arabic_zah, + //ffi::XK_Arabic_ain => VirtualKeyCode::Arabic_ain, + //ffi::XK_Arabic_ghain => VirtualKeyCode::Arabic_ghain, + //ffi::XK_Arabic_tatweel => VirtualKeyCode::Arabic_tatweel, + //ffi::XK_Arabic_feh => VirtualKeyCode::Arabic_feh, + //ffi::XK_Arabic_qaf => VirtualKeyCode::Arabic_qaf, + //ffi::XK_Arabic_kaf => VirtualKeyCode::Arabic_kaf, + //ffi::XK_Arabic_lam => VirtualKeyCode::Arabic_lam, + //ffi::XK_Arabic_meem => VirtualKeyCode::Arabic_meem, + //ffi::XK_Arabic_noon => VirtualKeyCode::Arabic_noon, + //ffi::XK_Arabic_ha => VirtualKeyCode::Arabic_ha, + //ffi::XK_Arabic_heh => VirtualKeyCode::Arabic_heh, + //ffi::XK_Arabic_waw => VirtualKeyCode::Arabic_waw, + //ffi::XK_Arabic_alefmaksura => VirtualKeyCode::Arabic_alefmaksura, + //ffi::XK_Arabic_yeh => VirtualKeyCode::Arabic_yeh, + //ffi::XK_Arabic_fathatan => VirtualKeyCode::Arabic_fathatan, + //ffi::XK_Arabic_dammatan => VirtualKeyCode::Arabic_dammatan, + //ffi::XK_Arabic_kasratan => VirtualKeyCode::Arabic_kasratan, + //ffi::XK_Arabic_fatha => VirtualKeyCode::Arabic_fatha, + //ffi::XK_Arabic_damma => VirtualKeyCode::Arabic_damma, + //ffi::XK_Arabic_kasra => VirtualKeyCode::Arabic_kasra, + //ffi::XK_Arabic_shadda => VirtualKeyCode::Arabic_shadda, + //ffi::XK_Arabic_sukun => VirtualKeyCode::Arabic_sukun, + //ffi::XK_Arabic_switch => VirtualKeyCode::Arabic_switch, + //ffi::XK_Serbian_dje => VirtualKeyCode::Serbian_dje, + //ffi::XK_Macedonia_gje => VirtualKeyCode::Macedonia_gje, + //ffi::XK_Cyrillic_io => VirtualKeyCode::Cyrillic_io, + //ffi::XK_Ukrainian_ie => VirtualKeyCode::Ukrainian_ie, + //ffi::XK_Ukranian_je => VirtualKeyCode::Ukranian_je, + //ffi::XK_Macedonia_dse => VirtualKeyCode::Macedonia_dse, + //ffi::XK_Ukrainian_i => VirtualKeyCode::Ukrainian_i, + //ffi::XK_Ukranian_i => VirtualKeyCode::Ukranian_i, + //ffi::XK_Ukrainian_yi => VirtualKeyCode::Ukrainian_yi, + //ffi::XK_Ukranian_yi => VirtualKeyCode::Ukranian_yi, + //ffi::XK_Cyrillic_je => VirtualKeyCode::Cyrillic_je, + //ffi::XK_Serbian_je => VirtualKeyCode::Serbian_je, + //ffi::XK_Cyrillic_lje => VirtualKeyCode::Cyrillic_lje, + //ffi::XK_Serbian_lje => VirtualKeyCode::Serbian_lje, + //ffi::XK_Cyrillic_nje => VirtualKeyCode::Cyrillic_nje, + //ffi::XK_Serbian_nje => VirtualKeyCode::Serbian_nje, + //ffi::XK_Serbian_tshe => VirtualKeyCode::Serbian_tshe, + //ffi::XK_Macedonia_kje => VirtualKeyCode::Macedonia_kje, + //ffi::XK_Byelorussian_shortu => VirtualKeyCode::Byelorussian_shortu, + //ffi::XK_Cyrillic_dzhe => VirtualKeyCode::Cyrillic_dzhe, + //ffi::XK_Serbian_dze => VirtualKeyCode::Serbian_dze, + //ffi::XK_numerosign => VirtualKeyCode::Numerosign, + //ffi::XK_Serbian_DJE => VirtualKeyCode::Serbian_dje, + //ffi::XK_Macedonia_GJE => VirtualKeyCode::Macedonia_gje, + //ffi::XK_Cyrillic_IO => VirtualKeyCode::Cyrillic_io, + //ffi::XK_Ukrainian_IE => VirtualKeyCode::Ukrainian_ie, + //ffi::XK_Ukranian_JE => VirtualKeyCode::Ukranian_je, + //ffi::XK_Macedonia_DSE => VirtualKeyCode::Macedonia_dse, + //ffi::XK_Ukrainian_I => VirtualKeyCode::Ukrainian_i, + //ffi::XK_Ukranian_I => VirtualKeyCode::Ukranian_i, + //ffi::XK_Ukrainian_YI => VirtualKeyCode::Ukrainian_yi, + //ffi::XK_Ukranian_YI => VirtualKeyCode::Ukranian_yi, + //ffi::XK_Cyrillic_JE => VirtualKeyCode::Cyrillic_je, + //ffi::XK_Serbian_JE => VirtualKeyCode::Serbian_je, + //ffi::XK_Cyrillic_LJE => VirtualKeyCode::Cyrillic_lje, + //ffi::XK_Serbian_LJE => VirtualKeyCode::Serbian_lje, + //ffi::XK_Cyrillic_NJE => VirtualKeyCode::Cyrillic_nje, + //ffi::XK_Serbian_NJE => VirtualKeyCode::Serbian_nje, + //ffi::XK_Serbian_TSHE => VirtualKeyCode::Serbian_tshe, + //ffi::XK_Macedonia_KJE => VirtualKeyCode::Macedonia_kje, + //ffi::XK_Byelorussian_SHORTU => VirtualKeyCode::Byelorussian_shortu, + //ffi::XK_Cyrillic_DZHE => VirtualKeyCode::Cyrillic_dzhe, + //ffi::XK_Serbian_DZE => VirtualKeyCode::Serbian_dze, + //ffi::XK_Cyrillic_yu => VirtualKeyCode::Cyrillic_yu, + //ffi::XK_Cyrillic_a => VirtualKeyCode::Cyrillic_a, + //ffi::XK_Cyrillic_be => VirtualKeyCode::Cyrillic_be, + //ffi::XK_Cyrillic_tse => VirtualKeyCode::Cyrillic_tse, + //ffi::XK_Cyrillic_de => VirtualKeyCode::Cyrillic_de, + //ffi::XK_Cyrillic_ie => VirtualKeyCode::Cyrillic_ie, + //ffi::XK_Cyrillic_ef => VirtualKeyCode::Cyrillic_ef, + //ffi::XK_Cyrillic_ghe => VirtualKeyCode::Cyrillic_ghe, + //ffi::XK_Cyrillic_ha => VirtualKeyCode::Cyrillic_ha, + //ffi::XK_Cyrillic_i => VirtualKeyCode::Cyrillic_i, + //ffi::XK_Cyrillic_shorti => VirtualKeyCode::Cyrillic_shorti, + //ffi::XK_Cyrillic_ka => VirtualKeyCode::Cyrillic_ka, + //ffi::XK_Cyrillic_el => VirtualKeyCode::Cyrillic_el, + //ffi::XK_Cyrillic_em => VirtualKeyCode::Cyrillic_em, + //ffi::XK_Cyrillic_en => VirtualKeyCode::Cyrillic_en, + //ffi::XK_Cyrillic_o => VirtualKeyCode::Cyrillic_o, + //ffi::XK_Cyrillic_pe => VirtualKeyCode::Cyrillic_pe, + //ffi::XK_Cyrillic_ya => VirtualKeyCode::Cyrillic_ya, + //ffi::XK_Cyrillic_er => VirtualKeyCode::Cyrillic_er, + //ffi::XK_Cyrillic_es => VirtualKeyCode::Cyrillic_es, + //ffi::XK_Cyrillic_te => VirtualKeyCode::Cyrillic_te, + //ffi::XK_Cyrillic_u => VirtualKeyCode::Cyrillic_u, + //ffi::XK_Cyrillic_zhe => VirtualKeyCode::Cyrillic_zhe, + //ffi::XK_Cyrillic_ve => VirtualKeyCode::Cyrillic_ve, + //ffi::XK_Cyrillic_softsign => VirtualKeyCode::Cyrillic_softsign, + //ffi::XK_Cyrillic_yeru => VirtualKeyCode::Cyrillic_yeru, + //ffi::XK_Cyrillic_ze => VirtualKeyCode::Cyrillic_ze, + //ffi::XK_Cyrillic_sha => VirtualKeyCode::Cyrillic_sha, + //ffi::XK_Cyrillic_e => VirtualKeyCode::Cyrillic_e, + //ffi::XK_Cyrillic_shcha => VirtualKeyCode::Cyrillic_shcha, + //ffi::XK_Cyrillic_che => VirtualKeyCode::Cyrillic_che, + //ffi::XK_Cyrillic_hardsign => VirtualKeyCode::Cyrillic_hardsign, + //ffi::XK_Cyrillic_YU => VirtualKeyCode::Cyrillic_yu, + //ffi::XK_Cyrillic_A => VirtualKeyCode::Cyrillic_a, + //ffi::XK_Cyrillic_BE => VirtualKeyCode::Cyrillic_be, + //ffi::XK_Cyrillic_TSE => VirtualKeyCode::Cyrillic_tse, + //ffi::XK_Cyrillic_DE => VirtualKeyCode::Cyrillic_de, + //ffi::XK_Cyrillic_IE => VirtualKeyCode::Cyrillic_ie, + //ffi::XK_Cyrillic_EF => VirtualKeyCode::Cyrillic_ef, + //ffi::XK_Cyrillic_GHE => VirtualKeyCode::Cyrillic_ghe, + //ffi::XK_Cyrillic_HA => VirtualKeyCode::Cyrillic_ha, + //ffi::XK_Cyrillic_I => VirtualKeyCode::Cyrillic_i, + //ffi::XK_Cyrillic_SHORTI => VirtualKeyCode::Cyrillic_shorti, + //ffi::XK_Cyrillic_KA => VirtualKeyCode::Cyrillic_ka, + //ffi::XK_Cyrillic_EL => VirtualKeyCode::Cyrillic_el, + //ffi::XK_Cyrillic_EM => VirtualKeyCode::Cyrillic_em, + //ffi::XK_Cyrillic_EN => VirtualKeyCode::Cyrillic_en, + //ffi::XK_Cyrillic_O => VirtualKeyCode::Cyrillic_o, + //ffi::XK_Cyrillic_PE => VirtualKeyCode::Cyrillic_pe, + //ffi::XK_Cyrillic_YA => VirtualKeyCode::Cyrillic_ya, + //ffi::XK_Cyrillic_ER => VirtualKeyCode::Cyrillic_er, + //ffi::XK_Cyrillic_ES => VirtualKeyCode::Cyrillic_es, + //ffi::XK_Cyrillic_TE => VirtualKeyCode::Cyrillic_te, + //ffi::XK_Cyrillic_U => VirtualKeyCode::Cyrillic_u, + //ffi::XK_Cyrillic_ZHE => VirtualKeyCode::Cyrillic_zhe, + //ffi::XK_Cyrillic_VE => VirtualKeyCode::Cyrillic_ve, + //ffi::XK_Cyrillic_SOFTSIGN => VirtualKeyCode::Cyrillic_softsign, + //ffi::XK_Cyrillic_YERU => VirtualKeyCode::Cyrillic_yeru, + //ffi::XK_Cyrillic_ZE => VirtualKeyCode::Cyrillic_ze, + //ffi::XK_Cyrillic_SHA => VirtualKeyCode::Cyrillic_sha, + //ffi::XK_Cyrillic_E => VirtualKeyCode::Cyrillic_e, + //ffi::XK_Cyrillic_SHCHA => VirtualKeyCode::Cyrillic_shcha, + //ffi::XK_Cyrillic_CHE => VirtualKeyCode::Cyrillic_che, + //ffi::XK_Cyrillic_HARDSIGN => VirtualKeyCode::Cyrillic_hardsign, + //ffi::XK_Greek_ALPHAaccent => VirtualKeyCode::Greek_alphaaccent, + //ffi::XK_Greek_EPSILONaccent => VirtualKeyCode::Greek_epsilonaccent, + //ffi::XK_Greek_ETAaccent => VirtualKeyCode::Greek_etaaccent, + //ffi::XK_Greek_IOTAaccent => VirtualKeyCode::Greek_iotaaccent, + //ffi::XK_Greek_IOTAdiaeresis => VirtualKeyCode::Greek_iotadiaeresis, + //ffi::XK_Greek_OMICRONaccent => VirtualKeyCode::Greek_omicronaccent, + //ffi::XK_Greek_UPSILONaccent => VirtualKeyCode::Greek_upsilonaccent, + //ffi::XK_Greek_UPSILONdieresis => VirtualKeyCode::Greek_upsilondieresis, + //ffi::XK_Greek_OMEGAaccent => VirtualKeyCode::Greek_omegaaccent, + //ffi::XK_Greek_accentdieresis => VirtualKeyCode::Greek_accentdieresis, + //ffi::XK_Greek_horizbar => VirtualKeyCode::Greek_horizbar, + //ffi::XK_Greek_alphaaccent => VirtualKeyCode::Greek_alphaaccent, + //ffi::XK_Greek_epsilonaccent => VirtualKeyCode::Greek_epsilonaccent, + //ffi::XK_Greek_etaaccent => VirtualKeyCode::Greek_etaaccent, + //ffi::XK_Greek_iotaaccent => VirtualKeyCode::Greek_iotaaccent, + //ffi::XK_Greek_iotadieresis => VirtualKeyCode::Greek_iotadieresis, + //ffi::XK_Greek_iotaaccentdieresis => VirtualKeyCode::Greek_iotaaccentdieresis, + //ffi::XK_Greek_omicronaccent => VirtualKeyCode::Greek_omicronaccent, + //ffi::XK_Greek_upsilonaccent => VirtualKeyCode::Greek_upsilonaccent, + //ffi::XK_Greek_upsilondieresis => VirtualKeyCode::Greek_upsilondieresis, + //ffi::XK_Greek_upsilonaccentdieresis => VirtualKeyCode::Greek_upsilonaccentdieresis, + //ffi::XK_Greek_omegaaccent => VirtualKeyCode::Greek_omegaaccent, + //ffi::XK_Greek_ALPHA => VirtualKeyCode::Greek_alpha, + //ffi::XK_Greek_BETA => VirtualKeyCode::Greek_beta, + //ffi::XK_Greek_GAMMA => VirtualKeyCode::Greek_gamma, + //ffi::XK_Greek_DELTA => VirtualKeyCode::Greek_delta, + //ffi::XK_Greek_EPSILON => VirtualKeyCode::Greek_epsilon, + //ffi::XK_Greek_ZETA => VirtualKeyCode::Greek_zeta, + //ffi::XK_Greek_ETA => VirtualKeyCode::Greek_eta, + //ffi::XK_Greek_THETA => VirtualKeyCode::Greek_theta, + //ffi::XK_Greek_IOTA => VirtualKeyCode::Greek_iota, + //ffi::XK_Greek_KAPPA => VirtualKeyCode::Greek_kappa, + //ffi::XK_Greek_LAMDA => VirtualKeyCode::Greek_lamda, + //ffi::XK_Greek_LAMBDA => VirtualKeyCode::Greek_lambda, + //ffi::XK_Greek_MU => VirtualKeyCode::Greek_mu, + //ffi::XK_Greek_NU => VirtualKeyCode::Greek_nu, + //ffi::XK_Greek_XI => VirtualKeyCode::Greek_xi, + //ffi::XK_Greek_OMICRON => VirtualKeyCode::Greek_omicron, + //ffi::XK_Greek_PI => VirtualKeyCode::Greek_pi, + //ffi::XK_Greek_RHO => VirtualKeyCode::Greek_rho, + //ffi::XK_Greek_SIGMA => VirtualKeyCode::Greek_sigma, + //ffi::XK_Greek_TAU => VirtualKeyCode::Greek_tau, + //ffi::XK_Greek_UPSILON => VirtualKeyCode::Greek_upsilon, + //ffi::XK_Greek_PHI => VirtualKeyCode::Greek_phi, + //ffi::XK_Greek_CHI => VirtualKeyCode::Greek_chi, + //ffi::XK_Greek_PSI => VirtualKeyCode::Greek_psi, + //ffi::XK_Greek_OMEGA => VirtualKeyCode::Greek_omega, + //ffi::XK_Greek_alpha => VirtualKeyCode::Greek_alpha, + //ffi::XK_Greek_beta => VirtualKeyCode::Greek_beta, + //ffi::XK_Greek_gamma => VirtualKeyCode::Greek_gamma, + //ffi::XK_Greek_delta => VirtualKeyCode::Greek_delta, + //ffi::XK_Greek_epsilon => VirtualKeyCode::Greek_epsilon, + //ffi::XK_Greek_zeta => VirtualKeyCode::Greek_zeta, + //ffi::XK_Greek_eta => VirtualKeyCode::Greek_eta, + //ffi::XK_Greek_theta => VirtualKeyCode::Greek_theta, + //ffi::XK_Greek_iota => VirtualKeyCode::Greek_iota, + //ffi::XK_Greek_kappa => VirtualKeyCode::Greek_kappa, + //ffi::XK_Greek_lamda => VirtualKeyCode::Greek_lamda, + //ffi::XK_Greek_lambda => VirtualKeyCode::Greek_lambda, + //ffi::XK_Greek_mu => VirtualKeyCode::Greek_mu, + //ffi::XK_Greek_nu => VirtualKeyCode::Greek_nu, + //ffi::XK_Greek_xi => VirtualKeyCode::Greek_xi, + //ffi::XK_Greek_omicron => VirtualKeyCode::Greek_omicron, + //ffi::XK_Greek_pi => VirtualKeyCode::Greek_pi, + //ffi::XK_Greek_rho => VirtualKeyCode::Greek_rho, + //ffi::XK_Greek_sigma => VirtualKeyCode::Greek_sigma, + //ffi::XK_Greek_finalsmallsigma => VirtualKeyCode::Greek_finalsmallsigma, + //ffi::XK_Greek_tau => VirtualKeyCode::Greek_tau, + //ffi::XK_Greek_upsilon => VirtualKeyCode::Greek_upsilon, + //ffi::XK_Greek_phi => VirtualKeyCode::Greek_phi, + //ffi::XK_Greek_chi => VirtualKeyCode::Greek_chi, + //ffi::XK_Greek_psi => VirtualKeyCode::Greek_psi, + //ffi::XK_Greek_omega => VirtualKeyCode::Greek_omega, + //ffi::XK_Greek_switch => VirtualKeyCode::Greek_switch, + //ffi::XK_leftradical => VirtualKeyCode::Leftradical, + //ffi::XK_topleftradical => VirtualKeyCode::Topleftradical, + //ffi::XK_horizconnector => VirtualKeyCode::Horizconnector, + //ffi::XK_topintegral => VirtualKeyCode::Topintegral, + //ffi::XK_botintegral => VirtualKeyCode::Botintegral, + //ffi::XK_vertconnector => VirtualKeyCode::Vertconnector, + //ffi::XK_topleftsqbracket => VirtualKeyCode::Topleftsqbracket, + //ffi::XK_botleftsqbracket => VirtualKeyCode::Botleftsqbracket, + //ffi::XK_toprightsqbracket => VirtualKeyCode::Toprightsqbracket, + //ffi::XK_botrightsqbracket => VirtualKeyCode::Botrightsqbracket, + //ffi::XK_topleftparens => VirtualKeyCode::Topleftparens, + //ffi::XK_botleftparens => VirtualKeyCode::Botleftparens, + //ffi::XK_toprightparens => VirtualKeyCode::Toprightparens, + //ffi::XK_botrightparens => VirtualKeyCode::Botrightparens, + //ffi::XK_leftmiddlecurlybrace => VirtualKeyCode::Leftmiddlecurlybrace, + //ffi::XK_rightmiddlecurlybrace => VirtualKeyCode::Rightmiddlecurlybrace, + //ffi::XK_topleftsummation => VirtualKeyCode::Topleftsummation, + //ffi::XK_botleftsummation => VirtualKeyCode::Botleftsummation, + //ffi::XK_topvertsummationconnector => VirtualKeyCode::Topvertsummationconnector, + //ffi::XK_botvertsummationconnector => VirtualKeyCode::Botvertsummationconnector, + //ffi::XK_toprightsummation => VirtualKeyCode::Toprightsummation, + //ffi::XK_botrightsummation => VirtualKeyCode::Botrightsummation, + //ffi::XK_rightmiddlesummation => VirtualKeyCode::Rightmiddlesummation, + //ffi::XK_lessthanequal => VirtualKeyCode::Lessthanequal, + //ffi::XK_notequal => VirtualKeyCode::Notequal, + //ffi::XK_greaterthanequal => VirtualKeyCode::Greaterthanequal, + //ffi::XK_integral => VirtualKeyCode::Integral, + //ffi::XK_therefore => VirtualKeyCode::Therefore, + //ffi::XK_variation => VirtualKeyCode::Variation, + //ffi::XK_infinity => VirtualKeyCode::Infinity, + //ffi::XK_nabla => VirtualKeyCode::Nabla, + //ffi::XK_approximate => VirtualKeyCode::Approximate, + //ffi::XK_similarequal => VirtualKeyCode::Similarequal, + //ffi::XK_ifonlyif => VirtualKeyCode::Ifonlyif, + //ffi::XK_implies => VirtualKeyCode::Implies, + //ffi::XK_identical => VirtualKeyCode::Identical, + //ffi::XK_radical => VirtualKeyCode::Radical, + //ffi::XK_includedin => VirtualKeyCode::Includedin, + //ffi::XK_includes => VirtualKeyCode::Includes, + //ffi::XK_intersection => VirtualKeyCode::Intersection, + //ffi::XK_union => VirtualKeyCode::Union, + //ffi::XK_logicaland => VirtualKeyCode::Logicaland, + //ffi::XK_logicalor => VirtualKeyCode::Logicalor, + //ffi::XK_partialderivative => VirtualKeyCode::Partialderivative, + //ffi::XK_function => VirtualKeyCode::Function, + //ffi::XK_leftarrow => VirtualKeyCode::Leftarrow, + //ffi::XK_uparrow => VirtualKeyCode::Uparrow, + //ffi::XK_rightarrow => VirtualKeyCode::Rightarrow, + //ffi::XK_downarrow => VirtualKeyCode::Downarrow, + //ffi::XK_blank => VirtualKeyCode::Blank, + //ffi::XK_soliddiamond => VirtualKeyCode::Soliddiamond, + //ffi::XK_checkerboard => VirtualKeyCode::Checkerboard, + //ffi::XK_ht => VirtualKeyCode::Ht, + //ffi::XK_ff => VirtualKeyCode::Ff, + //ffi::XK_cr => VirtualKeyCode::Cr, + //ffi::XK_lf => VirtualKeyCode::Lf, + //ffi::XK_nl => VirtualKeyCode::Nl, + //ffi::XK_vt => VirtualKeyCode::Vt, + //ffi::XK_lowrightcorner => VirtualKeyCode::Lowrightcorner, + //ffi::XK_uprightcorner => VirtualKeyCode::Uprightcorner, + //ffi::XK_upleftcorner => VirtualKeyCode::Upleftcorner, + //ffi::XK_lowleftcorner => VirtualKeyCode::Lowleftcorner, + //ffi::XK_crossinglines => VirtualKeyCode::Crossinglines, + //ffi::XK_horizlinescan1 => VirtualKeyCode::Horizlinescan1, + //ffi::XK_horizlinescan3 => VirtualKeyCode::Horizlinescan3, + //ffi::XK_horizlinescan5 => VirtualKeyCode::Horizlinescan5, + //ffi::XK_horizlinescan7 => VirtualKeyCode::Horizlinescan7, + //ffi::XK_horizlinescan9 => VirtualKeyCode::Horizlinescan9, + //ffi::XK_leftt => VirtualKeyCode::Leftt, + //ffi::XK_rightt => VirtualKeyCode::Rightt, + //ffi::XK_bott => VirtualKeyCode::Bott, + //ffi::XK_topt => VirtualKeyCode::Topt, + //ffi::XK_vertbar => VirtualKeyCode::Vertbar, + //ffi::XK_emspace => VirtualKeyCode::Emspace, + //ffi::XK_enspace => VirtualKeyCode::Enspace, + //ffi::XK_em3space => VirtualKeyCode::Em3space, + //ffi::XK_em4space => VirtualKeyCode::Em4space, + //ffi::XK_digitspace => VirtualKeyCode::Digitspace, + //ffi::XK_punctspace => VirtualKeyCode::Punctspace, + //ffi::XK_thinspace => VirtualKeyCode::Thinspace, + //ffi::XK_hairspace => VirtualKeyCode::Hairspace, + //ffi::XK_emdash => VirtualKeyCode::Emdash, + //ffi::XK_endash => VirtualKeyCode::Endash, + //ffi::XK_signifblank => VirtualKeyCode::Signifblank, + //ffi::XK_ellipsis => VirtualKeyCode::Ellipsis, + //ffi::XK_doubbaselinedot => VirtualKeyCode::Doubbaselinedot, + //ffi::XK_onethird => VirtualKeyCode::Onethird, + //ffi::XK_twothirds => VirtualKeyCode::Twothirds, + //ffi::XK_onefifth => VirtualKeyCode::Onefifth, + //ffi::XK_twofifths => VirtualKeyCode::Twofifths, + //ffi::XK_threefifths => VirtualKeyCode::Threefifths, + //ffi::XK_fourfifths => VirtualKeyCode::Fourfifths, + //ffi::XK_onesixth => VirtualKeyCode::Onesixth, + //ffi::XK_fivesixths => VirtualKeyCode::Fivesixths, + //ffi::XK_careof => VirtualKeyCode::Careof, + //ffi::XK_figdash => VirtualKeyCode::Figdash, + //ffi::XK_leftanglebracket => VirtualKeyCode::Leftanglebracket, + //ffi::XK_decimalpoint => VirtualKeyCode::Decimalpoint, + //ffi::XK_rightanglebracket => VirtualKeyCode::Rightanglebracket, + //ffi::XK_marker => VirtualKeyCode::Marker, + //ffi::XK_oneeighth => VirtualKeyCode::Oneeighth, + //ffi::XK_threeeighths => VirtualKeyCode::Threeeighths, + //ffi::XK_fiveeighths => VirtualKeyCode::Fiveeighths, + //ffi::XK_seveneighths => VirtualKeyCode::Seveneighths, + //ffi::XK_trademark => VirtualKeyCode::Trademark, + //ffi::XK_signaturemark => VirtualKeyCode::Signaturemark, + //ffi::XK_trademarkincircle => VirtualKeyCode::Trademarkincircle, + //ffi::XK_leftopentriangle => VirtualKeyCode::Leftopentriangle, + //ffi::XK_rightopentriangle => VirtualKeyCode::Rightopentriangle, + //ffi::XK_emopencircle => VirtualKeyCode::Emopencircle, + //ffi::XK_emopenrectangle => VirtualKeyCode::Emopenrectangle, + //ffi::XK_leftsinglequotemark => VirtualKeyCode::Leftsinglequotemark, + //ffi::XK_rightsinglequotemark => VirtualKeyCode::Rightsinglequotemark, + //ffi::XK_leftdoublequotemark => VirtualKeyCode::Leftdoublequotemark, + //ffi::XK_rightdoublequotemark => VirtualKeyCode::Rightdoublequotemark, + //ffi::XK_prescription => VirtualKeyCode::Prescription, + //ffi::XK_minutes => VirtualKeyCode::Minutes, + //ffi::XK_seconds => VirtualKeyCode::Seconds, + //ffi::XK_latincross => VirtualKeyCode::Latincross, + //ffi::XK_hexagram => VirtualKeyCode::Hexagram, + //ffi::XK_filledrectbullet => VirtualKeyCode::Filledrectbullet, + //ffi::XK_filledlefttribullet => VirtualKeyCode::Filledlefttribullet, + //ffi::XK_filledrighttribullet => VirtualKeyCode::Filledrighttribullet, + //ffi::XK_emfilledcircle => VirtualKeyCode::Emfilledcircle, + //ffi::XK_emfilledrect => VirtualKeyCode::Emfilledrect, + //ffi::XK_enopencircbullet => VirtualKeyCode::Enopencircbullet, + //ffi::XK_enopensquarebullet => VirtualKeyCode::Enopensquarebullet, + //ffi::XK_openrectbullet => VirtualKeyCode::Openrectbullet, + //ffi::XK_opentribulletup => VirtualKeyCode::Opentribulletup, + //ffi::XK_opentribulletdown => VirtualKeyCode::Opentribulletdown, + //ffi::XK_openstar => VirtualKeyCode::Openstar, + //ffi::XK_enfilledcircbullet => VirtualKeyCode::Enfilledcircbullet, + //ffi::XK_enfilledsqbullet => VirtualKeyCode::Enfilledsqbullet, + //ffi::XK_filledtribulletup => VirtualKeyCode::Filledtribulletup, + //ffi::XK_filledtribulletdown => VirtualKeyCode::Filledtribulletdown, + //ffi::XK_leftpointer => VirtualKeyCode::Leftpointer, + //ffi::XK_rightpointer => VirtualKeyCode::Rightpointer, + //ffi::XK_club => VirtualKeyCode::Club, + //ffi::XK_diamond => VirtualKeyCode::Diamond, + //ffi::XK_heart => VirtualKeyCode::Heart, + //ffi::XK_maltesecross => VirtualKeyCode::Maltesecross, + //ffi::XK_dagger => VirtualKeyCode::Dagger, + //ffi::XK_doubledagger => VirtualKeyCode::Doubledagger, + //ffi::XK_checkmark => VirtualKeyCode::Checkmark, + //ffi::XK_ballotcross => VirtualKeyCode::Ballotcross, + //ffi::XK_musicalsharp => VirtualKeyCode::Musicalsharp, + //ffi::XK_musicalflat => VirtualKeyCode::Musicalflat, + //ffi::XK_malesymbol => VirtualKeyCode::Malesymbol, + //ffi::XK_femalesymbol => VirtualKeyCode::Femalesymbol, + //ffi::XK_telephone => VirtualKeyCode::Telephone, + //ffi::XK_telephonerecorder => VirtualKeyCode::Telephonerecorder, + //ffi::XK_phonographcopyright => VirtualKeyCode::Phonographcopyright, + //ffi::XK_caret => VirtualKeyCode::Caret, + //ffi::XK_singlelowquotemark => VirtualKeyCode::Singlelowquotemark, + //ffi::XK_doublelowquotemark => VirtualKeyCode::Doublelowquotemark, + //ffi::XK_cursor => VirtualKeyCode::Cursor, + //ffi::XK_leftcaret => VirtualKeyCode::Leftcaret, + //ffi::XK_rightcaret => VirtualKeyCode::Rightcaret, + //ffi::XK_downcaret => VirtualKeyCode::Downcaret, + //ffi::XK_upcaret => VirtualKeyCode::Upcaret, + //ffi::XK_overbar => VirtualKeyCode::Overbar, + //ffi::XK_downtack => VirtualKeyCode::Downtack, + //ffi::XK_upshoe => VirtualKeyCode::Upshoe, + //ffi::XK_downstile => VirtualKeyCode::Downstile, + //ffi::XK_underbar => VirtualKeyCode::Underbar, + //ffi::XK_jot => VirtualKeyCode::Jot, + //ffi::XK_quad => VirtualKeyCode::Quad, + //ffi::XK_uptack => VirtualKeyCode::Uptack, + //ffi::XK_circle => VirtualKeyCode::Circle, + //ffi::XK_upstile => VirtualKeyCode::Upstile, + //ffi::XK_downshoe => VirtualKeyCode::Downshoe, + //ffi::XK_rightshoe => VirtualKeyCode::Rightshoe, + //ffi::XK_leftshoe => VirtualKeyCode::Leftshoe, + //ffi::XK_lefttack => VirtualKeyCode::Lefttack, + //ffi::XK_righttack => VirtualKeyCode::Righttack, + //ffi::XK_hebrew_doublelowline => VirtualKeyCode::Hebrew_doublelowline, + //ffi::XK_hebrew_aleph => VirtualKeyCode::Hebrew_aleph, + //ffi::XK_hebrew_bet => VirtualKeyCode::Hebrew_bet, + //ffi::XK_hebrew_beth => VirtualKeyCode::Hebrew_beth, + //ffi::XK_hebrew_gimel => VirtualKeyCode::Hebrew_gimel, + //ffi::XK_hebrew_gimmel => VirtualKeyCode::Hebrew_gimmel, + //ffi::XK_hebrew_dalet => VirtualKeyCode::Hebrew_dalet, + //ffi::XK_hebrew_daleth => VirtualKeyCode::Hebrew_daleth, + //ffi::XK_hebrew_he => VirtualKeyCode::Hebrew_he, + //ffi::XK_hebrew_waw => VirtualKeyCode::Hebrew_waw, + //ffi::XK_hebrew_zain => VirtualKeyCode::Hebrew_zain, + //ffi::XK_hebrew_zayin => VirtualKeyCode::Hebrew_zayin, + //ffi::XK_hebrew_chet => VirtualKeyCode::Hebrew_chet, + //ffi::XK_hebrew_het => VirtualKeyCode::Hebrew_het, + //ffi::XK_hebrew_tet => VirtualKeyCode::Hebrew_tet, + //ffi::XK_hebrew_teth => VirtualKeyCode::Hebrew_teth, + //ffi::XK_hebrew_yod => VirtualKeyCode::Hebrew_yod, + //ffi::XK_hebrew_finalkaph => VirtualKeyCode::Hebrew_finalkaph, + //ffi::XK_hebrew_kaph => VirtualKeyCode::Hebrew_kaph, + //ffi::XK_hebrew_lamed => VirtualKeyCode::Hebrew_lamed, + //ffi::XK_hebrew_finalmem => VirtualKeyCode::Hebrew_finalmem, + //ffi::XK_hebrew_mem => VirtualKeyCode::Hebrew_mem, + //ffi::XK_hebrew_finalnun => VirtualKeyCode::Hebrew_finalnun, + //ffi::XK_hebrew_nun => VirtualKeyCode::Hebrew_nun, + //ffi::XK_hebrew_samech => VirtualKeyCode::Hebrew_samech, + //ffi::XK_hebrew_samekh => VirtualKeyCode::Hebrew_samekh, + //ffi::XK_hebrew_ayin => VirtualKeyCode::Hebrew_ayin, + //ffi::XK_hebrew_finalpe => VirtualKeyCode::Hebrew_finalpe, + //ffi::XK_hebrew_pe => VirtualKeyCode::Hebrew_pe, + //ffi::XK_hebrew_finalzade => VirtualKeyCode::Hebrew_finalzade, + //ffi::XK_hebrew_finalzadi => VirtualKeyCode::Hebrew_finalzadi, + //ffi::XK_hebrew_zade => VirtualKeyCode::Hebrew_zade, + //ffi::XK_hebrew_zadi => VirtualKeyCode::Hebrew_zadi, + //ffi::XK_hebrew_qoph => VirtualKeyCode::Hebrew_qoph, + //ffi::XK_hebrew_kuf => VirtualKeyCode::Hebrew_kuf, + //ffi::XK_hebrew_resh => VirtualKeyCode::Hebrew_resh, + //ffi::XK_hebrew_shin => VirtualKeyCode::Hebrew_shin, + //ffi::XK_hebrew_taw => VirtualKeyCode::Hebrew_taw, + //ffi::XK_hebrew_taf => VirtualKeyCode::Hebrew_taf, + //ffi::XK_Hebrew_switch => VirtualKeyCode::Hebrew_switch, ffi::XF86XK_Back => VirtualKeyCode::NavigateBackward, ffi::XF86XK_Forward => VirtualKeyCode::NavigateForward, ffi::XF86XK_Copy => VirtualKeyCode::Copy, diff --git a/src/platform_impl/linux/x11/ffi.rs b/src/platform_impl/linux/x11/ffi.rs index 964b0ced..8399ec1f 100644 --- a/src/platform_impl/linux/x11/ffi.rs +++ b/src/platform_impl/linux/x11/ffi.rs @@ -6,3 +6,4 @@ pub use x11_dl::xinput2::*; pub use x11_dl::xlib_xcb::*; pub use x11_dl::error::OpenError; pub use x11_dl::xrandr::*; +pub use x11_dl::xrender::*; diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs index 2298050c..73aad7c9 100644 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ b/src/platform_impl/linux/x11/ime/mod.rs @@ -12,8 +12,9 @@ use super::{ffi, util, XConnection, XError}; use self::inner::{close_im, ImeInner}; use self::input_method::PotentialInputMethods; -use self::context::{ImeContextCreationError, ImeContext}; +use self::context::ImeContext; use self::callbacks::*; +pub use self::context::ImeContextCreationError; pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>; pub type ImeSender = Sender<(ffi::Window, i16, i16)>; diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 1e2d5930..5339bef2 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -8,66 +8,61 @@ mod xdisplay; mod dnd; mod ime; pub mod util; +mod event_processor; pub use self::monitor::MonitorHandle; pub use self::window::UnownedWindow; pub use self::xdisplay::{XConnection, XNotSupported, XError}; -use std::{mem, ptr, slice}; +use std::{mem, slice}; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{VecDeque, HashMap, HashSet}; use std::ffi::CStr; use std::ops::Deref; use std::os::raw::*; -use std::sync::{Arc, mpsc, Weak}; -use std::sync::atomic::{self, AtomicBool}; +use std::rc::Rc; +use std::sync::{Arc, mpsc, Weak, Mutex}; use libc::{self, setlocale, LC_CTYPE}; -use { - ControlFlow, - CreationError, - DeviceEvent, - Event, - EventLoopClosed, - KeyboardInput, - LogicalPosition, - LogicalSize, - WindowAttributes, - WindowEvent, -}; -use events::ModifiersState; +use error::OsError as RootOsError; +use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}; +use event::{WindowEvent, Event}; use platform_impl::PlatformSpecificWindowBuilderAttributes; +use platform_impl::platform::sticky_exit_callback; +use window::{WindowAttributes}; use self::dnd::{Dnd, DndState}; use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime}; +use self::event_processor::EventProcessor; -pub struct EventLoop { +pub struct EventLoopWindowTarget { xconn: Arc, wm_delete_window: ffi::Atom, - dnd: Dnd, - ime_receiver: ImeReceiver, ime_sender: ImeSender, - ime: RefCell, - randr_event_offset: c_int, - windows: RefCell>>, - devices: RefCell>, - xi2ext: XExtension, - pending_wakeup: Arc, root: ffi::Window, - // A dummy, `InputOnly` window that we can use to receive wakeup events and interrupt blocking - // `XNextEvent` calls. - wakeup_dummy_window: ffi::Window, + ime: RefCell, + windows: RefCell>>, + pending_redraws: Arc>>, + _marker: ::std::marker::PhantomData +} + +pub struct EventLoop { + inner_loop: ::calloop::EventLoop<()>, + _x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>, + _user_source: ::calloop::Source<::calloop::channel::Channel>, + pending_user_events: Rc>>, + user_sender: ::calloop::channel::Sender, + pending_events: Rc>>>, + target: Rc> } #[derive(Clone)] -pub struct EventLoopProxy { - pending_wakeup: Weak, - xconn: Weak, - wakeup_dummy_window: ffi::Window, +pub struct EventLoopProxy { + user_sender: ::calloop::channel::Sender, } -impl EventLoop { - pub fn new(xconn: Arc) -> EventLoop { +impl EventLoop { + pub fn new(xconn: Arc) -> EventLoop { let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") }; @@ -126,47 +121,88 @@ impl EventLoop { xconn.update_cached_wm_info(root); - let wakeup_dummy_window = unsafe { - let (x, y, w, h) = (10, 10, 10, 10); - let (border_w, border_px, background_px) = (0, 0, 0); - (xconn.xlib.XCreateSimpleWindow)( - xconn.display, + let target = Rc::new(RootELW{ + p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { + ime, root, - x, - y, - w, - h, - border_w, - border_px, - background_px, - ) - }; + windows: Default::default(), + _marker: ::std::marker::PhantomData, + ime_sender, + xconn, + wm_delete_window, + pending_redraws: Default::default(), + }), + _marker: ::std::marker::PhantomData + }); - let result = EventLoop { - xconn, - wm_delete_window, + // A calloop event loop to drive us + let inner_loop = ::calloop::EventLoop::new().unwrap(); + + // Handle user events + let pending_user_events = Rc::new(RefCell::new(VecDeque::new())); + let pending_user_events2 = pending_user_events.clone(); + + let (user_sender, user_channel) = ::calloop::channel::channel(); + + let _user_source = inner_loop.handle().insert_source(user_channel, move |evt, &mut()| { + if let ::calloop::channel::Event::Msg(msg) = evt { + pending_user_events2.borrow_mut().push_back(msg); + } + }).unwrap(); + + // Handle X11 events + let pending_events: Rc>> = Default::default(); + + let mut processor = EventProcessor { + target: target.clone(), dnd, - ime_receiver, - ime_sender, - ime, - randr_event_offset, - windows: Default::default(), devices: Default::default(), + randr_event_offset, + ime_receiver, xi2ext, - pending_wakeup: Default::default(), - root, - wakeup_dummy_window, }; // Register for device hotplug events // (The request buffer is flushed during `init_device`) - result.xconn.select_xinput_events( + get_xtarget(&target).xconn.select_xinput_events( root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask, ).queue(); - result.init_device(ffi::XIAllDevices); + processor.init_device(ffi::XIAllDevices); + + // Setup the X11 event source + let mut x11_events = ::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd); + x11_events.set_interest(::calloop::mio::Ready::readable()); + let _x11_source = inner_loop.handle().insert_source( + x11_events, + { + let pending_events = pending_events.clone(); + let mut callback = move |event| { + pending_events.borrow_mut().push_back(event); + }; + move |evt, &mut ()| { + if evt.readiness.is_readable() { + // process all pending events + let mut xev = unsafe { mem::uninitialized() }; + while unsafe { processor.poll_one_event(&mut xev) } { + processor.process_event(&mut xev, &mut callback); + } + } + } + } + ).unwrap(); + + let result = EventLoop { + inner_loop, + pending_events, + _x11_source, + _user_source, + user_sender, + pending_user_events, + target + }; result } @@ -174,1046 +210,150 @@ impl EventLoop { /// Returns the `XConnection` of this events loop. #[inline] pub fn x_connection(&self) -> &Arc { - &self.xconn + &get_xtarget(&self.target).xconn } - pub fn create_proxy(&self) -> EventLoopProxy { + pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - pending_wakeup: Arc::downgrade(&self.pending_wakeup), - xconn: Arc::downgrade(&self.xconn), - wakeup_dummy_window: self.wakeup_dummy_window, + user_sender: self.user_sender.clone(), } } - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(Event) - { - let mut xev = unsafe { mem::uninitialized() }; - loop { - // Get next event - unsafe { - // Ensure XNextEvent won't block - let count = (self.xconn.xlib.XPending)(self.xconn.display); - if count == 0 { - break; - } - - (self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev); - } - self.process_event(&mut xev, &mut callback); - } + pub(crate) fn window_target(&self) -> &RootELW { + &self.target } - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(Event) -> ControlFlow + pub fn run_return(&mut self, mut callback: F) + where F: FnMut(Event, &RootELW, &mut ControlFlow) { - let mut xev = unsafe { mem::uninitialized() }; + let mut control_flow = ControlFlow::default(); + let wt = get_xtarget(&self.target); loop { - unsafe { (self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev) }; // Blocks as necessary - - let mut control_flow = ControlFlow::Continue; - - // Track whether or not `Break` was returned when processing the event. + // Empty the event buffer { - let mut cb = |event| { - if let ControlFlow::Break = callback(event) { - control_flow = ControlFlow::Break; - } - }; - - self.process_event(&mut xev, &mut cb); - } - - if let ControlFlow::Break = control_flow { - break; - } - } - } - - fn process_event(&mut self, xev: &mut ffi::XEvent, mut callback: F) - where F: FnMut(Event) - { - // XFilterEvent tells us when an event has been discarded by the input method. - // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, - // along with an extra copy of the KeyRelease events. This also prevents backspace and - // arrow keys from being detected twice. - if ffi::True == unsafe { (self.xconn.xlib.XFilterEvent)( - xev, - { let xev: &ffi::XAnyEvent = xev.as_ref(); xev.window } - ) } { - return; - } - - let event_type = xev.get_type(); - match event_type { - ffi::MappingNotify => { - unsafe { (self.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); } - self.xconn.check_errors().expect("Failed to call XRefreshKeyboardMapping"); - } - - ffi::ClientMessage => { - let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); - - let window = client_msg.window; - let window_id = mkwid(window); - - if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window { - callback(Event::WindowEvent { window_id, event: WindowEvent::CloseRequested }); - } else if client_msg.message_type == self.dnd.atoms.enter { - let source_window = client_msg.data.get_long(0) as c_ulong; - let flags = client_msg.data.get_long(1); - let version = flags >> 24; - self.dnd.version = Some(version); - let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; - if !has_more_types { - let type_list = vec![ - client_msg.data.get_long(2) as c_ulong, - client_msg.data.get_long(3) as c_ulong, - client_msg.data.get_long(4) as c_ulong - ]; - self.dnd.type_list = Some(type_list); - } else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } { - self.dnd.type_list = Some(more_types); - } - } else if client_msg.message_type == self.dnd.atoms.position { - // This event occurs every time the mouse moves while a file's being dragged - // over our window. We emit HoveredFile in response; while the macOS backend - // does that upon a drag entering, XDND doesn't have access to the actual drop - // data until this event. For parity with other platforms, we only emit - // `HoveredFile` the first time, though if winit's API is later extended to - // supply position updates with `HoveredFile` or another event, implementing - // that here would be trivial. - - let source_window = client_msg.data.get_long(0) as c_ulong; - - // Equivalent to `(x << shift) | y` - // where `shift = mem::size_of::() * 8` - // Note that coordinates are in "desktop space", not "window space" - // (in X11 parlance, they're root window coordinates) - //let packed_coordinates = client_msg.data.get_long(2); - //let shift = mem::size_of::() * 8; - //let x = packed_coordinates >> shift; - //let y = packed_coordinates & !(x << shift); - - // By our own state flow, `version` should never be `None` at this point. - let version = self.dnd.version.unwrap_or(5); - - // Action is specified in versions 2 and up, though we don't need it anyway. - //let action = client_msg.data.get_long(4); - - let accepted = if let Some(ref type_list) = self.dnd.type_list { - type_list.contains(&self.dnd.atoms.uri_list) - } else { - false - }; - - if accepted { - self.dnd.source_window = Some(source_window); - unsafe { - if self.dnd.result.is_none() { - let time = if version >= 1 { - client_msg.data.get_long(3) as c_ulong - } else { - // In version 0, time isn't specified - ffi::CurrentTime - }; - // This results in the `SelectionNotify` event below - self.dnd.convert_selection(window, time); - } - self.dnd.send_status(window, source_window, DndState::Accepted) - .expect("Failed to send `XdndStatus` message."); - } - } else { - unsafe { - self.dnd.send_status(window, source_window, DndState::Rejected) - .expect("Failed to send `XdndStatus` message."); - } - self.dnd.reset(); - } - } else if client_msg.message_type == self.dnd.atoms.drop { - let (source_window, state) = if let Some(source_window) = self.dnd.source_window { - if let Some(Ok(ref path_list)) = self.dnd.result { - for path in path_list { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::DroppedFile(path.clone()), - }); - } - } - (source_window, DndState::Accepted) - } else { - // `source_window` won't be part of our DND state if we already rejected the drop in our - // `XdndPosition` handler. - let source_window = client_msg.data.get_long(0) as c_ulong; - (source_window, DndState::Rejected) - }; - unsafe { - self.dnd.send_finished(window, source_window, state) - .expect("Failed to send `XdndFinished` message."); - } - self.dnd.reset(); - } else if client_msg.message_type == self.dnd.atoms.leave { - self.dnd.reset(); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::HoveredFileCancelled, - }); - } else if self.pending_wakeup.load(atomic::Ordering::Relaxed) { - self.pending_wakeup.store(false, atomic::Ordering::Relaxed); - callback(Event::Awakened); + let mut guard = self.pending_events.borrow_mut(); + for evt in guard.drain(..) { + sticky_exit_callback(evt, &self.target, &mut control_flow, &mut callback); } } - ffi::SelectionNotify => { - let xsel: &ffi::XSelectionEvent = xev.as_ref(); - - let window = xsel.requestor; - let window_id = mkwid(window); - - if xsel.property == self.dnd.atoms.selection { - let mut result = None; - - // This is where we receive data from drag and drop - if let Ok(mut data) = unsafe { self.dnd.read_data(window) } { - let parse_result = self.dnd.parse_data(&mut data); - if let Ok(ref path_list) = parse_result { - for path in path_list { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::HoveredFile(path.clone()), - }); - } - } - result = Some(parse_result); - } - - self.dnd.result = result; + // Empty the user event buffer + { + let mut guard = self.pending_user_events.borrow_mut(); + for evt in guard.drain(..) { + sticky_exit_callback( + ::event::Event::UserEvent(evt), + &self.target, + &mut control_flow, + &mut callback + ); } } - - ffi::ConfigureNotify => { - #[derive(Debug, Default)] - struct Events { - resized: Option, - moved: Option, - dpi_changed: Option, + // Empty the redraw requests + { + let mut guard = wt.pending_redraws.lock().unwrap(); + for wid in guard.drain() { + sticky_exit_callback( + Event::WindowEvent { + window_id: ::window::WindowId(super::WindowId::X(wid)), + event: WindowEvent::RedrawRequested + }, + &self.target, + &mut control_flow, + &mut callback + ); } + } + // send Events cleared + { + sticky_exit_callback( + ::event::Event::EventsCleared, + &self.target, + &mut control_flow, + &mut callback + ); + } - let xev: &ffi::XConfigureEvent = xev.as_ref(); - let xwindow = xev.window; - let events = self.with_window(xwindow, |window| { - // So apparently... - // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root - // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent - // https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5 - // We don't want to send `Moved` when this is false, since then every `Resized` - // (whether the window moved or not) is accompanied by an extraneous `Moved` event - // that has a position relative to the parent window. - let is_synthetic = xev.send_event == ffi::True; + // flush the X11 connection + unsafe { (wt.xconn.xlib.XFlush)(wt.xconn.display); } - // These are both in physical space. - let new_inner_size = (xev.width as u32, xev.height as u32); - let new_inner_position = (xev.x as i32, xev.y as i32); - - let mut monitor = window.get_current_monitor(); // This must be done *before* locking! - let mut shared_state_lock = window.shared_state.lock(); - - let (mut resized, moved) = { - let resized = util::maybe_change(&mut shared_state_lock.size, new_inner_size); - let moved = if is_synthetic { - util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position) - } else { - // Detect when frame extents change. - // Since this isn't synthetic, as per the notes above, this position is relative to the - // parent window. - let rel_parent = new_inner_position; - if util::maybe_change(&mut shared_state_lock.inner_position_rel_parent, rel_parent) { - // This ensures we process the next `Moved`. - shared_state_lock.inner_position = None; - // Extra insurance against stale frame extents. - shared_state_lock.frame_extents = None; - } - false - }; - (resized, moved) - }; - - let mut events = Events::default(); - - let new_outer_position = if moved || shared_state_lock.position.is_none() { - // We need to convert client area position to window position. - let frame_extents = shared_state_lock.frame_extents - .as_ref() - .cloned() - .unwrap_or_else(|| { - let frame_extents = self.xconn.get_frame_extents_heuristic(xwindow, self.root); - shared_state_lock.frame_extents = Some(frame_extents.clone()); - frame_extents - }); - let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1); - shared_state_lock.position = Some(outer); - if moved { - let logical_position = LogicalPosition::from_physical(outer, monitor.hidpi_factor); - events.moved = Some(WindowEvent::Moved(logical_position)); - } - outer + match control_flow { + ControlFlow::Exit => break, + ControlFlow::Poll => { + // non-blocking dispatch + self.inner_loop.dispatch(Some(::std::time::Duration::from_millis(0)), &mut ()).unwrap(); + control_flow = ControlFlow::default(); + callback(::event::Event::NewEvents(::event::StartCause::Poll), &self.target, &mut control_flow); + }, + ControlFlow::Wait => { + self.inner_loop.dispatch(None, &mut ()).unwrap(); + control_flow = ControlFlow::default(); + callback( + ::event::Event::NewEvents(::event::StartCause::WaitCancelled { + start: ::std::time::Instant::now(), + requested_resume: None + }), + &self.target, + &mut control_flow + ); + }, + ControlFlow::WaitUntil(deadline) => { + let start = ::std::time::Instant::now(); + // compute the blocking duration + let duration = if deadline > start { + deadline - start } else { - shared_state_lock.position.unwrap() + ::std::time::Duration::from_millis(0) }; - - if is_synthetic { - // If we don't use the existing adjusted value when available, then the user can screw up the - // resizing by dragging across monitors *without* dropping the window. - let (width, height) = shared_state_lock.dpi_adjusted - .unwrap_or_else(|| (xev.width as f64, xev.height as f64)); - let last_hidpi_factor = shared_state_lock.guessed_dpi - .take() - .unwrap_or_else(|| { - shared_state_lock.last_monitor - .as_ref() - .map(|last_monitor| last_monitor.hidpi_factor) - .unwrap_or(1.0) - }); - let new_hidpi_factor = { - let window_rect = util::AaRect::new(new_outer_position, new_inner_size); - monitor = self.xconn.get_monitor_for_window(Some(window_rect)); - let new_hidpi_factor = monitor.hidpi_factor; - shared_state_lock.last_monitor = Some(monitor.clone()); - new_hidpi_factor - }; - if last_hidpi_factor != new_hidpi_factor { - events.dpi_changed = Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor)); - let (new_width, new_height, flusher) = window.adjust_for_dpi( - last_hidpi_factor, - new_hidpi_factor, - width, - height, - ); - flusher.queue(); - shared_state_lock.dpi_adjusted = Some((new_width, new_height)); - // if the DPI factor changed, force a resize event to ensure the logical - // size is computed with the right DPI factor - resized = true; - } - } - - // This is a hack to ensure that the DPI adjusted resize is actually applied on all WMs. KWin - // doesn't need this, but Xfwm does. The hack should not be run on other WMs, since tiling - // WMs constrain the window size, making the resize fail. This would cause an endless stream of - // XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU. - if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { - let rounded_size = (adjusted_size.0.round() as u32, adjusted_size.1.round() as u32); - if new_inner_size == rounded_size || !util::wm_name_is_one_of(&["Xfwm4"]) { - // When this finally happens, the event will not be synthetic. - shared_state_lock.dpi_adjusted = None; - } else { - unsafe { - (self.xconn.xlib.XResizeWindow)( - self.xconn.display, - xwindow, - rounded_size.0 as c_uint, - rounded_size.1 as c_uint, - ); - } - } - } - - if resized { - let logical_size = LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor); - events.resized = Some(WindowEvent::Resized(logical_size)); - } - - events - }); - - if let Some(events) = events { - let window_id = mkwid(xwindow); - if let Some(event) = events.dpi_changed { - callback(Event::WindowEvent { window_id, event }); - } - if let Some(event) = events.resized { - callback(Event::WindowEvent { window_id, event }); - } - if let Some(event) = events.moved { - callback(Event::WindowEvent { window_id, event }); - } - } - } - - ffi::ReparentNotify => { - let xev: &ffi::XReparentEvent = xev.as_ref(); - - // This is generally a reliable way to detect when the window manager's been - // replaced, though this event is only fired by reparenting window managers - // (which is almost all of them). Failing to correctly update WM info doesn't - // really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only - // effect is that we waste some time trying to query unsupported properties. - self.xconn.update_cached_wm_info(self.root); - - self.with_window(xev.window, |window| { - window.invalidate_cached_frame_extents(); - }); - } - - ffi::DestroyNotify => { - let xev: &ffi::XDestroyWindowEvent = xev.as_ref(); - - let window = xev.window; - let window_id = mkwid(window); - - // In the event that the window's been destroyed without being dropped first, we - // cleanup again here. - self.windows.borrow_mut().remove(&WindowId(window)); - - // Since all XIM stuff needs to happen from the same thread, we destroy the input - // context here instead of when dropping the window. - self.ime - .borrow_mut() - .remove_context(window) - .expect("Failed to destroy input context"); - - callback(Event::WindowEvent { window_id, event: WindowEvent::Destroyed }); - } - - ffi::Expose => { - let xev: &ffi::XExposeEvent = xev.as_ref(); - - let window = xev.window; - let window_id = mkwid(window); - - callback(Event::WindowEvent { window_id, event: WindowEvent::Redraw }); - } - - ffi::KeyPress | ffi::KeyRelease => { - use events::ElementState::{Pressed, Released}; - - // Note that in compose/pre-edit sequences, this will always be Released. - let state = if xev.get_type() == ffi::KeyPress { - Pressed - } else { - Released - }; - - let xkev: &mut ffi::XKeyEvent = xev.as_mut(); - - let window = xkev.window; - let window_id = mkwid(window); - - // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable - // value, though this should only be an issue under multiseat configurations. - let device = util::VIRTUAL_CORE_KEYBOARD; - let device_id = mkdid(device); - - // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with - // a keycode of 0. - if xkev.keycode != 0 { - let modifiers = ModifiersState { - alt: xkev.state & ffi::Mod1Mask != 0, - shift: xkev.state & ffi::ShiftMask != 0, - ctrl: xkev.state & ffi::ControlMask != 0, - logo: xkev.state & ffi::Mod4Mask != 0, - }; - - let keysym = unsafe { - let mut keysym = 0; - (self.xconn.xlib.XLookupString)( - xkev, - ptr::null_mut(), - 0, - &mut keysym, - ptr::null_mut(), + self.inner_loop.dispatch(Some(duration), &mut ()).unwrap(); + control_flow = ControlFlow::default(); + let now = std::time::Instant::now(); + if now < deadline { + callback( + ::event::Event::NewEvents(::event::StartCause::WaitCancelled { + start, + requested_resume: Some(deadline) + }), + &self.target, + &mut control_flow ); - self.xconn.check_errors().expect("Failed to lookup keysym"); - keysym - }; - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id, - input: KeyboardInput { - state, - scancode: xkev.keycode - 8, - virtual_keycode, - modifiers, - }, - } - }); - } - - if state == Pressed { - let written = if let Some(ic) = self.ime.borrow().get_context(window) { - self.xconn.lookup_utf8(ic, xkev) } else { - return; - }; - - for chr in written.chars() { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::ReceivedCharacter(chr), - }; - callback(event); - } - } - } - - ffi::GenericEvent => { - let guard = if let Some(e) = GenericEventCookie::from_event(&self.xconn, *xev) { e } else { return }; - let xev = &guard.cookie; - if self.xi2ext.opcode != xev.extension { - return; - } - - use events::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion}; - use events::ElementState::{Pressed, Released}; - use events::MouseButton::{Left, Right, Middle, Other}; - use events::MouseScrollDelta::LineDelta; - use events::{Touch, TouchPhase}; - - match xev.evtype { - ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); - if (xev.flags & ffi::XIPointerEmulated) != 0 { - // Deliver multi-touch events instead of emulated mouse events. - let return_now = self - .with_window(xev.event, |window| window.multitouch) - .unwrap_or(true); - if return_now { return; } - } - - let modifiers = ModifiersState::from(xev.mods); - - let state = if xev.evtype == ffi::XI_ButtonPress { - Pressed - } else { - Released - }; - match xev.detail as u32 { - ffi::Button1 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Left, - modifiers, - }, + callback( + ::event::Event::NewEvents(::event::StartCause::ResumeTimeReached { + start, + requested_resume: deadline }), - ffi::Button2 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Middle, - modifiers, - }, - }), - ffi::Button3 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Right, - modifiers, - }, - }), - - // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. - // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in - // turn) as axis motion, so we don't otherwise special-case these button presses. - 4 | 5 | 6 | 7 => if xev.flags & ffi::XIPointerEmulated == 0 { - callback(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match xev.detail { - 4 => LineDelta(0.0, 1.0), - 5 => LineDelta(0.0, -1.0), - 6 => LineDelta(-1.0, 0.0), - 7 => LineDelta(1.0, 0.0), - _ => unreachable!(), - }, - phase: TouchPhase::Moved, - modifiers, - }, - }); - }, - - x => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Other(x as u8), - modifiers, - }, - }), - } - } - ffi::XI_Motion => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let device_id = mkdid(xev.deviceid); - let window_id = mkwid(xev.event); - let new_cursor_pos = (xev.event_x, xev.event_y); - - let modifiers = ModifiersState::from(xev.mods); - - let cursor_moved = self.with_window(xev.event, |window| { - let mut shared_state_lock = window.shared_state.lock(); - util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) - }); - if cursor_moved == Some(true) { - let dpi_factor = self.with_window(xev.event, |window| { - window.get_hidpi_factor() - }); - if let Some(dpi_factor) = dpi_factor { - let position = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - modifiers, - }, - }); - } else { - return; - } - } else if cursor_moved.is_none() { - return; - } - - // More gymnastics, for self.devices - let mut events = Vec::new(); - { - let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; - let mut devices = self.devices.borrow_mut(); - let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { - Some(device) => device, - None => return, - }; - - let mut value = xev.valuators.values; - for i in 0..xev.valuators.mask_len*8 { - if ffi::XIMaskIsSet(mask, i) { - let x = unsafe { *value }; - if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) { - let delta = (x - info.position) / info.increment; - info.position = x; - events.push(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match info.orientation { - ScrollOrientation::Horizontal => LineDelta(delta as f32, 0.0), - // X11 vertical scroll coordinates are opposite to winit's - ScrollOrientation::Vertical => LineDelta(0.0, -delta as f32), - }, - phase: TouchPhase::Moved, - modifiers, - }, - }); - } else { - events.push(Event::WindowEvent { - window_id, - event: AxisMotion { - device_id, - axis: i as u32, - value: unsafe { *value }, - }, - }); - } - value = unsafe { value.offset(1) }; - } - } - } - for event in events { - callback(event); - } - } - - ffi::XI_Enter => { - let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) }; - - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); - - if let Some(all_info) = DeviceInfo::get(&self.xconn, ffi::XIAllDevices) { - let mut devices = self.devices.borrow_mut(); - for device_info in all_info.iter() { - if device_info.deviceid == xev.sourceid - // This is needed for resetting to work correctly on i3, and - // presumably some other WMs. On those, `XI_Enter` doesn't include - // the physical device ID, so both `sourceid` and `deviceid` are - // the virtual device. - || device_info.attachment == xev.sourceid { - let device_id = DeviceId(device_info.deviceid); - if let Some(device) = devices.get_mut(&device_id) { - device.reset_scroll_position(device_info); - } - } - } - } - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); - - if let Some(dpi_factor) = self.with_window(xev.event, |window| { - window.get_hidpi_factor() - }) { - let position = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); - - // The mods field on this event isn't actually populated, so query the - // pointer device. In the future, we can likely remove this round-trip by - // relying on `Xkb` for modifier values. - // - // This needs to only be done after confirming the window still exists, - // since otherwise we risk getting a `BadWindow` error if the window was - // dropped with queued events. - let modifiers = self.xconn - .query_pointer(xev.event, xev.deviceid) - .expect("Failed to query pointer device") - .get_modifier_state(); - - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - modifiers, - }, - }); - } - } - ffi::XI_Leave => { - let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; - - // Leave, FocusIn, and FocusOut can be received by a window that's already - // been destroyed, which the user presumably doesn't want to deal with. - let window_closed = !self.window_exists(xev.event); - if !window_closed { - callback(Event::WindowEvent { - window_id: mkwid(xev.event), - event: CursorLeft { device_id: mkdid(xev.deviceid) }, - }); - } - } - ffi::XI_FocusIn => { - let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - - let dpi_factor = match self.with_window(xev.event, |window| { - window.get_hidpi_factor() - }) { - Some(dpi_factor) => dpi_factor, - None => return, - }; - let window_id = mkwid(xev.event); - - self.ime - .borrow_mut() - .focus(xev.event) - .expect("Failed to focus input context"); - - callback(Event::WindowEvent { window_id, event: Focused(true) }); - - // The deviceid for this event is for a keyboard instead of a pointer, - // so we have to do a little extra work. - let pointer_id = self.devices - .borrow() - .get(&DeviceId(xev.deviceid)) - .map(|device| device.attachment) - .unwrap_or(2); - - let position = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, + &self.target, + &mut control_flow ); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position, - modifiers: ModifiersState::from(xev.mods), - } - }); - } - ffi::XI_FocusOut => { - let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - if !self.window_exists(xev.event) { return; } - self.ime - .borrow_mut() - .unfocus(xev.event) - .expect("Failed to unfocus input context"); - callback(Event::WindowEvent { - window_id: mkwid(xev.event), - event: Focused(false), - }) - } - - ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - let phase = match xev.evtype { - ffi::XI_TouchBegin => TouchPhase::Started, - ffi::XI_TouchUpdate => TouchPhase::Moved, - ffi::XI_TouchEnd => TouchPhase::Ended, - _ => unreachable!() - }; - let dpi_factor = self.with_window(xev.event, |window| { - window.get_hidpi_factor() - }); - if let Some(dpi_factor) = dpi_factor { - let location = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid), - phase, - location, - id: xev.detail as u64, - }), - }) - } - } - - ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - if xev.flags & ffi::XIPointerEmulated == 0 { - callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Button { - button: xev.detail as u32, - state: match xev.evtype { - ffi::XI_RawButtonPress => Pressed, - ffi::XI_RawButtonRelease => Released, - _ => unreachable!(), - }, - }}); - } - } - - ffi::XI_RawMotion => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - let did = mkdid(xev.deviceid); - - let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; - let mut value = xev.raw_values; - let mut mouse_delta = (0.0, 0.0); - let mut scroll_delta = (0.0, 0.0); - for i in 0..xev.valuators.mask_len*8 { - if ffi::XIMaskIsSet(mask, i) { - let x = unsafe { *value }; - // We assume that every XInput2 device with analog axes is a pointing device emitting - // relative coordinates. - match i { - 0 => mouse_delta.0 = x, - 1 => mouse_delta.1 = x, - 2 => scroll_delta.0 = x as f32, - 3 => scroll_delta.1 = x as f32, - _ => {}, - } - callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion { - axis: i as u32, - value: x, - }}); - value = unsafe { value.offset(1) }; - } - } - if mouse_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseMotion { - delta: mouse_delta, - }}); - } - if scroll_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseWheel { - delta: LineDelta(scroll_delta.0, scroll_delta.1), - }}); - } - } - - ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - - let state = match xev.evtype { - ffi::XI_RawKeyPress => Pressed, - ffi::XI_RawKeyRelease => Released, - _ => unreachable!(), - }; - - let device_id = xev.sourceid; - let keycode = xev.detail; - if keycode < 8 { return; } - let scancode = (keycode - 8) as u32; - - let keysym = unsafe { - (self.xconn.xlib.XKeycodeToKeysym)( - self.xconn.display, - xev.detail as ffi::KeyCode, - 0, - ) - }; - self.xconn.check_errors().expect("Failed to lookup raw keysym"); - - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - - callback(Event::DeviceEvent { - device_id: mkdid(device_id), - event: DeviceEvent::Key(KeyboardInput { - scancode, - virtual_keycode, - state, - // So, in an ideal world we can use libxkbcommon to get modifiers. - // However, libxkbcommon-x11 isn't as commonly installed as one - // would hope. We can still use the Xkb extension to get - // comprehensive keyboard state updates, but interpreting that - // info manually is going to be involved. - modifiers: ModifiersState::default(), - }), - }); - } - - ffi::XI_HierarchyChanged => { - let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) }; - for info in unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } { - if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { - self.init_device(info.deviceid); - callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added }); - } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) { - callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Removed }); - let mut devices = self.devices.borrow_mut(); - devices.remove(&DeviceId(info.deviceid)); - } - } - } - - _ => {} - } - }, - _ => { - if event_type == self.randr_event_offset { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = monitor::invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = self.xconn.get_available_monitors(); - for new_monitor in new_list { - prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| { - if new_monitor.hidpi_factor != prev_monitor.hidpi_factor { - for (window_id, window) in self.windows.borrow().iter() { - if let Some(window) = window.upgrade() { - // Check if the window is on this monitor - let monitor = window.get_current_monitor(); - if monitor.name == new_monitor.name { - callback(Event::WindowEvent { - window_id: mkwid(window_id.0), - event: WindowEvent::HiDpiFactorChanged( - new_monitor.hidpi_factor - ), - }); - let (width, height) = match window.get_inner_size_physical() { - Some(result) => result, - None => continue, - }; - let (_, _, flusher) = window.adjust_for_dpi( - prev_monitor.hidpi_factor, - new_monitor.hidpi_factor, - width as f64, - height as f64, - ); - flusher.queue(); - } - } - } - } - }); - } } } - }, - } - - match self.ime_receiver.try_recv() { - Ok((window_id, x, y)) => { - self.ime.borrow_mut().send_xim_spot(window_id, x, y); - }, - Err(_) => (), - } - } - - fn init_device(&self, device: c_int) { - let mut devices = self.devices.borrow_mut(); - if let Some(info) = DeviceInfo::get(&self.xconn, device) { - for info in info.iter() { - devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); } } + + callback(::event::Event::LoopDestroyed, &self.target, &mut control_flow); } - fn with_window(&self, window_id: ffi::Window, callback: F) -> Option - where F: Fn(&UnownedWindow) -> T + pub fn run(mut self, callback: F) -> ! + where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow) { - let mut deleted = false; - let window_id = WindowId(window_id); - let result = self.windows - .borrow() - .get(&window_id) - .and_then(|window| { - let arc = window.upgrade(); - deleted = arc.is_none(); - arc - }) - .map(|window| callback(&*window)); - if deleted { - // Garbage collection - self.windows.borrow_mut().remove(&window_id); - } - result - } - - fn window_exists(&self, window_id: ffi::Window) -> bool { - self.with_window(window_id, |_| ()).is_some() + self.run_return(callback); + ::std::process::exit(0); } } -impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), EventLoopClosed> { - // Update the `EventLoop`'s `pending_wakeup` flag. - let display = match (self.pending_wakeup.upgrade(), self.xconn.upgrade()) { - (Some(wakeup), Some(display)) => { - wakeup.store(true, atomic::Ordering::Relaxed); - display - }, - _ => return Err(EventLoopClosed), - }; +fn get_xtarget(rt: &RootELW) -> &EventLoopWindowTarget { + if let super::EventLoopWindowTarget::X(ref target) = rt.p { + target + } else { + unreachable!(); + } +} - // Push an event on the X event queue so that methods run_forever will advance. - // - // NOTE: This design is taken from the old `WindowProxy::wakeup` implementation. It - // assumes that X11 is thread safe. Is this true? - // (WARNING: it's probably not true) - display.send_client_msg( - self.wakeup_dummy_window, - self.wakeup_dummy_window, - 0, - None, - [0, 0, 0, 0, 0], - ).flush().expect("Failed to call XSendEvent after wakeup"); - - Ok(()) +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_sender.send(event).map_err(|_| EventLoopClosed) } } @@ -1288,11 +428,11 @@ impl Deref for Window { } impl Window { - pub fn new( - event_loop: &EventLoop, + pub fn new( + event_loop: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes - ) -> Result { + ) -> Result { let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?); event_loop.windows .borrow_mut() @@ -1348,8 +488,8 @@ struct XExtension { first_error_id: c_int, } -fn mkwid(w: ffi::Window) -> ::WindowId { ::WindowId(::platform::WindowId::X(WindowId(w))) } -fn mkdid(w: c_int) -> ::DeviceId { ::DeviceId(::platform::DeviceId::X(DeviceId(w))) } +fn mkwid(w: ffi::Window) -> ::window::WindowId { ::window::WindowId(::platform_impl::WindowId::X(WindowId(w))) } +fn mkdid(w: c_int) -> ::event::DeviceId { ::event::DeviceId(::platform_impl::DeviceId::X(DeviceId(w))) } #[derive(Debug)] struct Device { @@ -1374,10 +514,12 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventLoop, info: &ffi::XIDeviceInfo) -> Self { + fn new(el: &EventProcessor, info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); + let wt = get_xtarget(&el.target); + if Device::physical_device(info) { // Register for global raw events let mask = ffi::XI_RawMotionMask @@ -1386,7 +528,7 @@ impl Device { | ffi::XI_RawKeyPressMask | ffi::XI_RawKeyReleaseMask; // The request buffer is flushed when we poll for events - el.xconn.select_xinput_events(el.root, info.deviceid, mask).queue(); + wt.xconn.select_xinput_events(wt.root, info.deviceid, mask).queue(); // Identify scroll axes for class_ptr in Device::classes(info) { diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index a97809f2..638ebce1 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -2,7 +2,7 @@ use std::os::raw::*; use parking_lot::Mutex; -use {PhysicalPosition, PhysicalSize}; +use dpi::{PhysicalPosition, PhysicalSize}; use super::{util, XConnection, XError}; use super::ffi::{ RRCrtcChangeNotifyMask, @@ -67,7 +67,7 @@ impl MonitorHandle { primary: bool, ) -> Option { let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? }; - let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) }; + let (dimensions, position) = unsafe { (repr.dimensions(), repr.position()) }; let rect = util::AaRect::new(position, dimensions); Some(MonitorHandle { id, @@ -80,32 +80,32 @@ impl MonitorHandle { }) } - pub fn get_name(&self) -> Option { + pub fn name(&self) -> Option { Some(self.name.clone()) } #[inline] - pub fn get_native_identifier(&self) -> u32 { + pub fn native_identifier(&self) -> u32 { self.id as u32 } - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { self.dimensions.into() } - pub fn get_position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { self.position.into() } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { self.hidpi_factor } } impl XConnection { pub fn get_monitor_for_window(&self, window_rect: Option) -> MonitorHandle { - let monitors = self.get_available_monitors(); + let monitors = self.available_monitors(); let default = monitors .get(0) .expect("[winit] Failed to find any monitors using XRandR."); @@ -131,9 +131,14 @@ impl XConnection { fn query_monitor_list(&self) -> Vec { unsafe { let root = (self.xlib.XDefaultRootWindow)(self.display); - // WARNING: this function is supposedly very slow, on the order of hundreds of ms. - // Upon failure, `resources` will be null. - let resources = (self.xrandr.XRRGetScreenResources)(self.display, root); + 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."); } @@ -201,7 +206,7 @@ impl XConnection { } } - pub fn get_available_monitors(&self) -> Vec { + pub fn available_monitors(&self) -> Vec { let mut monitors_lock = MONITORS.lock(); (*monitors_lock) .as_ref() @@ -217,8 +222,8 @@ impl XConnection { } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - self.get_available_monitors() + pub fn primary_monitor(&self) -> MonitorHandle { + self.available_monitors() .into_iter() .find(|monitor| monitor.primary) .expect("[winit] Failed to find any monitors using XRandR.") diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index 1a85b1c8..f3bd1281 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -1,7 +1,7 @@ use std::cmp; use super::*; -use {LogicalPosition, LogicalSize}; +use dpi::{LogicalPosition, LogicalSize}; // Friendly neighborhood axis-aligned rectangle #[derive(Debug, Clone, PartialEq, Eq)] @@ -152,7 +152,7 @@ impl FrameExtentsHeuristic { } impl XConnection { - // This is adequate for get_inner_position + // This is adequate for inner_position pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result { let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() }; unsafe { @@ -171,7 +171,7 @@ impl XConnection { self.check_errors().map(|_| translated_coords) } - // This is adequate for get_inner_size + // This is adequate for inner_size pub fn get_geometry(&self, window: ffi::Window) -> Result { let mut geometry: Geometry = unsafe { mem::uninitialized() }; let _status = unsafe { diff --git a/src/platform_impl/linux/x11/util/icon.rs b/src/platform_impl/linux/x11/util/icon.rs index c19b52a8..9bcaf952 100644 --- a/src/platform_impl/linux/x11/util/icon.rs +++ b/src/platform_impl/linux/x11/util/icon.rs @@ -1,4 +1,4 @@ -use {Icon, Pixel, PIXEL_SIZE}; +use window::{Icon, Pixel, PIXEL_SIZE}; use super::*; impl Pixel { diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 4b75df24..aa530e77 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,7 +1,7 @@ use std::str; use super::*; -use events::ModifiersState; +use event::ModifiersState; pub const VIRTUAL_CORE_POINTER: c_int = 2; pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index e730469b..3abbf849 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,7 +1,7 @@ use std::{env, slice}; use std::str::FromStr; -use validate_hidpi_factor; +use dpi::validate_hidpi_factor; use super::*; pub fn calc_dpi_factor( @@ -51,14 +51,14 @@ impl MonitorRepr { } } - pub unsafe fn get_dimensions(&self) -> (u32, u32) { + pub unsafe fn 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) { + pub unsafe fn position(&self) -> (i32, i32) { match *self { MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32), MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32), @@ -79,6 +79,24 @@ impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr { } impl XConnection { + // Retrieve DPI from Xft.dpi property + pub unsafe fn get_xft_dpi(&self) -> Option { + (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, @@ -101,10 +119,15 @@ impl XConnection { (*output_info).nameLen as usize, ); let name = String::from_utf8_lossy(name_slice).into(); - let hidpi_factor = calc_dpi_factor( - repr.get_dimensions(), - ((*output_info).mm_width as u64, (*output_info).mm_height as u64), - ); + let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() { + dpi / 96. + } else { + calc_dpi_factor( + repr.dimensions(), + ((*output_info).mm_width as u64, (*output_info).mm_height as u64), + ) + }; + (self.xrandr.XRRFreeOutputInfo)(output_info); Some((name, hidpi_factor)) } diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index 47d984e6..81d65eba 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -1,5 +1,3 @@ -use std; - use super::*; pub type Cardinal = c_long; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index db82e0e2..1e5330c7 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,4 +1,5 @@ use std::{cmp, env, mem}; +use std::collections::HashSet; use std::ffi::CString; use std::os::raw::*; use std::path::Path; @@ -7,15 +8,16 @@ use std::sync::Arc; use libc; use parking_lot::Mutex; -use {Icon, MouseCursor, WindowAttributes}; -use CreationError::{self, OsError}; +use error::{ExternalError, NotSupportedError, OsError as RootOsError}; +use window::{Icon, CursorIcon, WindowAttributes}; use dpi::{LogicalPosition, LogicalSize}; use platform_impl::MonitorHandle as PlatformMonitorHandle; -use platform_impl::PlatformSpecificWindowBuilderAttributes; +use platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; +use platform_impl::x11::ime::ImeContextCreationError; use platform_impl::x11::MonitorHandle as X11MonitorHandle; -use window::MonitorHandle as RootMonitorHandle; +use monitor::MonitorHandle as RootMonitorHandle; -use super::{ffi, util, ImeSender, XConnection, XError, WindowId, EventLoop}; +use super::{ffi, util, ImeSender, XConnection, XError, WindowId, EventLoopWindowTarget}; unsafe extern "C" fn visibility_predicate( _display: *mut ffi::Display, @@ -37,11 +39,12 @@ pub struct SharedState { pub guessed_dpi: Option, pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, + pub fullscreen: Option, // Used to restore position after exiting fullscreen. pub restore_position: Option<(i32, i32)>, pub frame_extents: Option, - pub min_dimensions: Option, - pub max_dimensions: Option, + pub min_inner_size: Option, + pub max_inner_size: Option, } impl SharedState { @@ -60,28 +63,28 @@ pub struct UnownedWindow { xwindow: ffi::Window, // never changes root: ffi::Window, // never changes screen_id: i32, // never changes - cursor: Mutex, + cursor: Mutex, cursor_grabbed: Mutex, - cursor_hidden: Mutex, + cursor_visible: Mutex, ime_sender: Mutex, - pub multitouch: bool, // never changes pub shared_state: Mutex, + pending_redraws: Arc<::std::sync::Mutex>>, } impl UnownedWindow { - pub fn new( - event_loop: &EventLoop, + pub fn new( + event_loop: &EventLoopWindowTarget, window_attrs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { + ) -> Result { let xconn = &event_loop.xconn; let root = event_loop.root; - let monitors = xconn.get_available_monitors(); + let monitors = xconn.available_monitors(); let dpi_factor = if !monitors.is_empty() { - let mut dpi_factor = Some(monitors[0].get_hidpi_factor()); + let mut dpi_factor = Some(monitors[0].hidpi_factor()); for monitor in &monitors { - if Some(monitor.get_hidpi_factor()) != dpi_factor { + if Some(monitor.hidpi_factor()) != dpi_factor { dpi_factor = None; } } @@ -93,7 +96,7 @@ impl UnownedWindow { let mut dpi_factor = None; for monitor in &monitors { if monitor.rect.contains_point(x, y) { - dpi_factor = Some(monitor.get_hidpi_factor()); + dpi_factor = Some(monitor.hidpi_factor()); break; } } @@ -102,31 +105,31 @@ impl UnownedWindow { .unwrap_or(1.0) }) } else { - return Err(OsError(format!("No monitors were detected."))); + return Err(os_error!(OsError::XMisc("No monitors were detected."))); }; info!("Guessed window DPI factor: {}", dpi_factor); - let max_dimensions: Option<(u32, u32)> = window_attrs.max_dimensions.map(|size| { + let max_inner_size: Option<(u32, u32)> = window_attrs.max_inner_size.map(|size| { size.to_physical(dpi_factor).into() }); - let min_dimensions: Option<(u32, u32)> = window_attrs.min_dimensions.map(|size| { + let min_inner_size: Option<(u32, u32)> = window_attrs.min_inner_size.map(|size| { size.to_physical(dpi_factor).into() }); let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints - let mut dimensions: (u32, u32) = window_attrs.dimensions + let mut dimensions: (u32, u32) = window_attrs.inner_size .or_else(|| Some((800, 600).into())) .map(|size| size.to_physical(dpi_factor)) .map(Into::into) .unwrap(); - if let Some(max) = max_dimensions { + if let Some(max) = max_inner_size { dimensions.0 = cmp::min(dimensions.0, max.0); dimensions.1 = cmp::min(dimensions.1, max.1); } - if let Some(min) = min_dimensions { + if let Some(min) = min_inner_size { dimensions.0 = cmp::max(dimensions.0, min.0); dimensions.1 = cmp::max(dimensions.1, min.1); } @@ -183,6 +186,14 @@ impl UnownedWindow { None => ffi::CopyFromParent, }, ffi::InputOutput as c_uint, + // TODO: If window wants transparency and `visual_infos` is None, + // we need to find our own visual which has an `alphaMask` which + // is > 0, like we do in glutin. + // + // It is non obvious which masks, if any, we should pass to + // `XGetVisualInfo`. winit doesn't recieve any info about what + // properties the user wants. Users should consider choosing the + // visual themselves as glutin does. match pl_attribs.visual_infos { Some(vi) => vi.visual, None => ffi::CopyFromParent as *mut ffi::Visual, @@ -198,11 +209,11 @@ impl UnownedWindow { root, screen_id, cursor: Default::default(), - cursor_grabbed: Default::default(), - cursor_hidden: Default::default(), + cursor_grabbed: Mutex::new(false), + cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), - multitouch: window_attrs.multitouch, shared_state: SharedState::new(dpi_factor), + pending_redraws: event_loop.pending_redraws.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window @@ -278,27 +289,27 @@ impl UnownedWindow { // set size hints { - let mut min_dimensions = window_attrs.min_dimensions + let mut min_inner_size = window_attrs.min_inner_size .map(|size| size.to_physical(dpi_factor)); - let mut max_dimensions = window_attrs.max_dimensions + let mut max_inner_size = window_attrs.max_inner_size .map(|size| size.to_physical(dpi_factor)); if !window_attrs.resizable { if util::wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); } else { - max_dimensions = Some(dimensions.into()); - min_dimensions = Some(dimensions.into()); + max_inner_size = Some(dimensions.into()); + min_inner_size = Some(dimensions.into()); let mut shared_state_lock = window.shared_state.lock(); - shared_state_lock.min_dimensions = window_attrs.min_dimensions; - shared_state_lock.max_dimensions = window_attrs.max_dimensions; + shared_state_lock.min_inner_size = window_attrs.min_inner_size; + shared_state_lock.max_inner_size = window_attrs.max_inner_size; } } let mut normal_hints = util::NormalHints::new(xconn); normal_hints.set_size(Some(dimensions)); - normal_hints.set_min_size(min_dimensions.map(Into::into)); - normal_hints.set_max_size(max_dimensions.map(Into::into)); + normal_hints.set_min_size(min_inner_size.map(Into::into)); + normal_hints.set_max_size(max_inner_size.map(Into::into)); normal_hints.set_resize_increments(pl_attribs.resize_increments); normal_hints.set_base_size(pl_attribs.base_size); xconn.set_normal_hints(window.xwindow, normal_hints).queue(); @@ -335,13 +346,13 @@ impl UnownedWindow { &mut supported_ptr, ); if supported_ptr == ffi::False { - return Err(OsError(format!("`XkbSetDetectableAutoRepeat` failed"))); + return Err(os_error!(OsError::XMisc("`XkbSetDetectableAutoRepeat` failed"))); } } // Select XInput2 events let mask = { - let mut mask = ffi::XI_MotionMask + let mask = ffi::XI_MotionMask | ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask //| ffi::XI_KeyPressMask @@ -349,12 +360,10 @@ impl UnownedWindow { | ffi::XI_EnterMask | ffi::XI_LeaveMask | ffi::XI_FocusInMask - | ffi::XI_FocusOutMask; - if window_attrs.multitouch { - mask |= ffi::XI_TouchBeginMask - | ffi::XI_TouchUpdateMask - | ffi::XI_TouchEndMask; - } + | ffi::XI_FocusOutMask + | ffi::XI_TouchBeginMask + | ffi::XI_TouchUpdateMask + | ffi::XI_TouchEndMask; mask }; xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask).queue(); @@ -364,7 +373,11 @@ impl UnownedWindow { .borrow_mut() .create_context(window.xwindow); if let Err(err) = result { - return Err(OsError(format!("Failed to create input context: {:?}", err))); + let e = match err { + ImeContextCreationError::XError(err) => OsError::XError(err), + ImeContextCreationError::Null => OsError::XMisc("IME Context creation failed"), + }; + return Err(os_error!(e)); } } @@ -403,18 +416,16 @@ impl UnownedWindow { // We never want to give the user a broken window, since by then, it's too late to handle. xconn.sync_with_server() .map(|_| window) - .map_err(|x_err| OsError( - format!("X server returned error while building window: {:?}", x_err) - )) + .map_err(|x_err| os_error!(OsError::XError(x_err))) } fn logicalize_coords(&self, (x, y): (i32, i32)) -> LogicalPosition { - let dpi = self.get_hidpi_factor(); + let dpi = self.hidpi_factor(); LogicalPosition::from_physical((x, y), dpi) } fn logicalize_size(&self, (width, height): (u32, u32)) -> LogicalSize { - let dpi = self.get_hidpi_factor(); + let dpi = self.hidpi_factor(); LogicalSize::from_physical((width, height), dpi) } @@ -523,9 +534,9 @@ impl UnownedWindow { flusher }, Some(RootMonitorHandle { inner: PlatformMonitorHandle::X(monitor) }) => { - let window_position = self.get_position_physical(); - self.shared_state.lock().restore_position = window_position; - let monitor_origin: (i32, i32) = monitor.get_position().into(); + let window_position = self.outer_position_physical(); + self.shared_state.lock().restore_position = Some(window_position); + let monitor_origin: (i32, i32) = monitor.position().into(); self.set_position_inner(monitor_origin.0, monitor_origin.1).queue(); self.set_fullscreen_hint(true) } @@ -533,25 +544,29 @@ impl UnownedWindow { } } + #[inline] + pub fn fullscreen(&self) -> Option { + self.shared_state.lock().fullscreen.clone() + } + #[inline] pub fn set_fullscreen(&self, monitor: Option) { + self.shared_state.lock().fullscreen = monitor.clone(); self.set_fullscreen_inner(monitor) .flush() .expect("Failed to change window fullscreen state"); self.invalidate_cached_frame_extents(); } - fn get_rect(&self) -> Option { + fn get_rect(&self) -> util::AaRect { // TODO: This might round-trip more times than needed. - if let (Some(position), Some(size)) = (self.get_position_physical(), self.get_outer_size_physical()) { - Some(util::AaRect::new(position, size)) - } else { - None - } + let position = self.outer_position_physical(); + let size = self.outer_size_physical(); + util::AaRect::new(position, size) } #[inline] - pub fn get_current_monitor(&self) -> X11MonitorHandle { + pub fn current_monitor(&self) -> X11MonitorHandle { let monitor = self.shared_state .lock() .last_monitor @@ -559,18 +574,18 @@ impl UnownedWindow { .cloned(); monitor .unwrap_or_else(|| { - let monitor = self.xconn.get_monitor_for_window(self.get_rect()).to_owned(); + let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect())).to_owned(); self.shared_state.lock().last_monitor = Some(monitor.clone()); monitor }) } - pub fn get_available_monitors(&self) -> Vec { - self.xconn.get_available_monitors() + pub fn available_monitors(&self) -> Vec { + self.xconn.available_monitors() } - pub fn get_primary_monitor(&self) -> X11MonitorHandle { - self.xconn.get_primary_monitor() + pub fn primary_monitor(&self) -> X11MonitorHandle { + self.xconn.primary_monitor() } fn set_maximized_inner(&self, maximized: bool) -> util::Flusher { @@ -684,20 +699,18 @@ impl UnownedWindow { } #[inline] - pub fn show(&self) { - unsafe { - (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); - self.xconn.flush_requests() - .expect("Failed to call XMapRaised"); - } - } - - #[inline] - pub fn hide(&self) { - unsafe { - (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); - self.xconn.flush_requests() - .expect("Failed to call XUnmapWindow"); + pub fn set_visible(&self, visible: bool) { + match visible { + true => unsafe { + (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); + self.xconn.flush_requests() + .expect("Failed to call XMapRaised"); + }, + false => unsafe { + (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); + self.xconn.flush_requests() + .expect("Failed to call XUnmapWindow"); + } } } @@ -710,39 +723,40 @@ impl UnownedWindow { (*self.shared_state.lock()).frame_extents.take(); } - pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> { + pub(crate) fn outer_position_physical(&self) -> (i32, i32) { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { - self.get_inner_position_physical() - .map(|(x, y)| extents.inner_pos_to_outer(x, y)) + let (x, y) = self.inner_position_physical(); + extents.inner_pos_to_outer(x, y) } else { self.update_cached_frame_extents(); - self.get_position_physical() + self.outer_position_physical() } } #[inline] - pub fn get_position(&self) -> Option { + pub fn outer_position(&self) -> Result { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { - self.get_inner_position() - .map(|logical| extents.inner_pos_to_outer_logical(logical, self.get_hidpi_factor())) + let logical = self.inner_position().unwrap(); + Ok(extents.inner_pos_to_outer_logical(logical, self.hidpi_factor())) } else { self.update_cached_frame_extents(); - self.get_position() + self.outer_position() } } - pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> { + pub(crate) fn inner_position_physical(&self) -> (i32, i32) { + // This should be okay to unwrap since the only error XTranslateCoordinates can return + // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn.translate_coords(self.xwindow, self.root) - .ok() .map(|coords| (coords.x_rel_root, coords.y_rel_root)) + .unwrap() } #[inline] - pub fn get_inner_position(&self) -> Option { - self.get_inner_position_physical() - .map(|coords| self.logicalize_coords(coords)) + pub fn inner_position(&self) -> Result { + Ok(self.logicalize_coords(self.inner_position_physical())) } pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher { @@ -776,43 +790,44 @@ impl UnownedWindow { } #[inline] - pub fn set_position(&self, logical_position: LogicalPosition) { - let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into(); + pub fn set_outer_position(&self, logical_position: LogicalPosition) { + let (x, y) = logical_position.to_physical(self.hidpi_factor()).into(); self.set_position_physical(x, y); } - pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> { + pub(crate) fn inner_size_physical(&self) -> (u32, u32) { + // This should be okay to unwrap since the only error XGetGeometry can return + // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn.get_geometry(self.xwindow) - .ok() .map(|geo| (geo.width, geo.height)) + .unwrap() } #[inline] - pub fn get_inner_size(&self) -> Option { - self.get_inner_size_physical() - .map(|size| self.logicalize_size(size)) + pub fn inner_size(&self) -> LogicalSize { + self.logicalize_size(self.inner_size_physical()) } - pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> { + pub(crate) fn outer_size_physical(&self) -> (u32, u32) { let extents = self.shared_state.lock().frame_extents.clone(); if let Some(extents) = extents { - self.get_inner_size_physical() - .map(|(w, h)| extents.inner_size_to_outer(w, h)) + let (w, h) = self.inner_size_physical(); + extents.inner_size_to_outer(w, h) } else { self.update_cached_frame_extents(); - self.get_outer_size_physical() + self.outer_size_physical() } } #[inline] - pub fn get_outer_size(&self) -> Option { + pub fn outer_size(&self) -> LogicalSize { let extents = self.shared_state.lock().frame_extents.clone(); if let Some(extents) = extents { - self.get_inner_size() - .map(|logical| extents.inner_size_to_outer_logical(logical, self.get_hidpi_factor())) + let logical = self.inner_size(); + extents.inner_size_to_outer_logical(logical, self.hidpi_factor()) } else { self.update_cached_frame_extents(); - self.get_outer_size() + self.outer_size() } } @@ -830,7 +845,7 @@ impl UnownedWindow { #[inline] pub fn set_inner_size(&self, logical_size: LogicalSize) { - let dpi_factor = self.get_hidpi_factor(); + let dpi_factor = self.hidpi_factor(); let (width, height) = logical_size.to_physical(dpi_factor).into(); self.set_inner_size_physical(width, height); } @@ -843,32 +858,32 @@ impl UnownedWindow { self.xconn.set_normal_hints(self.xwindow, normal_hints).flush() } - pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.update_normal_hints(|normal_hints| normal_hints.set_min_size(dimensions)) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] - pub fn set_min_dimensions(&self, logical_dimensions: Option) { - self.shared_state.lock().min_dimensions = logical_dimensions; + pub fn set_min_inner_size(&self, logical_dimensions: Option) { + self.shared_state.lock().min_inner_size = logical_dimensions; let physical_dimensions = logical_dimensions.map(|logical_dimensions| { - logical_dimensions.to_physical(self.get_hidpi_factor()).into() + logical_dimensions.to_physical(self.hidpi_factor()).into() }); - self.set_min_dimensions_physical(physical_dimensions); + self.set_min_inner_size_physical(physical_dimensions); } - pub(crate) fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.update_normal_hints(|normal_hints| normal_hints.set_max_size(dimensions)) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] - pub fn set_max_dimensions(&self, logical_dimensions: Option) { - self.shared_state.lock().max_dimensions = logical_dimensions; + pub fn set_max_inner_size(&self, logical_dimensions: Option) { + self.shared_state.lock().max_inner_size = logical_dimensions; let physical_dimensions = logical_dimensions.map(|logical_dimensions| { - logical_dimensions.to_physical(self.get_hidpi_factor()).into() + logical_dimensions.to_physical(self.hidpi_factor()).into() }); - self.set_max_dimensions_physical(physical_dimensions); + self.set_max_inner_size_physical(physical_dimensions); } pub(crate) fn adjust_for_dpi( @@ -918,47 +933,47 @@ impl UnownedWindow { let (logical_min, logical_max) = if resizable { let shared_state_lock = self.shared_state.lock(); - (shared_state_lock.min_dimensions, shared_state_lock.max_dimensions) + (shared_state_lock.min_inner_size, shared_state_lock.max_inner_size) } else { - let window_size = self.get_inner_size(); + let window_size = Some(self.inner_size()); (window_size.clone(), window_size) }; - let dpi_factor = self.get_hidpi_factor(); - let min_dimensions = logical_min + let dpi_factor = self.hidpi_factor(); + let min_inner_size = logical_min .map(|logical_size| logical_size.to_physical(dpi_factor)) .map(Into::into); - let max_dimensions = logical_max + let max_inner_size = logical_max .map(|logical_size| logical_size.to_physical(dpi_factor)) .map(Into::into); self.update_normal_hints(|normal_hints| { - normal_hints.set_min_size(min_dimensions); - normal_hints.set_max_size(max_dimensions); + normal_hints.set_min_size(min_inner_size); + normal_hints.set_max_size(max_inner_size); }).expect("Failed to call `XSetWMNormalHints`"); } #[inline] - pub fn get_xlib_display(&self) -> *mut c_void { + pub fn xlib_display(&self) -> *mut c_void { self.xconn.display as _ } #[inline] - pub fn get_xlib_screen_id(&self) -> c_int { + pub fn xlib_screen_id(&self) -> c_int { self.screen_id } #[inline] - pub fn get_xlib_xconnection(&self) -> Arc { + pub fn xlib_xconnection(&self) -> Arc { Arc::clone(&self.xconn) } #[inline] - pub fn get_xlib_window(&self) -> c_ulong { + pub fn xlib_window(&self) -> c_ulong { self.xwindow } #[inline] - pub fn get_xcb_connection(&self) -> *mut c_void { + pub fn xcb_connection(&self) -> *mut c_void { unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ } @@ -983,7 +998,7 @@ impl UnownedWindow { 0 } - fn get_cursor(&self, cursor: MouseCursor) -> ffi::Cursor { + fn get_cursor(&self, cursor: CursorIcon) -> ffi::Cursor { let load = |name: &[u8]| { self.load_cursor(name) }; @@ -997,48 +1012,48 @@ impl UnownedWindow { // // Try the better looking (or more suiting) names first. match cursor { - MouseCursor::Alias => load(b"link\0"), - MouseCursor::Arrow => load(b"arrow\0"), - MouseCursor::Cell => load(b"plus\0"), - MouseCursor::Copy => load(b"copy\0"), - MouseCursor::Crosshair => load(b"crosshair\0"), - MouseCursor::Default => load(b"left_ptr\0"), - MouseCursor::Hand => loadn(&[b"hand2\0", b"hand1\0"]), - MouseCursor::Help => load(b"question_arrow\0"), - MouseCursor::Move => load(b"move\0"), - MouseCursor::Grab => loadn(&[b"openhand\0", b"grab\0"]), - MouseCursor::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), - MouseCursor::Progress => load(b"left_ptr_watch\0"), - MouseCursor::AllScroll => load(b"all-scroll\0"), - MouseCursor::ContextMenu => load(b"context-menu\0"), + CursorIcon::Alias => load(b"link\0"), + CursorIcon::Arrow => load(b"arrow\0"), + CursorIcon::Cell => load(b"plus\0"), + CursorIcon::Copy => load(b"copy\0"), + CursorIcon::Crosshair => load(b"crosshair\0"), + CursorIcon::Default => load(b"left_ptr\0"), + CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]), + CursorIcon::Help => load(b"question_arrow\0"), + CursorIcon::Move => load(b"move\0"), + CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]), + CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), + CursorIcon::Progress => load(b"left_ptr_watch\0"), + CursorIcon::AllScroll => load(b"all-scroll\0"), + CursorIcon::ContextMenu => load(b"context-menu\0"), - MouseCursor::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), - MouseCursor::NotAllowed => load(b"crossed_circle\0"), + CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), + CursorIcon::NotAllowed => load(b"crossed_circle\0"), // Resize cursors - MouseCursor::EResize => load(b"right_side\0"), - MouseCursor::NResize => load(b"top_side\0"), - MouseCursor::NeResize => load(b"top_right_corner\0"), - MouseCursor::NwResize => load(b"top_left_corner\0"), - MouseCursor::SResize => load(b"bottom_side\0"), - MouseCursor::SeResize => load(b"bottom_right_corner\0"), - MouseCursor::SwResize => load(b"bottom_left_corner\0"), - MouseCursor::WResize => load(b"left_side\0"), - MouseCursor::EwResize => load(b"h_double_arrow\0"), - MouseCursor::NsResize => load(b"v_double_arrow\0"), - MouseCursor::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), - MouseCursor::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), - MouseCursor::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), - MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), + CursorIcon::EResize => load(b"right_side\0"), + CursorIcon::NResize => load(b"top_side\0"), + CursorIcon::NeResize => load(b"top_right_corner\0"), + CursorIcon::NwResize => load(b"top_left_corner\0"), + CursorIcon::SResize => load(b"bottom_side\0"), + CursorIcon::SeResize => load(b"bottom_right_corner\0"), + CursorIcon::SwResize => load(b"bottom_left_corner\0"), + CursorIcon::WResize => load(b"left_side\0"), + CursorIcon::EwResize => load(b"h_double_arrow\0"), + CursorIcon::NsResize => load(b"v_double_arrow\0"), + CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), + CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), + CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), + CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), - MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]), - MouseCursor::VerticalText => load(b"vertical-text\0"), + CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]), + CursorIcon::VerticalText => load(b"vertical-text\0"), - MouseCursor::Wait => load(b"watch\0"), + CursorIcon::Wait => load(b"watch\0"), - MouseCursor::ZoomIn => load(b"zoom-in\0"), - MouseCursor::ZoomOut => load(b"zoom-out\0"), + CursorIcon::ZoomIn => load(b"zoom-in\0"), + CursorIcon::ZoomOut => load(b"zoom-out\0"), } } @@ -1053,9 +1068,9 @@ impl UnownedWindow { } #[inline] - pub fn set_cursor(&self, cursor: MouseCursor) { + pub fn set_cursor_icon(&self, cursor: CursorIcon) { *self.cursor.lock() = cursor; - if !*self.cursor_hidden.lock() { + if *self.cursor_visible.lock() { self.update_cursor(self.get_cursor(cursor)); } } @@ -1099,7 +1114,7 @@ impl UnownedWindow { } #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { let mut grabbed_lock = self.cursor_grabbed.lock(); if grab == *grabbed_lock { return Ok(()); } unsafe { @@ -1143,10 +1158,10 @@ impl UnownedWindow { ffi::GrabNotViewable => Err("Cursor could not be grabbed: grab location not viewable"), ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"), _ => unreachable!(), - }.map_err(|err| err.to_owned()) + }.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err)))) } else { self.xconn.flush_requests() - .map_err(|err| format!("Failed to call `XUngrabPointer`: {:?}", err)) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))) }; if result.is_ok() { *grabbed_lock = grab; @@ -1155,25 +1170,25 @@ impl UnownedWindow { } #[inline] - pub fn hide_cursor(&self, hide: bool) { - let mut hidden_lock = self.cursor_hidden.lock(); - if hide == *hidden_lock {return; } - let cursor = if hide { - self.create_empty_cursor().expect("Failed to create empty cursor") - } else { + pub fn set_cursor_visible(&self, visible: bool) { + let mut visible_lock = self.cursor_visible.lock(); + if visible == *visible_lock {return; } + let cursor = if visible { self.get_cursor(*self.cursor.lock()) + } else { + self.create_empty_cursor().expect("Failed to create empty cursor") }; - *hidden_lock = hide; - drop(hidden_lock); + *visible_lock = visible; + drop(visible_lock); self.update_cursor(cursor); } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - self.get_current_monitor().hidpi_factor + pub fn hidpi_factor(&self) -> f64 { + self.current_monitor().hidpi_factor } - pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> { + pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { unsafe { (self.xconn.xlib.XWarpPointer)( self.xconn.display, @@ -1186,28 +1201,33 @@ impl UnownedWindow { x, y, ); - self.xconn.flush_requests().map_err(|e| format!("`XWarpPointer` failed: {:?}", e)) + self.xconn.flush_requests().map_err(|e| ExternalError::Os(os_error!(OsError::XError(e)))) } } #[inline] - pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> { - let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into(); + pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), ExternalError> { + let (x, y) = logical_position.to_physical(self.hidpi_factor()).into(); self.set_cursor_position_physical(x, y) } - pub(crate) fn set_ime_spot_physical(&self, x: i32, y: i32) { + pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) { let _ = self.ime_sender .lock() .send((self.xwindow, x as i16, y as i16)); } #[inline] - pub fn set_ime_spot(&self, logical_spot: LogicalPosition) { - let (x, y) = logical_spot.to_physical(self.get_hidpi_factor()).into(); - self.set_ime_spot_physical(x, y); + pub fn set_ime_position(&self, logical_spot: LogicalPosition) { + let (x, y) = logical_spot.to_physical(self.hidpi_factor()).into(); + self.set_ime_position_physical(x, y); } #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow) } + + #[inline] + pub fn request_redraw(&self) { + self.pending_redraws.lock().unwrap().insert(WindowId(self.xwindow)); + } } diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index c0f1e7d2..61e441bd 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,6 +1,7 @@ use std::ptr; use std::fmt; use std::error::Error; +use std::os::raw::c_int; use libc; use parking_lot::Mutex; @@ -17,7 +18,9 @@ pub struct XConnection { pub xcursor: ffi::Xcursor, pub xinput2: ffi::XInput2, pub xlib_xcb: ffi::Xlib_xcb, + pub xrender: ffi::Xrender, pub display: *mut ffi::Display, + pub x11_fd: c_int, pub latest_error: Mutex>, } @@ -35,6 +38,7 @@ impl XConnection { let xrandr_1_5 = ffi::Xrandr::open().ok(); let xinput2 = ffi::XInput2::open()?; let xlib_xcb = ffi::Xlib_xcb::open()?; + let xrender = ffi::Xrender::open()?; unsafe { (xlib.XInitThreads)() }; unsafe { (xlib.XSetErrorHandler)(error_handler) }; @@ -48,6 +52,11 @@ impl XConnection { display }; + // Get X11 socket file descriptor + let fd = unsafe { + (xlib.XConnectionNumber)(display) + }; + Ok(XConnection { xlib, xrandr, @@ -55,7 +64,9 @@ impl XConnection { xcursor, xinput2, xlib_xcb, + xrender, display, + x11_fd: fd, latest_error: Mutex::new(None), }) } diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs new file mode 100644 index 00000000..2699eedc --- /dev/null +++ b/src/platform_impl/macos/app.rs @@ -0,0 +1,88 @@ +use std::collections::VecDeque; + +use cocoa::{appkit::{self, NSEvent}, base::id}; +use objc::{declare::ClassDecl, runtime::{Class, Object, Sel}}; + +use event::{DeviceEvent, Event}; +use platform_impl::platform::{app_state::AppState, DEVICE_ID, util}; + +pub struct AppClass(pub *const Class); +unsafe impl Send for AppClass {} +unsafe impl Sync for AppClass {} + +lazy_static! { + pub static ref APP_CLASS: AppClass = unsafe { + let superclass = class!(NSApplication); + let mut decl = ClassDecl::new("WinitApp", superclass).unwrap(); + + decl.add_method( + sel!(sendEvent:), + send_event as extern fn(&Object, Sel, id), + ); + + AppClass(decl.register()) + }; +} + +// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. +// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) +// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) +extern fn send_event(this: &Object, _sel: Sel, event: id) { + unsafe { + // For posterity, there are some undocumented event types + // (https://github.com/servo/cocoa-rs/issues/155) + // but that doesn't really matter here. + let event_type = event.eventType(); + let modifier_flags = event.modifierFlags(); + if event_type == appkit::NSKeyUp && util::has_flag( + modifier_flags, + appkit::NSEventModifierFlags::NSCommandKeyMask, + ) { + let key_window: id = msg_send![this, keyWindow]; + let _: () = msg_send![key_window, sendEvent:event]; + } else { + maybe_dispatch_device_event(event); + let superclass = util::superclass(this); + let _: () = msg_send![super(this, superclass), sendEvent:event]; + } + } +} + +unsafe fn maybe_dispatch_device_event(event: id) { + let event_type = event.eventType(); + match event_type { + appkit::NSMouseMoved | + appkit::NSLeftMouseDragged | + appkit::NSOtherMouseDragged | + appkit::NSRightMouseDragged => { + let mut events = VecDeque::with_capacity(3); + + let delta_x = event.deltaX() as f64; + let delta_y = event.deltaY() as f64; + + if delta_x != 0.0 { + events.push_back(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Motion { axis: 0, value: delta_x }, + }); + } + + if delta_y != 0.0 { + events.push_back(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Motion { axis: 1, value: delta_y }, + }); + } + + if delta_x != 0.0 || delta_y != 0.0 { + events.push_back(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseMotion { delta: (delta_x, delta_y) }, + }); + } + + AppState::queue_events(events); + }, + _ => (), + } +} diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs new file mode 100644 index 00000000..8e4ab19d --- /dev/null +++ b/src/platform_impl/macos/app_delegate.rs @@ -0,0 +1,101 @@ +use cocoa::base::id; +use objc::{runtime::{Class, Object, Sel, BOOL, YES}, declare::ClassDecl}; + +use platform_impl::platform::app_state::AppState; + +pub struct AppDelegateClass(pub *const Class); +unsafe impl Send for AppDelegateClass {} +unsafe impl Sync for AppDelegateClass {} + +lazy_static! { + pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap(); + + decl.add_method( + sel!(applicationDidFinishLaunching:), + did_finish_launching as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(applicationDidBecomeActive:), + did_become_active as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillResignActive:), + will_resign_active as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillEnterForeground:), + will_enter_foreground as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationDidEnterBackground:), + did_enter_background as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillTerminate:), + will_terminate as extern fn(&Object, Sel, id), + ); + + AppDelegateClass(decl.register()) + }; +} + +extern fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `didFinishLaunching`"); + AppState::launched(); + trace!("Completed `didFinishLaunching`"); + YES +} + +extern fn did_become_active(_: &Object, _: Sel, _: id) { + trace!("Triggered `didBecomeActive`"); + /*unsafe { + HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(false)) + }*/ + trace!("Completed `didBecomeActive`"); +} + +extern fn will_resign_active(_: &Object, _: Sel, _: id) { + trace!("Triggered `willResignActive`"); + /*unsafe { + HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(true)) + }*/ + trace!("Completed `willResignActive`"); +} + +extern fn will_enter_foreground(_: &Object, _: Sel, _: id) { + trace!("Triggered `willEnterForeground`"); + trace!("Completed `willEnterForeground`"); +} + +extern fn did_enter_background(_: &Object, _: Sel, _: id) { + trace!("Triggered `didEnterBackground`"); + trace!("Completed `didEnterBackground`"); +} + +extern fn will_terminate(_: &Object, _: Sel, _: id) { + trace!("Triggered `willTerminate`"); + /*unsafe { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break + } + let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)]; + if is_winit_window == YES { + events.push(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Destroyed, + }); + } + } + HANDLER.lock().unwrap().handle_nonuser_events(events); + HANDLER.lock().unwrap().terminated(); + }*/ + trace!("Completed `willTerminate`"); +} diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs new file mode 100644 index 00000000..cc9d4b22 --- /dev/null +++ b/src/platform_impl/macos/app_state.rs @@ -0,0 +1,310 @@ +use std::{ + collections::VecDeque, fmt::{self, Debug, Formatter}, + hint::unreachable_unchecked, mem, + sync::{atomic::{AtomicBool, Ordering}, Mutex, MutexGuard}, time::Instant, +}; + +use cocoa::{appkit::NSApp, base::nil}; + +use { + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, + window::WindowId, +}; +use platform_impl::platform::{observer::EventLoopWaker, util::Never}; + +lazy_static! { + static ref HANDLER: Handler = Default::default(); +} + +impl Event { + fn userify(self) -> Event { + self.map_nonuser_event() + // `Never` can't be constructed, so the `UserEvent` variant can't + // be present here. + .unwrap_or_else(|_| unsafe { unreachable_unchecked() }) + } +} + +pub trait EventHandler: Debug { + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow); + fn handle_user_events(&mut self, control_flow: &mut ControlFlow); +} + +struct EventLoopHandler { + callback: F, + will_exit: bool, + window_target: RootWindowTarget, +} + +impl Debug for EventLoopHandler { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.debug_struct("EventLoopHandler") + .field("window_target", &self.window_target) + .finish() + } +} + +impl EventHandler for EventLoopHandler +where + F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + T: 'static, +{ + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { + (self.callback)( + event.userify(), + &self.window_target, + control_flow, + ); + self.will_exit |= *control_flow == ControlFlow::Exit; + if self.will_exit { + *control_flow = ControlFlow::Exit; + } + } + + fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { + let mut will_exit = self.will_exit; + for event in self.window_target.p.receiver.try_iter() { + (self.callback)( + Event::UserEvent(event), + &self.window_target, + control_flow, + ); + will_exit |= *control_flow == ControlFlow::Exit; + if will_exit { + *control_flow = ControlFlow::Exit; + } + } + self.will_exit = will_exit; + } +} + +#[derive(Default)] +struct Handler { + ready: AtomicBool, + in_callback: AtomicBool, + control_flow: Mutex, + control_flow_prev: Mutex, + start_time: Mutex>, + callback: Mutex>>, + pending_events: Mutex>>, + deferred_events: Mutex>>, + pending_redraw: Mutex>, + waker: Mutex, +} + +unsafe impl Send for Handler {} +unsafe impl Sync for Handler {} + +impl Handler { + fn events<'a>(&'a self) -> MutexGuard<'a, VecDeque>> { + self.pending_events.lock().unwrap() + } + + fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque>> { + self.deferred_events.lock().unwrap() + } + + fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec> { + self.pending_redraw.lock().unwrap() + } + + fn waker<'a>(&'a self) -> MutexGuard<'a, EventLoopWaker> { + self.waker.lock().unwrap() + } + + fn is_ready(&self) -> bool { + self.ready.load(Ordering::Acquire) + } + + fn set_ready(&self) { + self.ready.store(true, Ordering::Release); + } + + fn should_exit(&self) -> bool { + *self.control_flow.lock().unwrap() == ControlFlow::Exit + } + + fn get_control_flow_and_update_prev(&self) -> ControlFlow { + let control_flow = self.control_flow.lock().unwrap(); + *self.control_flow_prev.lock().unwrap() = *control_flow; + *control_flow + } + + fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) { + let old = *self.control_flow_prev.lock().unwrap(); + let new = *self.control_flow.lock().unwrap(); + (old, new) + } + + fn get_start_time(&self) -> Option { + *self.start_time.lock().unwrap() + } + + fn update_start_time(&self) { + *self.start_time.lock().unwrap() = Some(Instant::now()); + } + + fn take_events(&self) -> VecDeque> { + mem::replace(&mut *self.events(), Default::default()) + } + + fn take_deferred(&self) -> VecDeque> { + mem::replace(&mut *self.deferred(), Default::default()) + } + + fn should_redraw(&self) -> Vec { + mem::replace(&mut *self.redraw(), Default::default()) + } + + fn get_in_callback(&self) -> bool { + self.in_callback.load(Ordering::Acquire) + } + + fn set_in_callback(&self, in_callback: bool) { + self.in_callback.store(in_callback, Ordering::Release); + } + + fn handle_nonuser_event(&self, event: Event) { + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + callback.handle_nonuser_event( + event, + &mut *self.control_flow.lock().unwrap(), + ); + } + } + + fn handle_user_events(&self) { + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + callback.handle_user_events( + &mut *self.control_flow.lock().unwrap(), + ); + } + } +} + +pub enum AppState {} + +impl AppState { + pub fn set_callback(callback: F, window_target: RootWindowTarget) + where + F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + T: 'static, + { + *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { + callback, + will_exit: false, + window_target, + })); + } + + pub fn exit() { + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(Event::LoopDestroyed); + HANDLER.set_in_callback(false); + } + + pub fn launched() { + HANDLER.set_ready(); + HANDLER.waker().start(); + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init)); + HANDLER.set_in_callback(false); + } + + pub fn wakeup() { + if !HANDLER.is_ready() { return } + let start = HANDLER.get_start_time().unwrap(); + let cause = match HANDLER.get_control_flow_and_update_prev() { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(requested_resume) => { + if Instant::now() >= requested_resume { + StartCause::ResumeTimeReached { + start, + requested_resume, + } + } else { + StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + } + } + }, + ControlFlow::Exit => StartCause::Poll,//panic!("unexpected `ControlFlow::Exit`"), + }; + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(Event::NewEvents(cause)); + HANDLER.set_in_callback(false); + } + + // This is called from multiple threads at present + pub fn queue_redraw(window_id: WindowId) { + let mut pending_redraw = HANDLER.redraw(); + if !pending_redraw.contains(&window_id) { + pending_redraw.push(window_id); + } + } + + pub fn queue_event(event: Event) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("Event queued from different thread: {:#?}", event); + } + HANDLER.events().push_back(event); + } + + pub fn queue_events(mut events: VecDeque>) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("Events queued from different thread: {:#?}", events); + } + HANDLER.events().append(&mut events); + } + + pub fn send_event_immediately(event: Event) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("Event sent from different thread: {:#?}", event); + } + HANDLER.deferred().push_back(event); + if !HANDLER.get_in_callback() { + HANDLER.set_in_callback(true); + for event in HANDLER.take_deferred() { + HANDLER.handle_nonuser_event(event); + } + HANDLER.set_in_callback(false); + } + } + + pub fn cleared() { + if !HANDLER.is_ready() { return } + if !HANDLER.get_in_callback() { + HANDLER.set_in_callback(true); + HANDLER.handle_user_events(); + for event in HANDLER.take_events() { + HANDLER.handle_nonuser_event(event); + } + for window_id in HANDLER.should_redraw() { + HANDLER.handle_nonuser_event(Event::WindowEvent { + window_id, + event: WindowEvent::RedrawRequested, + }); + } + HANDLER.handle_nonuser_event(Event::EventsCleared); + HANDLER.set_in_callback(false); + } + if HANDLER.should_exit() { + let _: () = unsafe { msg_send![NSApp(), stop:nil] }; + return + } + HANDLER.update_start_time(); + match HANDLER.get_old_and_new_control_flow() { + (ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(), + (old, new) if old == new => (), + (_, ControlFlow::Wait) => HANDLER.waker().stop(), + (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), + (_, ControlFlow::Poll) => HANDLER.waker().start(), + } + } +} diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs new file mode 100644 index 00000000..c19c5300 --- /dev/null +++ b/src/platform_impl/macos/event.rs @@ -0,0 +1,273 @@ +use std::os::raw::c_ushort; + +use cocoa::{appkit::{NSEvent, NSEventModifierFlags}, base::id}; + +use event::{ + ElementState, KeyboardInput, + ModifiersState, VirtualKeyCode, WindowEvent, +}; +use platform_impl::platform::DEVICE_ID; + +pub fn char_to_keycode(c: char) -> Option { + // 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' => VirtualKeyCode::A, + 'b' | 'B' => VirtualKeyCode::B, + 'c' | 'C' => VirtualKeyCode::C, + 'd' | 'D' => VirtualKeyCode::D, + 'e' | 'E' => VirtualKeyCode::E, + 'f' | 'F' => VirtualKeyCode::F, + 'g' | 'G' => VirtualKeyCode::G, + 'h' | 'H' => VirtualKeyCode::H, + 'i' | 'I' => VirtualKeyCode::I, + 'j' | 'J' => VirtualKeyCode::J, + 'k' | 'K' => VirtualKeyCode::K, + 'l' | 'L' => VirtualKeyCode::L, + 'm' | 'M' => VirtualKeyCode::M, + 'n' | 'N' => VirtualKeyCode::N, + 'o' | 'O' => VirtualKeyCode::O, + 'p' | 'P' => VirtualKeyCode::P, + 'q' | 'Q' => VirtualKeyCode::Q, + 'r' | 'R' => VirtualKeyCode::R, + 's' | 'S' => VirtualKeyCode::S, + 't' | 'T' => VirtualKeyCode::T, + 'u' | 'U' => VirtualKeyCode::U, + 'v' | 'V' => VirtualKeyCode::V, + 'w' | 'W' => VirtualKeyCode::W, + 'x' | 'X' => VirtualKeyCode::X, + 'y' | 'Y' => VirtualKeyCode::Y, + 'z' | 'Z' => VirtualKeyCode::Z, + '1' | '!' => VirtualKeyCode::Key1, + '2' | '@' => VirtualKeyCode::Key2, + '3' | '#' => VirtualKeyCode::Key3, + '4' | '$' => VirtualKeyCode::Key4, + '5' | '%' => VirtualKeyCode::Key5, + '6' | '^' => VirtualKeyCode::Key6, + '7' | '&' => VirtualKeyCode::Key7, + '8' | '*' => VirtualKeyCode::Key8, + '9' | '(' => VirtualKeyCode::Key9, + '0' | ')' => VirtualKeyCode::Key0, + '=' | '+' => VirtualKeyCode::Equals, + '-' | '_' => VirtualKeyCode::Minus, + ']' | '}' => VirtualKeyCode::RBracket, + '[' | '{' => VirtualKeyCode::LBracket, + '\''| '"' => VirtualKeyCode::Apostrophe, + ';' | ':' => VirtualKeyCode::Semicolon, + '\\'| '|' => VirtualKeyCode::Backslash, + ',' | '<' => VirtualKeyCode::Comma, + '/' | '?' => VirtualKeyCode::Slash, + '.' | '>' => VirtualKeyCode::Period, + '`' | '~' => VirtualKeyCode::Grave, + _ => return None, + }) +} + +pub fn scancode_to_keycode(scancode: c_ushort) -> Option { + Some(match scancode { + 0x00 => VirtualKeyCode::A, + 0x01 => VirtualKeyCode::S, + 0x02 => VirtualKeyCode::D, + 0x03 => VirtualKeyCode::F, + 0x04 => VirtualKeyCode::H, + 0x05 => VirtualKeyCode::G, + 0x06 => VirtualKeyCode::Z, + 0x07 => VirtualKeyCode::X, + 0x08 => VirtualKeyCode::C, + 0x09 => VirtualKeyCode::V, + //0x0a => World 1, + 0x0b => VirtualKeyCode::B, + 0x0c => VirtualKeyCode::Q, + 0x0d => VirtualKeyCode::W, + 0x0e => VirtualKeyCode::E, + 0x0f => VirtualKeyCode::R, + 0x10 => VirtualKeyCode::Y, + 0x11 => VirtualKeyCode::T, + 0x12 => VirtualKeyCode::Key1, + 0x13 => VirtualKeyCode::Key2, + 0x14 => VirtualKeyCode::Key3, + 0x15 => VirtualKeyCode::Key4, + 0x16 => VirtualKeyCode::Key6, + 0x17 => VirtualKeyCode::Key5, + 0x18 => VirtualKeyCode::Equals, + 0x19 => VirtualKeyCode::Key9, + 0x1a => VirtualKeyCode::Key7, + 0x1b => VirtualKeyCode::Minus, + 0x1c => VirtualKeyCode::Key8, + 0x1d => VirtualKeyCode::Key0, + 0x1e => VirtualKeyCode::RBracket, + 0x1f => VirtualKeyCode::O, + 0x20 => VirtualKeyCode::U, + 0x21 => VirtualKeyCode::LBracket, + 0x22 => VirtualKeyCode::I, + 0x23 => VirtualKeyCode::P, + 0x24 => VirtualKeyCode::Return, + 0x25 => VirtualKeyCode::L, + 0x26 => VirtualKeyCode::J, + 0x27 => VirtualKeyCode::Apostrophe, + 0x28 => VirtualKeyCode::K, + 0x29 => VirtualKeyCode::Semicolon, + 0x2a => VirtualKeyCode::Backslash, + 0x2b => VirtualKeyCode::Comma, + 0x2c => VirtualKeyCode::Slash, + 0x2d => VirtualKeyCode::N, + 0x2e => VirtualKeyCode::M, + 0x2f => VirtualKeyCode::Period, + 0x30 => VirtualKeyCode::Tab, + 0x31 => VirtualKeyCode::Space, + 0x32 => VirtualKeyCode::Grave, + 0x33 => VirtualKeyCode::Back, + //0x34 => unkown, + 0x35 => VirtualKeyCode::Escape, + 0x36 => VirtualKeyCode::RWin, + 0x37 => VirtualKeyCode::LWin, + 0x38 => VirtualKeyCode::LShift, + //0x39 => Caps lock, + 0x3a => VirtualKeyCode::LAlt, + 0x3b => VirtualKeyCode::LControl, + 0x3c => VirtualKeyCode::RShift, + 0x3d => VirtualKeyCode::RAlt, + 0x3e => VirtualKeyCode::RControl, + //0x3f => Fn key, + 0x40 => VirtualKeyCode::F17, + 0x41 => VirtualKeyCode::Decimal, + //0x42 -> unkown, + 0x43 => VirtualKeyCode::Multiply, + //0x44 => unkown, + 0x45 => VirtualKeyCode::Add, + //0x46 => unkown, + 0x47 => VirtualKeyCode::Numlock, + //0x48 => KeypadClear, + 0x49 => VirtualKeyCode::VolumeUp, + 0x4a => VirtualKeyCode::VolumeDown, + 0x4b => VirtualKeyCode::Divide, + 0x4c => VirtualKeyCode::NumpadEnter, + //0x4d => unkown, + 0x4e => VirtualKeyCode::Subtract, + 0x4f => VirtualKeyCode::F18, + 0x50 => VirtualKeyCode::F19, + 0x51 => VirtualKeyCode::NumpadEquals, + 0x52 => VirtualKeyCode::Numpad0, + 0x53 => VirtualKeyCode::Numpad1, + 0x54 => VirtualKeyCode::Numpad2, + 0x55 => VirtualKeyCode::Numpad3, + 0x56 => VirtualKeyCode::Numpad4, + 0x57 => VirtualKeyCode::Numpad5, + 0x58 => VirtualKeyCode::Numpad6, + 0x59 => VirtualKeyCode::Numpad7, + 0x5a => VirtualKeyCode::F20, + 0x5b => VirtualKeyCode::Numpad8, + 0x5c => VirtualKeyCode::Numpad9, + 0x5d => VirtualKeyCode::Yen, + //0x5e => JIS Ro, + //0x5f => unkown, + 0x60 => VirtualKeyCode::F5, + 0x61 => VirtualKeyCode::F6, + 0x62 => VirtualKeyCode::F7, + 0x63 => VirtualKeyCode::F3, + 0x64 => VirtualKeyCode::F8, + 0x65 => VirtualKeyCode::F9, + //0x66 => JIS Eisuu (macOS), + 0x67 => VirtualKeyCode::F11, + //0x68 => JIS Kanna (macOS), + 0x69 => VirtualKeyCode::F13, + 0x6a => VirtualKeyCode::F16, + 0x6b => VirtualKeyCode::F14, + //0x6c => unkown, + 0x6d => VirtualKeyCode::F10, + //0x6e => unkown, + 0x6f => VirtualKeyCode::F12, + //0x70 => unkown, + 0x71 => VirtualKeyCode::F15, + 0x72 => VirtualKeyCode::Insert, + 0x73 => VirtualKeyCode::Home, + 0x74 => VirtualKeyCode::PageUp, + 0x75 => VirtualKeyCode::Delete, + 0x76 => VirtualKeyCode::F4, + 0x77 => VirtualKeyCode::End, + 0x78 => VirtualKeyCode::F2, + 0x79 => VirtualKeyCode::PageDown, + 0x7a => VirtualKeyCode::F1, + 0x7b => VirtualKeyCode::Left, + 0x7c => VirtualKeyCode::Right, + 0x7d => VirtualKeyCode::Down, + 0x7e => VirtualKeyCode::Up, + //0x7f => unkown, + + 0xa => VirtualKeyCode::Caret, + _ => return None, + }) +} + +// While F1-F20 have scancodes we can match on, we have to check against UTF-16 +// constants for the rest. +// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ +pub fn check_function_keys(string: &String) -> Option { + if let Some(ch) = string.encode_utf16().next() { + return Some(match ch { + 0xf718 => VirtualKeyCode::F21, + 0xf719 => VirtualKeyCode::F22, + 0xf71a => VirtualKeyCode::F23, + 0xf71b => VirtualKeyCode::F24, + _ => return None, + }) + } + + None +} + +pub fn event_mods(event: id) -> ModifiersState { + let flags = unsafe { + NSEvent::modifierFlags(event) + }; + ModifiersState { + shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask), + ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask), + alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask), + logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask), + } +} + +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] + } +} + +pub unsafe fn modifier_event( + ns_event: id, + keymask: NSEventModifierFlags, + was_key_pressed: bool, +) -> Option { + 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 _, + virtual_keycode, + modifiers: event_mods(ns_event), + }, + }) + } else { + None + } +} diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index d5da8faf..510dd84b 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -1,742 +1,144 @@ -use {ControlFlow, EventLoopClosed}; -use cocoa::{self, appkit, foundation}; -use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}; -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; +use std::{ + collections::VecDeque, mem, os::raw::c_void, process, ptr, sync::mpsc, marker::PhantomData +}; -pub struct EventLoop { - modifiers: Modifiers, - pub shared: Arc, +use cocoa::{appkit::NSApp, base::{id, nil}, foundation::NSAutoreleasePool}; + +use { + event::Event, + event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, +}; +use platform_impl::platform::{ + app::APP_CLASS, app_delegate::APP_DELEGATE_CLASS, + app_state::AppState, monitor::{self, MonitorHandle}, + observer::*, util::IdRef, +}; + +pub struct EventLoopWindowTarget { + pub sender: mpsc::Sender, // this is only here to be cloned elsewhere + pub receiver: mpsc::Receiver, } -// State shared between the `EventLoop` and its registered windows. -pub struct Shared { - pub windows: Mutex>>, - pub pending_events: Mutex>, - // The user event callback given via either of the `poll_events` or `run_forever` methods. - // - // We store the user's callback here so that it may be accessed by each of the window delegate - // callbacks (e.g. resize, close, etc) for the duration of a call to either of the - // `poll_events` or `run_forever` methods. - // - // This is *only* `Some` for the duration of a call to either of these methods and will be - // `None` otherwise. - user_callback: UserCallback, +impl Default for EventLoopWindowTarget { + fn default() -> Self { + let (sender, receiver) = mpsc::channel(); + EventLoopWindowTarget { sender, receiver } + } +} + +pub struct EventLoop { + window_target: RootWindowTarget, + _delegate: IdRef, +} + +impl EventLoop { + pub fn new() -> Self { + let delegate = unsafe { + if !msg_send![class!(NSThread), isMainThread] { + panic!("On macOS, `EventLoop` must be created on the main thread!"); + } + + // This must be done before `NSApp()` (equivalent to sending + // `sharedApplication`) is called anywhere else, or we'll end up + // with the wrong `NSApplication` class and the wrong thread could + // be marked as main. + let app: id = msg_send![APP_CLASS.0, sharedApplication]; + + let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); + let pool = NSAutoreleasePool::new(nil); + let _: () = msg_send![app, setDelegate:*delegate]; + let _: () = msg_send![pool, drain]; + delegate + }; + setup_control_flow_observers(); + EventLoop { + window_target: RootWindowTarget { + p: Default::default(), + _marker: PhantomData, + }, + _delegate: delegate, + } + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + monitor::primary_monitor() + } + + pub fn window_target(&self) -> &RootWindowTarget { + &self.window_target + } + + pub fn run(self, callback: F) -> ! + where F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + { + unsafe { + let _pool = NSAutoreleasePool::new(nil); + let app = NSApp(); + assert_ne!(app, nil); + AppState::set_callback(callback, self.window_target); + let _: () = msg_send![app, run]; + AppState::exit(); + process::exit(0) + } + } + + pub fn run_return(&mut self, _callback: F) + where F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), + { + unimplemented!(); + } + + pub fn create_proxy(&self) -> Proxy { + Proxy::new(self.window_target.p.sender.clone()) + } } #[derive(Clone)] -pub struct Proxy {} - -struct Modifiers { - shift_pressed: bool, - ctrl_pressed: bool, - win_pressed: bool, - alt_pressed: bool, +pub struct Proxy { + sender: mpsc::Sender, + source: CFRunLoopSourceRef, } -// Wrapping the user callback in a type allows us to: -// -// - ensure the callback pointer is never accidentally cloned -// - ensure that only the `EventLoop` can `store` and `drop` the callback pointer -// - Share access to the user callback with the NSWindow callbacks. -pub struct UserCallback { - mutex: Mutex>, -} +unsafe impl Send for Proxy {} +unsafe impl Sync for Proxy {} - -impl Shared { - - pub fn new() -> Self { - Shared { - windows: Mutex::new(Vec::new()), - pending_events: Mutex::new(VecDeque::new()), - user_callback: UserCallback { mutex: Mutex::new(None) }, - } - } - - fn call_user_callback_with_pending_events(&self) { - loop { - let event = match self.pending_events.lock().unwrap().pop_front() { - Some(event) => event, - None => return, - }; - unsafe { - self.user_callback.call_with_event(event); - } - } - } - - // Calls the user callback if one exists. - // - // Otherwise, stores the event in the `pending_events` queue. - // - // This is necessary for the case when `WindowDelegate` callbacks are triggered during a call - // to the user's callback. - pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) { - if self.user_callback.mutex.lock().unwrap().is_some() { - unsafe { - self.user_callback.call_with_event(event); - } - } else { - self.pending_events.lock().unwrap().push_back(event); - } - } - - // Removes the window with the given `Id` from the `windows` list. - // - // This is called in response to `windowWillClose`. - pub fn find_and_remove_window(&self, id: super::window::Id) { - if let Ok(mut windows) = self.windows.lock() { - windows.retain(|w| match w.upgrade() { - Some(w) => w.id() != id, - None => false, - }); - } - } - -} - - -impl Modifiers { - pub fn new() -> Self { - Modifiers { - shift_pressed: false, - ctrl_pressed: false, - win_pressed: false, - alt_pressed: false, - } - } -} - - -impl UserCallback { - - // Here we store user's `callback` behind the mutex so that they may be safely shared between - // each of the window delegates. - // - // In order to make sure that the pointer is always valid, we must manually guarantee that it - // is dropped before the callback itself is dropped. Thus, this should *only* be called at the - // beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the - // callback at the end of their scope using the `drop` method. - fn store(&self, callback: &mut F) - where F: FnMut(Event) - { - let trait_object = callback as &mut FnMut(Event); - let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event); - *self.mutex.lock().unwrap() = Some(trait_object_ptr); - } - - // Emits the given event via the user-given callback. - // - // This is unsafe as it requires dereferencing the pointer to the user-given callback. We - // guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given - // callback. - // - // Note that the callback may not always be `Some`. This is because some `NSWindowDelegate` - // callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window - // is destroyed or created during a call to the user's callback, the `WindowDelegate` methods - // may be called with `windowShouldClose` or `windowDidResignKey`. - unsafe fn call_with_event(&self, event: Event) { - let callback = match self.mutex.lock().unwrap().take() { - Some(callback) => callback, - None => return, - }; - (*callback)(event); - *self.mutex.lock().unwrap() = Some(callback); - } - - // Used to drop the user callback pointer at the end of the `poll_events` and `run_forever` - // methods. This is done to enforce our guarantee that the top callback will never live longer - // than the call to either `poll_events` or `run_forever` to which it was given. - fn drop(&self) { - self.mutex.lock().unwrap().take(); - } - -} - - -impl EventLoop { - - pub fn new() -> Self { - // Mark this thread as the main thread of the Cocoa event system. - // - // This must be done before any worker threads get a chance to call it - // (e.g., via `EventLoopProxy::wakeup()`), causing a wrong thread to be - // marked as the main thread. - unsafe { appkit::NSApp(); } - - EventLoop { - shared: Arc::new(Shared::new()), - modifiers: Modifiers::new(), - } - } - - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(Event), - { +impl Proxy { + fn new(sender: mpsc::Sender) -> Self { unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("Events can only be polled from the main thread on macOS"); - } + // just wakeup the eventloop + extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} + + // adding a Source to the main CFRunLoop lets us wake it up and + // process user events through the normal OS EventLoop mechanisms. + let rl = CFRunLoopGetMain(); + let mut context: CFRunLoopSourceContext = mem::zeroed(); + context.perform = event_loop_proxy_handler; + let source = CFRunLoopSourceCreate( + ptr::null_mut(), + CFIndex::max_value() - 1, + &mut context, + ); + CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); + CFRunLoopWakeUp(rl); + + Proxy { sender, source } } - - self.shared.user_callback.store(&mut callback); - - // Loop as long as we have pending events to return. - loop { - unsafe { - // First, yield all pending events. - self.shared.call_user_callback_with_pending_events(); - - let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); - - // Poll for the next event, returning `nil` if there are none. - let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(), - foundation::NSDate::distantPast(cocoa::base::nil), - foundation::NSDefaultRunLoopMode, - cocoa::base::YES); - - let event = self.ns_event_to_event(ns_event); - - let _: () = msg_send![pool, release]; - - match event { - // Call the user's callback. - Some(event) => self.shared.user_callback.call_with_event(event), - None => break, - } - } - } - - self.shared.user_callback.drop(); } - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(Event) -> ControlFlow - { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.sender.send(event).map_err(|_| EventLoopClosed)?; unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("Events can only be polled from the main thread on macOS"); - } - } - - // Track whether or not control flow has changed. - let control_flow = std::cell::Cell::new(ControlFlow::Continue); - - let mut callback = |event| { - if let ControlFlow::Break = callback(event) { - control_flow.set(ControlFlow::Break); - } - }; - - self.shared.user_callback.store(&mut callback); - - loop { - unsafe { - // First, yield all pending events. - self.shared.call_user_callback_with_pending_events(); - if let ControlFlow::Break = control_flow.get() { - break; - } - - let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); - - // Wait for the next event. Note that this function blocks during resize. - let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(), - foundation::NSDate::distantFuture(cocoa::base::nil), - foundation::NSDefaultRunLoopMode, - cocoa::base::YES); - - let maybe_event = self.ns_event_to_event(ns_event); - - // Release the pool before calling the top callback in case the user calls either - // `run_forever` or `poll_events` within the callback. - let _: () = msg_send![pool, release]; - - if let Some(event) = maybe_event { - self.shared.user_callback.call_with_event(event); - if let ControlFlow::Break = control_flow.get() { - break; - } - } - } - } - - self.shared.user_callback.drop(); - } - - // Convert some given `NSEvent` into a winit `Event`. - unsafe fn ns_event_to_event(&mut self, ns_event: cocoa::base::id) -> Option { - if ns_event == cocoa::base::nil { - return None; - } - - // FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens - // Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType` - // with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType` - // enum as there is no variant associated with the value. Thus, we return early if this - // sneaky event occurs. If someone does find some documentation on this, please fix this by - // adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate. - if ns_event.eventType() as u64 == 21 { - return None; - } - - let event_type = ns_event.eventType(); - let ns_window = ns_event.window(); - let window_id = super::window::get_window_id(ns_window); - - // 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. - appkit::NSApp().sendEvent_(ns_event); - - let windows = self.shared.windows.lock().unwrap(); - let maybe_window = windows.iter() - .filter_map(Weak::upgrade) - .find(|window| window_id == window.id()); - - let into_event = |window_event| Event::WindowEvent { - window_id: ::WindowId(window_id), - event: window_event, - }; - - // Returns `Some` window if one of our windows is the key window. - let maybe_key_window = || windows.iter() - .filter_map(Weak::upgrade) - .find(|window| { - let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow]; - is_key_window == cocoa::base::YES - }); - - match event_type { - // 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 ``, 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, - 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, - 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, - 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, - 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()); - event - }, - - appkit::NSMouseEntered => { - let window = match maybe_window.or_else(maybe_key_window) { - Some(window) => 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 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 })) - }, - appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) }, - - appkit::NSMouseMoved | - appkit::NSLeftMouseDragged | - appkit::NSOtherMouseDragged | - appkit::NSRightMouseDragged => { - // 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`. - match maybe_window.or_else(maybe_key_window) { - Some(_window) => (), - None => return None, - } - - 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 }; - events.push_back(event); - } - - 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 }; - 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 }; - events.push_back(event); - } - - let event = events.pop_front(); - self.shared.pending_events.lock().unwrap().extend(events.into_iter()); - event - }, - - appkit::NSScrollWheel => { - // If none of the windows received the scroll, return `None`. - if maybe_window.is_none() { - return None; - } - - use events::MouseScrollDelta::{LineDelta, PixelDelta}; - let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { - PixelDelta(( - ns_event.scrollingDeltaX() as f64, - ns_event.scrollingDeltaY() as f64, - ).into()) - } else { - // 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, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => TouchPhase::Moved, - }; - self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseWheel { - delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { - PixelDelta(( - ns_event.scrollingDeltaX() as f64, - ns_event.scrollingDeltaY() as f64, - ).into()) - } else { - LineDelta( - ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32, - ) - }, - } - }); - let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase, modifiers: event_mods(ns_event) }; - Some(into_event(window_event)) - }, - - appkit::NSEventTypePressure => { - let pressure = ns_event.pressure(); - let stage = ns_event.stage(); - let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage }; - Some(into_event(window_event)) - }, - - appkit::NSApplicationDefined => match ns_event.subtype() { - appkit::NSEventSubtype::NSApplicationActivatedEventType => { - Some(Event::Awakened) - }, - _ => None, - }, - - _ => None, - } - } - - pub fn create_proxy(&self) -> Proxy { - Proxy {} - } - -} - -impl Proxy { - pub fn wakeup(&self) -> Result<(), EventLoopClosed> { - // Awaken the event loop by triggering `NSApplicationActivatedEventType`. - unsafe { - let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); - let event = - NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_( - cocoa::base::nil, - appkit::NSApplicationDefined, - foundation::NSPoint::new(0.0, 0.0), - appkit::NSEventModifierFlags::empty(), - 0.0, - 0, - cocoa::base::nil, - appkit::NSEventSubtype::NSApplicationActivatedEventType, - 0, - 0); - appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO); - foundation::NSAutoreleasePool::drain(pool); + // let the main thread know there's a new event + CFRunLoopSourceSignal(self.source); + let rl = CFRunLoopGetMain(); + CFRunLoopWakeUp(rl); } Ok(()) } } - -pub fn to_virtual_key_code(code: c_ushort) -> Option { - Some(match code { - 0x00 => events::VirtualKeyCode::A, - 0x01 => events::VirtualKeyCode::S, - 0x02 => events::VirtualKeyCode::D, - 0x03 => events::VirtualKeyCode::F, - 0x04 => events::VirtualKeyCode::H, - 0x05 => events::VirtualKeyCode::G, - 0x06 => events::VirtualKeyCode::Z, - 0x07 => events::VirtualKeyCode::X, - 0x08 => events::VirtualKeyCode::C, - 0x09 => events::VirtualKeyCode::V, - //0x0a => World 1, - 0x0b => events::VirtualKeyCode::B, - 0x0c => events::VirtualKeyCode::Q, - 0x0d => events::VirtualKeyCode::W, - 0x0e => events::VirtualKeyCode::E, - 0x0f => events::VirtualKeyCode::R, - 0x10 => events::VirtualKeyCode::Y, - 0x11 => events::VirtualKeyCode::T, - 0x12 => events::VirtualKeyCode::Key1, - 0x13 => events::VirtualKeyCode::Key2, - 0x14 => events::VirtualKeyCode::Key3, - 0x15 => events::VirtualKeyCode::Key4, - 0x16 => events::VirtualKeyCode::Key6, - 0x17 => events::VirtualKeyCode::Key5, - 0x18 => events::VirtualKeyCode::Equals, - 0x19 => events::VirtualKeyCode::Key9, - 0x1a => events::VirtualKeyCode::Key7, - 0x1b => events::VirtualKeyCode::Minus, - 0x1c => events::VirtualKeyCode::Key8, - 0x1d => events::VirtualKeyCode::Key0, - 0x1e => events::VirtualKeyCode::RBracket, - 0x1f => events::VirtualKeyCode::O, - 0x20 => events::VirtualKeyCode::U, - 0x21 => events::VirtualKeyCode::LBracket, - 0x22 => events::VirtualKeyCode::I, - 0x23 => events::VirtualKeyCode::P, - 0x24 => events::VirtualKeyCode::Return, - 0x25 => events::VirtualKeyCode::L, - 0x26 => events::VirtualKeyCode::J, - 0x27 => events::VirtualKeyCode::Apostrophe, - 0x28 => events::VirtualKeyCode::K, - 0x29 => events::VirtualKeyCode::Semicolon, - 0x2a => events::VirtualKeyCode::Backslash, - 0x2b => events::VirtualKeyCode::Comma, - 0x2c => events::VirtualKeyCode::Slash, - 0x2d => events::VirtualKeyCode::N, - 0x2e => events::VirtualKeyCode::M, - 0x2f => events::VirtualKeyCode::Period, - 0x30 => events::VirtualKeyCode::Tab, - 0x31 => events::VirtualKeyCode::Space, - 0x32 => events::VirtualKeyCode::Grave, - 0x33 => events::VirtualKeyCode::Back, - //0x34 => unkown, - 0x35 => events::VirtualKeyCode::Escape, - 0x36 => events::VirtualKeyCode::LWin, - 0x37 => events::VirtualKeyCode::RWin, - 0x38 => events::VirtualKeyCode::LShift, - //0x39 => Caps lock, - 0x3a => events::VirtualKeyCode::LAlt, - 0x3b => events::VirtualKeyCode::LControl, - 0x3c => events::VirtualKeyCode::RShift, - 0x3d => events::VirtualKeyCode::RAlt, - 0x3e => events::VirtualKeyCode::RControl, - //0x3f => Fn key, - 0x40 => events::VirtualKeyCode::F17, - 0x41 => events::VirtualKeyCode::Decimal, - //0x42 -> unkown, - 0x43 => events::VirtualKeyCode::Multiply, - //0x44 => unkown, - 0x45 => events::VirtualKeyCode::Add, - //0x46 => unkown, - 0x47 => events::VirtualKeyCode::Numlock, - //0x48 => KeypadClear, - 0x49 => events::VirtualKeyCode::VolumeUp, - 0x4a => events::VirtualKeyCode::VolumeDown, - 0x4b => events::VirtualKeyCode::Divide, - 0x4c => events::VirtualKeyCode::NumpadEnter, - //0x4d => unkown, - 0x4e => events::VirtualKeyCode::Subtract, - 0x4f => events::VirtualKeyCode::F18, - 0x50 => events::VirtualKeyCode::F19, - 0x51 => events::VirtualKeyCode::NumpadEquals, - 0x52 => events::VirtualKeyCode::Numpad0, - 0x53 => events::VirtualKeyCode::Numpad1, - 0x54 => events::VirtualKeyCode::Numpad2, - 0x55 => events::VirtualKeyCode::Numpad3, - 0x56 => events::VirtualKeyCode::Numpad4, - 0x57 => events::VirtualKeyCode::Numpad5, - 0x58 => events::VirtualKeyCode::Numpad6, - 0x59 => events::VirtualKeyCode::Numpad7, - 0x5a => events::VirtualKeyCode::F20, - 0x5b => events::VirtualKeyCode::Numpad8, - 0x5c => events::VirtualKeyCode::Numpad9, - 0x5d => events::VirtualKeyCode::Yen, - //0x5e => JIS Ro, - //0x5f => unkown, - 0x60 => events::VirtualKeyCode::F5, - 0x61 => events::VirtualKeyCode::F6, - 0x62 => events::VirtualKeyCode::F7, - 0x63 => events::VirtualKeyCode::F3, - 0x64 => events::VirtualKeyCode::F8, - 0x65 => events::VirtualKeyCode::F9, - //0x66 => JIS Eisuu (macOS), - 0x67 => events::VirtualKeyCode::F11, - //0x68 => JIS Kana (macOS), - 0x69 => events::VirtualKeyCode::F13, - 0x6a => events::VirtualKeyCode::F16, - 0x6b => events::VirtualKeyCode::F14, - //0x6c => unkown, - 0x6d => events::VirtualKeyCode::F10, - //0x6e => unkown, - 0x6f => events::VirtualKeyCode::F12, - //0x70 => unkown, - 0x71 => events::VirtualKeyCode::F15, - 0x72 => events::VirtualKeyCode::Insert, - 0x73 => events::VirtualKeyCode::Home, - 0x74 => events::VirtualKeyCode::PageUp, - 0x75 => events::VirtualKeyCode::Delete, - 0x76 => events::VirtualKeyCode::F4, - 0x77 => events::VirtualKeyCode::End, - 0x78 => events::VirtualKeyCode::F2, - 0x79 => events::VirtualKeyCode::PageDown, - 0x7a => events::VirtualKeyCode::F1, - 0x7b => events::VirtualKeyCode::Left, - 0x7c => events::VirtualKeyCode::Right, - 0x7d => events::VirtualKeyCode::Down, - 0x7e => events::VirtualKeyCode::Up, - //0x7f => unkown, - - 0xa => events::VirtualKeyCode::Caret, - _ => return None, - }) -} - -pub fn check_additional_virtual_key_codes( - s: &Option -) -> Option { - if let &Some(ref s) = s { - 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) - }; - ModifiersState { - shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask), - ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask), - alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask), - logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask), - } -} - -unsafe fn modifier_event( - ns_event: cocoa::base::id, - keymask: NSEventModifierFlags, - was_key_pressed: bool, -) -> Option { - 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 keycode = NSEvent::keyCode(ns_event); - let scancode = keycode as u32; - let virtual_keycode = to_virtual_key_code(keycode); - Some(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state, - scancode, - 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. -pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 31c9ed14..d199ebb6 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -95,6 +95,7 @@ pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; pub const kCGCursorWindowLevelKey: NSInteger = 19; pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; +#[derive(Debug, Clone, Copy)] pub enum NSWindowLevel { NSNormalWindowLevel = kCGBaseWindowLevelKey as _, NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 5a87c54c..2b5d9f5d 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,9 +1,31 @@ #![cfg(target_os = "macos")] -pub use self::event_loop::{EventLoop, Proxy as EventLoopProxy}; -pub use self::monitor::MonitorHandle; -pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window2}; -use std::sync::Arc; +mod app; +mod app_delegate; +mod app_state; +mod event; +mod event_loop; +mod ffi; +mod monitor; +mod observer; +mod util; +mod view; +mod window; +mod window_delegate; + +use std::{fmt, ops::Deref, sync::Arc}; + +use { + event::DeviceId as RootDeviceId, window::WindowAttributes, + error::OsError as RootOsError, +}; +pub use self::{ + event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, + monitor::MonitorHandle, + window::{ + Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow, + }, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; @@ -14,38 +36,48 @@ impl DeviceId { } } -use {CreationError}; +// Constant device ID; to be removed when if backend is updated to report real device IDs. +pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); pub struct Window { - pub window: Arc, + window: Arc, + // We keep this around so that it doesn't get dropped until the window does. + _delegate: util::IdRef, } -impl ::std::ops::Deref for Window { - type Target = Window2; +#[derive(Debug)] +pub enum OsError { + CGError(core_graphics::base::CGError), + CreationError(&'static str) +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Deref for Window { + type Target = UnownedWindow; #[inline] - fn deref(&self) -> &Window2 { + fn deref(&self) -> &Self::Target { &*self.window } } impl Window { - - pub fn new(event_loop: &EventLoop, - attributes: ::WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result - { - let weak_shared = Arc::downgrade(&event_loop.shared); - let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs))); - let weak_window = Arc::downgrade(&window); - event_loop.shared.windows.lock().unwrap().push(weak_window); - Ok(Window { window: window }) + pub fn new( + _window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let (window, _delegate) = UnownedWindow::new(attributes, pl_attribs)?; + Ok(Window { window, _delegate }) } - } -mod event_loop; -mod ffi; -mod monitor; -mod util; -mod view; -mod window; +impl fmt::Display for OsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + OsError::CGError(e) => f.pad(&format!("CGError {}", e)), + OsError::CreationError(e) => f.pad(e), + } + } +} diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 7b3e8488..78e4e3ec 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -1,23 +1,19 @@ -use std::collections::VecDeque; -use std::fmt; +use std::{collections::VecDeque, fmt}; -use cocoa::appkit::NSScreen; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSString, NSUInteger}; +use cocoa::{appkit::NSScreen, base::{id, nil}, foundation::{NSString, NSUInteger}}; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; -use {PhysicalPosition, PhysicalSize}; -use super::EventLoop; -use super::window::{IdRef, Window2}; +use dpi::{PhysicalPosition, PhysicalSize}; +use platform_impl::platform::util::IdRef; #[derive(Clone, PartialEq)] pub struct MonitorHandle(CGDirectDisplayID); -fn get_available_monitors() -> VecDeque { +pub fn available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); - for d in displays { - monitors.push_back(MonitorHandle(d)); + for display in displays { + monitors.push_back(MonitorHandle(display)); } monitors } else { @@ -25,42 +21,13 @@ fn get_available_monitors() -> VecDeque { } } -pub fn get_primary_monitor() -> MonitorHandle { - let id = MonitorHandle(CGDisplay::main().id); - id -} - -impl EventLoop { - #[inline] - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors() - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor() - } - - pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorHandle { - let id = MonitorHandle(id); - id - } -} - -impl Window2 { - #[inline] - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors() - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor() - } +pub fn primary_monitor() -> MonitorHandle { + MonitorHandle(CGDisplay::main().id) } impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: Do this using the proper fmt API #[derive(Debug)] struct MonitorHandle { name: Option, @@ -71,11 +38,11 @@ impl fmt::Debug for MonitorHandle { } let monitor_id_proxy = MonitorHandle { - name: self.get_name(), - native_identifier: self.get_native_identifier(), - dimensions: self.get_dimensions(), - position: self.get_position(), - hidpi_factor: self.get_hidpi_factor(), + name: self.name(), + native_identifier: self.native_identifier(), + dimensions: self.dimensions(), + position: self.position(), + hidpi_factor: self.hidpi_factor(), }; monitor_id_proxy.fmt(f) @@ -83,48 +50,52 @@ impl fmt::Debug for MonitorHandle { } impl MonitorHandle { - pub fn get_name(&self) -> Option { + pub fn new(id: CGDirectDisplayID) -> Self { + MonitorHandle(id) + } + + pub fn name(&self) -> Option { let MonitorHandle(display_id) = *self; let screen_num = CGDisplay::new(display_id).model_number(); Some(format!("Monitor #{}", screen_num)) } #[inline] - pub fn get_native_identifier(&self) -> u32 { + pub fn native_identifier(&self) -> u32 { self.0 } - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { let MonitorHandle(display_id) = *self; let display = CGDisplay::new(display_id); let height = display.pixels_high(); let width = display.pixels_wide(); PhysicalSize::from_logical( (width as f64, height as f64), - self.get_hidpi_factor(), + self.hidpi_factor(), ) } #[inline] - pub fn get_position(&self) -> PhysicalPosition { - let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) }; + pub fn position(&self) -> PhysicalPosition { + let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; PhysicalPosition::from_logical( (bounds.origin.x as f64, bounds.origin.y as f64), - self.get_hidpi_factor(), + self.hidpi_factor(), ) } - pub fn get_hidpi_factor(&self) -> f64 { - let screen = match self.get_nsscreen() { + pub fn hidpi_factor(&self) -> f64 { + let screen = match self.nsscreen() { Some(screen) => screen, None => return 1.0, // default to 1.0 when we can't find the screen }; unsafe { NSScreen::backingScaleFactor(screen) as f64 } } - pub(crate) fn get_nsscreen(&self) -> Option { + pub(crate) fn nsscreen(&self) -> Option { unsafe { - let native_id = self.get_native_identifier(); + let native_id = self.native_identifier(); let screens = NSScreen::screens(nil); let count: NSUInteger = msg_send![screens, count]; let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs new file mode 100644 index 00000000..79b4c997 --- /dev/null +++ b/src/platform_impl/macos/observer.rs @@ -0,0 +1,259 @@ +use std::{self, ptr, os::raw::*, time::Instant}; + +use platform_impl::platform::app_state::AppState; + +#[link(name = "CoreFoundation", kind = "framework")] +extern { + pub static kCFRunLoopDefaultMode: CFRunLoopMode; + pub static kCFRunLoopCommonModes: CFRunLoopMode; + + pub fn CFRunLoopGetMain() -> CFRunLoopRef; + pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); + + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + pub fn CFRunLoopAddObserver( + rl: CFRunLoopRef, + observer: CFRunLoopObserverRef, + mode: CFRunLoopMode, + ); + + pub fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: CFAbsoluteTime, + interval: CFTimeInterval, + flags: CFOptionFlags, + order: CFIndex, + callout: CFRunLoopTimerCallBack, + context: *mut CFRunLoopTimerContext, + ) -> CFRunLoopTimerRef; + pub fn CFRunLoopAddTimer( + rl: CFRunLoopRef, + timer: CFRunLoopTimerRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopTimerSetNextFireDate( + timer: CFRunLoopTimerRef, + fireDate: CFAbsoluteTime, + ); + pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); + + pub fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: CFIndex, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + pub fn CFRunLoopAddSource( + rl: CFRunLoopRef, + source: CFRunLoopSourceRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); + pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + pub fn CFRelease(cftype: *const c_void); +} + +pub type Boolean = u8; +const FALSE: Boolean = 0; +const TRUE: Boolean = 1; + +pub enum CFAllocator {} +pub type CFAllocatorRef = *mut CFAllocator; +pub enum CFRunLoop {} +pub type CFRunLoopRef = *mut CFRunLoop; +pub type CFRunLoopMode = CFStringRef; +pub enum CFRunLoopObserver {} +pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; +pub enum CFRunLoopTimer {} +pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; +pub enum CFRunLoopSource {} +pub type CFRunLoopSourceRef = *mut CFRunLoopSource; +pub enum CFString {} +pub type CFStringRef = *const CFString; + +pub type CFHashCode = c_ulong; +pub type CFIndex = c_long; +pub type CFOptionFlags = c_ulong; +pub type CFRunLoopActivity = CFOptionFlags; + +pub type CFAbsoluteTime = CFTimeInterval; +pub type CFTimeInterval = f64; + +pub const kCFRunLoopEntry: CFRunLoopActivity = 0; +pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; +pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; +pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; + +pub type CFRunLoopObserverCallBack = extern "C" fn( + observer: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + info: *mut c_void, +); +pub type CFRunLoopTimerCallBack = extern "C" fn( + timer: CFRunLoopTimerRef, + info: *mut c_void +); + +pub enum CFRunLoopObserverContext {} +pub enum CFRunLoopTimerContext {} + +#[repr(C)] +pub struct CFRunLoopSourceContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: extern "C" fn(*const c_void) -> *const c_void, + pub release: extern "C" fn(*const c_void), + pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, + pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, + pub hash: extern "C" fn(*const c_void) -> CFHashCode, + pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub perform: extern "C" fn(*mut c_void), +} + +// begin is queued with the highest priority to ensure it is processed before other observers +extern fn control_flow_begin_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, +) { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => { + //trace!("Triggered `CFRunLoopAfterWaiting`"); + AppState::wakeup(); + //trace!("Completed `CFRunLoopAfterWaiting`"); + }, + kCFRunLoopEntry => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } +} + +// end is queued with the lowest priority to ensure it is processed after other observers +// without that, LoopDestroyed would get sent after EventsCleared +extern fn control_flow_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, +) { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => { + //trace!("Triggered `CFRunLoopBeforeWaiting`"); + AppState::cleared(); + //trace!("Completed `CFRunLoopBeforeWaiting`"); + }, + kCFRunLoopExit => (),//unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } +} + +struct RunLoop(CFRunLoopRef); + +impl RunLoop { + unsafe fn get() -> Self { + RunLoop(CFRunLoopGetMain()) + } + + unsafe fn add_observer( + &self, + flags: CFOptionFlags, + priority: CFIndex, + handler: CFRunLoopObserverCallBack, + ) { + let observer = CFRunLoopObserverCreate( + ptr::null_mut(), + flags, + TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run + handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode); + } +} + +pub fn setup_control_flow_observers() { + unsafe { + let run_loop = RunLoop::get(); + run_loop.add_observer( + kCFRunLoopEntry | kCFRunLoopAfterWaiting, + CFIndex::min_value(), + control_flow_begin_handler, + ); + run_loop.add_observer( + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + CFIndex::max_value(), + control_flow_end_handler, + ); + } +} + + +pub struct EventLoopWaker { + timer: CFRunLoopTimerRef, +} + +impl Drop for EventLoopWaker { + fn drop(&mut self) { + unsafe { + CFRunLoopTimerInvalidate(self.timer); + CFRelease(self.timer as _); + } + } +} + +impl Default for EventLoopWaker { + fn default() -> EventLoopWaker { + extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + unsafe { + // create a timer with a 1µs interval (1ns does not work) to mimic polling. + // it is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediatley in did_finish_launching + let timer = CFRunLoopTimerCreate( + ptr::null_mut(), + std::f64::MAX, + 0.000_000_1, + 0, + 0, + wakeup_main_loop, + ptr::null_mut(), + ); + CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); + EventLoopWaker { timer } + } + } +} + +impl EventLoopWaker { + pub fn stop(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + } + + pub fn start(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + } + + pub fn start_at(&mut self, instant: Instant) { + let now = Instant::now(); + if now >= instant { + self.start(); + } else { + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } +} diff --git a/src/platform_impl/macos/util.rs b/src/platform_impl/macos/util.rs deleted file mode 100644 index c4c348f0..00000000 --- a/src/platform_impl/macos/util.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cocoa::appkit::NSWindowStyleMask; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSRect, NSUInteger}; -use core_graphics::display::CGDisplay; - -use platform_impl::platform::ffi; -use platform_impl::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 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]; -} diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs new file mode 100644 index 00000000..4ececc03 --- /dev/null +++ b/src/platform_impl/macos/util/async.rs @@ -0,0 +1,327 @@ +use std::{os::raw::c_void, sync::{Mutex, Weak}}; + +use cocoa::{ + appkit::{CGFloat, NSWindow, NSWindowStyleMask}, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSPoint, NSSize}, +}; +use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f}; + +use dpi::LogicalSize; +use platform_impl::platform::{ffi, window::SharedState}; + +unsafe fn set_style_mask(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + nswindow.setStyleMask_(mask); + // If we don't do this, key handling will break + // (at least until the window is clicked again/etc.) + nswindow.makeFirstResponder_(nsview); +} + +struct SetStyleMaskData { + nswindow: id, + nsview: id, + mask: NSWindowStyleMask, +} +impl SetStyleMaskData { + fn new_ptr( + nswindow: id, + nsview: id, + mask: NSWindowStyleMask, + ) -> *mut Self { + Box::into_raw(Box::new(SetStyleMaskData { nswindow, nsview, mask })) + } +} +extern fn set_style_mask_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetStyleMaskData; + { + let context = &*context_ptr; + set_style_mask(context.nswindow, context.nsview, context.mask); + } + Box::from_raw(context_ptr); + } +} +// Always use this function instead of trying to modify `styleMask` directly! +// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. +// Otherwise, this would vomit out errors about not being on the main thread +// and fail to do anything. +pub unsafe fn set_style_mask_async(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_style_mask_callback, + ); +} +pub unsafe fn set_style_mask_sync(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask); + dispatch_sync_f( + dispatch_get_main_queue(), + context as *mut _, + set_style_mask_callback, + ); +} + +struct SetContentSizeData { + nswindow: id, + size: LogicalSize, +} +impl SetContentSizeData { + fn new_ptr( + nswindow: id, + size: LogicalSize, + ) -> *mut Self { + Box::into_raw(Box::new(SetContentSizeData { nswindow, size })) + } +} +extern fn set_content_size_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetContentSizeData; + { + let context = &*context_ptr; + NSWindow::setContentSize_( + context.nswindow, + NSSize::new( + context.size.width as CGFloat, + context.size.height as CGFloat, + ), + ); + } + Box::from_raw(context_ptr); + } +} +// `setContentSize:` isn't thread-safe either, though it doesn't log any errors +// and just fails silently. Anyway, GCD to the rescue! +pub unsafe fn set_content_size_async(nswindow: id, size: LogicalSize) { + let context = SetContentSizeData::new_ptr(nswindow, size); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_content_size_callback, + ); +} + +struct SetFrameTopLeftPointData { + nswindow: id, + point: NSPoint, +} +impl SetFrameTopLeftPointData { + fn new_ptr( + nswindow: id, + point: NSPoint, + ) -> *mut Self { + Box::into_raw(Box::new(SetFrameTopLeftPointData { nswindow, point })) + } +} +extern fn set_frame_top_left_point_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetFrameTopLeftPointData; + { + let context = &*context_ptr; + NSWindow::setFrameTopLeftPoint_(context.nswindow, context.point); + } + Box::from_raw(context_ptr); + } +} +// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy +// to log errors. +pub unsafe fn set_frame_top_left_point_async(nswindow: id, point: NSPoint) { + let context = SetFrameTopLeftPointData::new_ptr(nswindow, point); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_frame_top_left_point_callback, + ); +} + +struct SetLevelData { + nswindow: id, + level: ffi::NSWindowLevel, +} +impl SetLevelData { + fn new_ptr( + nswindow: id, + level: ffi::NSWindowLevel, + ) -> *mut Self { + Box::into_raw(Box::new(SetLevelData { nswindow, level })) + } +} +extern fn set_level_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetLevelData; + { + let context = &*context_ptr; + context.nswindow.setLevel_(context.level as _); + } + Box::from_raw(context_ptr); + } +} +// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. +pub unsafe fn set_level_async(nswindow: id, level: ffi::NSWindowLevel) { + let context = SetLevelData::new_ptr(nswindow, level); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_level_callback, + ); +} + +struct ToggleFullScreenData { + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, +} +impl ToggleFullScreenData { + fn new_ptr( + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, + ) -> *mut Self { + Box::into_raw(Box::new(ToggleFullScreenData { + nswindow, + nsview, + not_fullscreen, + shared_state, + })) + } +} +extern fn toggle_full_screen_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut ToggleFullScreenData; + { + let context = &*context_ptr; + + // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we + // set a normal style temporarily. The previous state will be + // restored in `WindowDelegate::window_did_exit_fullscreen`. + if context.not_fullscreen { + let curr_mask = context.nswindow.styleMask(); + let required = NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSResizableWindowMask; + if !curr_mask.contains(required) { + set_style_mask(context.nswindow, context.nsview, required); + if let Some(shared_state) = context.shared_state.upgrade() { + trace!("Locked shared state in `toggle_full_screen_callback`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + (*shared_state_lock).saved_style = Some(curr_mask); + trace!("Unlocked shared state in `toggle_full_screen_callback`"); + } + } + } + + context.nswindow.toggleFullScreen_(nil); + } + Box::from_raw(context_ptr); + } +} +// `toggleFullScreen` is thread-safe, but our additional logic to account for +// window styles isn't. +pub unsafe fn toggle_full_screen_async( + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, +) { + let context = ToggleFullScreenData::new_ptr( + nswindow, + nsview, + not_fullscreen, + shared_state, + ); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + toggle_full_screen_callback, + ); +} + +struct OrderOutData { + nswindow: id, +} +impl OrderOutData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(OrderOutData { nswindow })) + } +} +extern fn order_out_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut OrderOutData; + { + let context = &*context_ptr; + context.nswindow.orderOut_(nil); + } + Box::from_raw(context_ptr); + } +} +// `orderOut:` isn't thread-safe. Calling it from another thread actually works, +// but with an odd delay. +pub unsafe fn order_out_async(nswindow: id) { + let context = OrderOutData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + order_out_callback, + ); +} + +struct MakeKeyAndOrderFrontData { + nswindow: id, +} +impl MakeKeyAndOrderFrontData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(MakeKeyAndOrderFrontData { nswindow })) + } +} +extern fn make_key_and_order_front_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut MakeKeyAndOrderFrontData; + { + let context = &*context_ptr; + context.nswindow.makeKeyAndOrderFront_(nil); + } + Box::from_raw(context_ptr); + } +} +// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread +// actually works, but with an odd delay. +pub unsafe fn make_key_and_order_front_async(nswindow: id) { + let context = MakeKeyAndOrderFrontData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + make_key_and_order_front_callback, + ); +} + +struct CloseData { + nswindow: id, +} +impl CloseData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(CloseData { nswindow })) + } +} +extern fn close_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut CloseData; + { + let context = &*context_ptr; + let pool = NSAutoreleasePool::new(nil); + context.nswindow.close(); + pool.drain(); + } + Box::from_raw(context_ptr); + } +} +// `close:` is thread-safe, but we want the event to be triggered from the main +// thread. Though, it's a good idea to look into that more... +pub unsafe fn close_async(nswindow: id) { + let context = CloseData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + close_callback, + ); +} diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs new file mode 100644 index 00000000..9d93762d --- /dev/null +++ b/src/platform_impl/macos/util/cursor.rs @@ -0,0 +1,133 @@ +use cocoa::{ + appkit::NSImage, base::{id, nil}, + foundation::{NSDictionary, NSPoint, NSString}, +}; +use objc::runtime::Sel; + +use window::CursorIcon; + +pub enum Cursor { + Native(&'static str), + Undocumented(&'static str), + WebKit(&'static str), +} + +impl From for Cursor { + fn from(cursor: CursorIcon) -> Self { + match cursor { + CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"), + CursorIcon::Hand => Cursor::Native("pointingHandCursor"), + CursorIcon::Grabbing | CursorIcon::Grab => Cursor::Native("closedHandCursor"), + CursorIcon::Text => Cursor::Native("IBeamCursor"), + CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"), + CursorIcon::Copy => Cursor::Native("dragCopyCursor"), + CursorIcon::Alias => Cursor::Native("dragLinkCursor"), + CursorIcon::NotAllowed | CursorIcon::NoDrop => Cursor::Native("operationNotAllowedCursor"), + CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"), + CursorIcon::Crosshair => Cursor::Native("crosshairCursor"), + CursorIcon::EResize => Cursor::Native("resizeRightCursor"), + CursorIcon::NResize => Cursor::Native("resizeUpCursor"), + CursorIcon::WResize => Cursor::Native("resizeLeftCursor"), + CursorIcon::SResize => Cursor::Native("resizeDownCursor"), + CursorIcon::EwResize | CursorIcon::ColResize => Cursor::Native("resizeLeftRightCursor"), + CursorIcon::NsResize | CursorIcon::RowResize => Cursor::Native("resizeUpDownCursor"), + + // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 + CursorIcon::Help => Cursor::Undocumented("_helpCursor"), + CursorIcon::ZoomIn => Cursor::Undocumented("_zoomInCursor"), + CursorIcon::ZoomOut => Cursor::Undocumented("_zoomOutCursor"), + CursorIcon::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"), + CursorIcon::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"), + CursorIcon::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"), + CursorIcon::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"), + CursorIcon::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"), + CursorIcon::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"), + + // While these are available, the former just loads a white arrow, + // and the latter loads an ugly deflated beachball! + // CursorIcon::Move => Cursor::Undocumented("_moveCursor"), + // CursorIcon::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. + CursorIcon::Wait | CursorIcon::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 + CursorIcon::Move | CursorIcon::AllScroll => Cursor::WebKit("move"), + CursorIcon::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), + } + } +} + +// Note that loading `busybutclickable` with this code won't animate the frames; +// instead you'll just get them all in a column. +pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { + 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); + 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); + let info = NSDictionary::dictionaryWithContentsOfFile_( + nil, + info_path, + ); + 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]; + msg_send![cursor, + initWithImage:image + hotSpot:point + ] +} diff --git a/src/platform/macos/util/into_option.rs b/src/platform_impl/macos/util/into_option.rs similarity index 100% rename from src/platform/macos/util/into_option.rs rename to src/platform_impl/macos/util/into_option.rs diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs new file mode 100644 index 00000000..a8d6a83b --- /dev/null +++ b/src/platform_impl/macos/util/mod.rs @@ -0,0 +1,123 @@ +mod async; +mod cursor; + +pub use self::{async::*, cursor::*}; + +use std::ops::Deref; +use std::ops::BitAnd; + +use cocoa::{ + appkit::{NSApp, NSWindowStyleMask}, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSRect, NSUInteger}, +}; +use core_graphics::display::CGDisplay; +use objc::runtime::{BOOL, Class, Object, Sel, YES}; + +use platform_impl::platform::ffi; + +// Replace with `!` once stable +#[derive(Debug)] +pub enum Never {} + +pub fn has_flag(bitset: T, flag: T) -> bool +where T: + Copy + PartialEq + BitAnd +{ + bitset & flag == flag +} + +pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { + location: ffi::NSNotFound as NSUInteger, + length: 0, +}; + +pub struct IdRef(id); + +impl IdRef { + pub fn new(inner: id) -> IdRef { + IdRef(inner) + } + + #[allow(dead_code)] + pub fn retain(inner: id) -> IdRef { + if inner != nil { + let () = unsafe { msg_send![inner, retain] }; + } + IdRef(inner) + } + + pub fn non_nil(self) -> Option { + if self.0 == nil { None } else { Some(self) } + } +} + +impl Drop for IdRef { + fn drop(&mut self) { + if self.0 != nil { + unsafe { + let pool = NSAutoreleasePool::new(nil); + let () = msg_send![self.0, release]; + pool.drain(); + }; + } + } +} + +impl Deref for IdRef { + type Target = id; + fn deref<'a>(&'a self) -> &'a id { + &self.0 + } +} + +impl Clone for IdRef { + fn clone(&self) -> IdRef { + if self.0 != nil { + let _: id = unsafe { msg_send![self.0, retain] }; + } + IdRef(self.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 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 () = msg_send![NSApp(), orderFrontCharacterPalette:nil]; +} + +pub extern fn yes(_: &Object, _: Sel) -> BOOL { + YES +} + +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); +} + diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 757982c5..481087ff 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1,65 +1,74 @@ -// This is a pretty close port of the implementation in GLFW: -// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m +use std::{ + boxed::Box, collections::VecDeque, os::raw::*, slice, str, + sync::{Arc, Mutex, Weak}, +}; -use std::{slice, str}; -use std::boxed::Box; -use std::collections::VecDeque; -use std::os::raw::*; -use std::sync::{Arc, Mutex, Weak}; +use cocoa::{ + appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}, + base::{id, nil}, foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}, +}; +use objc::{declare::ClassDecl, runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES}}; -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 { + event::{ + DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, + MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent, + }, + window::WindowId, +}; +use platform_impl::platform::{ + app_state::AppState, DEVICE_ID, + event::{check_function_keys, event_mods, modifier_event, char_to_keycode, get_scancode, scancode_to_keycode}, + util::{self, IdRef}, ffi::*, window::get_window_id, +}; -use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId}; -use platform_impl::platform::event_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes}; -use platform_impl::platform::util; -use platform_impl::platform::ffi::*; -use platform_impl::platform::window::{get_window_id, IdRef}; +#[derive(Default)] +struct Modifiers { + shift_pressed: bool, + ctrl_pressed: bool, + win_pressed: bool, + alt_pressed: bool, +} struct ViewState { - window: id, - shared: Weak, - cursor: Arc>, + nswindow: id, + pub cursor: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, + modifiers: Modifiers, } -pub fn new_view(window: id, shared: Weak) -> (IdRef, Weak>) { +pub fn new_view(nswindow: id) -> (IdRef, Weak>) { let cursor = Default::default(); let cursor_access = Arc::downgrade(&cursor); let state = ViewState { - window, - shared, + nswindow, cursor, ime_spot: None, raw_characters: None, is_key_down: false, + modifiers: Default::default(), }; 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) + let nsview: id = msg_send![VIEW_CLASS.0, alloc]; + (IdRef::new(msg_send![nsview, 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]; - } +pub unsafe fn set_ime_position(nsview: id, input_context: id, x: f64, y: f64) { + let state_ptr: *mut c_void = *(*nsview).get_mut_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let content_rect = NSWindow::contentRectForFrameRect_( + state.nswindow, + NSWindow::frame(state.nswindow), + ); + 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); @@ -70,38 +79,61 @@ 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!(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!(viewDidMoveToWindow), + view_did_move_to_window as extern fn(&Object, Sel), + ); decl.add_method( sel!(drawRect:), - draw_rect as extern fn(&Object, Sel, NSRect), + draw_rect as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(acceptsFirstResponder), + accepts_first_responder as extern fn(&Object, Sel) -> BOOL, + ); + decl.add_method( + sel!(touchBar), + touch_bar as extern fn(&Object, Sel) -> BOOL, ); 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!(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!(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!(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, + attributed_substring_for_proposed_range as extern fn(&Object, Sel, NSRange, *mut c_void) -> id, ); decl.add_method( sel!(insertText:replacementRange:), @@ -113,28 +145,96 @@ lazy_static! { ); decl.add_method( sel!(firstRectForCharacterRange:actualRange:), - first_rect_for_character_range - as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect, + 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_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!(flagsChanged:), + flags_changed 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!(mouseEntered:), + mouse_entered as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseExited:), + mouse_exited as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(scrollWheel:), + scroll_wheel as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(pressureChangeWithEvent:), + pressure_change_with_event 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_method( + sel!(cancelOperation:), + cancel_operation as extern fn(&Object, Sel, id), + ); decl.add_ivar::<*mut c_void>("winitState"); decl.add_ivar::("markedText"); let protocol = Protocol::get("NSTextInputClient").unwrap(); @@ -166,27 +266,43 @@ extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id { } } -extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { +extern fn view_did_move_to_window(this: &Object, _sel: Sel) { + trace!("Triggered `viewDidMoveToWindow`"); + unsafe { + let rect: NSRect = msg_send![this, visibleRect]; + let _: () = msg_send![this, + addTrackingRect:rect + owner:this + userData:nil + assumeInside:NO + ]; + } + trace!("Completed `viewDidMoveToWindow`"); +} + +extern fn draw_rect(this: &Object, _sel: Sel, rect: id) { 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); - } + AppState::queue_redraw(WindowId(get_window_id(state.nswindow))); let superclass = util::superclass(this); let () = msg_send![super(this, superclass), drawRect:rect]; } } +extern fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { + YES +} + +// This is necessary to prevent a beefy terminal error on MacBook Pros: +// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem +// TODO: Add an API extension for using `NSTouchBar` +extern fn touch_bar(_this: &Object, _sel: Sel) -> BOOL { + NO +} + extern fn reset_cursor_rects(this: &Object, _sel: Sel) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); @@ -201,19 +317,22 @@ extern fn reset_cursor_rects(this: &Object, _sel: Sel) { } } + extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { - //println!("hasMarkedText"); unsafe { + trace!("Triggered `hasMarkedText`"); let marked_text: id = *this.get_ivar("markedText"); + trace!("Completed `hasMarkedText`"); (marked_text.length() > 0) as i8 } } extern fn marked_range(this: &Object, _sel: Sel) -> NSRange { - //println!("markedRange"); unsafe { + trace!("Triggered `markedRange`"); let marked_text: id = *this.get_ivar("markedText"); let length = marked_text.length(); + trace!("Completed `markedRange`"); if length > 0 { NSRange::new(0, length - 1) } else { @@ -223,7 +342,8 @@ extern fn marked_range(this: &Object, _sel: Sel) -> NSRange { } extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange { - //println!("selectedRange"); + trace!("Triggered `selectedRange`"); + trace!("Completed `selectedRange`"); util::EMPTY_RANGE } @@ -234,7 +354,7 @@ extern fn set_marked_text( _selected_range: NSRange, _replacement_range: NSRange, ) { - //println!("setMarkedText"); + trace!("Triggered `setMarkedText`"); unsafe { let marked_text_ref: &mut id = this.get_mut_ivar("markedText"); let _: () = msg_send![(*marked_text_ref), release]; @@ -247,10 +367,11 @@ extern fn set_marked_text( }; *marked_text_ref = marked_text; } + trace!("Completed `setMarkedText`"); } extern fn unmark_text(this: &Object, _sel: Sel) { - //println!("unmarkText"); + trace!("Triggered `unmarkText`"); unsafe { let marked_text: id = *this.get_ivar("markedText"); let mutable_string = marked_text.mutableString(); @@ -258,10 +379,12 @@ extern fn unmark_text(this: &Object, _sel: Sel) { let input_context: id = msg_send![this, inputContext]; let _: () = msg_send![input_context, discardMarkedText]; } + trace!("Completed `unmarkText`"); } extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id { - //println!("validAttributesForMarkedText"); + trace!("Triggered `validAttributesForMarkedText`"); + trace!("Completed `validAttributesForMarkedText`"); unsafe { msg_send![class!(NSArray), array] } } @@ -271,12 +394,14 @@ extern fn attributed_substring_for_proposed_range( _range: NSRange, _actual_range: *mut c_void, // *mut NSRange ) -> id { - //println!("attributedSubstringForProposedRange"); + trace!("Triggered `attributedSubstringForProposedRange`"); + trace!("Completed `attributedSubstringForProposedRange`"); nil } extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger { - //println!("characterIndexForPoint"); + trace!("Triggered `characterIndexForPoint`"); + trace!("Completed `characterIndexForPoint`"); 0 } @@ -286,20 +411,20 @@ extern fn first_rect_for_character_range( _range: NSRange, _actual_range: *mut c_void, // *mut NSRange ) -> NSRect { - //println!("firstRectForCharacterRange"); unsafe { + trace!("Triggered `firstRectForCharacterRange`"); 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), + state.nswindow, + NSWindow::frame(state.nswindow), ); let x = content_rect.origin.x; let y = util::bottom_left_to_top_left(content_rect); (x, y) }); - + trace!("Completed `firstRectForCharacterRange`"); NSRect::new( NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0), @@ -308,7 +433,7 @@ extern fn first_rect_for_character_range( } extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) { - //println!("insertText"); + trace!("Triggered `insertText`"); unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); @@ -330,46 +455,36 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: 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 event: id = msg_send![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)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::ReceivedCharacter(character), }); } - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .append(&mut events); - } + AppState::queue_events(events); } + trace!("Completed `insertText`"); } extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { - //println!("doCommandBySelector"); + trace!("Triggered `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)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::ReceivedCharacter('\r'), }); } else { @@ -377,50 +492,80 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { 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)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::ReceivedCharacter(character), }); } } }; - shared.pending_events - .lock() - .unwrap() - .append(&mut events); + AppState::queue_events(events); } + trace!("Completed `doCommandBySelector`"); } -fn get_characters(event: id) -> Option { +fn get_characters(event: id, ignore_modifiers: bool) -> String { unsafe { - let characters: id = msg_send![event, characters]; + 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); - Some(string.to_owned()) + string.to_owned() } } +// Retrieves a layout-independent keycode given an event. +fn retrieve_keycode(event: id) -> Option { + #[inline] + fn get_code(ev: id, raw: bool) -> Option { + 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"); + trace!("Triggered `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 window_id = WindowId(get_window_id(state.nswindow)); + let characters = get_characters(event, false); - state.raw_characters = get_characters(event); + state.raw_characters = Some(characters.clone()); + + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - // We are checking here for F21-F24 keys, since their keycode - // can vary, but we know that they are encoded - // in characters property. - let virtual_keycode = to_virtual_key_code(keycode) - .or_else(|| { - check_additional_virtual_key_codes(&state.raw_characters) - }); - let scancode = keycode as u32; let is_repeat = msg_send![event, isARepeat]; let window_event = Event::WindowEvent { @@ -436,65 +581,46 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { }, }; - let characters: id = msg_send![event, characters]; - let slice = slice::from_raw_parts( - characters.UTF8String() as *const c_uchar, - characters.len(), - ); - let string = str::from_utf8_unchecked(slice); - - state.raw_characters = { - Some(string.to_owned()) - }; - - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); + let pass_along = { + AppState::queue_event(window_event); // Emit `ReceivedCharacter` for key repeats - if is_repeat && state.is_key_down{ - for character in string.chars() { - let window_event = Event::WindowEvent { + if is_repeat && state.is_key_down { + for character in characters.chars() { + AppState::queue_event(Event::WindowEvent { window_id, event: WindowEvent::ReceivedCharacter(character), - }; - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); + }); } + false } 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]; + true } + }; + + if pass_along { + // 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]; } } + trace!("Completed `keyDown`"); } extern fn key_up(this: &Object, _sel: Sel, event: id) { - //println!("keyUp"); + trace!("Triggered `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; - // We need characters here to check for additional keys such as - // F21-F24. - let characters = get_characters(event); + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - let virtual_keycode = to_virtual_key_code(keycode) - .or_else(|| { - check_additional_virtual_key_codes(&characters) - }); - let scancode = keycode as u32; let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::KeyboardInput { device_id: DEVICE_ID, input: KeyboardInput { @@ -506,13 +632,63 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); + AppState::queue_event(window_event); + } + trace!("Completed `keyUp`"); +} + +extern fn flags_changed(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `flagsChanged`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let mut events = VecDeque::with_capacity(4); + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSShiftKeyMask, + state.modifiers.shift_pressed, + ) { + state.modifiers.shift_pressed = !state.modifiers.shift_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSControlKeyMask, + state.modifiers.ctrl_pressed, + ) { + state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSCommandKeyMask, + state.modifiers.win_pressed, + ) { + state.modifiers.win_pressed = !state.modifiers.win_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSAlternateKeyMask, + state.modifiers.alt_pressed, + ) { + state.modifiers.alt_pressed = !state.modifiers.alt_pressed; + events.push_back(window_event); + } + + for event in events { + AppState::queue_event(Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event, + }); } } + trace!("Completed `flagsChanged`"); } extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) { @@ -537,13 +713,45 @@ extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) { } } +// Allows us to receive Cmd-. (the shortcut for closing a dialog) +// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 +extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { + trace!("Triggered `cancelOperation`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let scancode = 0x2f; + let virtual_keycode = scancode_to_keycode(scancode); + debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); + + let event: id = msg_send![NSApp(), currentEvent]; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode: scancode as _, + virtual_keycode, + modifiers: event_mods(event), + }, + }, + }; + + AppState::queue_event(window_event); + } + trace!("Completed `cancelOperation`"); +} + 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)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::MouseInput { device_id: DEVICE_ID, state: button_state, @@ -552,12 +760,7 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); - } + AppState::queue_event(window_event); } } @@ -609,7 +812,7 @@ fn mouse_motion(this: &Object, event: id) { 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)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y).into(), @@ -617,12 +820,7 @@ fn mouse_motion(this: &Object, event: id) { }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); - } + AppState::queue_event(window_event); } } @@ -642,7 +840,125 @@ extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); } +extern fn mouse_entered(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `mouseEntered`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let enter_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorEntered { device_id: DEVICE_ID }, + }; + + let move_event = { + let window_point = event.locationInWindow(); + let view_point: NSPoint = msg_send![this, + convertPoint:window_point + fromView:nil // convert from window coordinates + ]; + let view_rect: NSRect = msg_send![this, frame]; + let x = view_point.x as f64; + let y = (view_rect.size.height - view_point.y) as f64; + Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorMoved { + device_id: DEVICE_ID, + position: (x, y).into(), + modifiers: event_mods(event), + } + } + }; + + AppState::queue_event(enter_event); + AppState::queue_event(move_event); + } + trace!("Completed `mouseEntered`"); +} + +extern fn mouse_exited(this: &Object, _sel: Sel, _event: id) { + trace!("Triggered `mouseExited`"); + 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.nswindow)), + event: WindowEvent::CursorLeft { device_id: DEVICE_ID }, + }; + + AppState::queue_event(window_event); + } + trace!("Completed `mouseExited`"); +} + +extern fn scroll_wheel(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `scrollWheel`"); + unsafe { + let delta = { + let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); + if event.hasPreciseScrollingDeltas() == YES { + MouseScrollDelta::PixelDelta((x as f64, y as f64).into()) + } else { + MouseScrollDelta::LineDelta(x as f32, y as f32) + } + }; + let phase = match event.phase() { + NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => TouchPhase::Moved, + }; + + let device_event = Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseWheel { delta }, + }; + + 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.nswindow)), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta, + phase, + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(device_event); + AppState::queue_event(window_event); + } + trace!("Completed `scrollWheel`"); +} + +extern fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `pressureChangeWithEvent`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let pressure = event.pressure(); + let stage = event.stage(); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::TouchpadPressure { + device_id: DEVICE_ID, + pressure, + stage, + }, + }; + + AppState::queue_event(window_event); + } + trace!("Completed `pressureChangeWithEvent`"); +} + +// Allows us to receive Ctrl-Tab and Ctrl-Esc. +// Note that this *doesn't* help with any missing Cmd inputs. // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 -extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL { +extern fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL { YES } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 34edc551..143d77e2 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1,51 +1,35 @@ -use std; -use std::cell::{Cell, RefCell}; -use std::f64; -use std::ops::Deref; -use std::os::raw::c_void; -use std::sync::{Mutex, Weak}; -use std::sync::atomic::{Ordering, AtomicBool}; - -use cocoa::appkit::{ - self, - CGFloat, - NSApp, - NSApplication, - NSColor, - NSRequestUserAttentionType, - NSScreen, - NSView, - NSWindow, - NSWindowButton, - NSWindowStyleMask, - NSApplicationActivationPolicy, - NSApplicationPresentationOptions, +use std::{ + collections::VecDeque, f64, os::raw::c_void, + sync::{Arc, atomic::{Ordering, AtomicBool}, Mutex, Weak}, }; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}; +use cocoa::{ + appkit::{ + self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, + NSColor, NSRequestUserAttentionType, NSScreen, NSView, NSWindow, + NSWindowButton, NSWindowStyleMask, NSApplicationPresentationOptions + }, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, +}; use core_graphics::display::CGDisplay; - -use objc; -use objc::runtime::{Class, Object, Sel, BOOL, YES, NO}; -use objc::declare::ClassDecl; +use objc::{runtime::{Class, Object, Sel, BOOL, YES, NO}, declare::ClassDecl}; use { - CreationError, - Event, - LogicalPosition, - LogicalSize, - MouseCursor, - WindowAttributes, - WindowEvent, - WindowId, + dpi::{LogicalPosition, LogicalSize}, icon::Icon, + error::{ExternalError, NotSupportedError, OsError as RootOsError}, + monitor::MonitorHandle as RootMonitorHandle, + window::{ + CursorIcon, WindowAttributes, WindowId as RootWindowId, + }, +}; +use platform::macos::{ActivationPolicy, WindowExtMacOS}; +use platform_impl::platform::{ + OsError, + app_state::AppState, ffi, monitor::{self, MonitorHandle}, + util::{self, IdRef}, view::{self, new_view}, + window_delegate::new_delegate, }; -use CreationError::OsError; -use os::macos::{ActivationPolicy, WindowExt}; -use platform_impl::platform::{ffi, util}; -use platform_impl::platform::event_loop::{EventLoop, Shared}; -use platform_impl::platform::view::{new_view, set_ime_spot}; -use window::MonitorHandle as RootMonitorHandle; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(pub usize); @@ -56,478 +40,10 @@ impl Id { } } -// TODO: It's possible for delegate methods to be called asynchronously, causing data races / `RefCell` panics. -pub struct DelegateState { - view: IdRef, - window: IdRef, - shared: Weak, - - win_attribs: RefCell, - standard_frame: Cell>, - is_simple_fullscreen: Cell, - save_style_mask: Cell>, - save_presentation_opts: Cell>, - - // This is set when WindowBuilder::with_fullscreen was set, - // see comments of `window_did_fail_to_enter_fullscreen` - handle_with_fullscreen: bool, - - // During `windowDidResize`, we use this to only send Moved if the position changed. - previous_position: Option<(f64, f64)>, - - // Used to prevent redundant events. - previous_dpi_factor: f64, -} - -impl DelegateState { - fn is_zoomed(&self) -> bool { - unsafe { - // Because isZoomed do not work in Borderless mode, we set it - // resizable temporality - let curr_mask = self.window.styleMask(); - - let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; - let needs_temp_mask = !curr_mask.contains(required); - if needs_temp_mask { - util::set_style_mask(*self.window, *self.view, required); - } - - let is_zoomed: BOOL = msg_send![*self.window, isZoomed]; - - // Roll back temp styles - if needs_temp_mask { - util::set_style_mask(*self.window, *self.view, curr_mask); - } - - is_zoomed != 0 - } - } - - unsafe fn saved_style_mask(&self, resizable: bool) -> NSWindowStyleMask { - let base_mask = self.save_style_mask - .take() - .unwrap_or_else(|| self.window.styleMask()); - if resizable { - base_mask | NSWindowStyleMask::NSResizableWindowMask - } else { - base_mask & !NSWindowStyleMask::NSResizableWindowMask - } - } - - fn saved_standard_frame(&self) -> NSRect { - self.standard_frame.get().unwrap_or_else(|| NSRect::new( - NSPoint::new(50.0, 50.0), - NSSize::new(800.0, 600.0), - )) - } - - fn restore_state_from_fullscreen(&mut self) { - let maximized = unsafe { - let mut win_attribs = self.win_attribs.borrow_mut(); - win_attribs.fullscreen = None; - - let mask = self.saved_style_mask(win_attribs.resizable); - util::set_style_mask(*self.window, *self.view, mask); - - win_attribs.maximized - }; - - self.perform_maximized(maximized); - } - - fn perform_maximized(&self, maximized: bool) { - let is_zoomed = self.is_zoomed(); - - if is_zoomed == maximized { - return; - } - - // Save the standard frame sized if it is not zoomed - if !is_zoomed { - unsafe { - self.standard_frame.set(Some(NSWindow::frame(*self.window))); - } - } - - let mut win_attribs = self.win_attribs.borrow_mut(); - win_attribs.maximized = maximized; - - let curr_mask = unsafe { self.window.styleMask() }; - if win_attribs.fullscreen.is_some() { - // Handle it in window_did_exit_fullscreen - return; - } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { - // Just use the native zoom if resizable - unsafe { - self.window.zoom_(nil); - } - } else { - // if it's not resizable, we set the frame directly - unsafe { - let new_rect = if maximized { - let screen = NSScreen::mainScreen(nil); - NSScreen::visibleFrame(screen) - } else { - self.saved_standard_frame() - }; - - self.window.setFrame_display_(new_rect, 0); - } - } - } -} - -pub struct WindowDelegate { - state: Box, - _this: IdRef, -} - -impl WindowDelegate { - // Emits an event via the `EventLoop`'s callback or stores it in the pending queue. - pub fn emit_event(state: &mut DelegateState, window_event: WindowEvent) { - let window_id = get_window_id(*state.window); - let event = Event::WindowEvent { - window_id: WindowId(window_id), - event: window_event, - }; - if let Some(shared) = state.shared.upgrade() { - shared.call_user_callback_with_event_or_store_in_pending(event); - } - } - - pub fn emit_resize_event(state: &mut DelegateState) { - let rect = unsafe { NSView::frame(*state.view) }; - let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); - WindowDelegate::emit_event(state, WindowEvent::Resized(size)); - } - - pub fn emit_move_event(state: &mut DelegateState) { - let rect = unsafe { NSWindow::frame(*state.window) }; - let x = rect.origin.x as f64; - let y = util::bottom_left_to_top_left(rect); - let moved = state.previous_position != Some((x, y)); - if moved { - state.previous_position = Some((x, y)); - WindowDelegate::emit_event(state, WindowEvent::Moved((x, y).into())); - } - } - - /// Get the delegate class, initiailizing it neccessary - fn class() -> *const Class { - use std::os::raw::c_void; - - extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::CloseRequested); - } - NO - } - - extern fn window_will_close(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - - WindowDelegate::emit_event(state, WindowEvent::Destroyed); - - // Remove the window from the shared state. - if let Some(shared) = state.shared.upgrade() { - let window_id = get_window_id(*state.window); - shared.find_and_remove_window(window_id); - } - } - } - - extern fn window_did_resize(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_resize_event(state); - WindowDelegate::emit_move_event(state); - } - } - - // This won't be triggered if the move was part of a resize. - extern fn window_did_move(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_move_event(state); - } - } - - extern fn window_did_change_screen(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64; - if state.previous_dpi_factor != dpi_factor { - state.previous_dpi_factor = dpi_factor; - WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor)); - WindowDelegate::emit_resize_event(state); - } - } - } - - // This will always be called before `window_did_change_screen`. - extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64; - if state.previous_dpi_factor != dpi_factor { - state.previous_dpi_factor = dpi_factor; - WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor)); - WindowDelegate::emit_resize_event(state); - } - } - } - - extern fn window_did_become_key(this: &Object, _: Sel, _: id) { - unsafe { - // TODO: center the cursor if the window had mouse grab when it - // lost focus - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::Focused(true)); - } - } - - extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::Focused(false)); - } - } - - /// Invoked when the dragged image enters destination bounds or frame - extern fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL { - use cocoa::appkit::NSPasteboard; - use cocoa::foundation::NSFastEnumeration; - use std::path::PathBuf; - - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - for file in unsafe { filenames.iter() } { - use cocoa::foundation::NSString; - use std::ffi::CStr; - - unsafe { - let f = NSString::UTF8String(file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); - - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::HoveredFile(PathBuf::from(path))); - } - }; - - YES - } - - /// Invoked when the image is released - extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL { - YES - } - - /// Invoked after the released image has been removed from the screen - extern fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL { - use cocoa::appkit::NSPasteboard; - use cocoa::foundation::NSFastEnumeration; - use std::path::PathBuf; - - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - for file in unsafe { filenames.iter() } { - use cocoa::foundation::NSString; - use std::ffi::CStr; - - unsafe { - let f = NSString::UTF8String(file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); - - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::DroppedFile(PathBuf::from(path))); - } - }; - - YES - } - - /// Invoked when the dragging operation is complete - extern fn conclude_drag_operation(_: &Object, _: Sel, _: id) {} - - /// Invoked when the dragging operation is cancelled - extern fn dragging_exited(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::HoveredFileCancelled); - } - } - - /// Invoked when entered fullscreen - extern fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id){ - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - state.win_attribs.borrow_mut().fullscreen = Some(get_current_monitor(*state.window)); - - state.handle_with_fullscreen = false; - } - } - - /// Invoked when before enter fullscreen - extern fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - let is_zoomed = state.is_zoomed(); - - state.win_attribs.borrow_mut().maximized = is_zoomed; - } - } - - /// Invoked when exited fullscreen - extern fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id){ - let state = unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - &mut *(state as *mut DelegateState) - }; - - state.restore_state_from_fullscreen(); - } - - /// Invoked when fail to enter fullscreen - /// - /// When this window launch from a fullscreen app (e.g. launch from VS Code - /// terminal), it creates a new virtual destkop and a transition animation. - /// This animation takes one second and cannot be disable without - /// elevated privileges. In this animation time, all toggleFullscreen events - /// will be failed. In this implementation, we will try again by using - /// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. - /// It should be fine as we only do this at initialzation (i.e with_fullscreen - /// was set). - /// - /// From Apple doc: - /// In some cases, the transition to enter full-screen mode can fail, - /// due to being in the midst of handling some other animation or user gesture. - /// This method indicates that there was an error, and you should clean up any - /// work you may have done to prepare to enter full-screen mode. - extern fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - - if state.handle_with_fullscreen { - let _: () = msg_send![*state.window, - performSelector:sel!(toggleFullScreen:) - withObject:nil - afterDelay: 0.5 - ]; - } else { - state.restore_state_from_fullscreen(); - } - } - } - - static mut DELEGATE_CLASS: *const Class = 0 as *const Class; - static INIT: std::sync::Once = std::sync::ONCE_INIT; - - INIT.call_once(|| unsafe { - // Create new NSWindowDelegate - let superclass = class!(NSObject); - let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); - - // Add callback methods - decl.add_method(sel!(windowShouldClose:), - window_should_close as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(windowWillClose:), - window_will_close as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidResize:), - window_did_resize as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidMove:), - window_did_move as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidChangeScreen:), - window_did_change_screen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidChangeBackingProperties:), - window_did_change_backing_properties as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidBecomeKey:), - window_did_become_key as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidResignKey:), - window_did_resign_key as extern fn(&Object, Sel, id)); - - // callbacks for drag and drop events - decl.add_method(sel!(draggingEntered:), - dragging_entered as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(prepareForDragOperation:), - prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(performDragOperation:), - perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(concludeDragOperation:), - conclude_drag_operation as extern fn(&Object, Sel, id)); - decl.add_method(sel!(draggingExited:), - dragging_exited as extern fn(&Object, Sel, id)); - - // callbacks for fullscreen events - decl.add_method(sel!(windowDidEnterFullScreen:), - window_did_enter_fullscreen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowWillEnterFullScreen:), - window_will_enter_fullscreen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidExitFullScreen:), - window_did_exit_fullscreen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidFailToEnterFullScreen:), - window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id)); - - // Store internal state as user data - decl.add_ivar::<*mut c_void>("winitState"); - - DELEGATE_CLASS = decl.register(); - }); - - unsafe { - DELEGATE_CLASS - } - } - - fn new(state: DelegateState) -> WindowDelegate { - // Box the state so we can give a pointer to it - let mut state = Box::new(state); - let state_ptr: *mut DelegateState = &mut *state; - unsafe { - let delegate = IdRef::new(msg_send![WindowDelegate::class(), new]); - - // setDelegate uses autorelease on objects, - // so need autorelease - let autoreleasepool = NSAutoreleasePool::new(nil); - - (&mut **delegate).set_ivar("winitState", state_ptr as *mut ::std::os::raw::c_void); - let _: () = msg_send![*state.window, setDelegate:*delegate]; - - let _: () = msg_send![autoreleasepool, drain]; - - WindowDelegate { state: state, _this: delegate } - } - } -} - -impl Drop for WindowDelegate { - fn drop(&mut self) { - unsafe { - // Nil the window's delegate so it doesn't still reference us - // NOTE: setDelegate:nil at first retains the previous value, - // and then autoreleases it, so autorelease pool is needed - let autoreleasepool = NSAutoreleasePool::new(nil); - let _: () = msg_send![*self.state.window, setDelegate:nil]; - let _: () = msg_send![autoreleasepool, drain]; - } - } +// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier +// for the window. +pub fn get_window_id(window_cocoa_id: id) -> Id { + Id(window_cocoa_id as *const Object as _) } #[derive(Clone, Default)] @@ -542,91 +58,751 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub resize_increments: Option, } -pub struct Window2 { - pub view: IdRef, - pub window: IdRef, - pub delegate: WindowDelegate, - pub input_context: IdRef, - cursor: Weak>, - cursor_hidden: AtomicBool, +fn create_app(activation_policy: ActivationPolicy) -> Option { + unsafe { + let nsapp = NSApp(); + if nsapp == nil { + None + } else { + use self::NSApplicationActivationPolicy::*; + nsapp.setActivationPolicy_(match activation_policy { + ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, + ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, + ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, + }); + nsapp.finishLaunching(); + Some(nsapp) + } + } } -unsafe impl Send for Window2 {} -unsafe impl Sync for Window2 {} +unsafe fn create_view(nswindow: id) -> Option<(IdRef, Weak>)> { + let (nsview, cursor) = new_view(nswindow); + nsview.non_nil().map(|nsview| { + nsview.setWantsBestResolutionOpenGLSurface_(YES); -unsafe fn get_current_monitor(window: id) -> RootMonitorHandle { - let screen: id = msg_send![window, screen]; - let desc = NSScreen::deviceDescription(screen); - let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); - let value = NSDictionary::valueForKey_(desc, *key); - let display_id = msg_send![value, unsignedIntegerValue]; - RootMonitorHandle { inner: EventLoop::make_monitor_from_display(display_id) } -} - -impl Drop for Window2 { - fn drop(&mut self) { - // Remove this window from the `EventLoop`s list of windows. - // The destructor order is: - // Window -> - // Rc (makes Weak<..> in shared.windows None) -> - // Window2 - // needed to remove the element from array - let id = self.id(); - if let Some(shared) = self.delegate.state.shared.upgrade() { - shared.find_and_remove_window(id); + // On Mojave, views automatically become layer-backed shortly after being added to + // a window. Changing the layer-backedness of a view breaks the association between + // the view and its associated OpenGL context. To work around this, on Mojave we + // explicitly make the view layer-backed up front so that AppKit doesn't do it + // itself and break the association with its context. + if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { + nsview.setWantsLayer(YES); } - // nswindow::close uses autorelease - // so autorelease pool - let autoreleasepool = unsafe { - NSAutoreleasePool::new(nil) + nswindow.setContentView_(*nsview); + nswindow.makeFirstResponder_(*nsview); + (nsview, cursor) + }) +} + +fn create_window( + attrs: &WindowAttributes, + pl_attrs: &PlatformSpecificWindowBuilderAttributes, +) -> Option { + unsafe { + let pool = NSAutoreleasePool::new(nil); + let screen = match attrs.fullscreen { + Some(ref monitor_id) => { + let monitor_screen = monitor_id.inner.nsscreen(); + Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) + }, + _ => None, + }; + let frame = match screen { + Some(screen) => appkit::NSScreen::frame(screen), + None => { + let (width, height) = attrs.inner_size + .map(|logical| (logical.width, logical.height)) + .unwrap_or_else(|| (800.0, 600.0)); + NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) + }, }; - // Close the window if it has not yet been closed. - let nswindow = *self.window; - if nswindow != nil { - unsafe { - let () = msg_send![nswindow, close]; + let mut masks = if !attrs.decorations && !screen.is_some() { + // Resizable UnownedWindow without a titlebar or borders + // if decorations is set to false, ignore pl_attrs + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + } else if pl_attrs.titlebar_hidden { + // if the titlebar is hidden, ignore other pl_attrs + NSWindowStyleMask::NSBorderlessWindowMask | + NSWindowStyleMask::NSResizableWindowMask + } else { + // default case, resizable window with titlebar and titlebar buttons + NSWindowStyleMask::NSClosableWindowMask | + NSWindowStyleMask::NSMiniaturizableWindowMask | + NSWindowStyleMask::NSResizableWindowMask | + NSWindowStyleMask::NSTitledWindowMask + }; + + if !attrs.resizable { + masks &= !NSWindowStyleMask::NSResizableWindowMask; + } + + if pl_attrs.fullsize_content_view { + masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; + } + + let nswindow: id = msg_send![WINDOW_CLASS.0, alloc]; + let nswindow = IdRef::new(nswindow.initWithContentRect_styleMask_backing_defer_( + frame, + masks, + appkit::NSBackingStoreBuffered, + NO, + )); + let res = nswindow.non_nil().map(|nswindow| { + let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); + nswindow.setReleasedWhenClosed_(NO); + nswindow.setTitle_(*title); + nswindow.setAcceptsMouseMovedEvents_(YES); + + if pl_attrs.titlebar_transparent { + nswindow.setTitlebarAppearsTransparent_(YES); + } + if pl_attrs.title_hidden { + nswindow.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); + } + if pl_attrs.titlebar_buttons_hidden { + for titlebar_button in &[ + NSWindowButton::NSWindowFullScreenButton, + NSWindowButton::NSWindowMiniaturizeButton, + NSWindowButton::NSWindowCloseButton, + NSWindowButton::NSWindowZoomButton, + ] { + let button = nswindow.standardWindowButton_(*titlebar_button); + let _: () = msg_send![button, setHidden:YES]; + } + } + if pl_attrs.movable_by_window_background { + nswindow.setMovableByWindowBackground_(YES); + } + + if attrs.always_on_top { + let _: () = msg_send![*nswindow, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel]; + } + + if let Some(increments) = pl_attrs.resize_increments { + let (x, y) = (increments.width, increments.height); + if x >= 1.0 && y >= 1.0 { + let size = NSSize::new(x as CGFloat, y as CGFloat); + nswindow.setResizeIncrements_(size); + } + } + + nswindow.center(); + nswindow + }); + pool.drain(); + res + } +} + +struct WindowClass(*const Class); +unsafe impl Send for WindowClass {} +unsafe impl Sync for WindowClass {} + +lazy_static! { + static ref WINDOW_CLASS: WindowClass = unsafe { + let window_superclass = class!(NSWindow); + let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); + decl.add_method(sel!(canBecomeMainWindow), util::yes as extern fn(&Object, Sel) -> BOOL); + decl.add_method(sel!(canBecomeKeyWindow), util::yes as extern fn(&Object, Sel) -> BOOL); + WindowClass(decl.register()) + }; +} + +#[derive(Default)] +pub struct SharedState { + pub resizable: bool, + pub fullscreen: Option, + pub maximized: bool, + standard_frame: Option, + is_simple_fullscreen: bool, + pub saved_style: Option, + save_presentation_opts: Option, +} + +impl From for SharedState { + fn from(attribs: WindowAttributes) -> Self { + SharedState { + resizable: attribs.resizable, + // This fullscreen field tracks the current state of the window + // (as seen by `WindowDelegate`), and since the window hasn't + // actually been fullscreened yet, we can't set it yet. This is + // necessary for state transitions to work right, since otherwise + // the initial value and the first `set_fullscreen` call would be + // identical, resulting in a no-op. + fullscreen: None, + maximized: attribs.maximized, + .. Default::default() + } + } +} + +pub struct UnownedWindow { + pub nswindow: IdRef, // never changes + pub nsview: IdRef, // never changes + input_context: IdRef, // never changes + pub shared_state: Arc>, + decorations: AtomicBool, + cursor: Weak>, + cursor_visible: AtomicBool, +} + +unsafe impl Send for UnownedWindow {} +unsafe impl Sync for UnownedWindow {} + +impl UnownedWindow { + pub fn new( + mut win_attribs: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result<(Arc, IdRef), RootOsError> { + unsafe { + if !msg_send![class!(NSThread), isMainThread] { + panic!("Windows can only be created on the main thread on macOS"); } } - let _: () = unsafe { msg_send![autoreleasepool, drain] }; + let pool = unsafe { NSAutoreleasePool::new(nil) }; + + let nsapp = create_app(pl_attribs.activation_policy).ok_or_else(|| { + unsafe { pool.drain() }; + os_error!(OsError::CreationError("Couldn't create `NSApplication`")) + })?; + + let nswindow = create_window(&win_attribs, &pl_attribs).ok_or_else(|| { + unsafe { pool.drain() }; + os_error!(OsError::CreationError("Couldn't create `NSWindow`")) + })?; + + let (nsview, cursor) = unsafe { create_view(*nswindow) }.ok_or_else(|| { + unsafe { pool.drain() }; + os_error!(OsError::CreationError("Couldn't create `NSView`")) + })?; + + let input_context = unsafe { util::create_input_context(*nsview) }; + + unsafe { + if win_attribs.transparent { + nswindow.setOpaque_(NO); + nswindow.setBackgroundColor_(NSColor::clearColor(nil)); + } + + nsapp.activateIgnoringOtherApps_(YES); + + win_attribs.min_inner_size.map(|dim| set_min_inner_size(*nswindow, dim)); + win_attribs.max_inner_size.map(|dim| set_max_inner_size(*nswindow, dim)); + + use cocoa::foundation::NSArray; + // register for drag and drop operations. + let () = msg_send![*nswindow, registerForDraggedTypes:NSArray::arrayWithObject( + nil, + appkit::NSFilenamesPboardType, + )]; + } + + // Since `win_attribs` is put into a mutex below, we'll just copy these + // attributes now instead of bothering to lock it later. + // Also, `SharedState` doesn't carry `fullscreen` over; it's set + // indirectly by us calling `set_fullscreen` below, causing handlers in + // `WindowDelegate` to update the state. + let fullscreen = win_attribs.fullscreen.take(); + let maximized = win_attribs.maximized; + let visible = win_attribs.visible; + let decorations = win_attribs.decorations; + + let window = Arc::new(UnownedWindow { + nsview, + nswindow, + input_context, + shared_state: Arc::new(Mutex::new(win_attribs.into())), + decorations: AtomicBool::new(decorations), + cursor, + cursor_visible: AtomicBool::new(true), + }); + + let delegate = new_delegate(&window, fullscreen.is_some()); + + // Set fullscreen mode after we setup everything + if let Some(monitor) = fullscreen { + if monitor.inner != window.current_monitor().inner { + // To do this with native fullscreen, we probably need to + // warp the window... while we could use + // `enterFullScreenMode`, they're idiomatically different + // fullscreen modes, so we'd have to support both anyway. + unimplemented!(); + } + window.set_fullscreen(Some(monitor)); + } + + // Setting the window as key has to happen *after* we set the fullscreen + // state, since otherwise we'll briefly see the window at normal size + // before it transitions. + unsafe { + if visible { + window.nswindow.makeKeyAndOrderFront_(nil); + } else { + window.nswindow.makeKeyWindow(); + } + } + + if maximized { + window.set_maximized(maximized); + } + + unsafe { pool.drain() }; + + Ok((window, delegate)) + } + + fn set_style_mask_async(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_async( + *self.nswindow, + *self.nsview, + mask, + ) }; + } + + fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_sync( + *self.nswindow, + *self.nsview, + mask, + ) }; + } + + pub fn id(&self) -> Id { + get_window_id(*self.nswindow) + } + + pub fn set_title(&self, title: &str) { + unsafe { + let title = IdRef::new(NSString::alloc(nil).init_str(title)); + self.nswindow.setTitle_(*title); + } + } + + pub fn set_visible(&self, visible: bool) { + match visible { + true => unsafe { util::make_key_and_order_front_async(*self.nswindow) }, + false => unsafe { util::order_out_async(*self.nswindow) }, + } + } + + pub fn request_redraw(&self) { + AppState::queue_redraw(RootWindowId(self.id())); + } + + pub fn outer_position(&self) -> Result { + let frame_rect = unsafe { NSWindow::frame(*self.nswindow) }; + Ok(( + frame_rect.origin.x as f64, + util::bottom_left_to_top_left(frame_rect), + ).into()) + } + + pub fn inner_position(&self) -> Result { + let content_rect = unsafe { + NSWindow::contentRectForFrameRect_( + *self.nswindow, + NSWindow::frame(*self.nswindow), + ) + }; + Ok(( + content_rect.origin.x as f64, + util::bottom_left_to_top_left(content_rect), + ).into()) + } + + pub fn set_outer_position(&self, position: LogicalPosition) { + let dummy = NSRect::new( + NSPoint::new( + position.x, + // While it's true that we're setting the top-left position, + // it still needs to be in a bottom-left coordinate system. + CGDisplay::main().pixels_high() as f64 - position.y, + ), + NSSize::new(0f64, 0f64), + ); + unsafe { + util::set_frame_top_left_point_async(*self.nswindow, dummy.origin); + } + } + + #[inline] + pub fn inner_size(&self) -> LogicalSize { + let view_frame = unsafe { NSView::frame(*self.nsview) }; + (view_frame.size.width as f64, view_frame.size.height as f64).into() + } + + #[inline] + pub fn outer_size(&self) -> LogicalSize { + let view_frame = unsafe { NSWindow::frame(*self.nswindow) }; + (view_frame.size.width as f64, view_frame.size.height as f64).into() + } + + #[inline] + pub fn set_inner_size(&self, size: LogicalSize) { + unsafe { + util::set_content_size_async(*self.nswindow, size); + } + } + + pub fn set_min_inner_size(&self, dimensions: Option) { + unsafe { + let dimensions = dimensions.unwrap_or_else(|| (0, 0).into()); + set_min_inner_size(*self.nswindow, dimensions); + } + } + + pub fn set_max_inner_size(&self, dimensions: Option) { + unsafe { + let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into()); + set_max_inner_size(*self.nswindow, dimensions); + } + } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + let fullscreen = { + trace!("Locked shared state in `set_resizable`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.resizable = resizable; + trace!("Unlocked shared state in `set_resizable`"); + shared_state_lock.fullscreen.is_some() + }; + if !fullscreen { + let mut mask = unsafe { self.nswindow.styleMask() }; + if resizable { + mask |= NSWindowStyleMask::NSResizableWindowMask; + } else { + mask &= !NSWindowStyleMask::NSResizableWindowMask; + } + self.set_style_mask_async(mask); + } // Otherwise, we don't change the mask until we exit fullscreen. + } + + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + let cursor = util::Cursor::from(cursor); + if let Some(cursor_access) = self.cursor.upgrade() { + *cursor_access.lock().unwrap() = cursor; + } + unsafe { + let _: () = msg_send![*self.nswindow, + invalidateCursorRectsForView:*self.nsview + ]; + } + } + + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 + CGDisplay::associate_mouse_and_mouse_cursor_position(!grab) + .map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status)))) + } + + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + let cursor_class = class!(NSCursor); + // macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once. + // (otherwise, `hide_cursor(false)` would need to be called n times!) + if visible != self.cursor_visible.load(Ordering::Acquire) { + if visible { + let _: () = unsafe { msg_send![cursor_class, unhide] }; + } else { + let _: () = unsafe { msg_send![cursor_class, hide] }; + } + self.cursor_visible.store(visible, Ordering::Release); + } + } + + #[inline] + pub fn hidpi_factor(&self) -> f64 { + unsafe { NSWindow::backingScaleFactor(*self.nswindow) as _ } + } + + #[inline] + pub fn set_cursor_position(&self, cursor_position: LogicalPosition) -> Result<(), ExternalError> { + let window_position = self.inner_position().unwrap(); + let point = appkit::CGPoint { + x: (cursor_position.x + window_position.x) as CGFloat, + y: (cursor_position.y + window_position.y) as CGFloat, + }; + CGDisplay::warp_mouse_cursor_position(point) + .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; + CGDisplay::associate_mouse_and_mouse_cursor_position(true) + .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; + + Ok(()) + } + + pub(crate) fn is_zoomed(&self) -> bool { + // because `isZoomed` doesn't work if the window's borderless, + // we make it resizable temporalily. + let curr_mask = unsafe { self.nswindow.styleMask() }; + + let required = NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSResizableWindowMask; + let needs_temp_mask = !curr_mask.contains(required); + if needs_temp_mask { + self.set_style_mask_sync(required); + } + + let is_zoomed: BOOL = unsafe { msg_send![*self.nswindow, isZoomed] }; + + // Roll back temp styles + if needs_temp_mask { + self.set_style_mask_async(curr_mask); + } + + is_zoomed != 0 + } + + fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { + let base_mask = shared_state.saved_style + .take() + .unwrap_or_else(|| unsafe { self.nswindow.styleMask() }); + if shared_state.resizable { + base_mask | NSWindowStyleMask::NSResizableWindowMask + } else { + base_mask & !NSWindowStyleMask::NSResizableWindowMask + } + } + + fn saved_standard_frame(shared_state: &mut SharedState) -> NSRect { + shared_state.standard_frame.unwrap_or_else(|| NSRect::new( + NSPoint::new(50.0, 50.0), + NSSize::new(800.0, 600.0), + )) + } + + pub(crate) fn restore_state_from_fullscreen(&self) { + let maximized = { + trace!("Locked shared state in `restore_state_from_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + shared_state_lock.fullscreen = None; + + let mask = self.saved_style(&mut *shared_state_lock); + + self.set_style_mask_async(mask); + shared_state_lock.maximized + }; + trace!("Unocked shared state in `restore_state_from_fullscreen`"); + self.set_maximized(maximized); + } + + #[inline] + pub fn set_maximized(&self, maximized: bool) { + let is_zoomed = self.is_zoomed(); + if is_zoomed == maximized { return }; + + trace!("Locked shared state in `set_maximized`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + // Save the standard frame sized if it is not zoomed + if !is_zoomed { + unsafe { + shared_state_lock.standard_frame = Some(NSWindow::frame(*self.nswindow)); + } + } + + shared_state_lock.maximized = maximized; + + let curr_mask = unsafe { self.nswindow.styleMask() }; + if shared_state_lock.fullscreen.is_some() { + // Handle it in window_did_exit_fullscreen + return; + } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { + // Just use the native zoom if resizable + unsafe { self.nswindow.zoom_(nil) }; + } else { + // if it's not resizable, we set the frame directly + unsafe { + let new_rect = if maximized { + let screen = NSScreen::mainScreen(nil); + NSScreen::visibleFrame(screen) + } else { + Self::saved_standard_frame(&mut *shared_state_lock) + }; + // This probably isn't thread-safe! + self.nswindow.setFrame_display_(new_rect, 0); + } + } + + trace!("Unlocked shared state in `set_maximized`"); + } + + #[inline] + pub fn fullscreen(&self) -> Option { + let shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen.clone() + } + + #[inline] + /// TODO: Right now set_fullscreen do not work on switching monitors + /// in fullscreen mode + pub fn set_fullscreen(&self, monitor: Option) { + let shared_state_lock = self.shared_state.lock().unwrap(); + if shared_state_lock.is_simple_fullscreen { + return + } + + let not_fullscreen = { + trace!("Locked shared state in `set_fullscreen`"); + let current = &shared_state_lock.fullscreen; + match (current, monitor) { + (&Some(ref a), Some(ref b)) if a.inner != b.inner => { + // Our best bet is probably to move to the origin of the + // target monitor. + unimplemented!() + }, + (&None, None) | (&Some(_), Some(_)) => return, + _ => (), + } + trace!("Unlocked shared state in `set_fullscreen`"); + current.is_none() + }; + + unsafe { util::toggle_full_screen_async( + *self.nswindow, + *self.nsview, + not_fullscreen, + Arc::downgrade(&self.shared_state), + ) }; + } + + #[inline] + pub fn set_decorations(&self, decorations: bool) { + if decorations != self.decorations.load(Ordering::Acquire) { + self.decorations.store(decorations, Ordering::Release); + + let (fullscreen, resizable) = { + trace!("Locked shared state in `set_decorations`"); + let shared_state_lock = self.shared_state.lock().unwrap(); + trace!("Unlocked shared state in `set_decorations`"); + ( + shared_state_lock.fullscreen.is_some(), + shared_state_lock.resizable, + ) + }; + + // If we're in fullscreen mode, we wait to apply decoration changes + // until we're in `window_did_exit_fullscreen`. + if fullscreen { return } + + let new_mask = { + let mut new_mask = if decorations { + NSWindowStyleMask::NSClosableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask + | NSWindowStyleMask::NSResizableWindowMask + | NSWindowStyleMask::NSTitledWindowMask + } else { + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + }; + if !resizable { + new_mask &= !NSWindowStyleMask::NSResizableWindowMask; + } + new_mask + }; + self.set_style_mask_async(new_mask); + } + } + + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + let level = if always_on_top { + ffi::NSWindowLevel::NSFloatingWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + unsafe { util::set_level_async(*self.nswindow, level) }; + } + + #[inline] + pub fn set_window_icon(&self, _icon: Option) { + // macOS doesn't have window icons. Though, there is + // `setRepresentedFilename`, but that's semantically distinct and should + // only be used when the window is in some way representing a specific + // file/directory. For instance, Terminal.app uses this for the CWD. + // Anyway, that should eventually be implemented as + // `WindowBuilderExt::with_represented_file` or something, and doesn't + // have anything to do with `set_window_icon`. + // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html + } + + #[inline] + pub fn set_ime_position(&self, logical_spot: LogicalPosition) { + unsafe { + view::set_ime_position( + *self.nsview, + *self.input_context, + logical_spot.x, + logical_spot.y, + ); + } + } + + #[inline] + pub fn current_monitor(&self) -> RootMonitorHandle { + unsafe { + let screen: id = msg_send![*self.nswindow, screen]; + let desc = NSScreen::deviceDescription(screen); + let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); + let value = NSDictionary::valueForKey_(desc, *key); + let display_id = msg_send![value, unsignedIntegerValue]; + RootMonitorHandle { inner: MonitorHandle::new(display_id) } + } + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + monitor::primary_monitor() } } -impl WindowExt for Window2 { +impl WindowExtMacOS for UnownedWindow { #[inline] - fn get_nswindow(&self) -> *mut c_void { - *self.window as *mut c_void + fn nswindow(&self) -> *mut c_void { + *self.nswindow as *mut _ } #[inline] - fn get_nsview(&self) -> *mut c_void { - *self.view as *mut c_void + fn nsview(&self) -> *mut c_void { + *self.nsview as *mut _ } #[inline] fn request_user_attention(&self, is_critical: bool) { - let request_type = if is_critical { - NSRequestUserAttentionType::NSCriticalRequest - } else { - NSRequestUserAttentionType::NSInformationalRequest - }; - unsafe { - NSApp().requestUserAttention_(request_type); + NSApp().requestUserAttention_(match is_critical { + true => NSRequestUserAttentionType::NSCriticalRequest, + false => NSRequestUserAttentionType::NSInformationalRequest, + }); } } + #[inline] + fn simple_fullscreen(&self) -> bool { + let shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.is_simple_fullscreen + } + #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { - let state = &self.delegate.state; + let mut shared_state_lock = self.shared_state.lock().unwrap(); unsafe { let app = NSApp(); - let win_attribs = state.win_attribs.borrow_mut(); - let is_native_fullscreen = win_attribs.fullscreen.is_some(); - let is_simple_fullscreen = state.is_simple_fullscreen.get(); + let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); + let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; // Do nothing if native fullscreen is active. if is_native_fullscreen || (fullscreen && is_simple_fullscreen) || (!fullscreen && !is_simple_fullscreen) { @@ -635,12 +811,12 @@ impl WindowExt for Window2 { if fullscreen { // Remember the original window's settings - state.standard_frame.set(Some(NSWindow::frame(*self.window))); - state.save_style_mask.set(Some(self.window.styleMask())); - state.save_presentation_opts.set(Some(app.presentationOptions_())); + shared_state_lock.standard_frame = Some(NSWindow::frame(*self.nswindow)); + shared_state_lock.saved_style = Some(self.nswindow.styleMask()); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); // Tell our window's state that we're in fullscreen - state.is_simple_fullscreen.set(true); + shared_state_lock.is_simple_fullscreen = true; // Simulate pre-Lion fullscreen by hiding the dock and menu bar let presentation_options = @@ -649,31 +825,31 @@ impl WindowExt for Window2 { app.setPresentationOptions_(presentation_options); // Hide the titlebar - util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSTitledWindowMask, false); + util::toggle_style_mask(*self.nswindow, *self.nsview, NSWindowStyleMask::NSTitledWindowMask, false); // Set the window frame to the screen frame size - let screen = self.window.screen(); + let screen = self.nswindow.screen(); let screen_frame = NSScreen::frame(screen); - NSWindow::setFrame_display_(*self.window, screen_frame, YES); + NSWindow::setFrame_display_(*self.nswindow, screen_frame, YES); // Fullscreen windows can't be resized, minimized, or moved - util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSMiniaturizableWindowMask, false); - util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSResizableWindowMask, false); - NSWindow::setMovable_(*self.window, NO); + util::toggle_style_mask(*self.nswindow, *self.nsview, NSWindowStyleMask::NSMiniaturizableWindowMask, false); + util::toggle_style_mask(*self.nswindow, *self.nsview, NSWindowStyleMask::NSResizableWindowMask, false); + NSWindow::setMovable_(*self.nswindow, NO); true } else { - let saved_style_mask = state.saved_style_mask(win_attribs.resizable); - util::set_style_mask(*self.window, *self.view, saved_style_mask); - state.is_simple_fullscreen.set(false); + let new_mask = self.saved_style(&mut *shared_state_lock); + self.set_style_mask_async(new_mask); + shared_state_lock.is_simple_fullscreen = false; - if let Some(presentation_opts) = state.save_presentation_opts.get() { + if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { app.setPresentationOptions_(presentation_opts); } - let frame = state.saved_standard_frame(); - NSWindow::setFrame_display_(*self.window, frame, YES); - NSWindow::setMovable_(*self.window, YES); + let frame = Self::saved_standard_frame(&mut *shared_state_lock); + NSWindow::setFrame_display_(*self.nswindow, frame, YES); + NSWindow::setMovable_(*self.nswindow, YES); true } @@ -681,587 +857,17 @@ impl WindowExt for Window2 { } } -impl Window2 { - pub fn new( - shared: Weak, - mut win_attribs: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("Windows can only be created on the main thread on macOS"); - } - } - - // Might as well save some RAM... - win_attribs.window_icon.take(); - - let autoreleasepool = unsafe { - NSAutoreleasePool::new(nil) - }; - - let app = match Window2::create_app(pl_attribs.activation_policy) { - Some(app) => app, - None => { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - return Err(OsError(format!("Couldn't create NSApplication"))); - }, - }; - - let window = match Window2::create_window(&win_attribs, &pl_attribs) { - Some(res) => res, - None => { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - return Err(OsError(format!("Couldn't create NSWindow"))); - }, - }; - let (view, cursor) = match Window2::create_view(*window, Weak::clone(&shared)) { - Some(view) => view, - None => { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - return Err(OsError(format!("Couldn't create NSView"))); - }, - }; - - let input_context = unsafe { util::create_input_context(*view) }; - - unsafe { - if win_attribs.transparent { - (*window as id).setOpaque_(NO); - (*window as id).setBackgroundColor_(NSColor::clearColor(nil)); - } - - app.activateIgnoringOtherApps_(YES); - - if let Some(dimensions) = win_attribs.min_dimensions { - nswindow_set_min_dimensions(window.0, dimensions); - } - if let Some(dimensions) = win_attribs.max_dimensions { - nswindow_set_max_dimensions(window.0, dimensions); - } - - use cocoa::foundation::NSArray; - // register for drag and drop operations. - let () = msg_send![(*window as id), - registerForDraggedTypes:NSArray::arrayWithObject(nil, appkit::NSFilenamesPboardType)]; - } - - let dpi_factor = unsafe { NSWindow::backingScaleFactor(*window) as f64 }; - - let mut delegate_state = DelegateState { - view: view.clone(), - window: window.clone(), - shared, - win_attribs: RefCell::new(win_attribs.clone()), - standard_frame: Cell::new(None), - is_simple_fullscreen: Cell::new(false), - save_style_mask: Cell::new(None), - save_presentation_opts: Cell::new(None), - handle_with_fullscreen: win_attribs.fullscreen.is_some(), - previous_position: None, - previous_dpi_factor: dpi_factor, - }; - delegate_state.win_attribs.borrow_mut().fullscreen = None; - - if dpi_factor != 1.0 { - WindowDelegate::emit_event(&mut delegate_state, WindowEvent::HiDpiFactorChanged(dpi_factor)); - WindowDelegate::emit_resize_event(&mut delegate_state); - } - - let window = Window2 { - view: view, - window: window, - delegate: WindowDelegate::new(delegate_state), - input_context, - cursor, - cursor_hidden: Default::default(), - }; - - // Set fullscreen mode after we setup everything - if let Some(ref monitor) = win_attribs.fullscreen { - unsafe { - if monitor.inner != get_current_monitor(*window.window).inner { - unimplemented!(); - } - } - window.set_fullscreen(Some(monitor.clone())); - } - - // Make key have to be after set fullscreen - // to prevent normal size window brefly appears - unsafe { - if win_attribs.visible { - window.window.makeKeyAndOrderFront_(nil); - } else { - window.window.makeKeyWindow(); - } - } - - if win_attribs.maximized { - window.delegate.state.perform_maximized(win_attribs.maximized); - } - - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - - Ok(window) - } - - pub fn id(&self) -> Id { - get_window_id(*self.window) - } - - fn create_app(activation_policy: ActivationPolicy) -> Option { - unsafe { - let app = appkit::NSApp(); - if app == nil { - None - } else { - let ns_activation_policy = match activation_policy { - ActivationPolicy::Regular => - NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - ActivationPolicy::Accessory => - NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory, - ActivationPolicy::Prohibited => - NSApplicationActivationPolicy::NSApplicationActivationPolicyProhibited, - }; - app.setActivationPolicy_(ns_activation_policy); - app.finishLaunching(); - Some(app) - } - } - } - - fn class() -> *const Class { - static mut WINDOW2_CLASS: *const Class = 0 as *const Class; - static INIT: std::sync::Once = std::sync::ONCE_INIT; - - INIT.call_once(|| unsafe { - let window_superclass = class!(NSWindow); - let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); - decl.add_method(sel!(canBecomeMainWindow), yes as extern fn(&Object, Sel) -> BOOL); - decl.add_method(sel!(canBecomeKeyWindow), yes as extern fn(&Object, Sel) -> BOOL); - WINDOW2_CLASS = decl.register(); - }); - - unsafe { - WINDOW2_CLASS - } - } - - fn create_window( - attrs: &WindowAttributes, - pl_attrs: &PlatformSpecificWindowBuilderAttributes - ) -> Option { - unsafe { - let autoreleasepool = NSAutoreleasePool::new(nil); - let screen = match attrs.fullscreen { - Some(ref monitor_id) => { - let monitor_screen = monitor_id.inner.get_nsscreen(); - Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) - }, - _ => None, - }; - let frame = match screen { - Some(screen) => appkit::NSScreen::frame(screen), - None => { - let (width, height) = attrs.dimensions - .map(|logical| (logical.width, logical.height)) - .unwrap_or((800.0, 600.0)); - NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) - } - }; - - let mut masks = if !attrs.decorations && !screen.is_some() { - // Resizable Window2 without a titlebar or borders - // if decorations is set to false, ignore pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask - | NSWindowStyleMask::NSResizableWindowMask - } else if pl_attrs.titlebar_hidden { - // if the titlebar is hidden, ignore other pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask | - NSWindowStyleMask::NSResizableWindowMask - } else { - // default case, resizable window with titlebar and titlebar buttons - NSWindowStyleMask::NSClosableWindowMask | - NSWindowStyleMask::NSMiniaturizableWindowMask | - NSWindowStyleMask::NSResizableWindowMask | - NSWindowStyleMask::NSTitledWindowMask - }; - - if !attrs.resizable { - masks &= !NSWindowStyleMask::NSResizableWindowMask; - } - - if pl_attrs.fullsize_content_view { - masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; - } - - let winit_window = Window2::class(); - - let window: id = msg_send![winit_window, alloc]; - - let window = IdRef::new(window.initWithContentRect_styleMask_backing_defer_( - frame, - masks, - appkit::NSBackingStoreBuffered, - NO, - )); - let res = window.non_nil().map(|window| { - let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); - window.setReleasedWhenClosed_(NO); - window.setTitle_(*title); - window.setAcceptsMouseMovedEvents_(YES); - - if pl_attrs.titlebar_transparent { - window.setTitlebarAppearsTransparent_(YES); - } - if pl_attrs.title_hidden { - window.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); - } - if pl_attrs.titlebar_buttons_hidden { - let button = window.standardWindowButton_(NSWindowButton::NSWindowFullScreenButton); - let () = msg_send![button, setHidden:YES]; - let button = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton); - let () = msg_send![button, setHidden:YES]; - let button = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton); - let () = msg_send![button, setHidden:YES]; - let button = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton); - let () = msg_send![button, setHidden:YES]; - } - if pl_attrs.movable_by_window_background { - window.setMovableByWindowBackground_(YES); - } - - if attrs.always_on_top { - let _: () = msg_send![*window, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel]; - } - - if let Some(increments) = pl_attrs.resize_increments { - let (x, y) = (increments.width, increments.height); - if x >= 1.0 && y >= 1.0 { - let size = NSSize::new(x as CGFloat, y as CGFloat); - window.setResizeIncrements_(size); - } - } - - window.center(); - window - }); - let _: () = msg_send![autoreleasepool, drain]; - res - } - } - - fn create_view(window: id, shared: Weak) -> Option<(IdRef, Weak>)> { - unsafe { - let (view, cursor) = new_view(window, shared); - view.non_nil().map(|view| { - view.setWantsBestResolutionOpenGLSurface_(YES); - - // On Mojave, views automatically become layer-backed shortly after being added to - // a window. Changing the layer-backedness of a view breaks the association between - // the view and its associated OpenGL context. To work around this, on Mojave we - // explicitly make the view layer-backed up front so that AppKit doesn't do it - // itself and break the association with its context. - if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { - view.setWantsLayer(YES); - } - - window.setContentView_(*view); - window.makeFirstResponder_(*view); - (view, cursor) - }) - } - } - - pub fn set_title(&self, title: &str) { - unsafe { - let title = IdRef::new(NSString::alloc(nil).init_str(title)); - self.window.setTitle_(*title); - } - } - - #[inline] - pub fn show(&self) { - unsafe { NSWindow::makeKeyAndOrderFront_(*self.window, nil); } - } - - #[inline] - pub fn hide(&self) { - unsafe { NSWindow::orderOut_(*self.window, nil); } - } - - pub fn get_position(&self) -> Option { - let frame_rect = unsafe { NSWindow::frame(*self.window) }; - Some(( - frame_rect.origin.x as f64, - util::bottom_left_to_top_left(frame_rect), - ).into()) - } - - pub fn get_inner_position(&self) -> Option { - let content_rect = unsafe { - NSWindow::contentRectForFrameRect_( - *self.window, - NSWindow::frame(*self.window), - ) - }; - Some(( - content_rect.origin.x as f64, - util::bottom_left_to_top_left(content_rect), - ).into()) - } - - pub fn set_position(&self, position: LogicalPosition) { - let dummy = NSRect::new( - NSPoint::new( - position.x, - // While it's true that we're setting the top-left position, it still needs to be - // in a bottom-left coordinate system. - CGDisplay::main().pixels_high() as f64 - position.y, - ), - NSSize::new(0f64, 0f64), - ); - unsafe { - NSWindow::setFrameTopLeftPoint_(*self.window, dummy.origin); - } - } - - #[inline] - pub fn get_inner_size(&self) -> Option { - let view_frame = unsafe { NSView::frame(*self.view) }; - Some((view_frame.size.width as f64, view_frame.size.height as f64).into()) - } - - #[inline] - pub fn get_outer_size(&self) -> Option { - let view_frame = unsafe { NSWindow::frame(*self.window) }; - Some((view_frame.size.width as f64, view_frame.size.height as f64).into()) - } - - #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { - unsafe { - NSWindow::setContentSize_(*self.window, NSSize::new(size.width as CGFloat, size.height as CGFloat)); - } - } - - pub fn set_min_dimensions(&self, dimensions: Option) { - unsafe { - let dimensions = dimensions.unwrap_or_else(|| (0, 0).into()); - nswindow_set_min_dimensions(self.window.0, dimensions); - } - } - - pub fn set_max_dimensions(&self, dimensions: Option) { - unsafe { - let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into()); - nswindow_set_max_dimensions(self.window.0, dimensions); - } - } - - #[inline] - pub fn set_resizable(&self, resizable: bool) { - let mut win_attribs = self.delegate.state.win_attribs.borrow_mut(); - win_attribs.resizable = resizable; - if win_attribs.fullscreen.is_none() { - let mut mask = unsafe { self.window.styleMask() }; - if resizable { - mask |= NSWindowStyleMask::NSResizableWindowMask; - } else { - mask &= !NSWindowStyleMask::NSResizableWindowMask; - } - unsafe { util::set_style_mask(*self.window, *self.view, mask) }; - } // Otherwise, we don't change the mask until we exit fullscreen. - } - - pub fn set_cursor(&self, cursor: MouseCursor) { - let cursor = util::Cursor::from(cursor); - if let Some(cursor_access) = self.cursor.upgrade() { - *cursor_access.lock().unwrap() = cursor; - } - unsafe { - let _: () = msg_send![*self.window, - invalidateCursorRectsForView:*self.view - ]; - } - } - - #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { - // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 - CGDisplay::associate_mouse_and_mouse_cursor_position(!grab) - .map_err(|status| format!("Failed to grab cursor: `CGError` {:?}", status)) - } - - #[inline] - pub fn hide_cursor(&self, hide: bool) { - let cursor_class = class!(NSCursor); - // macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once. - // (otherwise, `hide_cursor(false)` would need to be called n times!) - if hide != self.cursor_hidden.load(Ordering::Acquire) { - if hide { - let _: () = unsafe { msg_send![cursor_class, hide] }; - } else { - let _: () = unsafe { msg_send![cursor_class, unhide] }; - } - self.cursor_hidden.store(hide, Ordering::Release); - } - } - - #[inline] - pub fn get_hidpi_factor(&self) -> f64 { - unsafe { - NSWindow::backingScaleFactor(*self.window) as f64 - } - } - - #[inline] - pub fn set_cursor_position(&self, cursor_position: LogicalPosition) -> Result<(), String> { - let window_position = self.get_inner_position() - .ok_or("`get_inner_position` failed".to_owned())?; - let point = appkit::CGPoint { - x: (cursor_position.x + window_position.x) as CGFloat, - y: (cursor_position.y + window_position.y) as CGFloat, - }; - CGDisplay::warp_mouse_cursor_position(point) - .map_err(|e| format!("`CGWarpMouseCursorPosition` failed: {:?}", e))?; - CGDisplay::associate_mouse_and_mouse_cursor_position(true) - .map_err(|e| format!("`CGAssociateMouseAndMouseCursorPosition` failed: {:?}", e))?; - - Ok(()) - } - - #[inline] - pub fn set_maximized(&self, maximized: bool) { - self.delegate.state.perform_maximized(maximized) - } - - #[inline] - /// TODO: Right now set_fullscreen do not work on switching monitors - /// in fullscreen mode - pub fn set_fullscreen(&self, monitor: Option) { - let state = &self.delegate.state; - - // Do nothing if simple fullscreen is active. - if state.is_simple_fullscreen.get() { - return - } - - let current = { - let win_attribs = state.win_attribs.borrow_mut(); - - let current = win_attribs.fullscreen.clone(); - match (¤t, monitor) { - (&None, None) => { - return; - } - (&Some(ref a), Some(ref b)) if a.inner != b.inner => { - unimplemented!(); - } - (&Some(_), Some(_)) => { - return; - } - _ => (), - } - - current - }; - - unsafe { - // Because toggleFullScreen will not work if the StyleMask is none, - // We set a normal style to it temporary. - // It will clean up at window_did_exit_fullscreen. - if current.is_none() { - let curr_mask = state.window.styleMask(); - let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; - if !curr_mask.contains(required) { - util::set_style_mask(*self.window, *self.view, required); - state.save_style_mask.set(Some(curr_mask)); - } - } - - self.window.toggleFullScreen_(nil); - } - } - - #[inline] - pub fn set_decorations(&self, decorations: bool) { - let state = &self.delegate.state; - let mut win_attribs = state.win_attribs.borrow_mut(); - - if win_attribs.decorations == decorations { - return; - } - - win_attribs.decorations = decorations; - - // Skip modifiy if we are in fullscreen mode, - // window_did_exit_fullscreen will handle it - if win_attribs.fullscreen.is_some() { - return; - } - - unsafe { - let mut new_mask = if decorations { - NSWindowStyleMask::NSClosableWindowMask - | NSWindowStyleMask::NSMiniaturizableWindowMask - | NSWindowStyleMask::NSResizableWindowMask - | NSWindowStyleMask::NSTitledWindowMask - } else { - NSWindowStyleMask::NSBorderlessWindowMask - | NSWindowStyleMask::NSResizableWindowMask - }; - if !win_attribs.resizable { - new_mask &= !NSWindowStyleMask::NSResizableWindowMask; - } - util::set_style_mask(*state.window, *state.view, new_mask); - } - } - - #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { - unsafe { - let level = if always_on_top { - ffi::NSWindowLevel::NSFloatingWindowLevel - } else { - ffi::NSWindowLevel::NSNormalWindowLevel - }; - let _: () = msg_send![*self.window, setLevel:level]; - } - } - - #[inline] - pub fn set_window_icon(&self, _icon: Option<::Icon>) { - // macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's - // semantically distinct and should only be used when the window is in some way - // representing a specific file/directory. For instance, Terminal.app uses this for the - // CWD. Anyway, that should eventually be implemented as - // `WindowBuilderExt::with_represented_file` or something, and doesn't have anything to do - // with `set_window_icon`. - // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html - } - - #[inline] - pub fn set_ime_spot(&self, logical_spot: LogicalPosition) { - set_ime_spot(*self.view, *self.input_context, logical_spot.x, logical_spot.y); - } - - #[inline] - pub fn get_current_monitor(&self) -> RootMonitorHandle { - unsafe { - self::get_current_monitor(*self.window) +impl Drop for UnownedWindow { + fn drop(&mut self) { + trace!("Dropping `UnownedWindow` ({:?})", self as *mut _); + // Close the window if it has not yet been closed. + if *self.nswindow != nil { + unsafe { util::close_async(*self.nswindow) }; } } } -// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier -// for the window. -pub fn get_window_id(window_cocoa_id: id) -> Id { - Id(window_cocoa_id as *const objc::runtime::Object as usize) -} - -unsafe fn nswindow_set_min_dimensions(window: V, mut min_size: LogicalSize) { +unsafe fn set_min_inner_size(window: V, mut min_size: LogicalSize) { let mut current_rect = NSWindow::frame(window); let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); // Convert from client area size to window size @@ -1285,7 +891,7 @@ unsafe fn nswindow_set_min_dimensions(window: V, mut min_siz } } -unsafe fn nswindow_set_max_dimensions(window: V, mut max_size: LogicalSize) { +unsafe fn set_max_inner_size(window: V, mut max_size: LogicalSize) { let mut current_rect = NSWindow::frame(window); let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); // Convert from client area size to window size @@ -1308,55 +914,3 @@ unsafe fn nswindow_set_max_dimensions(window: V, mut max_siz window.setFrame_display_(current_rect, 0) } } - -pub struct IdRef(id); - -impl IdRef { - pub fn new(i: id) -> IdRef { - IdRef(i) - } - - #[allow(dead_code)] - pub fn retain(i: id) -> IdRef { - if i != nil { - let _: id = unsafe { msg_send![i, retain] }; - } - IdRef(i) - } - - pub fn non_nil(self) -> Option { - if self.0 == nil { None } else { Some(self) } - } -} - -impl Drop for IdRef { - fn drop(&mut self) { - if self.0 != nil { - unsafe { - let autoreleasepool = NSAutoreleasePool::new(nil); - let _ : () = msg_send![self.0, release]; - let _ : () = msg_send![autoreleasepool, release]; - }; - } - } -} - -impl Deref for IdRef { - type Target = id; - fn deref<'a>(&'a self) -> &'a id { - &self.0 - } -} - -impl Clone for IdRef { - fn clone(&self) -> IdRef { - if self.0 != nil { - let _: id = unsafe { msg_send![self.0, retain] }; - } - IdRef(self.0) - } -} - -extern fn yes(_: &Object, _: Sel) -> BOOL { - YES -} diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs new file mode 100644 index 00000000..46460c34 --- /dev/null +++ b/src/platform_impl/macos/window_delegate.rs @@ -0,0 +1,460 @@ +use std::{f64, os::raw::c_void, sync::{Arc, Weak}}; + +use cocoa::{ + appkit::{self, NSView, NSWindow}, base::{id, nil}, + foundation::NSAutoreleasePool, +}; +use objc::{runtime::{Class, Object, Sel, BOOL, YES, NO}, declare::ClassDecl}; + +use {dpi::LogicalSize, event::{Event, WindowEvent}, window::WindowId}; +use platform_impl::platform::{ + app_state::AppState, util::{self, IdRef}, + window::{get_window_id, UnownedWindow}, +}; + +pub struct WindowDelegateState { + nswindow: IdRef, // never changes + nsview: IdRef, // never changes + + window: Weak, + + // TODO: It's possible for delegate methods to be called asynchronously, + // causing data races / `RefCell` panics. + + // This is set when WindowBuilder::with_fullscreen was set, + // see comments of `window_did_fail_to_enter_fullscreen` + initial_fullscreen: bool, + + // During `windowDidResize`, we use this to only send Moved if the position changed. + previous_position: Option<(f64, f64)>, + + // Used to prevent redundant events. + previous_dpi_factor: f64, +} + +impl WindowDelegateState { + pub fn new( + window: &Arc, + initial_fullscreen: bool, + ) -> Self { + let dpi_factor = window.hidpi_factor(); + + let mut delegate_state = WindowDelegateState { + nswindow: window.nswindow.clone(), + nsview: window.nsview.clone(), + window: Arc::downgrade(&window), + initial_fullscreen, + previous_position: None, + previous_dpi_factor: dpi_factor, + }; + + if dpi_factor != 1.0 { + delegate_state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); + delegate_state.emit_resize_event(); + } + + delegate_state + } + + fn with_window(&mut self, callback: F) -> Option + where F: FnOnce(&UnownedWindow) -> T + { + self.window + .upgrade() + .map(|ref window| callback(window)) + } + + pub fn emit_event(&mut self, event: WindowEvent) { + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(*self.nswindow)), + event, + }; + AppState::queue_event(event); + } + + pub fn emit_resize_event(&mut self) { + let rect = unsafe { NSView::frame(*self.nsview) }; + let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(*self.nswindow)), + event: WindowEvent::Resized(size), + }; + AppState::send_event_immediately(event); + } + + fn emit_move_event(&mut self) { + let rect = unsafe { NSWindow::frame(*self.nswindow) }; + let x = rect.origin.x as f64; + let y = util::bottom_left_to_top_left(rect); + let moved = self.previous_position != Some((x, y)); + if moved { + self.previous_position = Some((x, y)); + self.emit_event(WindowEvent::Moved((x, y).into())); + } + } +} + +pub fn new_delegate(window: &Arc, initial_fullscreen: bool) -> IdRef { + let state = WindowDelegateState::new(window, initial_fullscreen); + unsafe { + // This is free'd in `dealloc` + let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; + let delegate: id = msg_send![WINDOW_DELEGATE_CLASS.0, alloc]; + IdRef::new(msg_send![delegate, initWithWinit:state_ptr]) + } +} + +struct WindowDelegateClass(*const Class); +unsafe impl Send for WindowDelegateClass {} +unsafe impl Sync for WindowDelegateClass {} + +lazy_static! { + static ref WINDOW_DELEGATE_CLASS: WindowDelegateClass = unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitWindowDelegate", 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!(windowShouldClose:), + window_should_close as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(windowWillClose:), + window_will_close as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResize:), + window_did_resize as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern fn(&Object, Sel, id)); + decl.add_method( + sel!(windowDidChangeScreen:), + window_did_change_screen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidChangeBackingProperties:), + window_did_change_backing_properties as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidBecomeKey:), + window_did_become_key as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResignKey:), + window_did_resign_key as extern fn(&Object, Sel, id), + ); + + decl.add_method( + sel!(draggingEntered:), + dragging_entered as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(prepareForDragOperation:), + prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(performDragOperation:), + perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(concludeDragOperation:), + conclude_drag_operation as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(draggingExited:), + dragging_exited as extern fn(&Object, Sel, id), + ); + + decl.add_method( + sel!(windowDidEnterFullScreen:), + window_did_enter_fullscreen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidExitFullScreen:), + window_did_exit_fullscreen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidFailToEnterFullScreen:), + window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id), + ); + + decl.add_ivar::<*mut c_void>("winitState"); + WindowDelegateClass(decl.register()) + }; +} + +// This function is definitely unsafe, but labeling that would increase +// boilerplate and wouldn't really clarify anything... +fn with_state T, T>(this: &Object, callback: F) { + let state_ptr = unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + &mut *(state_ptr as *mut WindowDelegateState) + }; + callback(state_ptr); +} + +extern fn dealloc(this: &Object, _sel: Sel) { + with_state(this, |state| unsafe { + Box::from_raw(state as *mut WindowDelegateState); + }); +} + +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); + with_state(&*this, |state| { + let () = msg_send![*state.nswindow, setDelegate:this]; + }); + } + this + } +} + +extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `windowShouldClose:`"); + with_state(this, |state| state.emit_event(WindowEvent::CloseRequested)); + trace!("Completed `windowShouldClose:`"); + NO +} + +extern fn window_will_close(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillClose:`"); + with_state(this, |state| unsafe { + // `setDelegate:` retains the previous value and then autoreleases it + let pool = NSAutoreleasePool::new(nil); + // Since El Capitan, we need to be careful that delegate methods can't + // be called after the window closes. + let () = msg_send![*state.nswindow, setDelegate:nil]; + pool.drain(); + state.emit_event(WindowEvent::Destroyed); + }); + trace!("Completed `windowWillClose:`"); +} + +extern fn window_did_resize(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidResize:`"); + with_state(this, |state| { + state.emit_resize_event(); + state.emit_move_event(); + }); + trace!("Completed `windowDidResize:`"); +} + +// This won't be triggered if the move was part of a resize. +extern fn window_did_move(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidMove:`"); + with_state(this, |state| { + state.emit_move_event(); + }); + trace!("Completed `windowDidMove:`"); +} + +extern fn window_did_change_screen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidChangeScreen:`"); + with_state(this, |state| { + let dpi_factor = unsafe { + NSWindow::backingScaleFactor(*state.nswindow) + } as f64; + if state.previous_dpi_factor != dpi_factor { + state.previous_dpi_factor = dpi_factor; + state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); + state.emit_resize_event(); + } + }); + trace!("Completed `windowDidChangeScreen:`"); +} + +// This will always be called before `window_did_change_screen`. +extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) { + trace!("Triggered `windowDidChangeBackingProperties:`"); + with_state(this, |state| { + let dpi_factor = unsafe { + NSWindow::backingScaleFactor(*state.nswindow) + } as f64; + if state.previous_dpi_factor != dpi_factor { + state.previous_dpi_factor = dpi_factor; + state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); + state.emit_resize_event(); + } + }); + trace!("Completed `windowDidChangeBackingProperties:`"); +} + +extern fn window_did_become_key(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidBecomeKey:`"); + with_state(this, |state| { + // TODO: center the cursor if the window had mouse grab when it + // lost focus + state.emit_event(WindowEvent::Focused(true)); + }); + trace!("Completed `windowDidBecomeKey:`"); +} + +extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidResignKey:`"); + with_state(this, |state| { + state.emit_event(WindowEvent::Focused(false)); + }); + trace!("Completed `windowDidResignKey:`"); +} + +/// Invoked when the dragged image enters destination bounds or frame +extern fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL { + trace!("Triggered `draggingEntered:`"); + + use cocoa::appkit::NSPasteboard; + use cocoa::foundation::NSFastEnumeration; + use std::path::PathBuf; + + let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; + + for file in unsafe { filenames.iter() } { + use cocoa::foundation::NSString; + use std::ffi::CStr; + + unsafe { + let f = NSString::UTF8String(file); + let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + + with_state(this, |state| { + state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); + }); + } + }; + + trace!("Completed `draggingEntered:`"); + YES +} + +/// Invoked when the image is released +extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `prepareForDragOperation:`"); + trace!("Completed `prepareForDragOperation:`"); + YES +} + +/// Invoked after the released image has been removed from the screen +extern fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL { + trace!("Triggered `performDragOperation:`"); + + use cocoa::appkit::NSPasteboard; + use cocoa::foundation::NSFastEnumeration; + use std::path::PathBuf; + + let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; + + for file in unsafe { filenames.iter() } { + use cocoa::foundation::NSString; + use std::ffi::CStr; + + unsafe { + let f = NSString::UTF8String(file); + let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + + with_state(this, |state| { + state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); + }); + } + }; + + trace!("Completed `performDragOperation:`"); + YES +} + +/// Invoked when the dragging operation is complete +extern fn conclude_drag_operation(_: &Object, _: Sel, _: id) { + trace!("Triggered `concludeDragOperation:`"); + trace!("Completed `concludeDragOperation:`"); +} + +/// Invoked when the dragging operation is cancelled +extern fn dragging_exited(this: &Object, _: Sel, _: id) { + trace!("Triggered `draggingExited:`"); + with_state(this, |state| state.emit_event(WindowEvent::HoveredFileCancelled)); + trace!("Completed `draggingExited:`"); +} + +/// Invoked when before enter fullscreen +extern fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillEnterFullscreen:`"); + with_state(this, |state| state.with_window(|window| { + trace!("Locked shared state in `window_will_enter_fullscreen`"); + window.shared_state.lock().unwrap().maximized = window.is_zoomed(); + trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + })); + trace!("Completed `windowWillEnterFullscreen:`"); +} + +/// Invoked when entered fullscreen +extern fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidEnterFullscreen:`"); + with_state(this, |state| { + state.with_window(|window| { + let monitor = window.current_monitor(); + trace!("Locked shared state in `window_did_enter_fullscreen`"); + window.shared_state.lock().unwrap().fullscreen = Some(monitor); + trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + }); + state.initial_fullscreen = false; + }); + trace!("Completed `windowDidEnterFullscreen:`"); +} + +/// Invoked when exited fullscreen +extern fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidExitFullscreen:`"); + with_state(this, |state| state.with_window(|window| { + window.restore_state_from_fullscreen(); + })); + trace!("Completed `windowDidExitFullscreen:`"); +} + +/// Invoked when fail to enter fullscreen +/// +/// When this window launch from a fullscreen app (e.g. launch from VS Code +/// terminal), it creates a new virtual destkop and a transition animation. +/// This animation takes one second and cannot be disable without +/// elevated privileges. In this animation time, all toggleFullscreen events +/// will be failed. In this implementation, we will try again by using +/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. +/// It should be fine as we only do this at initialzation (i.e with_fullscreen +/// was set). +/// +/// From Apple doc: +/// In some cases, the transition to enter full-screen mode can fail, +/// due to being in the midst of handling some other animation or user gesture. +/// This method indicates that there was an error, and you should clean up any +/// work you may have done to prepare to enter full-screen mode. +extern fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidFailToEnterFullscreen:`"); + with_state(this, |state| { + if state.initial_fullscreen { + let _: () = unsafe { msg_send![*state.nswindow, + performSelector:sel!(toggleFullScreen:) + withObject:nil + afterDelay: 0.5 + ] }; + } else { + state.with_window(|window| window.restore_state_from_fullscreen()); + } + }); + trace!("Completed `windowDidFailToEnterFullscreen:`"); +} diff --git a/src/platform_impl/stdweb/event_loop.rs b/src/platform_impl/stdweb/event_loop.rs index 8ec07355..37b00f77 100644 --- a/src/platform_impl/stdweb/event_loop.rs +++ b/src/platform_impl/stdweb/event_loop.rs @@ -81,11 +81,11 @@ impl EventLoop { } } - pub fn get_available_monitors(&self) -> VecDequeIter { + pub fn available_monitors(&self) -> VecDequeIter { VecDeque::new().into_iter() } - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } diff --git a/src/platform_impl/stdweb/mod.rs b/src/platform_impl/stdweb/mod.rs index 82d8a189..6751019e 100644 --- a/src/platform_impl/stdweb/mod.rs +++ b/src/platform_impl/stdweb/mod.rs @@ -1,3 +1,5 @@ +use std::fmt; + mod event_loop; mod events; mod window; @@ -6,6 +8,14 @@ pub use self::event_loop::{DeviceId, EventLoop, EventLoopRunnerShared, EventLoop pub use self::window::{MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes}; pub use self::events::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode}; +#[derive(Debug)] +pub struct OsError(String); + +impl fmt::Display for OsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} // TODO: dpi // TODO: close events (stdweb PR required) diff --git a/src/platform_impl/stdweb/window.rs b/src/platform_impl/stdweb/window.rs index 1ab8a0d2..b57d5f1e 100644 --- a/src/platform_impl/stdweb/window.rs +++ b/src/platform_impl/stdweb/window.rs @@ -1,10 +1,11 @@ use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use error::{ExternalError, NotSupportedError, OsError as RootOE}; use event::{Event, WindowEvent}; use icon::Icon; use platform::stdweb::WindowExtStdweb; use monitor::{MonitorHandle as RootMH}; -use window::{CreationError, MouseCursor, Window as RootWindow, WindowAttributes, WindowId as RootWI}; -use super::{EventLoopWindowTarget, register}; +use window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI}; +use super::{EventLoopWindowTarget, OsError, register}; use std::collections::VecDeque; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::cell::RefCell; @@ -21,19 +22,19 @@ use stdweb::web::{ pub struct MonitorHandle; impl MonitorHandle { - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { 1.0 } - pub fn get_position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { unimplemented!(); } - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { unimplemented!(); } - pub fn get_name(&self) -> Option { + pub fn name(&self) -> Option { unimplemented!(); } } @@ -59,14 +60,14 @@ pub struct Window { impl Window { pub fn new(target: &EventLoopWindowTarget, attr: WindowAttributes, - _: PlatformSpecificWindowBuilderAttributes) -> Result { + _: PlatformSpecificWindowBuilderAttributes) -> Result { let element = document() .create_element("canvas") - .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; let canvas: CanvasElement = element.try_into() - .map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?; + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; document().body() - .ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))? + .ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))? .append_child(&canvas); register(&target.runner, &canvas); @@ -90,24 +91,20 @@ impl Window { }) }; - if let Some(dimensions) = attr.dimensions { - window.set_inner_size(dimensions); + if let Some(inner_size) = attr.inner_size { + window.set_inner_size(inner_size); } else { window.set_inner_size(LogicalSize { width: 1024.0, height: 768.0, }) } - window.set_min_dimensions(attr.min_dimensions); - window.set_max_dimensions(attr.max_dimensions); + window.set_min_inner_size(attr.min_inner_size); + window.set_max_inner_size(attr.max_inner_size); window.set_resizable(attr.resizable); window.set_title(&attr.title); window.set_maximized(attr.maximized); - if attr.visible { - window.show(); - } else { - window.hide(); - } + window.set_visible(attr.visible); //window.set_transparent(attr.transparent); window.set_decorations(attr.decorations); window.set_always_on_top(attr.always_on_top); @@ -120,11 +117,7 @@ impl Window { document().set_title(title); } - pub fn show(&self) { - // Intentionally a no-op - } - - pub fn hide(&self) { + pub fn set_visible(&self, _visible: bool) { // Intentionally a no-op } @@ -132,19 +125,19 @@ impl Window { (self.redraw)(); } - pub fn get_position(&self) -> Option { + pub fn outer_position(&self) -> Result { let bounds = self.canvas.get_bounding_client_rect(); - Some(LogicalPosition { + Ok(LogicalPosition { x: bounds.get_x(), y: bounds.get_y(), }) } - pub fn get_inner_position(&self) -> Option { - Some(*self.position.borrow()) + pub fn inner_position(&self) -> Result { + Ok(*self.position.borrow()) } - pub fn set_position(&self, position: LogicalPosition) { + pub fn set_outer_position(&self, position: LogicalPosition) { *self.position.borrow_mut() = position; self.canvas.set_attribute("position", "fixed") .expect("Setting the position for the canvas"); @@ -155,19 +148,19 @@ impl Window { } #[inline] - pub fn get_inner_size(&self) -> Option { - Some(LogicalSize { + pub fn inner_size(&self) -> LogicalSize { + LogicalSize { width: self.canvas.width() as f64, height: self.canvas.height() as f64 - }) + } } #[inline] - pub fn get_outer_size(&self) -> Option { - Some(LogicalSize { + pub fn outer_size(&self) -> LogicalSize { + LogicalSize { width: self.canvas.width() as f64, height: self.canvas.height() as f64 - }) + } } #[inline] @@ -177,12 +170,12 @@ impl Window { } #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _dimensions: Option) { // Intentionally a no-op: users can't resize canvas elements } #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _dimensions: Option) { // Intentionally a no-op: users can't resize canvas elements } @@ -192,50 +185,50 @@ impl Window { } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { 1.0 } #[inline] - pub fn set_cursor(&self, cursor: MouseCursor) { + pub fn set_cursor_icon(&self, cursor: CursorIcon) { let text = match cursor { - MouseCursor::Default => "auto", - MouseCursor::Crosshair => "crosshair", - MouseCursor::Hand => "pointer", - MouseCursor::Arrow => "default", - MouseCursor::Move => "move", - MouseCursor::Text => "text", - MouseCursor::Wait => "wait", - MouseCursor::Help => "help", - MouseCursor::Progress => "progress", + CursorIcon::Default => "auto", + CursorIcon::Crosshair => "crosshair", + CursorIcon::Hand => "pointer", + CursorIcon::Arrow => "default", + CursorIcon::Move => "move", + CursorIcon::Text => "text", + CursorIcon::Wait => "wait", + CursorIcon::Help => "help", + CursorIcon::Progress => "progress", - MouseCursor::NotAllowed => "not-allowed", - MouseCursor::ContextMenu => "context-menu", - MouseCursor::Cell => "cell", - MouseCursor::VerticalText => "vertical-text", - MouseCursor::Alias => "alias", - MouseCursor::Copy => "copy", - MouseCursor::NoDrop => "no-drop", - MouseCursor::Grab => "grab", - MouseCursor::Grabbing => "grabbing", - MouseCursor::AllScroll => "all-scroll", - MouseCursor::ZoomIn => "zoom-in", - MouseCursor::ZoomOut => "zoom-out", + CursorIcon::NotAllowed => "not-allowed", + CursorIcon::ContextMenu => "context-menu", + CursorIcon::Cell => "cell", + CursorIcon::VerticalText => "vertical-text", + CursorIcon::Alias => "alias", + CursorIcon::Copy => "copy", + CursorIcon::NoDrop => "no-drop", + CursorIcon::Grab => "grab", + CursorIcon::Grabbing => "grabbing", + CursorIcon::AllScroll => "all-scroll", + CursorIcon::ZoomIn => "zoom-in", + CursorIcon::ZoomOut => "zoom-out", - MouseCursor::EResize => "e-resize", - MouseCursor::NResize => "n-resize", - MouseCursor::NeResize => "ne-resize", - MouseCursor::NwResize => "nw-resize", - MouseCursor::SResize => "s-resize", - MouseCursor::SeResize => "se-resize", - MouseCursor::SwResize => "sw-resize", - MouseCursor::WResize => "w-resize", - MouseCursor::EwResize => "ew-resize", - MouseCursor::NsResize => "ns-resize", - MouseCursor::NeswResize => "nesw-resize", - MouseCursor::NwseResize => "nwse-resize", - MouseCursor::ColResize => "col-resize", - MouseCursor::RowResize => "row-resize", + CursorIcon::EResize => "e-resize", + CursorIcon::NResize => "n-resize", + CursorIcon::NeResize => "ne-resize", + CursorIcon::NwResize => "nw-resize", + CursorIcon::SResize => "s-resize", + CursorIcon::SeResize => "se-resize", + CursorIcon::SwResize => "sw-resize", + CursorIcon::WResize => "w-resize", + CursorIcon::EwResize => "ew-resize", + CursorIcon::NsResize => "ns-resize", + CursorIcon::NeswResize => "nesw-resize", + CursorIcon::NwseResize => "nwse-resize", + CursorIcon::ColResize => "col-resize", + CursorIcon::RowResize => "row-resize", }; *self.previous_pointer.borrow_mut() = text; self.canvas.set_attribute("cursor", text) @@ -243,20 +236,20 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> { + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { // TODO: pointer capture Ok(()) } #[inline] - pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> { + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { // TODO: pointer capture Ok(()) } #[inline] - pub fn hide_cursor(&self, hide: bool) { - if hide { + pub fn set_cursor_visible(&self, visible: bool) { + if !visible { self.canvas.set_attribute("cursor", "none") .expect("Setting the cursor on the canvas"); } else { @@ -270,6 +263,12 @@ impl Window { // TODO: should there be a maximization / fullscreen API? } + #[inline] + pub fn fullscreen(&self) -> Option { + // TODO: should there be a maximization / fullscreen API? + None + } + #[inline] pub fn set_fullscreen(&self, _monitor: Option) { // TODO: should there be a maximization / fullscreen API? @@ -291,24 +290,24 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _position: LogicalPosition) { + pub fn set_ime_position(&self, _position: LogicalPosition) { // TODO: what is this? } #[inline] - pub fn get_current_monitor(&self) -> RootMH { + pub fn current_monitor(&self) -> RootMH { RootMH { inner: MonitorHandle } } #[inline] - pub fn get_available_monitors(&self) -> VecDequeIter { + pub fn available_monitors(&self) -> VecDequeIter { VecDeque::new().into_iter() } #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle } diff --git a/src/platform_impl/windows/dpi.rs b/src/platform_impl/windows/dpi.rs index ea4bc655..4bf31f39 100644 --- a/src/platform_impl/windows/dpi.rs +++ b/src/platform_impl/windows/dpi.rs @@ -144,7 +144,7 @@ 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 { +pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { let hdc = winuser::GetDC(hwnd); if hdc.is_null() { panic!("[winit] `GetDC` returned null!"); @@ -184,6 +184,6 @@ pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 { } } -pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 { - dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) }) +pub fn hwnd_scale_factor(hwnd: HWND) -> f64 { + dpi_to_scale_factor(unsafe { hwnd_dpi(hwnd) }) } diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 60e1b910..a3d733d2 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -1,30 +1,84 @@ -use std::char; +use std::{char, ptr}; use std::os::raw::c_int; +use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use event::{ScanCode, ModifiersState, VirtualKeyCode}; -use winapi::shared::minwindef::{WPARAM, LPARAM, UINT}; +use winapi::shared::minwindef::{WPARAM, LPARAM, UINT, HKL, HKL__}; use winapi::um::winuser; +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 } +unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { + 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 = 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 { // VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx match vkey { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 1d7a4aa1..1d49060d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -52,7 +52,7 @@ use platform_impl::platform::dpi::{ become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, - get_hwnd_scale_factor, + hwnd_scale_factor, }; use platform_impl::platform::drop_handler::FileDropHandler; use platform_impl::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; @@ -380,6 +380,7 @@ impl EventLoopRunner { // deferred. if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state { match self.control_flow { + ControlFlow::Exit | ControlFlow::Wait => { self.call_event_handler( Event::NewEvents(StartCause::WaitCancelled { @@ -409,7 +410,6 @@ impl EventLoopRunner { ControlFlow::Poll => { self.call_event_handler(Event::NewEvents(StartCause::Poll)) }, - ControlFlow::Exit => unreachable!() } } @@ -799,6 +799,20 @@ unsafe extern "system" fn public_window_callback( commctrl::DefSubclassProc(window, msg, wparam, lparam) }, + winuser::WM_NCLBUTTONDOWN => { + // jumpstart the modal loop + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT + ); + if wparam == winuser::HTCAPTION as _ { + winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0); + } + commctrl::DefSubclassProc(window, msg, wparam, lparam) + }, + winuser::WM_CLOSE => { use event::WindowEvent::CloseRequested; subclass_input.send_event(Event::WindowEvent { @@ -825,12 +839,32 @@ unsafe extern "system" fn public_window_callback( use event::WindowEvent::RedrawRequested; let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); if let Some(ref mut runner) = *runner { - match runner.runner_state { - RunnerState::Idle(..) | - RunnerState::DeferredNewEvents(..) => runner.call_event_handler(Event::WindowEvent { + // This check makes sure that calls to `request_redraw()` during `EventsCleared` + // handling dispatch `RedrawRequested` immediately after `EventsCleared`, without + // spinning up a new event loop iteration. We do this because that's what the API + // says to do. + let control_flow = runner.control_flow; + let runner_state = runner.runner_state; + let mut request_redraw = || { + runner.call_event_handler(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: RedrawRequested, - }), + }); + }; + match runner_state { + RunnerState::Idle(..) | + RunnerState::DeferredNewEvents(..) => request_redraw(), + RunnerState::HandlingEvents => { + match control_flow { + ControlFlow::Poll => request_redraw(), + ControlFlow::WaitUntil(resume_time) => { + if resume_time <= Instant::now() { + request_redraw() + } + }, + _ => () + } + } _ => () } } @@ -838,25 +872,10 @@ unsafe extern "system" fn public_window_callback( }, winuser::WM_PAINT => { use event::WindowEvent::RedrawRequested; - let event = || Event::WindowEvent { + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: RedrawRequested, - }; - - let mut send_event = false; - { - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); - if let Some(ref mut runner) = *runner { - match runner.runner_state { - RunnerState::Idle(..) | - RunnerState::DeferredNewEvents(..) => runner.call_event_handler(event()), - _ => send_event = true - } - } - } - if send_event { - subclass_input.send_event(event()); - } + }); commctrl::DefSubclassProc(window, msg, wparam, lparam) }, @@ -866,7 +885,7 @@ unsafe extern "system" fn public_window_callback( let windowpos = lparam as *const winuser::WINDOWPOS; if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { - let dpi_factor = get_hwnd_scale_factor(window); + let dpi_factor = hwnd_scale_factor(window); let logical_position = LogicalPosition::from_physical( ((*windowpos).x, (*windowpos).y), dpi_factor, @@ -886,7 +905,7 @@ unsafe extern "system" fn public_window_callback( let w = LOWORD(lparam as DWORD) as u32; let h = HIWORD(lparam as DWORD) as u32; - let dpi_factor = get_hwnd_scale_factor(window); + let dpi_factor = hwnd_scale_factor(window); let logical_size = LogicalSize::from_physical((w, h), dpi_factor); let event = Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -907,7 +926,6 @@ unsafe extern "system" fn public_window_callback( }, winuser::WM_CHAR => { - use std::mem; use event::WindowEvent::ReceivedCharacter; let chr: char = mem::transmute(wparam as u32); subclass_input.send_event(Event::WindowEvent { @@ -952,7 +970,7 @@ unsafe extern "system" fn public_window_callback( let x = windowsx::GET_X_LPARAM(lparam) as f64; let y = windowsx::GET_Y_LPARAM(lparam) as f64; - let dpi_factor = get_hwnd_scale_factor(window); + let dpi_factor = hwnd_scale_factor(window); let position = LogicalPosition::from_physical((x, y), dpi_factor); subclass_input.send_event(Event::WindowEvent { @@ -980,7 +998,6 @@ unsafe extern "system" fn public_window_callback( winuser::WM_MOUSEWHEEL => { use event::MouseScrollDelta::LineDelta; - use event::TouchPhase; let value = (wparam >> 16) as i16; let value = value as i32; @@ -994,6 +1011,21 @@ unsafe extern "system" fn public_window_callback( 0 }, + winuser::WM_MOUSEHWHEEL => { + use event::MouseScrollDelta::LineDelta; + + let value = (wparam >> 16) as i16; + let value = value as i32; + let value = value as f32 / winuser::WHEEL_DELTA as f32; + + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, modifiers: event::get_key_mods() }, + }); + + 0 + }, + winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { use event::ElementState::Pressed; use event::VirtualKeyCode; @@ -1289,7 +1321,7 @@ unsafe extern "system" fn public_window_callback( inputs.as_mut_ptr(), mem::size_of::() as INT, ) > 0 { - let dpi_factor = get_hwnd_scale_factor(window); + let dpi_factor = hwnd_scale_factor(window); for input in &inputs { let x = (input.x as f64) / 100f64; let y = (input.y as f64) / 100f64; @@ -1319,22 +1351,12 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_SETFOCUS => { - use event::WindowEvent::{Focused, CursorMoved}; + use event::WindowEvent::Focused; subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(true), }); - let x = windowsx::GET_X_LPARAM(lparam) as f64; - let y = windowsx::GET_Y_LPARAM(lparam) as f64; - let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x, y), dpi_factor); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, - }); - 0 }, diff --git a/src/platform_impl/windows/icon.rs b/src/platform_impl/windows/icon.rs index c45bdf6f..3a617ceb 100644 --- a/src/platform_impl/windows/icon.rs +++ b/src/platform_impl/windows/icon.rs @@ -1,4 +1,4 @@ -use std::{self, mem, ptr}; +use std::{mem, ptr, io}; use std::os::windows::ffi::OsStrExt; use std::path::Path; @@ -8,7 +8,6 @@ use winapi::shared::windef::{HICON, HWND}; use winapi::um::winuser; use icon::{Pixel, PIXEL_SIZE, Icon}; -use platform_impl::platform::util; impl Pixel { fn to_bgra(&mut self) { @@ -31,7 +30,7 @@ unsafe impl Send for WinIcon {} impl WinIcon { #[allow(dead_code)] - pub fn from_path>(path: P) -> Result { + pub fn from_path>(path: P) -> Result { let wide_path: Vec = path.as_ref().as_os_str().encode_wide().collect(); let handle = unsafe { winuser::LoadImageW( @@ -46,15 +45,15 @@ impl WinIcon { if !handle.is_null() { Ok(WinIcon { handle }) } else { - Err(util::WinError::from_last_error()) + Err(io::Error::last_os_error()) } } - pub fn from_icon(icon: Icon) -> Result { + pub fn from_icon(icon: Icon) -> Result { Self::from_rgba(icon.rgba, icon.width, icon.height) } - pub fn from_rgba(mut rgba: Vec, width: u32, height: u32) -> Result { + pub fn from_rgba(mut rgba: Vec, width: u32, height: u32) -> Result { assert_eq!(rgba.len() % PIXEL_SIZE, 0); let pixel_count = rgba.len() / PIXEL_SIZE; assert_eq!(pixel_count, (width * height) as usize); @@ -80,7 +79,7 @@ impl WinIcon { if !handle.is_null() { Ok(WinIcon { handle }) } else { - Err(util::WinError::from_last_error()) + Err(io::Error::last_os_error()) } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 855dd667..307423c3 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -36,7 +36,7 @@ impl DeviceId { } impl DeviceId { - pub fn get_persistent_identifier(&self) -> Option { + pub fn persistent_identifier(&self) -> Option { if self.0 != 0 { raw_input::get_raw_input_device_name(self.0 as _) } else { @@ -52,6 +52,8 @@ fn wrap_device_id(id: u32) -> RootDeviceId { RootDeviceId(DeviceId(id)) } +pub type OsError = std::io::Error; + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 585a2e0d..ccbe581b 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -3,7 +3,7 @@ use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT}; use winapi::um::winnt::LONG; use winapi::um::winuser; -use std::{mem, ptr}; +use std::{mem, ptr, io}; use std::collections::VecDeque; use super::{EventLoop, util}; @@ -50,7 +50,7 @@ unsafe extern "system" fn monitor_enum_proc( TRUE // continue enumeration } -pub fn get_available_monitors() -> VecDeque { +pub fn available_monitors() -> VecDeque { let mut monitors: VecDeque = VecDeque::new(); unsafe { winuser::EnumDisplayMonitors( @@ -63,7 +63,7 @@ pub fn get_available_monitors() -> VecDeque { monitors } -pub fn get_primary_monitor() -> MonitorHandle { +pub fn primary_monitor() -> MonitorHandle { const ORIGIN: POINT = POINT { x: 0, y: 0 }; let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) @@ -71,7 +71,7 @@ pub fn get_primary_monitor() -> MonitorHandle { MonitorHandle::from_hmonitor(hmonitor) } -pub fn get_current_monitor(hwnd: HWND) -> MonitorHandle { +pub fn current_monitor(hwnd: HWND) -> MonitorHandle { let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) }; @@ -80,26 +80,26 @@ pub fn get_current_monitor(hwnd: HWND) -> MonitorHandle { impl EventLoop { // TODO: Investigate opportunities for caching - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors() + pub fn available_monitors(&self) -> VecDeque { + available_monitors() } - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor() + pub fn primary_monitor(&self) -> MonitorHandle { + primary_monitor() } } impl Window { - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors() + pub fn available_monitors(&self) -> VecDeque { + available_monitors() } - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor() + pub fn primary_monitor(&self) -> MonitorHandle { + primary_monitor() } } -pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { +pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() }; monitor_info.cbSize = mem::size_of::() as DWORD; let status = unsafe { @@ -109,7 +109,7 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result Option { + pub fn name(&self) -> Option { Some(self.monitor_name.clone()) } #[inline] - pub fn get_native_identifier(&self) -> String { + pub fn native_identifier(&self) -> String { self.monitor_name.clone() } #[inline] - pub fn get_hmonitor(&self) -> HMONITOR { + pub fn hmonitor(&self) -> HMONITOR { self.hmonitor.0 } #[inline] - pub fn get_dimensions(&self) -> PhysicalSize { + pub fn dimensions(&self) -> PhysicalSize { self.dimensions.into() } #[inline] - pub fn get_position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { self.position.into() } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { self.hidpi_factor } } diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index cf20d3c7..a46c44bd 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,26 +1,12 @@ -use std::{self, mem, ptr, slice, io}; +use std::{mem, ptr, slice, io}; use std::ops::BitAnd; use std::sync::atomic::{AtomicBool, Ordering}; -use window::MouseCursor; +use window::CursorIcon; 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::winbase::lstrlenW; use winapi::um::winuser; pub fn has_flag(bitset: T, flag: T) -> bool @@ -117,66 +103,27 @@ pub fn is_focused(window: HWND) -> bool { window == unsafe{ winuser::GetActiveWindow() } } -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct WinError(Option); - -impl WinError { - pub fn from_last_error() -> Self { - WinError(unsafe { get_last_error() }) - } -} - -pub unsafe fn get_last_error() -> Option { - 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 { +impl CursorIcon { 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, + CursorIcon::Arrow | CursorIcon::Default => winuser::IDC_ARROW, + CursorIcon::Hand => winuser::IDC_HAND, + CursorIcon::Crosshair => winuser::IDC_CROSS, + CursorIcon::Text | CursorIcon::VerticalText => winuser::IDC_IBEAM, + CursorIcon::NotAllowed | CursorIcon::NoDrop => winuser::IDC_NO, + CursorIcon::Grab | CursorIcon::Grabbing | + CursorIcon::Move | CursorIcon::AllScroll => winuser::IDC_SIZEALL, + CursorIcon::EResize | CursorIcon::WResize | + CursorIcon::EwResize | CursorIcon::ColResize => winuser::IDC_SIZEWE, + CursorIcon::NResize | CursorIcon::SResize | + CursorIcon::NsResize | CursorIcon::RowResize => winuser::IDC_SIZENS, + CursorIcon::NeResize | CursorIcon::SwResize | + CursorIcon::NeswResize => winuser::IDC_SIZENESW, + CursorIcon::NwResize | CursorIcon::SeResize | + CursorIcon::NwseResize => winuser::IDC_SIZENWSE, + CursorIcon::Wait => winuser::IDC_WAIT, + CursorIcon::Progress => winuser::IDC_APPSTARTING, + CursorIcon::Help => winuser::IDC_HELP, _ => winuser::IDC_ARROW, // use arrow for the missing cases. } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 2155e8ec..351ffc1c 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -18,12 +18,13 @@ use winapi::um::wingdi::{CreateRectRgn, DeleteObject}; use winapi::um::oleidl::LPDROPTARGET; use winapi::um::winnt::{LONG, LPCWSTR}; -use window::{CreationError, Icon, MouseCursor, WindowAttributes}; +use window::{Icon, CursorIcon, WindowAttributes}; +use error::{ExternalError, NotSupportedError, OsError as RootOsError}; use dpi::{LogicalPosition, LogicalSize, PhysicalSize}; use monitor::MonitorHandle as RootMonitorHandle; use platform_impl::platform::{ {PlatformSpecificWindowBuilderAttributes, WindowId}, - dpi::{dpi_to_scale_factor, get_hwnd_dpi}, + dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID}, icon::{self, IconType, WinIcon}, @@ -50,7 +51,7 @@ impl Window { event_loop: &EventLoopWindowTarget, w_attr: WindowAttributes, pl_attr: PlatformSpecificWindowBuilderAttributes, - ) -> Result { + ) -> Result { // We dispatch an `init` function because of code style. // First person to remove the need for cloning here gets a cookie! // @@ -103,16 +104,10 @@ impl Window { } #[inline] - pub fn show(&self) { - unsafe { - winuser::ShowWindow(self.window.0, winuser::SW_SHOW); - } - } - - #[inline] - pub fn hide(&self) { - unsafe { - winuser::ShowWindow(self.window.0, winuser::SW_HIDE); + pub fn set_visible(&self, visible: bool) { + match visible { + true => unsafe { winuser::ShowWindow(self.window.0, winuser::SW_SHOW); }, + false => unsafe { winuser::ShowWindow(self.window.0, winuser::SW_HIDE); }, } } @@ -132,35 +127,32 @@ impl Window { } } - pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> { + pub(crate) fn outer_position_physical(&self) -> (i32, i32) { util::get_window_rect(self.window.0) .map(|rect| (rect.left as i32, rect.top as i32)) + .unwrap() } #[inline] - pub fn get_position(&self) -> Option { - self.get_position_physical() - .map(|physical_position| { - let dpi_factor = self.get_hidpi_factor(); - LogicalPosition::from_physical(physical_position, dpi_factor) - }) + pub fn outer_position(&self) -> Result { + let physical_position = self.outer_position_physical(); + let dpi_factor = self.hidpi_factor(); + Ok(LogicalPosition::from_physical(physical_position, dpi_factor)) } - pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> { + pub(crate) fn inner_position_physical(&self) -> (i32, i32) { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 { - return None; + panic!("Unexpected ClientToScreen failure: please report this error to https://github.com/rust-windowing/winit") } - Some((position.x, position.y)) + (position.x, position.y) } #[inline] - pub fn get_inner_position(&self) -> Option { - self.get_inner_position_physical() - .map(|physical_position| { - let dpi_factor = self.get_hidpi_factor(); - LogicalPosition::from_physical(physical_position, dpi_factor) - }) + pub fn inner_position(&self) -> Result { + let physical_position = self.inner_position_physical(); + let dpi_factor = self.hidpi_factor(); + Ok(LogicalPosition::from_physical(physical_position, dpi_factor)) } pub(crate) fn set_position_physical(&self, x: i32, y: i32) { @@ -179,47 +171,44 @@ impl Window { } #[inline] - pub fn set_position(&self, logical_position: LogicalPosition) { - let dpi_factor = self.get_hidpi_factor(); + pub fn set_outer_position(&self, logical_position: LogicalPosition) { + let dpi_factor = self.hidpi_factor(); let (x, y) = logical_position.to_physical(dpi_factor).into(); self.set_position_physical(x, y); } - pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> { + pub(crate) fn inner_size_physical(&self) -> (u32, u32) { let mut rect: RECT = unsafe { mem::uninitialized() }; if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 { - return None; + panic!("Unexpected GetClientRect failure: please report this error to https://github.com/rust-windowing/winit") } - Some(( + ( (rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32, - )) + ) } #[inline] - pub fn get_inner_size(&self) -> Option { - self.get_inner_size_physical() - .map(|physical_size| { - let dpi_factor = self.get_hidpi_factor(); - LogicalSize::from_physical(physical_size, dpi_factor) - }) + pub fn inner_size(&self) -> LogicalSize { + let physical_size = self.inner_size_physical(); + let dpi_factor = self.hidpi_factor(); + LogicalSize::from_physical(physical_size, dpi_factor) } - pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> { + pub(crate) fn outer_size_physical(&self) -> (u32, u32) { util::get_window_rect(self.window.0) .map(|rect| ( (rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32, )) + .unwrap() } #[inline] - pub fn get_outer_size(&self) -> Option { - self.get_outer_size_physical() - .map(|physical_size| { - let dpi_factor = self.get_hidpi_factor(); - LogicalSize::from_physical(physical_size, dpi_factor) - }) + pub fn outer_size(&self) -> LogicalSize { + let physical_size = self.outer_size_physical(); + let dpi_factor = self.hidpi_factor(); + LogicalSize::from_physical(physical_size, dpi_factor) } pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) { @@ -254,41 +243,41 @@ impl Window { #[inline] pub fn set_inner_size(&self, logical_size: LogicalSize) { - let dpi_factor = self.get_hidpi_factor(); + let dpi_factor = self.hidpi_factor(); let (width, height) = logical_size.to_physical(dpi_factor).into(); self.set_inner_size_physical(width, height); } - pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.window_state.lock().min_size = dimensions.map(Into::into); // Make windows re-check the window size bounds. - self.get_inner_size_physical() - .map(|(width, height)| self.set_inner_size_physical(width, height)); + let (width, height) = self.inner_size_physical(); + self.set_inner_size_physical(width, height); } #[inline] - pub fn set_min_dimensions(&self, logical_size: Option) { + pub fn set_min_inner_size(&self, logical_size: Option) { let physical_size = logical_size.map(|logical_size| { - let dpi_factor = self.get_hidpi_factor(); + let dpi_factor = self.hidpi_factor(); logical_size.to_physical(dpi_factor).into() }); - self.set_min_dimensions_physical(physical_size); + self.set_min_inner_size_physical(physical_size); } - pub fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + pub fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.window_state.lock().max_size = dimensions.map(Into::into); // Make windows re-check the window size bounds. - self.get_inner_size_physical() - .map(|(width, height)| self.set_inner_size_physical(width, height)); + let (width, height) = self.inner_size_physical(); + self.set_inner_size_physical(width, height); } #[inline] - pub fn set_max_dimensions(&self, logical_size: Option) { + pub fn set_max_inner_size(&self, logical_size: Option) { let physical_size = logical_size.map(|logical_size| { - let dpi_factor = self.get_hidpi_factor(); + let dpi_factor = self.hidpi_factor(); logical_size.to_physical(dpi_factor).into() }); - self.set_max_dimensions_physical(physical_size); + self.set_max_inner_size_physical(physical_size); } #[inline] @@ -313,7 +302,7 @@ impl Window { } #[inline] - pub fn set_cursor(&self, cursor: MouseCursor) { + pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window_state.lock().mouse.cursor = cursor; self.thread_executor.execute_in_thread(move || unsafe { let cursor = winuser::LoadCursorW( @@ -325,7 +314,7 @@ impl Window { } #[inline] - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); @@ -333,21 +322,21 @@ impl Window { self.thread_executor.execute_in_thread(move || { let result = window_state.lock().mouse .set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab)) - .map_err(|e| e.to_string()); + .map_err(|e| ExternalError::Os(os_error!(e))); let _ = tx.send(result); }); rx.recv().unwrap() } #[inline] - pub fn hide_cursor(&self, hide: bool) { + pub fn set_cursor_visible(&self, visible: bool) { let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); self.thread_executor.execute_in_thread(move || { let result = window_state.lock().mouse - .set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, hide)) + .set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, !visible)) .map_err(|e| e.to_string()); let _ = tx.send(result); }); @@ -355,26 +344,26 @@ impl Window { } #[inline] - pub fn get_hidpi_factor(&self) -> f64 { + pub fn hidpi_factor(&self) -> f64 { self.window_state.lock().dpi_factor } - fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> { + fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { let mut point = POINT { x, y }; unsafe { if winuser::ClientToScreen(self.window.0, &mut point) == 0 { - return Err("`ClientToScreen` failed".to_owned()); + return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } if winuser::SetCursorPos(point.x, point.y) == 0 { - return Err("`SetCursorPos` failed".to_owned()); + return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } } Ok(()) } #[inline] - pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> { - let dpi_factor = self.get_hidpi_factor(); + pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), ExternalError> { + let dpi_factor = self.hidpi_factor(); let (x, y) = logical_position.to_physical(dpi_factor).into(); self.set_cursor_position_physical(x, y) } @@ -399,6 +388,12 @@ impl Window { }); } + #[inline] + pub fn fullscreen(&self) -> Option { + let window_state = self.window_state.lock(); + window_state.fullscreen.clone() + } + #[inline] pub fn set_fullscreen(&self, monitor: Option) { unsafe { @@ -407,8 +402,8 @@ impl Window { match &monitor { &Some(RootMonitorHandle { ref inner }) => { - let (x, y): (i32, i32) = inner.get_position().into(); - let (width, height): (u32, u32) = inner.get_dimensions().into(); + let (x, y): (i32, i32) = inner.position().into(); + let (width, height): (u32, u32) = inner.dimensions().into(); let mut monitor = monitor.clone(); self.thread_executor.execute_in_thread(move || { @@ -490,9 +485,9 @@ impl Window { } #[inline] - pub fn get_current_monitor(&self) -> RootMonitorHandle { + pub fn current_monitor(&self) -> RootMonitorHandle { RootMonitorHandle { - inner: monitor::get_current_monitor(self.window.0), + inner: monitor::current_monitor(self.window.0), } } @@ -523,7 +518,7 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) { + pub fn set_ime_position(&self, _logical_spot: LogicalPosition) { unimplemented!(); } } @@ -569,9 +564,9 @@ pub unsafe fn adjust_size( unsafe fn init( mut attributes: WindowAttributes, - mut pl_attribs: PlatformSpecificWindowBuilderAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, event_loop: &EventLoopWindowTarget, -) -> Result { +) -> Result { let title = OsStr::new(&attributes.title) .encode_wide() .chain(Some(0).into_iter()) @@ -581,22 +576,18 @@ unsafe fn init( let icon = attributes.window_icon .take() .map(WinIcon::from_icon); - if icon.is_some() { - Some(icon.unwrap().map_err(|err| { - CreationError::OsError(format!("Failed to create `ICON_SMALL`: {:?}", err)) - })?) + if let Some(icon) = icon { + Some(icon.map_err(|e| os_error!(e))?) } else { None } }; let taskbar_icon = { - let icon = pl_attribs.taskbar_icon + let icon = attributes.window_icon .take() .map(WinIcon::from_icon); - if icon.is_some() { - Some(icon.unwrap().map_err(|err| { - CreationError::OsError(format!("Failed to create `ICON_BIG`: {:?}", err)) - })?) + if let Some(icon) = icon { + Some(icon.map_err(|e| os_error!(e))?) } else { None } @@ -606,17 +597,17 @@ unsafe fn init( let class_name = register_window_class(&window_icon, &taskbar_icon); let guessed_dpi_factor = { - let monitors = monitor::get_available_monitors(); + let monitors = monitor::available_monitors(); let dpi_factor = if !monitors.is_empty() { - let mut dpi_factor = Some(monitors[0].get_hidpi_factor()); + let mut dpi_factor = Some(monitors[0].hidpi_factor()); for monitor in &monitors { - if Some(monitor.get_hidpi_factor()) != dpi_factor { + if Some(monitor.hidpi_factor()) != dpi_factor { dpi_factor = None; } } dpi_factor } else { - return Err(CreationError::OsError(format!("No monitors were detected."))); + return Err(os_error!(io::Error::new(io::ErrorKind::NotFound, "No monitors were detected."))); }; dpi_factor.unwrap_or_else(|| { util::get_cursor_pos() @@ -624,7 +615,7 @@ unsafe fn init( let mut dpi_factor = None; for monitor in &monitors { if monitor.contains_point(&cursor_pos) { - dpi_factor = Some(monitor.get_hidpi_factor()); + dpi_factor = Some(monitor.hidpi_factor()); break; } } @@ -635,7 +626,7 @@ unsafe fn init( }; info!("Guessed window DPI factor: {}", guessed_dpi_factor); - let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into()); + let dimensions = attributes.inner_size.unwrap_or_else(|| (1024, 768).into()); let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::DECORATIONS, attributes.decorations); @@ -664,13 +655,9 @@ unsafe fn init( ); if handle.is_null() { - return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}", - format!("{}", io::Error::last_os_error())))); + return Err(os_error!(io::Error::last_os_error())); } - winuser::SetWindowLongW(handle, winuser::GWL_STYLE, 0); - winuser::SetWindowLongW(handle, winuser::GWL_EXSTYLE, 0); - WindowWrapper(handle) }; @@ -685,7 +672,7 @@ unsafe fn init( } } - let dpi = get_hwnd_dpi(real_window.0); + let dpi = hwnd_dpi(real_window.0); let dpi_factor = dpi_to_scale_factor(dpi); if dpi_factor != guessed_dpi_factor { let (width, height): (u32, u32) = dimensions.into(); @@ -733,7 +720,7 @@ unsafe fn init( window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized); let window_state = { - let mut window_state = WindowState::new( + let window_state = WindowState::new( &attributes, window_icon, taskbar_icon, @@ -760,7 +747,7 @@ unsafe fn init( force_window_active(win.window.0); } - if let Some(dimensions) = attributes.dimensions { + if let Some(dimensions) = attributes.inner_size { win.set_inner_size(dimensions); } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 844707d6..41dcb228 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,5 +1,5 @@ use monitor::MonitorHandle; -use window::{MouseCursor, WindowAttributes}; +use window::{CursorIcon, WindowAttributes}; use std::{io, ptr}; use parking_lot::MutexGuard; use dpi::LogicalSize; @@ -36,7 +36,7 @@ pub struct SavedWindow { #[derive(Clone)] pub struct MouseProperties { - pub cursor: MouseCursor, + pub cursor: CursorIcon, pub buttons_down: u32, cursor_flags: CursorFlags, } @@ -90,13 +90,13 @@ impl WindowState { ) -> WindowState { WindowState { mouse: MouseProperties { - cursor: MouseCursor::default(), + cursor: CursorIcon::default(), buttons_down: 0, cursor_flags: CursorFlags::empty(), }, - min_size: attributes.min_dimensions, - max_size: attributes.max_dimensions, + min_size: attributes.min_inner_size, + max_size: attributes.max_inner_size, window_icon, taskbar_icon, diff --git a/src/window.rs b/src/window.rs index 8efec1ad..2567d94d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,7 +1,8 @@ //! The `Window` struct and associated types. -use std::{fmt, error}; +use std::fmt; use platform_impl; +use error::{ExternalError, NotSupportedError, OsError}; use event_loop::EventLoopWindowTarget; use monitor::{AvailableMonitorsIter, MonitorHandle}; use dpi::{LogicalPosition, LogicalSize}; @@ -84,17 +85,17 @@ pub struct WindowAttributes { /// used. /// /// The default is `None`. - pub dimensions: Option, + pub inner_size: Option, /// 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, + pub min_inner_size: Option, /// 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, + pub max_inner_size: Option, /// Whether the window is resizable or not. /// @@ -141,19 +142,15 @@ pub struct WindowAttributes { /// /// The default is `None`. pub window_icon: Option, - - /// [iOS only] Enable multitouch, - /// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled) - pub multitouch: bool, } impl Default for WindowAttributes { #[inline] fn default() -> WindowAttributes { WindowAttributes { - dimensions: None, - min_dimensions: None, - max_dimensions: None, + inner_size: None, + min_inner_size: None, + max_inner_size: None, resizable: true, title: "winit window".to_owned(), maximized: false, @@ -163,7 +160,6 @@ impl Default for WindowAttributes { decorations: true, always_on_top: false, window_icon: None, - multitouch: false, } } } @@ -179,22 +175,22 @@ impl WindowBuilder { /// Requests the window to be of specific dimensions. #[inline] - pub fn with_dimensions(mut self, size: LogicalSize) -> WindowBuilder { - self.window.dimensions = Some(size); + pub fn with_inner_size(mut self, size: LogicalSize) -> WindowBuilder { + self.window.inner_size = Some(size); self } /// Sets a minimum dimension size for the window #[inline] - pub fn with_min_dimensions(mut self, min_size: LogicalSize) -> WindowBuilder { - self.window.min_dimensions = Some(min_size); + pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> WindowBuilder { + self.window.min_inner_size = Some(min_size); self } /// Sets a maximum dimension size for the window #[inline] - pub fn with_max_dimensions(mut self, max_size: LogicalSize) -> WindowBuilder { - self.window.max_dimensions = Some(max_size); + pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> WindowBuilder { + self.window.max_inner_size = Some(max_size); self } @@ -282,23 +278,16 @@ impl WindowBuilder { self } - /// Enables multitouch. - #[inline] - pub fn with_multitouch(mut self) -> WindowBuilder { - self.window.multitouch = true; - self - } - /// Builds the window. /// /// 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, window_target: &EventLoopWindowTarget) -> Result { - self.window.dimensions = Some(self.window.dimensions.unwrap_or_else(|| { + pub fn build(mut self, window_target: &EventLoopWindowTarget) -> Result { + self.window.inner_size = Some(self.window.inner_size.unwrap_or_else(|| { if let Some(ref monitor) = self.window.fullscreen { // resizing the window to the dimensions of the monitor when fullscreen - LogicalSize::from_physical(monitor.get_dimensions(), 1.0) + LogicalSize::from_physical(monitor.dimensions(), 1.0) } else { // default dimensions (1024, 768).into() @@ -314,6 +303,7 @@ impl WindowBuilder { } } +/// Base Window functions. impl Window { /// Creates a new Window for platforms where this is appropriate. /// @@ -322,39 +312,36 @@ impl Window { /// Error should be very rare and only occur in case of permission denied, incompatible system, /// out of memory, etc. #[inline] - pub fn new(event_loop: &EventLoopWindowTarget) -> Result { + pub fn new(event_loop: &EventLoopWindowTarget) -> Result { let builder = WindowBuilder::new(); builder.build(event_loop) } - /// Modifies the title of the window. - /// - /// This is a no-op if the window has already been closed. + /// Returns an identifier unique to the window. #[inline] - pub fn set_title(&self, title: &str) { - self.window.set_title(title) + pub fn id(&self) -> WindowId { + WindowId(self.window.id()) } - /// Shows the window if it was hidden. + /// 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 /// - /// - Has no effect on Android + /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **Android:** Always returns 1.0. + /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s + /// [`contentScaleFactor`]. /// + /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc #[inline] - pub fn show(&self) { - self.window.show() - } - - /// Hides the window if it was visible. - /// - /// ## Platform-specific - /// - /// - Has no effect on Android - /// - #[inline] - pub fn hide(&self) { - self.window.hide() + pub fn hidpi_factor(&self) -> f64 { + self.window.hidpi_factor() } /// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS @@ -368,9 +355,33 @@ impl Window { /// * While processing `EventsCleared`. /// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any /// directly subsequent `RedrawRequested` event. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] pub fn request_redraw(&self) { self.window.request_redraw() } +} + +/// Position and size functions. +impl Window { + /// Returns the position of the top-left hand corner of the window's client area relative to the + /// top-left hand corner of the desktop. + /// + /// The same conditions that apply to `outer_position` apply to this method. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window's [safe area] in the screen space coordinate system. + /// + /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[inline] + pub fn inner_position(&self) -> Result { + self.window.inner_position() + } /// Returns the position of the top-left hand corner of the window relative to the /// top-left hand corner of the desktop. @@ -382,29 +393,28 @@ impl Window { /// The coordinates can be negative if the top-left hand corner of the window is outside /// of the visible screen region. /// - /// Returns `None` if the window no longer exists. - #[inline] - pub fn get_position(&self) -> Option { - self.window.get_position() - } - - /// Returns the position of the top-left hand corner of the window's client area relative to the - /// top-left hand corner of the desktop. + /// ## Platform-specific /// - /// The same conditions that apply to `get_position` apply to this method. + /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window in the screen space coordinate system. #[inline] - pub fn get_inner_position(&self) -> Option { - self.window.get_inner_position() + pub fn outer_position(&self) -> Result { + self.window.outer_position() } /// Modifies the position of the window. /// - /// See `get_position` for more information about the coordinates. + /// See `outer_position` for more information about the coordinates. /// /// This is a no-op if the window has already been closed. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the + /// window in the screen space coordinate system. #[inline] - pub fn set_position(&self, position: LogicalPosition) { - self.window.set_position(position) + pub fn set_outer_position(&self, position: LogicalPosition) { + self.window.set_outer_position(position) } /// Returns the logical size of the window's client area. @@ -413,43 +423,87 @@ impl Window { /// /// 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 { - self.window.get_inner_size() - } - - /// Returns the logical size of the entire window. + /// ## Platform-specific /// - /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), - /// use `get_inner_size` instead. + /// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window's + /// [safe area] in screen space coordinates. /// - /// Returns `None` if the window no longer exists. + /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] - pub fn get_outer_size(&self) -> Option { - self.window.get_outer_size() + pub fn inner_size(&self) -> LogicalSize { + self.window.inner_size() } /// Modifies the inner size of the window. /// - /// See `get_inner_size` for more information about the values. + /// See `inner_size` for more information about the values. /// - /// This is a no-op if the window has already been closed. + /// ## Platform-specific + /// + /// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size` + /// would mean for iOS. #[inline] pub fn set_inner_size(&self, size: LogicalSize) { self.window.set_inner_size(size) } - /// Sets a minimum dimension size for the window. + /// Returns the logical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), + /// use `inner_size` instead. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window in + /// screen space coordinates. #[inline] - pub fn set_min_dimensions(&self, dimensions: Option) { - self.window.set_min_dimensions(dimensions) + pub fn outer_size(&self) -> LogicalSize { + self.window.outer_size() + } + + /// Sets a minimum dimension size for the window. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + #[inline] + pub fn set_min_inner_size(&self, dimensions: Option) { + self.window.set_min_inner_size(dimensions) } /// Sets a maximum dimension size for the window. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. #[inline] - pub fn set_max_dimensions(&self, dimensions: Option) { - self.window.set_max_dimensions(dimensions) + pub fn set_max_inner_size(&self, dimensions: Option) { + self.window.set_max_inner_size(dimensions) + } +} + +/// Misc. attribute functions. +impl Window { + /// Modifies the title of the window. + /// + /// ## Platform-specific + /// + /// - Has no effect on iOS. + #[inline] + pub fn set_title(&self, title: &str) { + self.window.set_title(title) + } + + /// Modifies the window's visibility. + /// + /// If `false`, this will hide the window. If `true`, this will show the window. + /// ## Platform-specific + /// + /// - **Android:** Has no effect. + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn set_visible(&self, visible: bool) { + self.window.set_visible(visible) } /// Sets whether the window is resizable or not. @@ -462,87 +516,63 @@ impl Window { /// This only has an effect on desktop platforms. /// /// Due to a bug in XFCE, this has no effect on Xfwm. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. #[inline] pub fn set_resizable(&self, resizable: bool) { self.window.set_resizable(resizable) } - /// 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. + /// Sets the window to maximized or back. /// /// ## Platform-specific /// - /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. - /// - **Android:** Always returns 1.0. - #[inline] - 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); - } - - /// Changes the position of the cursor in window coordinates. - #[inline] - pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { - self.window.set_cursor_position(position) - } - - /// Grabs the cursor, preventing it from leaving the window. - /// - /// ## 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 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 + /// - **iOS:** Has no effect. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window.set_maximized(maximized) } - /// Sets the window to fullscreen or back + /// Sets the window to fullscreen or back. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. #[inline] pub fn set_fullscreen(&self, monitor: Option) { self.window.set_fullscreen(monitor) } + /// Gets the window's current fullscreen state. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn fullscreen(&self) -> Option { + self.window.fullscreen() + } + /// Turn window decorations on or off. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden + /// via [`setPrefersStatusBarHidden`]. + /// + /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc #[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. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.window.set_always_on_top(always_on_top) @@ -562,74 +592,110 @@ impl Window { } /// Sets location of IME candidate box in client area coordinates relative to the top left. + /// + /// ## Platform-specific + /// + /// **iOS:** Has no effect. #[inline] - pub fn set_ime_spot(&self, position: LogicalPosition) { - self.window.set_ime_spot(position) + pub fn set_ime_position(&self, position: LogicalPosition) { + self.window.set_ime_position(position) + } +} + +/// Cursor functions. +impl Window { + /// Modifies the cursor icon of the window. + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect. + /// - **Android:** Has no effect. + #[inline] + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + self.window.set_cursor_icon(cursor); } - /// Returns the monitor on which the window currently resides + /// Changes the position of the cursor in window coordinates. + /// + /// ## Platform-specific + /// + /// - **iOS:** Always returns an `Err`. #[inline] - pub fn get_current_monitor(&self) -> MonitorHandle { - self.window.get_current_monitor() + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { + self.window.set_cursor_position(position) + } + + /// Grabs the cursor, preventing it from leaving the window. + /// + /// ## Platform-specific + /// + /// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually + /// awkward. + /// - **Android:** Has no effect. + /// - **iOS:** Always returns an Err. + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + self.window.set_cursor_grab(grab) + } + + /// Hides the cursor, making it invisible but still usable. + /// + /// ## Platform-specific + /// + /// - **Windows:** The cursor is only hidden within the confines of the window. + /// - **X11:** The cursor is only hidden within the confines of the window. + /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is + /// outside of the window. + /// - **iOS:** Has no effect. + /// - **Android:** Has no effect. + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + self.window.set_cursor_visible(visible) + } +} + +/// Monitor info functions. +impl Window { + /// Returns the monitor on which the window currently resides + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn current_monitor(&self) -> MonitorHandle { + self.window.current_monitor() } /// Returns the list of all the monitors available on the system. /// - /// This is the same as `EventLoop::get_available_monitors`, and is provided for convenience. + /// This is the same as `EventLoop::available_monitors`, and is provided for convenience. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. #[inline] - pub fn get_available_monitors(&self) -> AvailableMonitorsIter { - let data = self.window.get_available_monitors(); + pub fn available_monitors(&self) -> AvailableMonitorsIter { + let data = self.window.available_monitors(); AvailableMonitorsIter { data: data.into_iter() } } /// Returns the primary monitor of the system. /// - /// This is the same as `EventLoop::get_primary_monitor`, and is provided for convenience. + /// This is the same as `EventLoop::primary_monitor`, and is provided for convenience. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - MonitorHandle { inner: self.window.get_primary_monitor() } - } - - /// Returns an identifier unique to the window. - #[inline] - pub fn id(&self) -> WindowId { - WindowId(self.window.id()) - } -} - -/// Error that can happen while creating a window or a headless renderer. -#[derive(Debug, Clone)] -pub enum CreationError { - OsError(String), - /// TODO: remove this error - NotSupported, -} - -impl CreationError { - fn to_string(&self) -> &str { - match *self { - CreationError::OsError(ref text) => &text, - CreationError::NotSupported => "Some of the requested attributes are not supported", - } - } -} - -impl fmt::Display for CreationError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { - formatter.write_str(self.to_string()) - } -} - -impl error::Error for CreationError { - fn description(&self) -> &str { - self.to_string() + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle { inner: self.window.primary_monitor() } } } /// Describes the appearance of the mouse cursor. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum MouseCursor { +pub enum CursorIcon { /// The platform-dependent default cursor. Default, /// A simple crosshair. @@ -683,8 +749,8 @@ pub enum MouseCursor { RowResize, } -impl Default for MouseCursor { +impl Default for CursorIcon { fn default() -> Self { - MouseCursor::Default + CursorIcon::Default } } diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index 5effbbda..00f30b8f 100644 --- a/tests/serde_objects.rs +++ b/tests/serde_objects.rs @@ -3,7 +3,7 @@ extern crate serde; extern crate winit; -use winit::window::{MouseCursor}; +use winit::window::{CursorIcon}; use winit::event::{ KeyboardInput, TouchPhase, ElementState, MouseButton, MouseScrollDelta, VirtualKeyCode, ModifiersState @@ -15,7 +15,7 @@ fn needs_serde>() {} #[test] fn window_serde() { - needs_serde::(); + needs_serde::(); } #[test]