Compare commits

...

52 Commits

Author SHA1 Message Date
Joe Moon
30aa5a5057 version 0.18.0 (#680)
* version 0.18.0

* Changelog: F16-F24 support was a breaking change

* fix version in README

* bump image dep

* Updated release date
2018-11-07 00:43:15 -05:00
Barret Rennie
a46fcaee31 Support requesting user attention on macOS (#664)
* Support requesting user attention on macOS

* Documentation improvements
2018-11-06 23:50:40 -05:00
Osspial
52e2748869 Remove From<NSApplicationActivationPolicy> impl from ActivationPolicy (#690)
* Remove From<NSApplicationActivationPolicy> impl from ActivationPolicy

* Update CHANGELOG
2018-11-05 18:54:22 -05:00
Patrick Walton
d2d127a4c4 Make views explicitly layer-backed on macOS Mojave. (#685)
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.

This was breaking the `window` example in `glutin`.
2018-11-05 14:34:54 -05:00
Francesca Plebani
0fca8e8cb5 X11: Fix DND freezing the WM (#688)
Fixes #687

`XdndFinished` isn't supposed to be sent when rejecting a `XdndPosition`; it should only be
sent in response to `XdndDrop`.

https://freedesktop.org/wiki/Specifications/XDND/
2018-11-02 17:41:51 -04:00
Osspial
6bec912961 Add optional Serde implementations and missing derivable traits (#652)
* Add optional serde feature

* Document features in README

* Add changelog entry

* Implement some missing derivable traits

* Add changelog entry for std derives

* Remove extraneous space on serde doc comments

* Add period to end of serde line in readme

* Remove serde impls from WindowAttributes

* Add serde impls for TouchPhase

* Add serde test file

* Add feature lines to testing CIs

* Remove WindowAttributes from changelog
2018-11-01 04:24:56 -04:00
Artúr Kovács
214e157e5d Implement HoveredFile and HoveredFileCancelled on Windows (#662)
* Implement HoveredFile and HoveredFileCancelled on Windows (#448)

* Update CHANGELOG.

* Applied code organizational corrections and fixed IDropHandler leak on window destroy.

* Moved FileDropHandle to a separate file.
2018-10-24 14:40:12 -04:00
Lucas Kent
da1d479e55 update to image 0.20 (#683) 2018-10-23 20:29:11 -04:00
Eleanore Young
062bb0cef2 On linux without X11 or Wayland, reduced the panic message to a single line (#681) 2018-10-21 18:12:51 -04:00
Victor Berger
c744b016ce x11: compute resize logical size with new dpi (#668)
* x11: compute resize logical size with new dpi

Whenever a dpi change occurs, trigger a Resized event as well with the
new logical size. Given X11 primarily deals in physical pixels, a change
in DPI also changes the logical size (as the physical size remains
fixed).

* Doc tweaks
2018-10-19 16:32:57 -04:00
George Burton
f486845f7f Implement Debug trait on exported opaque types (#677)
* Implement `Debug` trait on exported opaque types

* Make formatting consistent
2018-10-17 23:20:12 -04:00
trimental
7baa96c5c7 Provide current modifiers state with pointer events on wayland (#676) 2018-10-17 22:34:02 -04:00
Joe Moon
ea07ec1fda macOS: fix modifiers during key repeat (#666)
* macOS: fix modifiers during key repeat

* fix compile warnings
2018-10-17 22:03:26 -04:00
Alex Taylor
26b70e457b Windows: Fix transparency (#675)
* Windows: Fix transparency (#260)

* Windows: Only enable WS_EX_LAYERED for transparent windows

* Update winapi to 0.3.6

* Windows: Amend transparency code

* Add transparency fix to CHANGELOG.md
2018-10-17 20:23:59 -04:00
Rob Horswell
5d5fcb3911 Windows: Fix window.set_maximized() (#672)
* Windows: Fix window.set_maximized()

* Add window.set_maximized fix to CHANGELOG.

* Windows: use same style for set_maximized as other set_x(bool) methods
2018-10-14 19:47:08 -04:00
trimental
50008dff3d Upgrade to smithay-client-toolkit 0.4 (#671)
* Upgrade to smithay-client-toolkit 0.4

* Fix PR points
2018-10-14 19:15:43 -04:00
Francesca Plebani
808638fee3 Windows: Fix set_cursor delay (#660) 2018-09-22 21:03:38 -04:00
Tobias Umbach
b0e3865562 Don't include NUL byte in _NET_WM_NAME (#658)
> The contents of the property are not required to be null-terminated;
> any terminating null should not be included in text_prop.nitems.

https://tronche.com/gui/x/xlib/ICC/client-to-window-manager/XmbTextPropertyToTextList.html
2018-09-20 17:59:37 -04:00
Tobias Umbach
bc03ffb317 Add X11-specific with_gtk_theme_variant option (#659) 2018-09-20 17:00:04 -04:00
trimental
1edbca1775 Wayland: use init_from_env() to create windows and allow server-sid… (#655)
* Wayland: use `init_from_env()` to create windows and allow server-side decorations

* Change the CHANGELOG.md entrys wording
2018-09-20 13:48:36 -04:00
Kirill Chibisov
5a0bc016e7 Add support for F16-F24 (#641)
* Added support for F16-F19 keys.

* Documented support for F16-F19 keys

* Added support for F20 key

* Added support for F21-F24 on platforms except macOs

* Added support for F21-F24 on macOs

* Documented addition of F16-F24 keys

* Added missing ref qualifier

* Fixed compilation error on 1.24.1

* Refactored methods in macOs events_loop and view files
2018-09-12 13:04:16 -04:00
Sven-Hendrik Haase
bb66b7f28e Update wm spec hints (#646)
* Update wm-spec hints to v1.5

* Update changelog

* Fix CHANGELOG entry

* Remove trailing quote
2018-09-11 15:03:42 -04:00
Sven-Hendrik Haase
0331491b2b Put badges next to each other instead of below eachother (#649) 2018-09-11 14:59:04 -04:00
Kornel
54a782c8ae Syntax fix in Cargo.toml (#644) 2018-09-11 00:23:48 -04:00
Osspial
a70bc20829 Remove resize block on Windows (#634)
* Remove Windows block on resize

* Add CHANGELOG entry

* Move CHANGELOG entry to Unreleased

* Further edits to CHANGELOG entry
2018-08-24 13:48:57 -04:00
trimental
102ed3b800 Wayland: commit frame surface on resize (#635) 2018-08-23 13:20:02 -04:00
Joe Moon
c8e339fe6d version 0.17.2 (#630)
* version 0.17.2

* Update release date
2018-08-19 18:27:57 -04:00
trimental
e4e53fe315 Add key repetition for the wayland backend (#628)
* Add key repetition for the wayland backend

* Upgrade smithay-client-toolkit to 0.3.0
2018-08-19 17:17:40 -04:00
Joe Moon
1c795c3f1c 625 macos modifiers (#629)
* fix <C-Tab>

* fix <CMD-{key}>

* move the NSKeyUp special handling into the match statement

* add special handling for `<Cmd-.>`

* formatting

* add return type to msg_send!
2018-08-15 19:42:57 -04:00
Francesca Frangipane
b2b740fed7 Windows: Fix fullscreen deadlock + release 0.17.1 (#622)
* Windows: Fix fullscreen deadlock

* Release winit 0.17.1
2018-08-07 14:24:43 -04:00
Azriel Hoh
09550397d7 Maintenance/620/fix x11 release mode compilation (#621)
* Raised minimum version of `x11-dl`.

This fixes a compilation error in release mode on X11.

Issue #620

* Updated `CHANGELOG.md` about X11 release mode compilation issue.
2018-08-05 02:24:49 -04:00
Paul Rouget
a32f7f2ec5 Update cocoa and core-graphics (#608)
* Update cocoa and core-graphics

* Release winit 0.17.0

* Updated date / README version
2018-08-02 16:26:30 -04:00
Dennis Möhlmann
e8e9fa2418 fix assertion failed: validate_hidpi_factor(dpi_factor) (#607) (#618)
* fix assertion failed: validate_hidpi_factor(dpi_factor) (#607)

* added changelog entry
2018-08-02 13:03:15 -04:00
Felix Rabe
1a119bdfe9 Use consistent order inside #[cfg(any(...))] (#619) 2018-08-01 15:22:14 -04:00
Andrew Hickman
21ff2e0ffc Fix unsoundness on Windows (#601)
* Fix unsoundness in windows backend

* Synchronize window state properly

* update changelog and add a comment to execute_in_thread

* Formatting fixes and improve changelog message
2018-07-27 18:34:08 -04:00
Felix Rabe
df9b23c96a Typo: retreiv... -> retriev... (#614) 2018-07-27 14:59:53 -04:00
mtak-
4c117aa282 iOS: Fix the longjmp/setjmp ffi (#613)
* iOS: Fix the `longjmp`/`setjmp` ffi. `jmp_buf` was the wrong size (too small) causing crashes on application launch, make longjmp return Never

* remove extra parentheses around JBLEN, and add a changelog entry about the JmpBuf fix
2018-07-26 19:27:26 -04:00
Francesca Frangipane
88427262a6 macOS: Fix cursor hiding thread unsafety (#611) 2018-07-26 17:14:16 -04:00
mtak-
72b24a9348 iOS Abstract Out the UIView type from winit (#609)
* remove opengl code from winit

* iOS: restrict EventsLoop to be created on the main thread
iOS: Window can only be made once, make Drop on Window thread safe
iOS: make DelegateState owned by Window, cleanup
iOS: fixes from merge (class! macro)

* update the changelog

* Fixed nitpicks
2018-07-25 14:49:46 -04:00
Paul Rouget
01cb8e59e3 Fix key state on MacOS (#610) 2018-07-25 13:36:33 -04:00
trimental
3910326709 Update wayland-client and client-toolkit (#602) 2018-07-20 12:08:55 -04:00
Josh Groves
7ee46d80e6 Use class macro (#605) 2018-07-19 12:02:33 -04:00
Victor Berger
0cb5450999 Clarify DPI docs to highlight WindowEvent::HiDpiFactorChanged (#598)
* Clarify DPI docs to highlight WindowEvent::HiDpiFactorChanged

* Address review of #598

* dpi docs: grammar corrections

* The final nitpick
2018-07-16 10:44:29 -04:00
Iku Iwasa
8c78013257 Support NetBSD platform (#603)
* Support NetBSD platform

* CHANGELOG tweak + target ordering
2018-07-16 10:25:26 -04:00
mtak-
bd944898f0 set the UIViewController's view to the one that was just created (#595)
* set the UIViewController's view to the one that was just created

* capture the return value in a Unit to avoid SIGILL/SIGSEGV's.
change whitespace to be more idiomatic of Obj-C

* CHANGELOG entry
2018-07-13 15:10:12 -04:00
Bastien Orivel
c1ef1acfc0 Update parking_lot and bump version (#593) 2018-07-07 17:21:53 -04:00
Francesca Frangipane
040d3f5d8b Remove incorrect unreachable usage when guessing DPI factor (#592) 2018-07-05 11:52:25 -04:00
Joshua Minter
ec393e4a90 Disable maximize button on non-resizable windows (#588)
* Disabled maximize button

When creating a non resizable window in win32.
Also added example code to test.

* Added to changelog

* Added to documentation

* Removed non_resizable test

* Other suggested PR changes

* Documentation changes

* CHANGELOG nits
2018-07-03 20:15:19 -04:00
Francesca Frangipane
1703d0417a Release winit 0.16.1 (#587) 2018-07-02 20:14:38 -04:00
Francesca Frangipane
fad72c0441 X11: Fix compilation when c_char==c_uchar (#586) 2018-07-02 11:05:25 -04:00
Francesca Frangipane
2f7321a076 X11+Windows: Guess initial DPI factor (#583)
* X11: Guess initial DPI factor

* Windows: Guess initial DPI factor
2018-07-01 11:01:46 -04:00
icefoxen
85ee422acd Define "DPI" in docs. (#580)
It makes my pedant reflexes tingle.
2018-06-28 14:05:56 -04:00
51 changed files with 1765 additions and 1036 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
Cargo.lock
target/
rls/
.vscode/
*~
#*#

View File

@@ -65,8 +65,12 @@ install:
script:
- cargo build --target $TARGET --verbose
- cargo build --target $TARGET --features serde --verbose
- cargo build --target $TARGET --features icon_loading --verbose
# Running iOS apps on OSX requires the simulator so we skip that for now
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features icon_loading --verbose; fi
after_success:
- |

View File

@@ -1,5 +1,70 @@
# Unreleased
# Version 0.18.0 (2018-11-07)
- **Breaking:** `image` crate upgraded to 0.20. This is exposed as part of the `icon_loading` API.
- On Wayland, pointer events will now provide the current modifiers state.
- On Wayland, titles will now be displayed in the window header decoration.
- On Wayland, key repetition is now ended when keyboard loses focus.
- On Wayland, windows will now use more stylish and modern client side decorations.
- On Wayland, windows will use server-side decorations when available.
- **Breaking:** Added support for F16-F24 keys (variants were added to the `VirtualKeyCode` enum).
- Fixed graphical glitches when resizing on Wayland.
- On Windows, fix freezes when performing certain actions after a window resize has been triggered. Reintroduces some visual artifacts when resizing.
- Updated window manager hints under X11 to v1.5 of [Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm140200472629520).
- Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions.
- Fixed UTF8 handling bug in X11 `set_title` function.
- On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first.
- On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented.
- On Windows, fix `Window::set_maximized`.
- On Windows 10, fix transparency (#260).
- On macOS, fix modifiers during key repeat.
- Implemented the `Debug` trait for `Window`, `EventsLoop`, `EventsLoopProxy` and `WindowBuilder`.
- On X11, now a `Resized` event will always be generated after a DPI change to ensure the window's logical size is consistent with the new DPI.
- Added further clarifications to the DPI docs.
- On Linux, if neither X11 nor Wayland manage to initialize, the corresponding panic now consists of a single line only.
- Add optional `serde` feature with implementations of `Serialize`/`Deserialize` for DPI types and various event types.
- Add `PartialEq`, `Eq`, and `Hash` implementations on public types that could have them but were missing them.
- On X11, drag-and-drop receiving an unsupported drop type can no longer cause the WM to freeze.
- Fix issue whereby the OpenGL context would not appear at startup on macOS Mojave (#1069).
- **Breaking:** Removed `From<NSApplicationActivationPolicy>` impl from `ActivationPolicy` on macOS.
- On macOS, the application can request the user's attention with `WindowExt::request_user_attention`.
# Version 0.17.2 (2018-08-19)
- On macOS, fix `<C-Tab>` so applications receive the event.
- On macOS, fix `<Cmd-{key}>` so applications receive the event.
- On Wayland, key press events will now be repeated.
# Version 0.17.1 (2018-08-05)
- On X11, prevent a compilation failure in release mode for versions of Rust greater than or equal to 1.30.
- Fixed deadlock that broke fullscreen mode on Windows.
# Version 0.17.0 (2018-08-02)
- Cocoa and core-graphics updates.
- Fixed thread-safety issues in several `Window` functions on Windows.
- On MacOS, the key state for modifiers key events is now properly set.
- On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again.
- Added NetBSD support.
- **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK).
- On iOS, the `UIApplication` is not started until `Window::new` is called.
- Fixed thread unsafety with cursor hiding on macOS.
- On iOS, fixed the size of the `JmpBuf` type used for `setjmp`/`longjmp` calls. Previously this was a buffer overflow on most architectures.
- On Windows, use cached window DPI instead of repeatedly querying the system. This fixes sporadic crashes on Windows 7.
# Version 0.16.2 (2018-07-07)
- On Windows, non-resizable windows now have the maximization button disabled. This is consistent with behavior on macOS and popular X11 WMs.
- Corrected incorrect `unreachable!` usage when guessing the DPI factor with no detected monitors.
# Version 0.16.1 (2018-07-02)
- Added logging through `log`. Logging will become more extensive over time.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
# Version 0.16.0 (2018-06-25)
- Windows additionally has `WindowBuilderExt::with_no_redirection_bitmap`.

View File

@@ -1,7 +1,7 @@
[package]
name = "winit"
version = "0.16.0"
authors = ["The winit contributors, Pierre Krieger <pierre.krieger1708@gmail.com>"]
version = "0.18.0"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
keywords = ["windowing"]
license = "Apache-2.0"
@@ -11,7 +11,7 @@ documentation = "https://docs.rs/winit"
categories = ["gui"]
[package.metadata.docs.rs]
features = ["icon_loading"]
features = ["icon_loading", "serde"]
[features]
icon_loading = ["image"]
@@ -19,42 +19,48 @@ icon_loading = ["image"]
[dependencies]
lazy_static = "1"
libc = "0.2"
image = { version = "0.19", optional = true }
log = "0.4"
image = { version = "0.20.1", optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2"
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
cocoa = "0.15"
objc = "0.2.3"
cocoa = "0.18.4"
core-foundation = "0.6"
core-graphics = "0.14"
core-graphics = "0.17.3"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.5"
version = "0.3.6"
features = [
"combaseapi",
"dwmapi",
"errhandlingapi",
"hidusage",
"libloaderapi",
"objbase",
"ole2",
"processthreadsapi",
"shellapi",
"shellscalingapi",
"shobjidl_core",
"unknwnbase",
"winbase",
"windowsx",
"winerror",
"wingdi",
"winnt",
"winuser",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies]
wayland-client = { version = "0.20.6", features = [ "dlopen", "egl", "cursor"] }
smithay-client-toolkit = "0.2.2"
x11-dl = "2.17.5"
parking_lot = "0.5"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.21", features = [ "dlopen", "egl", "cursor"] }
smithay-client-toolkit = "0.4"
x11-dl = "2.18.3"
parking_lot = "0.6"
percent-encoding = "1.0"

View File

@@ -1,15 +1,13 @@
# winit - Cross-platform window creation and management in Rust
[![](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)
```toml
[dependencies]
winit = "0.16"
winit = "0.18"
```
## [Documentation](https://docs.rs/winit)
@@ -43,6 +41,12 @@ fn main() {
}
```
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `icon_loading`: Enables loading window icons directly from files. Depends on the [`image` crate](https://crates.io/crates/image).
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
### Platform-specific usage
#### Emscripten and WebAssembly

View File

@@ -22,3 +22,5 @@ build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo test --features icon_loading --verbose

View File

@@ -6,10 +6,9 @@ extern crate winit;
#[cfg(feature = "icon_loading")]
extern crate image;
use winit::Icon;
#[cfg(feature = "icon_loading")]
fn main() {
use winit::Icon;
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
@@ -37,7 +36,7 @@ fn main() {
match event {
CloseRequested => return winit::ControlFlow::Break,
DroppedFile(path) => {
use image::GenericImage;
use image::GenericImageView;
let icon_image = image::open(path).expect("Failed to open window icon");

View File

@@ -19,19 +19,29 @@
//! small.
//!
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI factor. On a "typical"
//! desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical pixels. However,
//! a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels. Ideally, the
//! button now has approximately the same perceived size across varying displays.
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
//! Ideally, the button now has approximately the same perceived size across varying displays.
//!
//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users
//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! There are two ways to get the DPI factor: either by calling
//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or
//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor). You'll almost always use the latter,
//! which is basically equivalent to `window.get_current_monitor().get_hidpi_factor()` anyway.
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor), which is roughly equivalent
//! to `window.get_current_monitor().get_hidpi_factor()`.
//!
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings.
@@ -43,18 +53,28 @@
//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **Wayland:** On Wayland, DPI factors are very much at the discretion of the user.
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
//!
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes produce a
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
//! [`Resized`](../enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
//!
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! `winit` will send [`Resized`](../enum.WindowEvent.html#variant.Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
/// Checks that the DPI factor is a normal positive `f64`.
///
@@ -72,6 +92,7 @@ pub fn validate_hidpi_factor(dpi_factor: f64) -> bool {
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition {
pub x: f64,
pub y: f64,
@@ -132,6 +153,7 @@ impl Into<(i32, i32)> for LogicalPosition {
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition {
pub x: f64,
pub y: f64,
@@ -192,6 +214,7 @@ impl Into<(i32, i32)> for PhysicalPosition {
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize {
pub width: f64,
pub height: f64,
@@ -252,6 +275,7 @@ impl Into<(u32, u32)> for LogicalSize {
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize {
pub width: f64,
pub height: f64,

View File

@@ -3,7 +3,7 @@ use std::path::PathBuf;
use {DeviceId, LogicalPosition, LogicalSize, WindowId};
/// Describes a generic event.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
WindowEvent {
window_id: WindowId,
@@ -22,7 +22,7 @@ pub enum Event {
}
/// Describes an event from a `Window`.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum WindowEvent {
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(LogicalSize),
@@ -116,7 +116,7 @@ pub enum WindowEvent {
/// may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
@@ -147,7 +147,8 @@ pub enum DeviceEvent {
}
/// Describes a keyboard input event.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
/// Identifies the physical key pressed
///
@@ -173,6 +174,7 @@ pub struct KeyboardInput {
/// Describes touch-screen input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
Started,
Moved,
@@ -195,7 +197,7 @@ pub enum TouchPhase {
/// as previously received End event is a new finger and has nothing to do with an old one.
///
/// Touch may be cancelled if for example window lost focus.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
@@ -215,6 +217,7 @@ pub type ButtonId = u32;
/// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
@@ -222,6 +225,7 @@ pub enum ElementState {
/// Describes a button of a mouse controller.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
Right,
@@ -231,6 +235,7 @@ pub enum MouseButton {
/// Describes a difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
/// Amount in lines or rows to scroll in the horizontal
/// and vertical directions.
@@ -250,6 +255,7 @@ pub enum MouseScrollDelta {
/// Symbolic name for a keyboard key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum VirtualKeyCode {
/// The '1' key over the letters.
Key1,
@@ -317,6 +323,15 @@ pub enum VirtualKeyCode {
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq.
Snapshot,
@@ -440,6 +455,8 @@ pub enum VirtualKeyCode {
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct ModifiersState {
/// The "shift" key
pub shift: bool,

View File

@@ -146,7 +146,7 @@ impl Icon {
/// Requires the `icon_loading` feature.
impl From<image::DynamicImage> for Icon {
fn from(image: image::DynamicImage) -> Self {
use image::{GenericImage, Pixel};
use image::{GenericImageView, Pixel};
let (width, height) = image.dimensions();
let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
for (_, _, pixel) in image.pixels() {

View File

@@ -25,11 +25,11 @@
//! Once a window has been created, it will *generate events*. For example whenever the user moves
//! the window, resizes the window, moves the mouse, etc. an event is generated.
//!
//! The events generated by a window can be retreived from the `EventsLoop` the window was created
//! The events generated by a window can be retrieved from the `EventsLoop` the window was created
//! with.
//!
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
//! retreive all the events pending on the windows and immediately return after no new event is
//! retrieve all the events pending on the windows and immediately return after no new event is
//! available. You usually want to use this method in application that render continuously on the
//! screen, such as video games.
//!
@@ -80,7 +80,7 @@
//! # Drawing on the window
//!
//! Winit doesn't provide any function that allows drawing on a window. However it allows you to
//! retreive the raw handle of the window (see the `os` module for that), which in turn allows you
//! retrieve the raw handle of the window (see the `os` module for that), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
//!
@@ -88,8 +88,13 @@
#[macro_use]
extern crate lazy_static;
extern crate libc;
#[macro_use]
extern crate log;
#[cfg(feature = "icon_loading")]
extern crate image;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(target_os = "windows")]
extern crate winapi;
@@ -102,13 +107,13 @@ extern crate cocoa;
extern crate core_foundation;
#[cfg(target_os = "macos")]
extern crate core_graphics;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate x11_dl;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate parking_lot;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate percent_encoding;
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate smithay_client_toolkit as sctk;
pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase.
@@ -147,6 +152,12 @@ pub struct Window {
window: platform::Window,
}
impl std::fmt::Debug for Window {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("Window { .. }")
}
}
/// Identifier of a window. Unique for each window.
///
/// Can be obtained with `window.id()`.
@@ -164,7 +175,7 @@ pub struct WindowId(platform::WindowId);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(platform::DeviceId);
/// Provides a way to retreive events from the system and from the windows that were registered to
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventsLoop` can be seen more or less as a "context". Calling `EventsLoop::new()`
@@ -182,10 +193,17 @@ pub struct EventsLoop {
_marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync
}
impl std::fmt::Debug for EventsLoop {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("EventsLoop { .. }")
}
}
/// Returned by the user callback given to the `EventsLoop::run_forever` method.
///
/// Indicates whether the `run_forever` method should continue or complete.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ControlFlow {
/// Continue looping and waiting for events.
Continue,
@@ -261,6 +279,12 @@ pub struct EventsLoopProxy {
events_loop_proxy: platform::EventsLoopProxy,
}
impl std::fmt::Debug for EventsLoopProxy {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("EventsLoopProxy { .. }")
}
}
impl EventsLoopProxy {
/// Wake up the `EventsLoop` from which this proxy was created.
///
@@ -299,6 +323,14 @@ pub struct WindowBuilder {
platform_specific: platform::PlatformSpecificWindowBuilderAttributes,
}
impl std::fmt::Debug for WindowBuilder {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.debug_struct("WindowBuilder")
.field("window", &self.window)
.finish()
}
}
/// Error that can happen while creating a window or a headless renderer.
#[derive(Debug, Clone)]
pub enum CreationError {
@@ -329,7 +361,8 @@ impl std::error::Error for CreationError {
}
/// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseCursor {
/// The platform-dependent default cursor.
Default,

View File

@@ -2,7 +2,7 @@
use std::os::raw::c_void;
use {MonitorId, Window};
use {MonitorId, Window, WindowBuilder};
/// Additional methods on `Window` that are specific to iOS.
pub trait WindowExt {
@@ -29,6 +29,22 @@ impl WindowExt for Window {
}
}
/// Additional methods on `WindowBuilder` that are specific to iOS.
pub trait WindowBuilderExt {
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided.
///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
#[inline]
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self
}
}
/// Additional methods on `MonitorId` that are specific to iOS.
pub trait MonitorIdExt {
/// Returns a pointer to the `UIScreen` that is used by this monitor.

View File

@@ -1,8 +1,6 @@
#![cfg(target_os = "macos")]
use std::convert::From;
use std::os::raw::c_void;
use cocoa::appkit::NSApplicationActivationPolicy;
use {LogicalSize, MonitorId, Window, WindowBuilder};
/// Additional methods on `Window` that are specific to MacOS.
@@ -16,6 +14,14 @@ pub trait WindowExt {
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nsview(&self) -> *mut c_void;
/// Request user attention, causing the application's dock icon to bounce.
/// Note that this has no effect if the application is already focused.
///
/// The `is_critical` flag has the following effects:
/// - `false`: the dock icon will only bounce once.
/// - `true`: the dock icon will bounce until the application is focused.
fn request_user_attention(&self, is_critical: bool);
}
impl WindowExt for Window {
@@ -28,6 +34,11 @@ impl WindowExt for Window {
fn get_nsview(&self) -> *mut c_void {
self.window.get_nsview()
}
#[inline]
fn request_user_attention(&self, is_critical: bool) {
self.window.request_user_attention(is_critical)
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -47,19 +58,6 @@ impl Default for ActivationPolicy {
}
}
impl From<ActivationPolicy> for NSApplicationActivationPolicy {
fn from(activation_policy: ActivationPolicy) -> Self {
match activation_policy {
ActivationPolicy::Regular =>
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory =>
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited =>
NSApplicationActivationPolicy::NSApplicationActivationPolicyProhibited,
}
}
}
/// Additional methods on `WindowBuilder` that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method

View File

@@ -1,4 +1,4 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::os::raw;
use std::ptr;
@@ -219,6 +219,8 @@ pub trait WindowBuilderExt {
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
/// Build window with base size hint. Only implemented on X11.
@@ -269,6 +271,12 @@ impl WindowBuilderExt for WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
}
/// Additional methods on `MonitorId` that are specific to Linux.

View File

@@ -810,11 +810,15 @@ fn key_translate_virt(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES
"F13" => Some(F13),
"F14" => Some(F14),
"F15" => Some(F15),
"F16" => None,
"F17" => None,
"F18" => None,
"F19" => None,
"F20" => None,
"F16" => Some(F16),
"F17" => Some(F17),
"F18" => Some(F18),
"F19" => Some(F19),
"F20" => Some(F20),
"F21" => Some(F21),
"F22" => Some(F22),
"F23" => Some(F23),
"F24" => Some(F24),
"Soft1" => None,
"Soft2" => None,
"Soft3" => None,

View File

@@ -1,10 +1,9 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::ffi::CString;
use std::mem;
use std::os::raw::*;
use objc::runtime::{Class, Object};
use objc::runtime::Object;
pub type id = *mut Object;
pub const nil: id = 0 as id;
@@ -15,19 +14,11 @@ pub type Boolean = u32;
pub const kCFRunLoopRunHandledSource: i32 = 4;
pub const UIViewAutoresizingFlexibleWidth: NSUInteger = 1 << 1;
pub const UIViewAutoresizingFlexibleHeight: NSUInteger = 1 << 4;
#[cfg(target_pointer_width = "32")]
pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
#[cfg(target_pointer_width = "32")]
pub type NSUInteger = u32;
#[cfg(target_pointer_width = "64")]
pub type NSUInteger = u64;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGPoint {
@@ -73,12 +64,24 @@ extern {
extern {
pub fn setjmp(env: *mut c_void) -> c_int;
pub fn longjmp(env: *mut c_void, val: c_int);
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
}
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
#[cfg(any(target_arch = "x86_64"))]
pub const JBLEN: usize = (9 * 2) + 3 + 16;
#[cfg(any(target_arch = "x86"))]
pub const JBLEN: usize = 18;
#[cfg(target_arch = "arm")]
pub const JBLEN: usize = 10 + 16 + 2;
#[cfg(target_arch = "aarch64")]
pub const JBLEN: usize = (14 + 8 + 2) * 2;
pub type JmpBuf = [c_int; JBLEN];
pub trait NSString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class("NSString"), alloc]
msg_send![class!(NSString), alloc]
}
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
@@ -105,10 +108,3 @@ impl NSString for id {
msg_send![self, UTF8String]
}
}
#[inline]
pub fn class(name: &str) -> *mut Class {
unsafe {
mem::transmute(Class::get(name))
}
}

View File

@@ -61,8 +61,10 @@
#![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};
@@ -90,6 +92,8 @@ use self::ffi::{
CGPoint,
CGRect,
id,
JBLEN,
JmpBuf,
kCFRunLoopDefaultMode,
kCFRunLoopRunHandledSource,
longjmp,
@@ -97,14 +101,13 @@ use self::ffi::{
NSString,
setjmp,
UIApplicationMain,
UIViewAutoresizingFlexibleWidth,
UIViewAutoresizingFlexibleHeight,
};
static mut JMPBUF: [c_int; 27] = [0; 27];
static mut JMPBUF: Option<Box<JmpBuf>> = None;
pub struct Window {
delegate_state: *mut DelegateState,
_events_queue: Arc<RefCell<VecDeque<Event>>>,
delegate_state: Box<DelegateState>,
}
unsafe impl Send for Window {}
@@ -112,7 +115,6 @@ unsafe impl Sync for Window {}
#[derive(Debug)]
struct DelegateState {
events_queue: VecDeque<Event>,
window: id,
controller: id,
view: id,
@@ -123,7 +125,6 @@ struct DelegateState {
impl DelegateState {
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
DelegateState {
events_queue: VecDeque::new(),
window,
controller,
view,
@@ -170,7 +171,7 @@ impl fmt::Debug for MonitorId {
impl MonitorId {
#[inline]
pub fn get_uiscreen(&self) -> id {
let class = Class::get("UIScreen").expect("Failed to get class `UIScreen`");
let class = class!(UIScreen);
unsafe { msg_send![class, mainScreen] }
}
@@ -199,7 +200,7 @@ impl MonitorId {
}
pub struct EventsLoop {
delegate_state: *mut DelegateState,
events_queue: Arc<RefCell<VecDeque<Event>>>,
}
#[derive(Clone)]
@@ -208,21 +209,11 @@ pub struct EventsLoopProxy;
impl EventsLoop {
pub fn new() -> EventsLoop {
unsafe {
if setjmp(mem::transmute(&mut JMPBUF)) != 0 {
let app_class = Class::get("UIApplication").expect("Failed to get 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 delegate_state = state as *mut DelegateState;
return EventsLoop { delegate_state };
if !msg_send![class!(NSThread), isMainThread] {
panic!("`EventsLoop` can only be created on the main thread on iOS");
}
}
create_view_class();
create_delegate_class();
start_app();
panic!("Couldn't create `UIApplication`!")
EventsLoop { events_queue: Default::default() }
}
#[inline]
@@ -240,29 +231,30 @@ impl EventsLoop {
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
unsafe {
let state = &mut *self.delegate_state;
if let Some(event) = state.events_queue.pop_front() {
callback(event);
return;
}
// jump hack, so we won't quit on willTerminate event before processing it
if setjmp(mem::transmute(&mut JMPBUF)) != 0 {
if let Some(event) = state.events_queue.pop_front() {
assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS");
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
}
}
unsafe {
// run runloop
let seconds: CFTimeInterval = 0.000002;
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
}
if let Some(event) = state.events_queue.pop_front() {
callback(event)
}
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event)
}
}
@@ -301,8 +293,18 @@ pub struct WindowId;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes;
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
}
}
}
// TODO: AFAIK transparency is enabled by default on iOS,
// so to be consistent with other platforms we have to change that.
@@ -310,19 +312,60 @@ impl Window {
pub fn new(
ev: &EventsLoop,
_attributes: WindowAttributes,
_pl_alltributes: PlatformSpecificWindowBuilderAttributes,
pl_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
Ok(Window { delegate_state: ev.delegate_state })
unsafe {
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
}
unsafe {
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
let app_class = class!(UIApplication);
let app: id = msg_send![app_class, sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
let events_queue = &*ev.events_queue;
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
let uiview_class = class!(UIView);
let root_view_class = pl_attributes.root_view_class;
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
delegate_state.view = msg_send![root_view_class, alloc];
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
return Ok(Window {
_events_queue: ev.events_queue.clone(),
delegate_state,
});
}
}
create_delegate_class();
start_app();
panic!("Couldn't create `UIApplication`!")
}
#[inline]
pub fn get_uiwindow(&self) -> id {
unsafe { (*self.delegate_state).window }
self.delegate_state.window
}
#[inline]
pub fn get_uiview(&self) -> id {
unsafe { (*self.delegate_state).view }
self.delegate_state.view
}
#[inline]
@@ -359,7 +402,7 @@ impl Window {
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
unsafe { Some((&*self.delegate_state).size) }
Some(self.delegate_state.size)
}
#[inline]
@@ -404,7 +447,7 @@ impl Window {
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
unsafe { (&*self.delegate_state) }.scale
self.delegate_state.scale
}
#[inline]
@@ -469,10 +512,9 @@ impl Window {
fn create_delegate_class() {
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
let screen_class = Class::get("UIScreen").expect("Failed to get class `UIScreen`");
let window_class = Class::get("UIWindow").expect("Failed to get class `UIWindow`");
let controller_class = Class::get("MainViewController").expect("Failed to get class `MainViewController`");
let view_class = Class::get("MainView").expect("Failed to get class `MainView`");
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];
@@ -486,30 +528,28 @@ fn create_delegate_class() {
let view_controller: id = msg_send![controller_class, alloc];
let view_controller: id = msg_send![view_controller, init];
let view: id = msg_send![view_class, alloc];
let view: id = msg_send![view, initForGl:&bounds];
let _: () = msg_send![window, setRootViewController:view_controller];
let _: () = msg_send![window, makeKeyAndVisible];
let state = Box::new(DelegateState::new(window, view_controller, view, size, scale as f64));
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(&mut JMPBUF),1); }
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
}
extern fn did_become_active(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::WindowEvent {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(true),
});
@@ -518,9 +558,9 @@ fn create_delegate_class() {
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::WindowEvent {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(false),
});
@@ -529,38 +569,38 @@ fn create_delegate_class() {
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::Suspended(false));
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(false));
}
}
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::Suspended(true));
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(true));
}
}
extern fn will_terminate(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
// push event to the front to garantee that we'll process it
// immidiatly after jump
state.events_queue.push_front(Event::WindowEvent {
events_queue.borrow_mut().push_front(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Destroyed,
});
longjmp(mem::transmute(&mut JMPBUF),1);
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
}
}
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
let touches_enum: id = msg_send![touches, objectEnumerator];
@@ -573,7 +613,7 @@ fn create_delegate_class() {
let touch_id = touch as u64;
let phase: i32 = msg_send![touch, phase];
state.events_queue.push_back(Event::WindowEvent {
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID,
@@ -593,7 +633,7 @@ fn create_delegate_class() {
}
}
let ui_responder = Class::get("UIResponder").expect("Failed to get class `UIResponder`");
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
@@ -633,46 +673,12 @@ fn create_delegate_class() {
post_launch as extern fn(&Object, Sel, id));
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<*mut c_void>("eventsQueue");
decl.register();
}
}
// TODO: winit shouldn't contain GL-specfiic code
pub fn create_view_class() {
let superclass = Class::get("UIViewController").expect("Failed to get class `UIViewController`");
let decl = ClassDecl::new("MainViewController", superclass).expect("Failed to declare class `MainViewController`");
decl.register();
extern fn init_for_gl(this: &Object, _: Sel, frame: *const c_void) -> id {
unsafe {
let bounds = frame as *const CGRect;
let view: id = msg_send![this, initWithFrame:(*bounds).clone()];
let mask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
let _: () = msg_send![view, setAutoresizingMask:mask];
let _: () = msg_send![view, setAutoresizesSubviews:YES];
let layer: id = msg_send![view, layer];
let _ : () = msg_send![layer, setOpaque:YES];
view
}
}
extern fn layer_class(_: &Class, _: Sel) -> *const Class {
unsafe { mem::transmute(Class::get("CAEAGLLayer").expect("Failed to get class `CAEAGLLayer`")) }
}
let superclass = Class::get("GLKView").expect("Failed to get class `GLKView`");
let mut decl = ClassDecl::new("MainView", superclass).expect("Failed to declare class `MainView`");
unsafe {
decl.add_method(sel!(initForGl:), init_for_gl as extern fn(&Object, Sel, *const c_void) -> id);
decl.add_class_method(sel!(layerClass), layer_class as extern fn(&Class, Sel) -> *const Class);
decl.register();
}
}
#[inline]
fn start_app() {
unsafe {

View File

@@ -1,4 +1,4 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![allow(dead_code)]
use std::os::raw::{c_void, c_char, c_int};

View File

@@ -1,4 +1,4 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::collections::VecDeque;
use std::{env, mem};
@@ -45,6 +45,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub class: Option<(String, String)>,
pub override_redirect: bool,
pub x11_window_type: x11::util::WindowType,
pub gtk_theme_variant: Option<String>,
}
lazy_static!(
@@ -429,10 +430,7 @@ impl EventsLoop {
};
let err_string = format!(
r#"Failed to initialize any backend!
Wayland status: {:#?}
X11 status: {:#?}
"#,
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err,
x11_err,
);

View File

@@ -1,22 +1,25 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::sync::{Arc, Mutex, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use {ControlFlow, EventsLoopClosed, PhysicalPosition, PhysicalSize};
use super::WindowId;
use super::window::WindowStore;
use super::WindowId;
use sctk::Environment;
use sctk::output::OutputMgr;
use sctk::reexports::client::{Display, EventQueue, GlobalEvent, Proxy, ConnectError};
use sctk::reexports::client::commons::Implementation;
use sctk::reexports::client::protocol::{wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat,
wl_touch};
use sctk::reexports::client::protocol::{
wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch,
};
use sctk::reexports::client::{ConnectError, Display, EventQueue, GlobalEvent, Proxy};
use sctk::Environment;
use sctk::reexports::client::protocol::wl_display::RequestsTrait as DisplayRequests;
use sctk::reexports::client::protocol::wl_surface::RequestsTrait;
use ModifiersState;
pub struct EventsLoopSink {
buffer: VecDeque<::Event>,
@@ -91,7 +94,7 @@ impl EventsLoopProxy {
// Update the `EventsLoop`'s `pending_wakeup` flag.
wakeup.store(true, Ordering::Relaxed);
// Cause the `EventsLoop` to break from `dispatch` if it is currently blocked.
let _ = display.sync();
let _ = display.sync(|callback| callback.implement(|_, _| {}, ()));
display.flush().map_err(|_| EventsLoopClosed)?;
Ok(())
}
@@ -104,29 +107,39 @@ impl EventsLoop {
pub fn new() -> Result<EventsLoop, ConnectError> {
let (display, mut event_queue) = Display::connect_to_env()?;
let display = Arc::new(display);
let pending_wakeup = Arc::new(AtomicBool::new(false));
let sink = Arc::new(Mutex::new(EventsLoopSink::new()));
let store = Arc::new(Mutex::new(WindowStore::new()));
let seats = Arc::new(Mutex::new(Vec::new()));
let env = Environment::from_registry_with_cb(
display.get_registry().unwrap(),
let mut seat_manager = SeatManager {
sink: sink.clone(),
store: store.clone(),
seats: seats.clone(),
events_loop_proxy: EventsLoopProxy {
display: Arc::downgrade(&display),
pending_wakeup: Arc::downgrade(&pending_wakeup),
},
};
let env = Environment::from_display_with_cb(
&display,
&mut event_queue,
SeatManager {
sink: sink.clone(),
store: store.clone(),
seats: seats.clone(),
move |event, registry| {
seat_manager.receive(event, registry)
},
).unwrap();
Ok(EventsLoop {
display: Arc::new(display),
display,
evq: RefCell::new(event_queue),
sink: sink,
pending_wakeup: Arc::new(AtomicBool::new(false)),
store: store,
env: env,
sink,
pending_wakeup,
store,
env,
cleanup_needed: Arc::new(Mutex::new(false)),
seats: seats,
seats,
})
}
@@ -242,6 +255,9 @@ impl EventsLoop {
*size = (w, h);
} else if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
}
}
if let Some(dpi) = new_dpi {
@@ -266,9 +282,10 @@ struct SeatManager {
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
seats: Arc<Mutex<Vec<(u32, Proxy<wl_seat::WlSeat>)>>>,
events_loop_proxy: EventsLoopProxy,
}
impl Implementation<Proxy<wl_registry::WlRegistry>, GlobalEvent> for SeatManager {
impl SeatManager {
fn receive(&mut self, evt: GlobalEvent, registry: Proxy<wl_registry::WlRegistry>) {
use self::wl_registry::RequestsTrait as RegistryRequests;
use self::wl_seat::RequestsTrait as SeatRequests;
@@ -280,16 +297,23 @@ impl Implementation<Proxy<wl_registry::WlRegistry>, GlobalEvent> for SeatManager
} if interface == "wl_seat" =>
{
use std::cmp::min;
let mut seat_data = SeatData {
sink: self.sink.clone(),
store: self.store.clone(),
pointer: None,
keyboard: None,
touch: None,
events_loop_proxy: self.events_loop_proxy.clone(),
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
};
let seat = registry
.bind::<wl_seat::WlSeat>(min(version, 5), id)
.unwrap()
.implement(SeatData {
sink: self.sink.clone(),
store: self.store.clone(),
pointer: None,
keyboard: None,
touch: None,
});
.bind(min(version, 5), id, move |seat| {
seat.implement(move |event, seat| {
seat_data.receive(event, seat)
}, ())
})
.unwrap();
self.store.lock().unwrap().new_seat(&seat);
self.seats.lock().unwrap().push((id, seat));
}
@@ -313,20 +337,22 @@ struct SeatData {
pointer: Option<Proxy<wl_pointer::WlPointer>>,
keyboard: Option<Proxy<wl_keyboard::WlKeyboard>>,
touch: Option<Proxy<wl_touch::WlTouch>>,
events_loop_proxy: EventsLoopProxy,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
}
impl Implementation<Proxy<wl_seat::WlSeat>, wl_seat::Event> for SeatData {
impl SeatData {
fn receive(&mut self, evt: wl_seat::Event, seat: Proxy<wl_seat::WlSeat>) {
use self::wl_seat::RequestsTrait as SeatRequests;
match evt {
wl_seat::Event::Name { .. } => (),
wl_seat::Event::Capabilities { capabilities } => {
// create pointer if applicable
if capabilities.contains(wl_seat::Capability::Pointer) && self.pointer.is_none() {
self.pointer = Some(super::pointer::implement_pointer(
seat.get_pointer().unwrap(),
&seat,
self.sink.clone(),
self.store.clone(),
self.modifiers_tracker.clone(),
))
}
// destroy pointer if applicable
@@ -341,8 +367,10 @@ impl Implementation<Proxy<wl_seat::WlSeat>, wl_seat::Event> for SeatData {
// create keyboard if applicable
if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() {
self.keyboard = Some(super::keyboard::init_keyboard(
seat.get_keyboard().unwrap(),
&seat,
self.sink.clone(),
self.events_loop_proxy.clone(),
self.modifiers_tracker.clone(),
))
}
// destroy keyboard if applicable
@@ -357,7 +385,7 @@ impl Implementation<Proxy<wl_seat::WlSeat>, wl_seat::Event> for SeatData {
// create touch if applicable
if capabilities.contains(wl_seat::Capability::Touch) && self.touch.is_none() {
self.touch = Some(super::touch::implement_touch(
seat.get_touch().unwrap(),
&seat,
self.sink.clone(),
self.store.clone(),
))

View File

@@ -1,81 +1,121 @@
use std::sync::{Arc, Mutex};
use super::{make_wid, DeviceId, EventsLoopProxy, EventsLoopSink};
use sctk::keyboard::{
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
};
use sctk::reexports::client::protocol::wl_keyboard;
use sctk::reexports::client::Proxy;
use sctk::reexports::client::protocol::wl_seat;
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
use {ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
use super::{make_wid, DeviceId, EventsLoopSink};
use sctk::keyboard::{self, map_keyboard_auto, Event as KbEvent};
use sctk::reexports::client::{NewProxy, Proxy};
use sctk::reexports::client::protocol::wl_keyboard;
pub fn init_keyboard(
keyboard: NewProxy<wl_keyboard::WlKeyboard>,
seat: &Proxy<wl_seat::WlSeat>,
sink: Arc<Mutex<EventsLoopSink>>,
events_loop_proxy: EventsLoopProxy,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> Proxy<wl_keyboard::WlKeyboard> {
// { variables to be captured by the closure
let mut target = None;
// { variables to be captured by the closures
let target = Arc::new(Mutex::new(None));
let my_sink = sink.clone();
let repeat_sink = sink.clone();
let repeat_target = target.clone();
let my_modifiers = modifiers_tracker.clone();
// }
let ret = map_keyboard_auto(keyboard, move |evt: KbEvent, _| match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(true), wid);
target = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(false), wid);
target = None;
}
KbEvent::Key {
modifiers,
rawkey,
keysym,
state,
utf8,
..
} => {
if let Some(wid) = target {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
};
let vkcode = key_to_vkey(rawkey, keysym);
let mut guard = my_sink.lock().unwrap();
let ret = map_keyboard_auto_with_repeat(
seat,
KeyRepeatKind::System,
move |evt: KbEvent, _| match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(true), wid);
*target.lock().unwrap() = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(false), wid);
*target.lock().unwrap() = None;
}
KbEvent::Key {
rawkey,
keysym,
state,
utf8,
..
} => {
if let Some(wid) = *target.lock().unwrap() {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
};
let vkcode = key_to_vkey(rawkey, keysym);
let mut guard = my_sink.lock().unwrap();
guard.send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
},
wid,
);
// send char event only on key press, not release
if let ElementState::Released = state {
return;
}
if let Some(txt) = utf8 {
for chr in txt.chars() {
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
}
}
}
}
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers { modifiers: event_modifiers } => {
*modifiers_tracker.lock().unwrap() = event_modifiers.into()
}
},
move |repeat_event: KeyRepeatEvent, _| {
if let Some(wid) = *repeat_target.lock().unwrap() {
let state = ElementState::Pressed;
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
let mut guard = repeat_sink.lock().unwrap();
guard.send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: rawkey,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: modifiers.into(),
modifiers: my_modifiers.lock().unwrap().clone(),
},
},
wid,
);
// send char event only on key press, not release
if let ElementState::Released = state {
return;
}
if let Some(txt) = utf8 {
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
guard.send_event(WindowEvent::ReceivedCharacter(chr), wid);
}
}
events_loop_proxy.wakeup().unwrap();
}
}
KbEvent::RepeatInfo { .. } => { /* TODO: handle repeat info */ }
});
},
);
match ret {
Ok(keyboard) => keyboard,
Err((_, keyboard)) => {
Err(_) => {
// This is a fallback impl if libxkbcommon was not available
// This case should probably never happen, as most wayland
// compositors _need_ libxkbcommon anyway...
@@ -87,45 +127,47 @@ pub fn init_keyboard(
let mut target = None;
let my_sink = sink;
// }
keyboard.implement(move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(true), wid);
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(false), wid);
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
if let Some(wid) = target {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
};
my_sink.lock().unwrap().send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
},
wid,
);
seat.get_keyboard(|keyboard| {
keyboard.implement(move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(true), wid);
target = Some(wid);
}
}
_ => (),
})
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink
.lock()
.unwrap()
.send_event(WindowEvent::Focused(false), wid);
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
if let Some(wid) = target {
let state = match state {
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
wl_keyboard::KeyState::Released => ElementState::Released,
};
my_sink.lock().unwrap().send_event(
WindowEvent::KeyboardInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
input: KeyboardInput {
state: state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
},
wid,
);
}
}
_ => (),
}, ())
}).unwrap()
}
}
}
@@ -193,6 +235,15 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
// flow control
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),

View File

@@ -1,5 +1,5 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
target_os = "openbsd"))]
target_os = "netbsd", target_os = "openbsd"))]
pub use self::window::Window;
pub use self::event_loop::{EventsLoop, EventsLoopProxy, EventsLoopSink, MonitorId};

View File

@@ -7,184 +7,183 @@ use super::DeviceId;
use super::event_loop::EventsLoopSink;
use super::window::WindowStore;
use sctk::reexports::client::{NewProxy, Proxy};
use sctk::reexports::client::Proxy;
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PtrEvent, WlPointer};
use sctk::reexports::client::protocol::wl_seat;
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
pub fn implement_pointer(
pointer: NewProxy<WlPointer>,
seat: &Proxy<wl_seat::WlSeat>,
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> Proxy<WlPointer> {
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
let mut axis_state = TouchPhase::Ended;
pointer.implement(move |evt, pointer: Proxy<_>| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
PtrEvent::Enter {
surface,
surface_x,
surface_y,
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
sink.send_event(
WindowEvent::CursorEntered {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
},
wid,
);
sink.send_event(
WindowEvent::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (surface_x, surface_y).into(),
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
},
wid,
);
}
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_event(
WindowEvent::CursorLeft {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
},
wid,
);
}
}
PtrEvent::Motion {
surface_x,
surface_y,
..
} => {
if let Some(wid) = mouse_focus {
sink.send_event(
WindowEvent::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (surface_x, surface_y).into(),
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
},
wid,
);
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(wid) = mouse_focus {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
};
let button = match button {
0x110 => MouseButton::Left,
0x111 => MouseButton::Right,
0x112 => MouseButton::Middle,
// TODO figure out the translation ?
_ => return,
};
sink.send_event(
WindowEvent::MouseInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
state: state,
button: button,
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
},
wid,
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(wid) = mouse_focus {
if pointer.version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
}
seat.get_pointer(|pointer| {
pointer.implement(move |evt, pointer| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
PtrEvent::Enter {
surface,
surface_x,
surface_y,
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
sink.send_event(
WindowEvent::MouseWheel {
WindowEvent::CursorEntered {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
phase: TouchPhase::Moved,
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
},
wid,
);
} else {
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
}
axis_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
}
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(wid) = mouse_focus {
if let Some((x, y)) = axis_discrete_buffer {
sink.send_event(
WindowEvent::MouseWheel {
WindowEvent::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: axis_state,
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
},
wid,
);
} else if let Some((x, y)) = axis_buffer {
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
phase: axis_state,
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
}
PtrEvent::AxisSource { .. } => (),
PtrEvent::AxisStop { .. } => {
axis_state = TouchPhase::Ended;
}
PtrEvent::AxisDiscrete { axis, discrete } => {
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= discrete,
wl_pointer::Axis::HorizontalScroll => x += discrete,
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_event(
WindowEvent::CursorLeft {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
},
wid,
);
}
}
axis_discrete_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
PtrEvent::Motion {
surface_x,
surface_y,
..
} => {
if let Some(wid) = mouse_focus {
sink.send_event(
WindowEvent::CursorMoved {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(wid) = mouse_focus {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
};
let button = match button {
0x110 => MouseButton::Left,
0x111 => MouseButton::Right,
0x112 => MouseButton::Middle,
// TODO figure out the translation ?
_ => return,
};
sink.send_event(
WindowEvent::MouseInput {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
state: state,
button: button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(wid) = mouse_focus {
if pointer.version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
}
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
phase: TouchPhase::Moved,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
} else {
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
}
axis_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
}
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(wid) = mouse_focus {
if let Some((x, y)) = axis_discrete_buffer {
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: axis_state,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
} else if let Some((x, y)) = axis_buffer {
sink.send_event(
WindowEvent::MouseWheel {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()),
phase: axis_state,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
}
PtrEvent::AxisSource { .. } => (),
PtrEvent::AxisStop { .. } => {
axis_state = TouchPhase::Ended;
}
PtrEvent::AxisDiscrete { axis, discrete } => {
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
match axis {
// wayland vertical sign convention is the inverse of winit
wl_pointer::Axis::VerticalScroll => y -= discrete,
wl_pointer::Axis::HorizontalScroll => x += discrete,
}
axis_discrete_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
}
})
}, ())
}).unwrap()
}

View File

@@ -6,8 +6,10 @@ use super::{DeviceId, WindowId};
use super::event_loop::EventsLoopSink;
use super::window::WindowStore;
use sctk::reexports::client::{NewProxy, Proxy};
use sctk::reexports::client::Proxy;
use sctk::reexports::client::protocol::wl_touch::{Event as TouchEvent, WlTouch};
use sctk::reexports::client::protocol::wl_seat;
use sctk::reexports::client::protocol::wl_seat::RequestsTrait as SeatRequests;
struct TouchPoint {
wid: WindowId,
@@ -16,78 +18,80 @@ struct TouchPoint {
}
pub(crate) fn implement_touch(
touch: NewProxy<WlTouch>,
seat: &Proxy<wl_seat::WlSeat>,
sink: Arc<Mutex<EventsLoopSink>>,
store: Arc<Mutex<WindowStore>>,
) -> Proxy<WlTouch> {
let mut pending_ids = Vec::new();
touch.implement(move |evt, _| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Started,
location: (x, y).into(),
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid: wid,
location: (x, y),
id: id,
});
seat.get_touch(|touch| {
touch.implement(move |evt, _| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Started,
location: (x, y).into(),
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid: wid,
location: (x, y),
id: id,
});
}
}
}
TouchEvent::Up { id, .. } => {
let idx = pending_ids.iter().position(|p| p.id == id);
if let Some(idx) = idx {
let pt = pending_ids.remove(idx);
TouchEvent::Up { id, .. } => {
let idx = pending_ids.iter().position(|p| p.id == id);
if let Some(idx) = idx {
let pt = pending_ids.remove(idx);
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Ended,
location: pt.location.into(),
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.location = (x, y);
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Moved,
location: (x, y).into(),
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => for pt in pending_ids.drain(..) {
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Ended,
phase: TouchPhase::Cancelled,
location: pt.location.into(),
id: id as u64,
id: pt.id as u64,
}),
pt.wid,
);
}
},
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.location = (x, y);
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Moved,
location: (x, y).into(),
id: id as u64,
}),
pt.wid,
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => for pt in pending_ids.drain(..) {
sink.send_event(
WindowEvent::Touch(::Touch {
device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
phase: TouchPhase::Cancelled,
location: pt.location.into(),
id: pt.id as u64,
}),
pt.wid,
);
},
}
})
}, ())
}).unwrap()
}

View File

@@ -6,7 +6,7 @@ use dpi::{LogicalPosition, LogicalSize};
use platform::MonitorId as PlatformMonitorId;
use window::MonitorId as RootMonitorId;
use sctk::window::{BasicFrame, Event as WEvent, Window as SWindow};
use sctk::window::{ConceptFrame, Event as WEvent, Window as SWindow};
use sctk::reexports::client::{Display, Proxy};
use sctk::reexports::client::protocol::{wl_seat, wl_surface, wl_output};
use sctk::reexports::client::protocol::wl_compositor::RequestsTrait as CompositorRequests;
@@ -18,7 +18,7 @@ use platform::platform::wayland::event_loop::{get_available_monitors, get_primar
pub struct Window {
surface: Proxy<wl_surface::WlSurface>,
frame: Arc<Mutex<SWindow<BasicFrame>>>,
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
monitors: Arc<Mutex<MonitorList>>, // Monitors this window is currently on
outputs: OutputMgr, // Access to info for all monitors
size: Arc<Mutex<(u32, u32)>>,
@@ -36,11 +36,11 @@ impl Window {
// monitor tracking
let monitor_list = Arc::new(Mutex::new(MonitorList::new()));
let surface = evlp.env.compositor.create_surface().unwrap().implement({
let surface = evlp.env.compositor.create_surface(|surface| {
let list = monitor_list.clone();
let omgr = evlp.env.outputs.clone();
let window_store = evlp.store.clone();
move |event, surface: Proxy<wl_surface::WlSurface>| match event {
surface.implement(move |event, surface| match event {
wl_surface::Event::Enter { output } => {
let dpi_change = list.lock().unwrap().add_output(MonitorId {
proxy: output,
@@ -64,19 +64,16 @@ impl Window {
}
}
}
}
});
}, ())
}).unwrap();
let window_store = evlp.store.clone();
let my_surface = surface.clone();
let mut frame = SWindow::<BasicFrame>::init(
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
surface.clone(),
(width, height),
&evlp.env.compositor,
&evlp.env.subcompositor,
&evlp.env.shm,
&evlp.env.shell,
move |event, ()| match event {
move |event| match event {
WEvent::Configure { new_size, .. } => {
let mut store = window_store.lock().unwrap();
for window in &mut store.windows {
@@ -327,7 +324,7 @@ struct InternalWindow {
need_frame_refresh: Arc<Mutex<bool>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<BasicFrame>>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>
}
@@ -385,7 +382,7 @@ impl WindowStore {
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(Option<(u32, u32)>, &mut (u32, u32), Option<i32>, bool, bool, bool, WindowId, Option<&mut SWindow<BasicFrame>>),
F: FnMut(Option<(u32, u32)>, &mut (u32, u32), Option<i32>, bool, bool, bool, WindowId, Option<&mut SWindow<ConceptFrame>>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();

View File

@@ -117,23 +117,23 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_L4 => events::VirtualKeyCode::L4,
ffi::XK_F15 => events::VirtualKeyCode::F15,
//ffi::XK_L5 => events::VirtualKeyCode::L5,
//ffi::XK_F16 => events::VirtualKeyCode::F16,
ffi::XK_F16 => events::VirtualKeyCode::F16,
//ffi::XK_L6 => events::VirtualKeyCode::L6,
//ffi::XK_F17 => events::VirtualKeyCode::F17,
ffi::XK_F17 => events::VirtualKeyCode::F17,
//ffi::XK_L7 => events::VirtualKeyCode::L7,
//ffi::XK_F18 => events::VirtualKeyCode::F18,
ffi::XK_F18 => events::VirtualKeyCode::F18,
//ffi::XK_L8 => events::VirtualKeyCode::L8,
//ffi::XK_F19 => events::VirtualKeyCode::F19,
ffi::XK_F19 => events::VirtualKeyCode::F19,
//ffi::XK_L9 => events::VirtualKeyCode::L9,
//ffi::XK_F20 => events::VirtualKeyCode::F20,
ffi::XK_F20 => events::VirtualKeyCode::F20,
//ffi::XK_L10 => events::VirtualKeyCode::L10,
//ffi::XK_F21 => events::VirtualKeyCode::F21,
ffi::XK_F21 => events::VirtualKeyCode::F21,
//ffi::XK_R1 => events::VirtualKeyCode::R1,
//ffi::XK_F22 => events::VirtualKeyCode::F22,
ffi::XK_F22 => events::VirtualKeyCode::F22,
//ffi::XK_R2 => events::VirtualKeyCode::R2,
//ffi::XK_F23 => events::VirtualKeyCode::F23,
ffi::XK_F23 => events::VirtualKeyCode::F23,
//ffi::XK_R3 => events::VirtualKeyCode::R3,
//ffi::XK_F24 => events::VirtualKeyCode::F24,
ffi::XK_F24 => events::VirtualKeyCode::F24,
//ffi::XK_R4 => events::VirtualKeyCode::R4,
//ffi::XK_F25 => events::VirtualKeyCode::F25,
//ffi::XK_R5 => events::VirtualKeyCode::R5,

View File

@@ -1,4 +1,4 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
pub mod ffi;
mod events;
@@ -278,25 +278,25 @@ impl EventsLoop {
}
} 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 Mac OS X backend
// does that upon a drag entering, XDnD doesn't have access to the actual drop
// 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
// `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::<c_short>() * 8
// Equivalent to `(x << shift) | y`
// where `shift = mem::size_of::<c_short>() * 8`
// Note that coordinates are in "desktop space", not "window space"
// (in x11 parlance, they're root window coordinates)
// (in X11 parlance, they're root window coordinates)
//let packed_coordinates = client_msg.data.get_long(2);
//let shift = mem::size_of::<libc::c_short>() * 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.
// 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.
@@ -318,23 +318,21 @@ impl EventsLoop {
// In version 0, time isn't specified
ffi::CurrentTime
};
// This results in the SelectionNotify event below
// 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 XDnD status message.");
.expect("Failed to send `XdndStatus` message.");
}
} else {
unsafe {
self.dnd.send_status(window, source_window, DndState::Rejected)
.expect("Failed to send XDnD status message.");
self.dnd.send_finished(window, source_window, DndState::Rejected)
.expect("Failed to send XDnD finished message.");
.expect("Failed to send `XdndStatus` message.");
}
self.dnd.reset();
}
} else if client_msg.message_type == self.dnd.atoms.drop {
if let Some(source_window) = self.dnd.source_window {
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 {
@@ -343,10 +341,16 @@ impl EventsLoop {
});
}
}
unsafe {
self.dnd.send_finished(window, source_window, DndState::Accepted)
.expect("Failed to send XDnD finished message.");
}
(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 {
@@ -412,10 +416,10 @@ impl EventsLoop {
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 monitor = window.get_current_monitor(); // This must be done *before* locking!
let mut monitor = window.get_current_monitor(); // This must be done *before* locking!
let mut shared_state_lock = window.shared_state.lock();
let (resized, moved) = {
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)
@@ -435,32 +439,8 @@ impl EventsLoop {
(resized, moved)
};
// 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.
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 {
// 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,
);
}
}
}
let mut events = Events::default();
if resized {
let logical_size = LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor);
events.resized = Some(WindowEvent::Resized(logical_size));
}
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
@@ -482,36 +462,64 @@ impl EventsLoop {
shared_state_lock.position.unwrap()
};
// 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 = if shared_state_lock.is_new_window {
shared_state_lock.is_new_window = false;
1.0
} 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::Rect::new(new_outer_position, new_inner_size);
let monitor = self.xconn.get_monitor_for_window(Some(window_rect));
let new_hidpi_factor = monitor.hidpi_factor;
shared_state_lock.last_monitor = Some(monitor);
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 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.
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 {
// 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
@@ -519,15 +527,15 @@ impl EventsLoop {
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 });
}
if let Some(event) = events.dpi_changed {
callback(Event::WindowEvent { window_id, event });
}
}
}
@@ -592,7 +600,7 @@ impl EventsLoop {
// 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 = 3;
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

View File

@@ -55,7 +55,7 @@ pub struct MonitorId {
/// The DPI scale factor
pub(crate) hidpi_factor: f64,
/// Used to determine which windows are on this monitor
pub(crate) rect: util::Rect,
pub(crate) rect: util::AaRect,
}
impl MonitorId {
@@ -68,7 +68,7 @@ impl MonitorId {
) -> Self {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr) };
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
let rect = util::Rect::new(position, dimensions);
let rect = util::AaRect::new(position, dimensions);
MonitorId {
id,
name,
@@ -104,7 +104,7 @@ impl MonitorId {
}
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::Rect>) -> MonitorId {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorId {
let monitors = self.get_available_monitors();
let default = monitors
.get(0)

View File

@@ -50,7 +50,7 @@ pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
}
// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it.
impl Formattable for c_char { const FORMAT: Format = Format::Char; }
impl Formattable for c_schar { const FORMAT: Format = Format::Char; }
impl Formattable for c_uchar { const FORMAT: Format = Format::Char; }
impl Formattable for c_short { const FORMAT: Format = Format::Short; }
impl Formattable for c_ushort { const FORMAT: Format = Format::Short; }

View File

@@ -3,34 +3,34 @@ use std::cmp;
use super::*;
use {LogicalPosition, LogicalSize};
// Friendly neighborhood axis-aligned rectangle
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rect {
left: i64,
right: i64,
top: i64,
bottom: i64,
pub struct AaRect {
x: i64,
y: i64,
width: i64,
height: i64,
}
impl Rect {
impl AaRect {
pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
let (x, y) = (x as i64, y as i64);
let (width, height) = (width as i64, height as i64);
Rect {
left: x,
right: x + width,
top: y,
bottom: y + height,
}
AaRect { x, y, width, height }
}
pub fn contains_point(&self, x: i64, y: i64) -> bool {
x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
}
pub fn get_overlapping_area(&self, other: &Self) -> i64 {
let x_overlap = cmp::max(
0,
cmp::min(self.right, other.right) - cmp::max(self.left, other.left),
cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
);
let y_overlap = cmp::max(
0,
cmp::min(self.bottom, other.bottom) - cmp::max(self.top, other.top),
cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
);
x_overlap * y_overlap
}

View File

@@ -22,7 +22,7 @@ impl From<bool> for StateOperation {
}
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html).
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
@@ -41,6 +41,24 @@ pub enum WindowType {
Splash,
/// This is a dialog window.
Dialog,
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
/// This property is typically used on override-redirect windows.
DropdownMenu,
/// A popup menu that usually appears when the user right clicks on an object.
/// This property is typically used on override-redirect windows.
PopupMenu,
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
/// This property is typically used on override-redirect windows.
Tooltip,
/// The window is a notification.
/// This property is typically used on override-redirect windows.
Notification,
/// This should be used on the windows that are popped up by combo boxes.
/// This property is typically used on override-redirect windows.
Combo,
/// This indicates the the window is being dragged.
/// This property is typically used on override-redirect windows.
Dnd,
/// This is a normal, top-level window.
Normal,
}
@@ -62,6 +80,12 @@ impl WindowType {
&Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
&Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
&Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
&DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
&PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
&Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
&Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
&Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
&Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
&Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
};
unsafe { xconn.get_atom_unchecked(atom_name) }

View File

@@ -3,6 +3,9 @@ use std::str;
use super::*;
use events::ModifiersState;
pub const VIRTUAL_CORE_POINTER: c_int = 2;
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.
// To test if `lookup_utf8` works correctly, set this to 1.
@@ -24,8 +27,8 @@ pub struct PointerState<'a> {
xconn: &'a XConnection,
root: ffi::Window,
child: ffi::Window,
root_x: c_double,
root_y: c_double,
pub root_x: c_double,
pub root_y: c_double,
win_x: c_double,
win_y: c_double,
buttons: ffi::XIButtonState,

View File

@@ -24,6 +24,7 @@ pub fn calc_dpi_factor(
// See http://xpra.org/trac/ticket/728 for more information.
if width_mm == 0 || width_mm == 0 {
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
return 1.0;
}

View File

@@ -29,13 +29,12 @@ unsafe extern "C" fn visibility_predicate(
#[derive(Debug, Default)]
pub struct SharedState {
// Window creation assumes a DPI factor of 1.0, so we use this flag to handle that special case.
pub is_new_window: bool,
pub cursor_pos: Option<(f64, f64)>,
pub size: Option<(u32, u32)>,
pub position: Option<(i32, i32)>,
pub inner_position: Option<(i32, i32)>,
pub inner_position_rel_parent: Option<(i32, i32)>,
pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorId>,
pub dpi_adjusted: Option<(f64, f64)>,
// Used to restore position after exiting fullscreen.
@@ -46,9 +45,9 @@ pub struct SharedState {
}
impl SharedState {
fn new() -> Mutex<Self> {
fn new(dpi_factor: f64) -> Mutex<Self> {
let mut shared_state = SharedState::default();
shared_state.is_new_window = true;
shared_state.guessed_dpi = Some(dpi_factor);
Mutex::new(shared_state)
}
}
@@ -78,15 +77,51 @@ impl UnownedWindow {
let xconn = &event_loop.xconn;
let root = event_loop.root;
let max_dimensions: Option<(u32, u32)> = window_attrs.max_dimensions.map(Into::into);
let min_dimensions: Option<(u32, u32)> = window_attrs.min_dimensions.map(Into::into);
let monitors = xconn.get_available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
for monitor in &monitors {
if Some(monitor.get_hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
dpi_factor.unwrap_or_else(|| {
xconn.query_pointer(root, util::VIRTUAL_CORE_POINTER)
.ok()
.and_then(|pointer_state| {
let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.rect.contains_point(x, y) {
dpi_factor = Some(monitor.get_hidpi_factor());
break;
}
}
dpi_factor
})
.unwrap_or(1.0)
})
} else {
return Err(OsError(format!("No monitors were detected.")));
};
info!("Guessed window DPI factor: {}", dpi_factor);
let max_dimensions: Option<(u32, u32)> = window_attrs.max_dimensions.map(|size| {
size.to_physical(dpi_factor).into()
});
let min_dimensions: Option<(u32, u32)> = window_attrs.min_dimensions.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 = window_attrs.dimensions
let mut dimensions: (u32, u32) = window_attrs.dimensions
.or_else(|| Some((800, 600).into()))
.map(|size| size.to_physical(dpi_factor))
.map(Into::into)
.unwrap_or((800, 600));
.unwrap();
if let Some(max) = max_dimensions {
dimensions.0 = cmp::min(dimensions.0, max.0);
dimensions.1 = cmp::min(dimensions.1, max.1);
@@ -95,6 +130,7 @@ impl UnownedWindow {
dimensions.0 = cmp::max(dimensions.0, min.0);
dimensions.1 = cmp::max(dimensions.1, min.1);
}
debug!("Calculated physical dimensions: {}x{}", dimensions.0, dimensions.1);
dimensions
};
@@ -166,7 +202,7 @@ impl UnownedWindow {
cursor_hidden: Default::default(),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
multitouch: window_attrs.multitouch,
shared_state: SharedState::new(),
shared_state: SharedState::new(dpi_factor),
};
// Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
@@ -236,17 +272,25 @@ impl UnownedWindow {
window.set_window_type(pl_attribs.x11_window_type).queue();
}
if let Some(variant) = pl_attribs.gtk_theme_variant {
window.set_gtk_theme_variant(variant).queue();
}
// set size hints
{
let mut min_dimensions = window_attrs.min_dimensions;
let mut max_dimensions = window_attrs.max_dimensions;
if !window_attrs.resizable && !util::wm_name_is_one_of(&["Xfwm4"]) {
max_dimensions = Some(dimensions.into());
min_dimensions = Some(dimensions.into());
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());
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;
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;
}
}
let mut normal_hints = util::NormalHints::new(xconn);
@@ -417,6 +461,19 @@ impl UnownedWindow {
)
}
fn set_gtk_theme_variant(&self, variant: String) -> util::Flusher {
let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_GTK_THEME_VARIANT\0") };
let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") };
let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte");
self.xconn.change_property(
self.xwindow,
hint_atom,
utf8_atom,
util::PropMode::Replace,
variant.as_bytes(),
)
}
#[inline]
pub fn set_urgent(&self, is_urgent: bool) {
let mut wm_hints = self.xconn.get_wm_hints(self.xwindow).expect("`XGetWMHints` failed");
@@ -482,10 +539,10 @@ impl UnownedWindow {
self.invalidate_cached_frame_extents();
}
fn get_rect(&self) -> Option<util::Rect> {
fn get_rect(&self) -> Option<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::Rect::new(position, size))
Some(util::AaRect::new(position, size))
} else {
None
}
@@ -543,7 +600,7 @@ impl UnownedWindow {
wm_name_atom,
utf8_atom,
util::PropMode::Replace,
title.as_bytes_with_nul(),
title.as_bytes(),
)
}
}
@@ -853,6 +910,7 @@ impl UnownedWindow {
// Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` from being detected.
// This makes it impossible for resizing to be re-enabled, and also breaks DPI scaling. As such, we choose
// the lesser of two evils and do nothing.
warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
return;
}

View File

@@ -180,7 +180,7 @@ impl EventsLoop {
where F: FnMut(Event),
{
unsafe {
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
@@ -221,7 +221,7 @@ impl EventsLoop {
where F: FnMut(Event) -> ControlFlow
{
unsafe {
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
@@ -315,6 +315,27 @@ impl EventsLoop {
});
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 `<Cmd-.>`, the keyDown is suppressed instead of the
// KeyUp, and the above trick does not appear to work.
appkit::NSKeyDown => {
let modifiers = event_mods(ns_event);
let keycode = NSEvent::keyCode(ns_event);
if modifiers.logo && keycode == 47 {
modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false)
.map(into_event)
} else {
None
}
},
appkit::NSFlagsChanged => {
let mut events = std::collections::VecDeque::new();
@@ -589,7 +610,7 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x3d => events::VirtualKeyCode::RAlt,
0x3e => events::VirtualKeyCode::RControl,
//0x3f => Fn key,
//0x40 => F17 Key,
0x40 => events::VirtualKeyCode::F17,
0x41 => events::VirtualKeyCode::Decimal,
//0x42 -> unkown,
0x43 => events::VirtualKeyCode::Multiply,
@@ -604,8 +625,8 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x4c => events::VirtualKeyCode::NumpadEnter,
//0x4d => unkown,
0x4e => events::VirtualKeyCode::Subtract,
//0x4f => F18 key,
//0x50 => F19 Key,
0x4f => events::VirtualKeyCode::F18,
0x50 => events::VirtualKeyCode::F19,
0x51 => events::VirtualKeyCode::NumpadEquals,
0x52 => events::VirtualKeyCode::Numpad0,
0x53 => events::VirtualKeyCode::Numpad1,
@@ -615,7 +636,7 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x57 => events::VirtualKeyCode::Numpad5,
0x58 => events::VirtualKeyCode::Numpad6,
0x59 => events::VirtualKeyCode::Numpad7,
//0x5a => F20 Key,
0x5a => events::VirtualKeyCode::F20,
0x5b => events::VirtualKeyCode::Numpad8,
0x5c => events::VirtualKeyCode::Numpad9,
//0x5d => unkown,
@@ -631,7 +652,7 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x67 => events::VirtualKeyCode::F11,
//0x68 => unkown,
0x69 => events::VirtualKeyCode::F13,
//0x6a => F16 Key,
0x6a => events::VirtualKeyCode::F16,
0x6b => events::VirtualKeyCode::F14,
//0x6c => unkown,
0x6d => events::VirtualKeyCode::F10,
@@ -659,6 +680,23 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
})
}
pub fn check_additional_virtual_key_codes(
s: &Option<String>
) -> Option<events::VirtualKeyCode> {
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)
@@ -674,11 +712,15 @@ pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
unsafe fn modifier_event(
ns_event: cocoa::base::id,
keymask: NSEventModifierFlags,
key_pressed: bool,
was_key_pressed: bool,
) -> Option<WindowEvent> {
if !key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = ElementState::Released;
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);

View File

@@ -2,7 +2,7 @@
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
use cocoa::base::{class, id};
use cocoa::base::id;
use cocoa::foundation::{NSInteger, NSUInteger};
use objc;
@@ -35,7 +35,7 @@ unsafe impl objc::Encode for NSRange {
pub trait NSMutableAttributedString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class("NSMutableAttributedString"), alloc]
msg_send![class!(NSMutableAttributedString), alloc]
}
unsafe fn init(self) -> id; // *mut NSMutableAttributedString

View File

@@ -1,5 +1,5 @@
use cocoa::appkit::NSWindowStyleMask;
use cocoa::base::{class, id, nil};
use cocoa::base::{id, nil};
use cocoa::foundation::{NSRect, NSUInteger};
use core_graphics::display::CGDisplay;
@@ -26,13 +26,13 @@ pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class("NSTextInputContext"), alloc];
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 app: id = msg_send![class!(NSApplication), sharedApplication];
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
}

View File

@@ -7,14 +7,14 @@ use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::Weak;
use cocoa::base::{class, id, nil};
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};
use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES};
use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code};
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes};
use platform::platform::util;
use platform::platform::ffi::*;
use platform::platform::window::{get_window_id, IdRef};
@@ -24,7 +24,7 @@ struct ViewState {
shared: Weak<Shared>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
last_insert: Option<String>,
is_key_down: bool,
}
pub fn new_view(window: id, shared: Weak<Shared>) -> IdRef {
@@ -33,7 +33,7 @@ pub fn new_view(window: id, shared: Weak<Shared>) -> IdRef {
shared,
ime_spot: None,
raw_characters: None,
last_insert: None,
is_key_down: false,
};
unsafe {
// This is free'd in `dealloc`
@@ -64,7 +64,7 @@ unsafe impl Sync for ViewClass {}
lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let superclass = Class::get("NSView").unwrap();
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(
@@ -122,6 +122,7 @@ lazy_static! {
decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
@@ -191,7 +192,7 @@ extern fn set_marked_text(
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
let _: () = msg_send![(*marked_text_ref), release];
let marked_text = NSMutableAttributedString::alloc(nil);
let has_attr = msg_send![string, isKindOfClass:class("NSAttributedString")];
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
if has_attr {
marked_text.initWithAttributedString(string);
} else {
@@ -214,7 +215,7 @@ extern fn unmark_text(this: &Object, _sel: Sel) {
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
//println!("validAttributesForMarkedText");
unsafe { msg_send![class("NSArray"), array] }
unsafe { msg_send![class!(NSArray), array] }
}
extern fn attributed_substring_for_proposed_range(
@@ -265,7 +266,7 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range:
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let has_attr = msg_send![string, isKindOfClass:class("NSAttributedString")];
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
let characters = if has_attr {
// This is a *mut NSAttributedString
msg_send![string, string]
@@ -279,10 +280,10 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range:
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
state.last_insert = Some(string.to_owned());
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![class!(NSApp), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
@@ -343,6 +344,18 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
}
}
fn get_characters(event: id) -> Option<String> {
unsafe {
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);
Some(string.to_owned())
}
}
extern fn key_down(this: &Object, _sel: Sel, event: id) {
//println!("keyDown");
unsafe {
@@ -350,8 +363,16 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
let state = &mut *(state_ptr as *mut ViewState);
let window_id = WindowId(get_window_id(state.window));
state.raw_characters = get_characters(event);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(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];
@@ -368,13 +389,14 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
},
};
state.raw_characters = {
let characters: id = msg_send![event, characters];
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
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);
let string = str::from_utf8_unchecked(slice);
state.raw_characters = {
Some(string.to_owned())
};
@@ -384,9 +406,8 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
.unwrap()
.push_back(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.last_insert.is_some() {
let last_insert = state.last_insert.as_ref().unwrap();
for character in last_insert.chars() {
if is_repeat && state.is_key_down{
for character in string.chars() {
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
@@ -400,7 +421,7 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
// 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 array: id = msg_send![class!(NSArray), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
}
}
@@ -413,10 +434,17 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
state.last_insert = None;
state.is_key_down = false;
// We need characters here to check for additional keys such as
// F21-F24.
let characters = get_characters(event);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(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)),
@@ -566,3 +594,8 @@ extern fn right_mouse_dragged(this: &Object, _sel: Sel, event: id) {
extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL {
YES
}

View File

@@ -1,20 +1,24 @@
use std;
use std::cell::{Cell, RefCell};
use std::f64;
use std::ops::Deref;
use std::os::raw::c_void;
use std::sync::Weak;
use std::cell::{Cell, RefCell};
use std::sync::atomic::{Ordering, AtomicBool};
use cocoa;
use cocoa::appkit::{
self,
CGFloat,
NSApp,
NSApplication,
NSColor,
NSRequestUserAttentionType,
NSScreen,
NSView,
NSWindow,
NSWindowButton,
NSWindowStyleMask,
NSApplicationActivationPolicy,
};
use cocoa::base::{id, nil};
use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString};
@@ -45,6 +49,7 @@ use window::MonitorId as RootMonitorId;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub usize);
// TODO: It's possible for delegate methods to be called asynchronously, causing data races / `RefCell` panics.
pub struct DelegateState {
view: IdRef,
window: IdRef,
@@ -421,7 +426,7 @@ impl WindowDelegate {
INIT.call_once(|| unsafe {
// Create new NSWindowDelegate
let superclass = Class::get("NSObject").unwrap();
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap();
// Add callback methods
@@ -526,7 +531,7 @@ pub struct Window2 {
pub window: IdRef,
pub delegate: WindowDelegate,
pub input_context: IdRef,
cursor_hidden: Cell<bool>,
cursor_hidden: AtomicBool,
}
unsafe impl Send for Window2 {}
@@ -582,6 +587,19 @@ impl WindowExt for Window2 {
fn get_nsview(&self) -> *mut c_void {
*self.view as *mut c_void
}
#[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);
}
}
}
impl Window2 {
@@ -591,7 +609,7 @@ impl Window2 {
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window2, CreationError> {
unsafe {
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Windows can only be created on the main thread on macOS");
}
}
@@ -716,7 +734,15 @@ impl Window2 {
if app == nil {
None
} else {
app.setActivationPolicy_(activation_policy.into());
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)
}
@@ -728,7 +754,7 @@ impl Window2 {
static INIT: std::sync::Once = std::sync::ONCE_INIT;
INIT.call_once(|| unsafe {
let window_superclass = Class::get("NSWindow").unwrap();
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);
@@ -849,6 +875,16 @@ impl Window2 {
let view = 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
@@ -987,7 +1023,7 @@ impl Window2 {
MouseCursor::ZoomOut => "arrowCursor",
};
let sel = Sel::register(cursor_name);
let cls = Class::get("NSCursor").unwrap();
let cls = class!(NSCursor);
unsafe {
use objc::Message;
let cursor: id = cls.send_message(sel, ()).unwrap();
@@ -1004,16 +1040,16 @@ impl Window2 {
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let cursor_class = Class::get("NSCursor").unwrap();
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.get() {
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.replace(hide);
self.cursor_hidden.store(hide, Ordering::Release);
}
}

View File

@@ -3,7 +3,7 @@ pub use self::platform::*;
#[cfg(target_os = "windows")]
#[path="windows/mod.rs"]
mod platform;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#[path="linux/mod.rs"]
mod platform;
#[cfg(target_os = "macos")]
@@ -21,5 +21,6 @@ mod platform;
#[cfg(all(not(target_os = "ios"), not(target_os = "windows"), not(target_os = "linux"),
not(target_os = "macos"), not(target_os = "android"), not(target_os = "dragonfly"),
not(target_os = "freebsd"), not(target_os = "openbsd"), not(target_os = "emscripten")))]
not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"),
not(target_os = "emscripten")))]
compile_error!("The platform you're compiling for is not supported by winit");

View File

@@ -8,7 +8,6 @@ use winapi::shared::minwindef::{BOOL, UINT, FALSE};
use winapi::shared::windef::{
DPI_AWARENESS_CONTEXT,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
HDC,
HMONITOR,
HWND,
};
@@ -145,7 +144,11 @@ pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}
pub unsafe fn get_window_dpi(hwnd: HWND, hdc: HDC) -> u32 {
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
let hdc = winuser::GetDC(hwnd);
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
}
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
// We are on Windows 10 Anniversary Update (1607) or later.
match GetDpiForWindow(hwnd) {
@@ -181,16 +184,6 @@ pub unsafe fn get_window_dpi(hwnd: HWND, hdc: HDC) -> u32 {
}
}
// Use this when you have both the HWND and HDC on hand (i.e. window methods)
pub fn get_window_scale_factor(hwnd: HWND, hdc: HDC) -> f64 {
dpi_to_scale_factor(unsafe { get_window_dpi(hwnd, hdc) })
}
// Use this when you only have the HWND (i.e. event handling)
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
let hdc = unsafe { winuser::GetDC(hwnd) };
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
}
unsafe { get_window_scale_factor(hwnd, hdc) }
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
}

View File

@@ -0,0 +1,203 @@
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{mem, ptr};
use winapi::ctypes::c_void;
use winapi::shared::guiddef::REFIID;
use winapi::shared::minwindef::{DWORD, MAX_PATH, UINT, ULONG};
use winapi::shared::windef::{HWND, POINTL};
use winapi::shared::winerror::S_OK;
use winapi::um::objidl::IDataObject;
use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl};
use winapi::um::winnt::HRESULT;
use winapi::um::{shellapi, unknwnbase};
use platform::platform::events_loop::send_event;
use platform::platform::WindowId;
use {Event, WindowId as SuperWindowId};
#[repr(C)]
pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
}
pub struct FileDropHandler {
pub data: *mut FileDropHandlerData,
}
#[allow(non_snake_case)]
impl FileDropHandler {
pub fn new(window: HWND) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget {
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
},
refcount: AtomicUsize::new(1),
window,
});
FileDropHandler {
data: Box::into_raw(data),
}
}
// Implement IUnknown
pub unsafe extern "system" fn QueryInterface(
_this: *mut unknwnbase::IUnknown,
_riid: REFIID,
_ppvObject: *mut *mut c_void,
) -> HRESULT {
// This function doesn't appear to be required for an `IDropTarget`.
// An implementation would be nice however.
unimplemented!();
}
pub unsafe extern "system" fn AddRef(this: *mut unknwnbase::IUnknown) -> ULONG {
let drop_handler_data = Self::from_interface(this);
let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1;
count as ULONG
}
pub unsafe extern "system" fn Release(this: *mut unknwnbase::IUnknown) -> ULONG {
let drop_handler = Self::from_interface(this);
let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1;
if count == 0 {
// Destroy the underlying data
Box::from_raw(drop_handler as *mut FileDropHandlerData);
}
count as ULONG
}
pub unsafe extern "system" fn DragEnter(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
_pdwEffect: *mut DWORD,
) -> HRESULT {
use events::WindowEvent::HoveredFile;
let drop_handler = Self::from_interface(this);
Self::iterate_filenames(pDataObj, |filename| {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: HoveredFile(filename),
});
});
S_OK
}
pub unsafe extern "system" fn DragOver(
_this: *mut IDropTarget,
_grfKeyState: DWORD,
_pt: *const POINTL,
_pdwEffect: *mut DWORD,
) -> HRESULT {
S_OK
}
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
use events::WindowEvent::HoveredFileCancelled;
let drop_handler = Self::from_interface(this);
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: HoveredFileCancelled,
});
S_OK
}
pub unsafe extern "system" fn Drop(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
_pdwEffect: *mut DWORD,
) -> HRESULT {
use events::WindowEvent::DroppedFile;
let drop_handler = Self::from_interface(this);
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: DroppedFile(filename),
});
});
shellapi::DragFinish(hdrop);
S_OK
}
unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData {
&mut *(this as *mut _)
}
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> shellapi::HDROP
where
F: Fn(PathBuf),
{
use winapi::ctypes::wchar_t;
use winapi::shared::winerror::SUCCEEDED;
use winapi::shared::wtypes::{CLIPFORMAT, DVASPECT_CONTENT};
use winapi::um::objidl::{FORMATETC, TYMED_HGLOBAL};
use winapi::um::shellapi::DragQueryFileW;
use winapi::um::winuser::CF_HDROP;
let mut drop_format = FORMATETC {
cfFormat: CF_HDROP as CLIPFORMAT,
ptd: ptr::null(),
dwAspect: DVASPECT_CONTENT,
lindex: -1,
tymed: TYMED_HGLOBAL,
};
let mut medium = mem::uninitialized();
if SUCCEEDED((*data_obj).GetData(&mut drop_format, &mut medium)) {
let hglobal = (*medium.u).hGlobal();
let hdrop = (*hglobal) as shellapi::HDROP;
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
let mut pathbuf: [wchar_t; MAX_PATH] = mem::uninitialized();
for i in 0..item_count {
let character_count =
DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), MAX_PATH as UINT) as usize;
if character_count > 0 {
callback(OsString::from_wide(&pathbuf[0..character_count]).into());
}
}
return hdrop;
}
// The call to `GetData` must succeed and the file handle must be returned before this
// point
unreachable!();
}
}
impl Drop for FileDropHandler {
fn drop(&mut self) {
unsafe {
FileDropHandler::Release(self.data as *mut unknwnbase::IUnknown);
}
}
}
static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
parent: unknwnbase::IUnknownVtbl {
QueryInterface: FileDropHandler::QueryInterface,
AddRef: FileDropHandler::AddRef,
Release: FileDropHandler::Release,
},
DragEnter: FileDropHandler::DragEnter,
DragOver: FileDropHandler::DragOver,
DragLeave: FileDropHandler::DragLeave,
Drop: FileDropHandler::Drop,
};

View File

@@ -148,7 +148,7 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
winuser::VK_F13 => Some(VirtualKeyCode::F13),
winuser::VK_F14 => Some(VirtualKeyCode::F14),
winuser::VK_F15 => Some(VirtualKeyCode::F15),
/*winuser::VK_F16 => Some(VirtualKeyCode::F16),
winuser::VK_F16 => Some(VirtualKeyCode::F16),
winuser::VK_F17 => Some(VirtualKeyCode::F17),
winuser::VK_F18 => Some(VirtualKeyCode::F18),
winuser::VK_F19 => Some(VirtualKeyCode::F19),
@@ -156,7 +156,7 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
winuser::VK_F21 => Some(VirtualKeyCode::F21),
winuser::VK_F22 => Some(VirtualKeyCode::F22),
winuser::VK_F23 => Some(VirtualKeyCode::F23),
winuser::VK_F24 => Some(VirtualKeyCode::F24),*/
winuser::VK_F24 => Some(VirtualKeyCode::F24),
winuser::VK_NUMLOCK => Some(VirtualKeyCode::Numlock),
winuser::VK_SCROLL => Some(VirtualKeyCode::Scroll),
winuser::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward),

View File

@@ -15,10 +15,8 @@
use std::{mem, ptr, thread};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
use std::sync::{Arc, Barrier, Condvar, mpsc, Mutex};
use std::sync::{Arc, Barrier, mpsc, Mutex};
use winapi::ctypes::c_int;
use winapi::shared::minwindef::{
@@ -29,13 +27,14 @@ use winapi::shared::minwindef::{
LOWORD,
LPARAM,
LRESULT,
MAX_PATH,
UINT,
WPARAM,
};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::shared::windowsx;
use winapi::um::{winuser, shellapi, processthreadsapi};
use winapi::shared::winerror::S_OK;
use winapi::um::{winuser, processthreadsapi, ole2};
use winapi::um::oleidl::LPDROPTARGET;
use winapi::um::winnt::{LONG, LPCSTR, SHORT};
use {
@@ -57,7 +56,9 @@ use platform::platform::dpi::{
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::icon::WinIcon;
use platform::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state};
use platform::platform::window::adjust_size;
@@ -94,6 +95,13 @@ pub struct WindowState {
// This is different from the value in `SavedWindowInfo`! That one represents the DPI saved upon entering
// fullscreen. This will always be the most recent DPI for the window.
pub dpi_factor: f64,
pub fullscreen: Option<::MonitorId>,
pub window_icon: Option<WinIcon>,
pub taskbar_icon: Option<WinIcon>,
pub decorations: bool,
pub always_on_top: bool,
pub maximized: bool,
pub resizable: bool,
}
impl WindowState {
@@ -130,10 +138,6 @@ pub struct EventsLoop {
thread_id: DWORD,
// Receiver for the events. The sender is in the background thread.
receiver: mpsc::Receiver<Event>,
// Variable that contains the block state of the win32 event loop thread during a WM_SIZE event.
// The mutex's value is `true` when it's blocked, and should be set to false when it's done
// blocking. That's done by the parent thread when it receives a Resized event.
win32_block_loop: Arc<(Mutex<bool>, Condvar)>,
}
impl EventsLoop {
@@ -146,8 +150,6 @@ impl EventsLoop {
// The main events transfer channel.
let (tx, rx) = mpsc::channel();
let win32_block_loop = Arc::new((Mutex::new(false), Condvar::new()));
let win32_block_loop_child = win32_block_loop.clone();
// Local barrier in order to block the `new()` function until the background thread has
// an events queue.
@@ -159,8 +161,8 @@ impl EventsLoop {
*context_stash.borrow_mut() = Some(ThreadLocalData {
sender: tx,
windows: HashMap::with_capacity(4),
win32_block_loop: win32_block_loop_child,
mouse_buttons_down: 0
file_drop_handlers: HashMap::with_capacity(4),
mouse_buttons_down: 0,
});
});
@@ -212,7 +214,6 @@ impl EventsLoop {
EventsLoop {
thread_id,
receiver: rx,
win32_block_loop,
}
}
@@ -224,18 +225,8 @@ impl EventsLoop {
Ok(e) => e,
Err(_) => return
};
let is_resize = match event {
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
_ => false
};
callback(event);
if is_resize {
let (ref mutex, ref cvar) = *self.win32_block_loop;
let mut block_thread = mutex.lock().unwrap();
*block_thread = false;
cvar.notify_all();
}
}
}
@@ -247,18 +238,8 @@ impl EventsLoop {
Ok(e) => e,
Err(_) => return
};
let is_resize = match event {
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
_ => false
};
let flow = callback(event);
if is_resize {
let (ref mutex, ref cvar) = *self.win32_block_loop;
let mut block_thread = mutex.lock().unwrap();
*block_thread = false;
cvar.notify_all();
}
match flow {
ControlFlow::Continue => continue,
ControlFlow::Break => break,
@@ -326,6 +307,11 @@ impl EventsLoopProxy {
///
/// 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<F>(&self, function: F)
where
F: FnMut(Inserter) + Send + 'static,
@@ -357,7 +343,7 @@ lazy_static! {
}
};
// Message sent when we want to execute a closure in the thread.
// WPARAM contains a Box<Box<FnMut()>> that must be retreived with `Box::from_raw`,
// WPARAM contains a Box<Box<FnMut()>> that must be retrieved with `Box::from_raw`,
// and LPARAM is unused.
static ref EXEC_MSG_ID: u32 = {
unsafe {
@@ -386,12 +372,12 @@ thread_local!(static CONTEXT_STASH: RefCell<Option<ThreadLocalData>> = RefCell::
struct ThreadLocalData {
sender: mpsc::Sender<Event>,
windows: HashMap<HWND, Arc<Mutex<WindowState>>>,
win32_block_loop: Arc<(Mutex<bool>, Condvar)>,
mouse_buttons_down: u32
file_drop_handlers: HashMap<HWND, FileDropHandler>, // Each window has its own drop handler.
mouse_buttons_down: u32,
}
// Utility function that dispatches an event on the current thread.
fn send_event(event: Event) {
pub fn send_event(event: Event) {
CONTEXT_STASH.with(|context_stash| {
let context_stash = context_stash.borrow();
@@ -439,6 +425,30 @@ pub unsafe extern "system" fn callback(
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)
@@ -457,7 +467,10 @@ pub unsafe extern "system" fn callback(
use events::WindowEvent::Destroyed;
CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
context_stash.as_mut().unwrap().windows.remove(&window);
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)),
@@ -514,24 +527,7 @@ pub unsafe extern "system" fn callback(
event: Resized(logical_size),
};
// If this window has been inserted into the window map, the resize event happened
// during the event loop. If it hasn't, the event happened on window creation and
// should be ignored.
if cstash.windows.get(&window).is_some() {
let (ref mutex, ref cvar) = *cstash.win32_block_loop;
let mut block_thread = mutex.lock().unwrap();
*block_thread = true;
// The event needs to be sent after the lock to ensure that `notify_all` is
// called after `wait`.
cstash.sender.send(event).ok();
while *block_thread {
block_thread = cvar.wait(block_thread).unwrap();
}
} else {
cstash.sender.send(event).ok();
}
cstash.sender.send(event).ok();
});
0
},
@@ -1026,24 +1022,7 @@ pub unsafe extern "system" fn callback(
},
winuser::WM_DROPFILES => {
use events::WindowEvent::DroppedFile;
let hdrop = wparam as shellapi::HDROP;
let mut pathbuf: [u16; MAX_PATH] = mem::uninitialized();
let num_drops = shellapi::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
for i in 0..num_drops {
let nch = shellapi::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(),
MAX_PATH as u32) as usize;
if nch > 0 {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into())
});
}
}
shellapi::DragFinish(hdrop);
// See `FileDropHandler` for implementation.
0
},

View File

@@ -22,11 +22,13 @@ pub enum IconType {
Big = winuser::ICON_BIG as isize,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct WinIcon {
pub handle: HICON,
}
unsafe impl Send for WinIcon {}
impl WinIcon {
#[allow(dead_code)]
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, util::WinError> {

View File

@@ -18,7 +18,7 @@ unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Cursor(pub *const winapi::ctypes::wchar_t);
unsafe impl Send for Cursor {}
unsafe impl Sync for Cursor {}
@@ -49,6 +49,7 @@ unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
mod dpi;
mod drop_handler;
mod event;
mod events_loop;
mod icon;

View File

@@ -1,5 +1,6 @@
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
use winapi::um::winnt::LONG;
use winapi::um::winuser;
use std::{mem, ptr};
@@ -49,7 +50,7 @@ unsafe extern "system" fn monitor_enum_proc(
TRUE // continue enumeration
}
fn get_available_monitors() -> VecDeque<MonitorId> {
pub fn get_available_monitors() -> VecDeque<MonitorId> {
let mut monitors: VecDeque<MonitorId> = VecDeque::new();
unsafe {
winuser::EnumDisplayMonitors(
@@ -62,7 +63,7 @@ fn get_available_monitors() -> VecDeque<MonitorId> {
monitors
}
fn get_primary_monitor() -> MonitorId {
pub fn get_primary_monitor() -> MonitorId {
const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe {
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
@@ -132,6 +133,14 @@ impl MonitorId {
}
}
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
let left = self.position.0 as LONG;
let right = left + self.dimensions.0 as LONG;
let top = self.position.1 as LONG;
let bottom = top + self.dimensions.1 as LONG;
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some(self.monitor_name.clone())

View File

@@ -2,8 +2,8 @@ use std::{self, mem, ptr, slice};
use std::ops::BitAnd;
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::DWORD;
use winapi::shared::windef::{HWND, RECT};
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winbase::{
FormatMessageW,
@@ -38,15 +38,23 @@ pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
wchar_to_string(wchar_slice)
}
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
let mut rect: RECT = unsafe { mem::uninitialized() };
if unsafe { winuser::GetWindowRect(hwnd, &mut rect) } != 0 {
Some(rect)
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
let mut data: T = mem::uninitialized();
if fun(&mut data) != 0 {
Some(data)
} else {
None
}
}
pub fn get_cursor_pos() -> Option<POINT> {
unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) }
}
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) }
}
// This won't be needed anymore if we just add a derive to winapi.
pub fn rect_eq(a: &RECT, b: &RECT) -> bool {
let left_eq = a.left == b.left;

View File

@@ -1,18 +1,19 @@
#![cfg(target_os = "windows")]
use std::cell::{Cell, RefCell};
use std::ffi::OsStr;
use std::{io, mem, ptr};
use std::cell::Cell;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::channel;
use winapi::ctypes::c_int;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPARAM, TRUE, UINT, WORD, WPARAM};
use winapi::shared::windef::{HDC, HWND, LPPOINT, POINT, RECT};
use winapi::shared::windef::{HWND, LPPOINT, POINT, RECT};
use winapi::um::{combaseapi, dwmapi, libloaderapi, winuser};
use winapi::um::objbase::{COINIT_MULTITHREADED};
use winapi::um::objbase::COINIT_MULTITHREADED;
use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList2};
use winapi::um::wingdi::{CreateRectRgn, DeleteObject};
use winapi::um::winnt::{LONG, LPCWSTR};
use {
@@ -26,36 +27,28 @@ use {
WindowAttributes,
};
use platform::platform::{Cursor, PlatformSpecificWindowBuilderAttributes, WindowId};
use platform::platform::dpi::{BASE_DPI, dpi_to_scale_factor, get_window_dpi, get_window_scale_factor};
use platform::platform::events_loop::{self, DESTROY_MSG_ID, EventsLoop, INITIAL_DPI_MSG_ID};
use platform::platform::dpi::{dpi_to_scale_factor, get_hwnd_dpi};
use platform::platform::events_loop::{self, EventsLoop, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID};
use platform::platform::events_loop::WindowState;
use platform::platform::icon::{self, IconType, WinIcon};
use platform::platform::monitor::get_available_monitors;
use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input;
use platform::platform::util;
const WS_RESIZABLE: DWORD = winuser::WS_SIZEBOX | winuser::WS_MAXIMIZEBOX;
/// The Win32 implementation of the main `Window` object.
pub struct Window {
/// Main handle for the window.
window: WindowWrapper,
decorations: Cell<bool>,
maximized: Cell<bool>,
resizable: Cell<bool>,
fullscreen: RefCell<Option<::MonitorId>>,
always_on_top: Cell<bool>,
/// The current window state.
window_state: Arc<Mutex<events_loop::WindowState>>,
window_icon: Cell<Option<WinIcon>>,
taskbar_icon: Cell<Option<WinIcon>>,
window_state: Arc<Mutex<WindowState>>,
// The events loop proxy.
events_loop_proxy: events_loop::EventsLoopProxy,
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903
// The idea here is that we use the Adjust­Window­Rect­Ex function to calculate how much additional
// non-client area gets added due to the styles we passed. To make the math simple,
@@ -280,32 +273,25 @@ impl Window {
#[inline]
pub fn set_resizable(&self, resizable: bool) {
if resizable == self.resizable.get() {
return;
}
if self.fullscreen.borrow().is_some() {
let mut window_state = self.window_state.lock().unwrap();
if mem::replace(&mut window_state.resizable, resizable) != resizable {
// If we're in fullscreen, update stored configuration but don't apply anything.
self.resizable.replace(resizable);
return;
}
if window_state.fullscreen.is_none() {
let mut style = unsafe {
winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE)
};
let mut style = unsafe {
winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE)
};
if resizable {
style |= winuser::WS_SIZEBOX as LONG;
} else {
style &= !winuser::WS_SIZEBOX as LONG;
}
if resizable {
style |= WS_RESIZABLE as LONG;
} else {
style &= !WS_RESIZABLE as LONG;
}
unsafe {
winuser::SetWindowLongW(
self.window.0,
winuser::GWL_STYLE,
style as _,
);
};
self.resizable.replace(resizable);
unsafe {
winuser::SetWindowLongW(self.window.0, winuser::GWL_STYLE, style as _);
};
}
}
}
/// Returns the `hwnd` of this window.
@@ -316,7 +302,7 @@ impl Window {
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
let cursor_id = match cursor {
let cursor_id = Cursor(match cursor {
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
MouseCursor::Hand => winuser::IDC_HAND,
MouseCursor::Crosshair => winuser::IDC_CROSS,
@@ -336,10 +322,15 @@ impl Window {
MouseCursor::Progress => winuser::IDC_APPSTARTING,
MouseCursor::Help => winuser::IDC_HELP,
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
};
let mut cur = self.window_state.lock().unwrap();
cur.cursor = Cursor(cursor_id);
});
self.window_state.lock().unwrap().cursor = cursor_id;
self.events_loop_proxy.execute_in_thread(move |_| unsafe {
let cursor = winuser::LoadCursorW(
ptr::null_mut(),
cursor_id.0,
);
winuser::SetCursor(cursor);
});
}
unsafe fn cursor_is_grabbed(&self) -> Result<bool, String> {
@@ -388,15 +379,12 @@ impl Window {
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let currently_grabbed = unsafe { self.cursor_is_grabbed() }?;
let window_state = Arc::clone(&self.window_state);
{
let window_state_lock = window_state.lock().unwrap();
if currently_grabbed == grab
&& grab == window_state_lock.cursor_grabbed {
return Ok(());
}
let window_state_lock = self.window_state.lock().unwrap();
if currently_grabbed == grab && grab == window_state_lock.cursor_grabbed {
return Ok(());
}
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
self.events_loop_proxy.execute_in_thread(move |_| {
let result = unsafe { Self::grab_cursor_inner(&window, grab) };
@@ -405,6 +393,7 @@ impl Window {
}
let _ = tx.send(result);
});
drop(window_state_lock);
rx.recv().unwrap()
}
@@ -418,24 +407,23 @@ impl Window {
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let window_state = Arc::clone(&self.window_state);
{
let window_state_lock = window_state.lock().unwrap();
// We don't want to increment/decrement the display count more than once!
if hide == window_state_lock.cursor_hidden { return; }
}
let window_state_lock = self.window_state.lock().unwrap();
// We don't want to increment/decrement the display count more than once!
if hide == window_state_lock.cursor_hidden { return; }
let (tx, rx) = channel();
let window_state = Arc::clone(&self.window_state);
self.events_loop_proxy.execute_in_thread(move |_| {
unsafe { Self::hide_cursor_inner(hide) };
window_state.lock().unwrap().cursor_hidden = hide;
let _ = tx.send(());
});
drop(window_state_lock);
rx.recv().unwrap()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
get_window_scale_factor(self.window.0, self.window.1)
self.window_state.lock().unwrap().dpi_factor
}
fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
@@ -465,32 +453,32 @@ impl Window {
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.maximized.replace(maximized);
// We only maximize if we're not in fullscreen.
if self.fullscreen.borrow().is_some() { return; }
let window = self.window.clone();
unsafe {
// `ShowWindow` resizes the window, so it must be called from the main thread.
self.events_loop_proxy.execute_in_thread(move |_| {
winuser::ShowWindow(
window.0,
if maximized {
winuser::SW_MAXIMIZE
} else {
winuser::SW_RESTORE
},
);
});
let mut window_state = self.window_state.lock().unwrap();
if mem::replace(&mut window_state.maximized, maximized) != maximized {
// We only maximize if we're not in fullscreen.
if window_state.fullscreen.is_none() {
let window = self.window.clone();
unsafe {
// `ShowWindow` resizes the window, so it must be called from the main thread.
self.events_loop_proxy.execute_in_thread(move |_| {
winuser::ShowWindow(
window.0,
if maximized {
winuser::SW_MAXIMIZE
} else {
winuser::SW_RESTORE
},
);
});
}
}
}
}
unsafe fn set_fullscreen_style(&self) -> (LONG, LONG) {
let mut window_state = self.window_state.lock().unwrap();
if self.fullscreen.borrow().is_none() || window_state.saved_window_info.is_none() {
unsafe fn set_fullscreen_style(&self, window_state: &mut WindowState) -> (LONG, LONG) {
if window_state.fullscreen.is_none() || window_state.saved_window_info.is_none() {
let rect = util::get_window_rect(self.window.0).expect("`GetWindowRect` failed");
let dpi_factor = Some(self.get_hidpi_factor());
let dpi_factor = Some(window_state.dpi_factor);
window_state.saved_window_info = Some(events_loop::SavedWindowInfo {
style: winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE),
ex_style: winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE),
@@ -504,16 +492,14 @@ impl Window {
let mut placement: winuser::WINDOWPLACEMENT = mem::zeroed();
placement.length = mem::size_of::<winuser::WINDOWPLACEMENT>() as u32;
winuser::GetWindowPlacement(self.window.0, &mut placement);
self.maximized.replace(placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32));
window_state.maximized = placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32);
let saved_window_info = window_state.saved_window_info.as_ref().unwrap();
(saved_window_info.style, saved_window_info.ex_style)
}
unsafe fn restore_saved_window(&self) {
unsafe fn restore_saved_window(&self, window_state_lock: &mut WindowState) {
let (rect, mut style, ex_style) = {
let mut window_state_lock = self.window_state.lock().unwrap();
// 'saved_window_info' can be None if the window has never been
// in fullscreen mode before this method gets called.
if window_state_lock.saved_window_info.is_none() {
@@ -534,8 +520,8 @@ impl Window {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let maximized = self.maximized.get();
let resizable = self.resizable.get();
let resizable = window_state_lock.resizable;
let maximized = window_state_lock.maximized;
// We're restoring the window to its size and position from before being fullscreened.
// `ShowWindow` resizes the window, so it must be called from the main thread.
@@ -543,9 +529,9 @@ impl Window {
let _ = Self::grab_cursor_inner(&window, false);
if resizable {
style |= winuser::WS_SIZEBOX as LONG;
style |= WS_RESIZABLE as LONG;
} else {
style &= !winuser::WS_SIZEBOX as LONG;
style &= !WS_RESIZABLE as LONG;
}
winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style);
winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style);
@@ -582,6 +568,7 @@ impl Window {
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
let mut window_state_lock = self.window_state.lock().unwrap();
unsafe {
match &monitor {
&Some(RootMonitorId { ref inner }) => {
@@ -590,8 +577,7 @@ impl Window {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (style, ex_style) = self.set_fullscreen_style();
let (style, ex_style) = self.set_fullscreen_style(&mut window_state_lock);
self.events_loop_proxy.execute_in_thread(move |_| {
let _ = Self::grab_cursor_inner(&window, false);
@@ -631,27 +617,23 @@ impl Window {
});
}
&None => {
self.restore_saved_window();
self.restore_saved_window(&mut window_state_lock);
}
}
}
self.fullscreen.replace(monitor);
window_state_lock.fullscreen = monitor;
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
if self.decorations.get() == decorations {
return;
}
let mut window_state = self.window_state.lock().unwrap();
if mem::replace(&mut window_state.decorations, decorations) != decorations {
let style_flags = (winuser::WS_CAPTION | winuser::WS_THICKFRAME) as LONG;
let ex_style_flags = (winuser::WS_EX_WINDOWEDGE) as LONG;
// if we are in fullscreen mode, we only change the saved window info
if self.fullscreen.borrow().is_some() {
{
let mut window_state = self.window_state.lock().unwrap();
// if we are in fullscreen mode, we only change the saved window info
if window_state.fullscreen.is_some() {
let saved = window_state.saved_window_info.as_mut().unwrap();
unsafe {
@@ -674,81 +656,73 @@ impl Window {
saved.ex_style as _,
);
}
}
self.decorations.replace(decorations);
return;
}
unsafe {
let mut rect: RECT = mem::zeroed();
winuser::GetWindowRect(self.window.0, &mut rect);
let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE);
let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE);
unjust_window_rect(&mut rect, style as _, ex_style as _);
if decorations {
style = style | style_flags;
ex_style = ex_style | ex_style_flags;
} else {
style = style & !style_flags;
ex_style = ex_style & !ex_style_flags;
unsafe {
let mut rect: RECT = mem::zeroed();
winuser::GetWindowRect(self.window.0, &mut rect);
let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE);
let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE);
unjust_window_rect(&mut rect, style as _, ex_style as _);
if decorations {
style = style | style_flags;
ex_style = ex_style | ex_style_flags;
} else {
style = style & !style_flags;
ex_style = ex_style & !ex_style_flags;
}
let window = self.window.clone();
self.events_loop_proxy.execute_in_thread(move |_| {
winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style);
winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style);
winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _);
winuser::SetWindowPos(
window.0,
ptr::null_mut(),
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOACTIVATE
| winuser::SWP_FRAMECHANGED,
);
});
}
}
let window = self.window.clone();
self.events_loop_proxy.execute_in_thread(move |_| {
winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style);
winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style);
winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _);
winuser::SetWindowPos(
window.0,
ptr::null_mut(),
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOACTIVATE
| winuser::SWP_FRAMECHANGED,
);
});
}
self.decorations.replace(decorations);
}
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
if self.always_on_top.get() == always_on_top {
return;
let mut window_state = self.window_state.lock().unwrap();
if mem::replace(&mut window_state.always_on_top, always_on_top) != always_on_top {
let window = self.window.clone();
self.events_loop_proxy.execute_in_thread(move |_| {
let insert_after = if always_on_top {
winuser::HWND_TOPMOST
} else {
winuser::HWND_NOTOPMOST
};
unsafe {
winuser::SetWindowPos(
window.0,
insert_after,
0,
0,
0,
0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
winuser::UpdateWindow(window.0);
}
});
}
let window = self.window.clone();
self.events_loop_proxy.execute_in_thread(move |_| {
let insert_after = if always_on_top {
winuser::HWND_TOPMOST
} else {
winuser::HWND_NOTOPMOST
};
unsafe {
winuser::SetWindowPos(
window.0,
insert_after,
0,
0,
0,
0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
winuser::UpdateWindow(window.0);
}
});
self.always_on_top.replace(always_on_top);
}
#[inline]
@@ -768,7 +742,7 @@ impl Window {
} else {
icon::unset_for_window(self.window.0, IconType::Small);
}
self.window_icon.replace(window_icon);
self.window_state.lock().unwrap().window_icon = window_icon;
}
#[inline]
@@ -781,7 +755,7 @@ impl Window {
} else {
icon::unset_for_window(self.window.0, IconType::Big);
}
self.taskbar_icon.replace(taskbar_icon);
self.window_state.lock().unwrap().taskbar_icon = taskbar_icon;
}
#[inline]
@@ -804,17 +778,27 @@ impl Drop for Window {
/// A simple non-owning wrapper around a window.
#[doc(hidden)]
#[derive(Clone)]
pub struct WindowWrapper(HWND, HDC);
pub struct WindowWrapper(HWND);
// Send is not implemented for HWND and HDC, we have to wrap it and implement it manually.
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
// For more info see:
// https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396
unsafe impl Sync for WindowWrapper {}
unsafe impl Send for WindowWrapper {}
pub unsafe fn adjust_size(physical_size: PhysicalSize, style: DWORD, ex_style: DWORD) -> (LONG, LONG) {
pub unsafe fn adjust_size(
physical_size: PhysicalSize,
style: DWORD,
ex_style: DWORD,
) -> (LONG, LONG) {
let (width, height): (u32, u32) = physical_size.into();
let mut rect = RECT { left: 0, right: width as LONG, top: 0, bottom: height as LONG };
let mut rect = RECT {
left: 0,
right: width as LONG,
top: 0,
bottom: height as LONG,
};
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
(rect.right - rect.left, rect.bottom - rect.top)
}
@@ -858,9 +842,38 @@ unsafe fn init(
// registering the window class
let class_name = register_window_class(&window_icon, &taskbar_icon);
let (width, height) = attributes.dimensions
.map(Into::into)
.unwrap_or((1024, 768));
let guessed_dpi_factor = {
let monitors = get_available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
for monitor in &monitors {
if Some(monitor.get_hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
dpi_factor
} else {
return Err(CreationError::OsError(format!("No monitors were detected.")));
};
dpi_factor.unwrap_or_else(|| {
util::get_cursor_pos()
.and_then(|cursor_pos| {
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.contains_point(&cursor_pos) {
dpi_factor = Some(monitor.get_hidpi_factor());
break;
}
}
dpi_factor
})
.unwrap_or(1.0)
})
};
info!("Guessed window DPI factor: {}", guessed_dpi_factor);
let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into());
let (width, height): (u32, u32) = dimensions.to_physical(guessed_dpi_factor).into();
// building a RECT object with coordinates
let mut rect = RECT {
left: 0,
@@ -891,6 +904,9 @@ unsafe fn init(
if pl_attribs.no_redirection_bitmap {
ex_style |= winuser::WS_EX_NOREDIRECTIONBITMAP;
}
if attributes.transparent && attributes.decorations {
ex_style |= winuser::WS_EX_LAYERED;
}
// adjusting the window coordinates using the style
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
@@ -899,11 +915,11 @@ unsafe fn init(
let real_window = {
let (adjusted_width, adjusted_height) = if attributes.dimensions.is_some() {
let min_dimensions = attributes.min_dimensions
.map(|logical_size| PhysicalSize::from_logical(logical_size, 1.0))
.map(|logical_size| PhysicalSize::from_logical(logical_size, guessed_dpi_factor))
.map(|physical_size| adjust_size(physical_size, style, ex_style))
.unwrap_or((0, 0));
let max_dimensions = attributes.max_dimensions
.map(|logical_size| PhysicalSize::from_logical(logical_size, 1.0))
.map(|logical_size| PhysicalSize::from_logical(logical_size, guessed_dpi_factor))
.map(|physical_size| adjust_size(physical_size, style, ex_style))
.unwrap_or((c_int::max_value(), c_int::max_value()));
(
@@ -921,7 +937,7 @@ unsafe fn init(
};
if !attributes.resizable {
style &= !winuser::WS_SIZEBOX;
style &= !WS_RESIZABLE;
}
if pl_attribs.parent.is_some() {
@@ -946,13 +962,7 @@ unsafe fn init(
format!("{}", io::Error::last_os_error()))));
}
let hdc = winuser::GetDC(handle);
if hdc.is_null() {
return Err(CreationError::OsError(format!("GetDC function failed: {}",
format!("{}", io::Error::last_os_error()))));
}
WindowWrapper(handle, hdc)
WindowWrapper(handle)
};
// Set up raw input
@@ -966,9 +976,10 @@ unsafe fn init(
}
}
let dpi = get_window_dpi(real_window.0, real_window.1);
let dpi = get_hwnd_dpi(real_window.0);
let dpi_factor = dpi_to_scale_factor(dpi);
if dpi != BASE_DPI {
if dpi_factor != guessed_dpi_factor {
let (width, height): (u32, u32) = dimensions.into();
let mut packed_dimensions = 0;
// MAKELPARAM isn't provided by winapi yet.
let ptr = &mut packed_dimensions as *mut LPARAM as *mut WORD;
@@ -996,6 +1007,13 @@ unsafe fn init(
mouse_in_window: false,
saved_window_info: None,
dpi_factor,
fullscreen: attributes.fullscreen.clone(),
window_icon,
taskbar_icon,
decorations: attributes.decorations,
maximized: attributes.maximized,
resizable: attributes.resizable,
always_on_top: attributes.always_on_top,
};
// Creating a mutex to track the current window state
Arc::new(Mutex::new(window_state))
@@ -1003,26 +1021,34 @@ unsafe fn init(
// making the window transparent
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
let region = CreateRectRgn(0, 0, -1, -1); // makes the window transparent
let bb = dwmapi::DWM_BLURBEHIND {
dwFlags: 0x1, // FIXME: DWM_BB_ENABLE;
dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION,
fEnable: 1,
hRgnBlur: ptr::null_mut(),
hRgnBlur: region,
fTransitionOnMaximized: 0,
};
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
DeleteObject(region as _);
if attributes.decorations {
// HACK: When opaque (opacity 255), there is a trail whenever
// the transparent window is moved. By reducing it to 254,
// the window is rendered properly.
let opacity = 254;
// The color key can be any value except for black (0x0).
let color_key = 0x0030c100;
winuser::SetLayeredWindowAttributes(real_window.0, color_key, opacity, winuser::LWA_ALPHA);
}
}
let win = Window {
window: real_window,
window_state: window_state,
decorations: Cell::new(attributes.decorations),
maximized: Cell::new(attributes.maximized.clone()),
resizable: Cell::new(attributes.resizable.clone()),
fullscreen: RefCell::new(attributes.fullscreen.clone()),
always_on_top: Cell::new(attributes.always_on_top),
window_icon: Cell::new(window_icon),
taskbar_icon: Cell::new(taskbar_icon),
window_state,
events_loop_proxy,
};
@@ -1079,7 +1105,6 @@ unsafe fn register_window_class(
class_name
}
struct ComInitialized(*mut ());
impl Drop for ComInitialized {
fn drop(&mut self) {

View File

@@ -304,6 +304,10 @@ impl Window {
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
/// the most robust way to track the DPI you need to use to draw.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.

39
tests/serde_objects.rs Normal file
View File

@@ -0,0 +1,39 @@
#![cfg(feature = "serde")]
extern crate serde;
extern crate winit;
use winit::{ControlFlow, MouseCursor};
use winit::{
KeyboardInput, TouchPhase, ElementState, MouseButton, MouseScrollDelta, VirtualKeyCode,
ModifiersState
};
use winit::dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize};
use serde::{Serialize, Deserialize};
fn needs_serde<S: Serialize + Deserialize<'static>>() {}
#[test]
fn root_serde() {
needs_serde::<ControlFlow>();
needs_serde::<MouseCursor>();
}
#[test]
fn events_serde() {
needs_serde::<KeyboardInput>();
needs_serde::<TouchPhase>();
needs_serde::<ElementState>();
needs_serde::<MouseButton>();
needs_serde::<MouseScrollDelta>();
needs_serde::<VirtualKeyCode>();
needs_serde::<ModifiersState>();
}
#[test]
fn dpi_serde() {
needs_serde::<LogicalPosition>();
needs_serde::<PhysicalPosition>();
needs_serde::<LogicalSize>();
needs_serde::<PhysicalSize>();
}