Compare commits

..

131 Commits

Author SHA1 Message Date
Aaro Perämaa
a815a12627 fix broken / missing raw-window-handle implementation on android (#1258) 2019-11-04 11:53:57 -05:00
Osspial
2455ab8a03 Bump version in README.md 2019-10-16 22:39:15 -04:00
Osspial
b33fbc0806 Add raw-window-handle support to Winit 0.19 and bump version (#1225)
* Add raw-window-handle support to Winit 0.19 and bump version

* Hopefully fix linux and ios builds
2019-10-16 22:37:53 -04:00
Alex Touchet
341fe47c56 Update parking_lot and bump version (#1109) 2019-08-26 18:02:43 -04:00
Osspial
8119a7df13 Bump version to 0.19.2 2019-07-29 16:17:34 -04:00
Simon Sapin
5c02c20b05 Update the percent-encoding crate to 2.0 (#1076) 2019-07-29 16:14:26 -04:00
Osspial
744913d482 Update README.md
For the last time on this branch, it seems. Godspeed.
2019-06-13 01:43:13 -04:00
aloucks
7dc48ed9ef Direct new contributors to the eventloop-2.0 branch (#903) 2019-06-10 01:06:55 -06:00
Osspial
30c0058a88 Update CONTRIBUTING.md (#897) 2019-06-03 16:38:15 -04:00
Bastien Orivel
8794157370 Update parking_lot to 0.8 (#881) 2019-05-25 10:05:47 -06:00
Lucas Kent
1a92c46ad7 Fix warnings on linux (#879) 2019-05-24 05:10:49 -06:00
Alex Touchet
22288ec4c1 Update URLs (#863) 2019-05-11 07:48:33 -06:00
Felix Rabe
ffa6815321 README: Link to FEATURES.md and missing features wiki page (#860)
Closes #854
2019-05-09 02:45:28 -04:00
Felix Rabe
8ddc7fdaf6 README: Use shields.io instead of Herokuapp (#859) 2019-05-09 02:44:55 -04:00
Jakub Piecuch
594cd18567 Fix monitor width & height sanity check (#861)
Fix monitor width & height sanity check
2019-05-05 14:54:57 -06:00
acheronfail
4469f29e70 Feat/fullscreen getters (#838)
* feat: [macos] add get_fullscreen and get_simple_fullscreen

* feat: [windows] add get_fullscreen

* feat: [ios] add get_fullscreen

* feat: [android] add get_fullscreen

* feat: [emscripten] add get_fullscreen

* feat: [linux] add get_fullscreen

* feedback: `get_fullscreen() -> bool` -> `get_fullscreen() -> Option<Id>`
2019-04-25 13:09:32 -04:00
Osspial
4e5321b0aa Fix CI links in README.md (#852) 2019-04-25 01:20:54 -04:00
Felix Rabe
4b2e9da4e4 Popup windows are also known as modal windows (#848) 2019-04-24 22:56:40 -04:00
Osspial
b94572621a Makes changes to CONTRIBUTING.md's table as discussed in #830 (#841)
This includes de-listing @francesca64 from the table, as I suggested. I
realize that this is a controversial decision, and it's not a decision I
make lightly, but I believe I have justification for doing so:

I contacted @francesca64 last month asking her about her inactivity as a
maintainer on this project. She replied on March 26th as follows. For the
sake of her privacy, I've removed removed certain sections of her response,
as it contains some personal details that I'm not comfortable sharing with
the world at large without her explicit permission.

> Hello! Thanks for reaching out!❤

> In short, I've moved on. <removed> ...in November, I was hired by a
> company that recently started using Rust. I'm very happily building
> infrastructure there!

> I'm simply not interested in spending more than 8 hours a day
> programming. You couldn't even pay me to do it! My time is best spent
> going on adventures with my beloved.

> I'll still be active in the Rust ecosystem, but only insofar as the
> company I work for is. We use winit on iOS, Android, and (to a limited
> extent) macOS, so I'll work on those backends as needed.

> Thank you for taking care of winit. I hope you're taking care of
> yourself too; <removed>.

I don't begrudge her for her decision, and others shouldn't either - I
firmly believe that, as this is unpaid, volunteer work, everybody should
have the right to move on when they decide they no longer have the time
or will to contribute.

The exact impliciation of this in regards to her status as maintainer
is open to interpretation. I would argue that this means, should she in
her work stumble upon an issue in any of her listed backends, she would be
willing to submit PRs addressing those issues. However, it also means
that she is not able to put in the time to be active as a maintainer on
those platforms, or review PRs and issues for those platforms.

On March 28th I responded to her email as follows:

> Hey, thanks for responding.

> <removed, personal details>

> In the meanwhile, there are still a few loose ends from your time as
> maintainer that I'd like to get cleaned up. It's okay if you aren't
> going to be spending much time on Winit, but there's still a broad
> assumption that you're able to review PRs for them and that doesn't seem
> to be the case. Would you be able to do a couple things to ease the
> transition to whoever next takes over the macOS, X11, and Android
> backends?

> 1) Submit a PR downgrading yourself from maintainer for macOS, X11, and
>    Android, so somebody else can more actively take them over.
> 2) Post your WIP macOS backend for EL2.0 as well as the issues it
>    currently has, so whoever next maintains macOS can finish it.

> Going forward, I'd like to reach out to the broader Rust community and
> find more active maintainers so Winit can get to 1.0 and I can mostly
> move on from it.

I recieved no response to that email. On April 4th, I followed up on
that email:

> If you aren't able to act as a maintainer for Winit, and aren't able to
> submit a PR updating your official status as maintainer to reflect
> reality, would you mind if I submitted a PR removing you as maintainer?
> That's not something I want to do since it's a bad image for me, a bad
> image for Winit, and sets an extremely uncomfortable precedent, but I'd
> like to start more aggressive outreach to ensure each backend is less
> dependent on one specific person and I don't want to see new
> contributors pinging you for help when you're unable to provide it.

> If you don't reply by the 11th that's the path I'm going to take, but I
> consider it the nuclear option and I want to avoid invoking it if at all
> possible.

Up to this date (April 13th), I have recieved no response. Given the
amount of time I've given her to respond, as well as her lack of
response, I believe we have the justification to remove her from the
table. Should she show back up again, any clarifications on her status
would be welcome, and she is welcome to submit a PR re-listing herself
on the table with a more accurate description of her current contributor
status. However, once we begin the contributor marketing push discussed
in #830, I don't want new contributors to attempt to ask her questions
on the macOS, X11, or Android backends when she isn't able to give a
response.

-----------------------------------------------------------------------

This PR also introduces HALL_OF_CHAMPIONS.md, which commends the efforts
of former maintainers that have contributed greatly to the Winit project.
This wasn't discussed previously, but I think it's important to recognize
the people that brought us to where we are today. It currently lists
@tomaka and @francesca64, as they are the two individuals I'm aware of
that both deserve such recognition and no longer actively contribute to
Winit, but if there's anybody I missed feel free to suggest them and a
blurb describing their work.
2019-04-24 12:44:21 +02:00
Osspial
7203ec45b5 Fix TODO in CONTRIBUTING.md 2019-04-14 12:05:37 -04:00
Osspial
fc835f383b Fix link in PULL_REQUEST_TEMPLATE.md 2019-04-13 20:42:22 -04:00
Osspial
2f9607694f Winit Features and Scope (#695)
* Add initial draft of SCOPE document

* Rephrase/rename feature tiers

* Rename to FEATURES and add a few annotations

* Fix API Reworks table

* Add more annotations

* Some phrasing

* Split compat matrix into seperate section, to be moved into wiki

* Mention compatibility in CONTRIBUTING

* Remove some discuss annotations

* Apply review changes and rename child windows feature to popup windows

* Update based on discussion

* Add issue for Android HiDPI

* Update FEATURES.md

* Update FEATURES.md

* Update PULL_REQUEST_TEMPLATE.md

* Update PULL_REQUEST_TEMPLATE.md

* Reformat FEATURES.MD

* Remove comments

* Improve formatting and add guide for extending #Features
2019-04-13 18:57:08 -04:00
Osspial
cd5caf6a22 Update for 0.19.1 (#823) 2019-04-08 01:08:31 -04:00
Hal Gentz
8522071c2c Add ability to get wayland display from events loop. (#829)
Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-04-08 01:07:47 -04:00
Osspial
dfa972eab1 Fix window icon (#831)
* Fix window icon

* Add CHANGELOG entry
2019-04-08 01:07:12 -04:00
mitchmindtree
69585fe2f2 [Rebased] [x11-backend] Retrieve DPI from Xft.dpi XResource (#824)
* [x11-backend] Retrieve DPI from Xft.dpi XResource

* Update CHANGELOG.md

* Update window.rs

* Update CHANGELOG.md
2019-04-07 12:48:21 -04:00
Christian Duerr
c0b2cad3f9 Add additional numpad key mappings (#805)
* Add additional numpad key mappings

Since some platforms have already used the existing `Add`, `Subtract`
and `Divide` codes to map numpad keys, the X11 and Wayland platform has
been updated to achieve parity between platforms. On macOS only the
`Subtract` numpad key had to be added.

Since the numpad key is different from the normal keys, an alternative
option would be to add new `NumpadAdd`, `NumpadSubtract` and
`NumpadDivide` actions, however I think in this case it should be fine
to map them to the same virtual key code.

* Add Numpad PageUp/Down, Home and End on Wayland
2019-04-07 01:25:37 -04:00
TakWolf
57680d2d17 fix command key event left and right reverse on macOS (#810)
* fix command key event left and right reverse on macOS

https://github.com/tomaka/winit/issues/808

* update changelog
2019-03-25 14:05:07 -04:00
Tobias Kortkamp
0019ff210c Fix build on FreeBSD (#815)
* Fix build on FreeBSD

error[E0432]: unresolved import `libc::__errno_location`
  --> src/platform/linux/x11/mod.rs:22:85
   |
22 | use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF, __errno_location};
   |                                                                                     ^^^^^^^^^^^^^^^^ no `__errno_location` in the root

__errno_location is called __error on FreeBSD and __errno on Open- and NetBSD.

Signed-off-by: Tobias Kortkamp <t@tobik.me>

* Import __error / __errno on *BSD as __errno_location

Signed-off-by: Tobias Kortkamp <t@tobik.me>

* Add changelog entry

Signed-off-by: Tobias Kortkamp <t@tobik.me>
2019-03-22 10:44:00 -04:00
Hal Gentz
4a103387e5 Add contact info. (#818)
Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-03-19 22:20:03 -04:00
Osspial
b6ca584ecf On Windows, fix CursorMoved(0, 0) getting sent on focus (#819)
* On Windows, fix CursorMoved(0, 0) getting sent on focus

* Add changelog entry
2019-03-19 22:19:41 -04:00
Osspial
e0340d52b0 Update winit to 0.19.0 (#798)
* Update winit to 0.19.0

* Update date for 0.19
2019-03-06 21:50:13 -05:00
Hal Gentz
f928a4b917 Use XRRGetScreenResourcesCurrent when avail. (#801)
* Use `XRRGetScreenResourcesCurrent` when avail.

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

* Changelog

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2019-03-05 20:58:14 -05:00
Osspial
c5d22fda2b Ignore the AltGr key when populating ModifersState (#763)
* When building ModifiersState, ignore AltGr on Windows

* Add CHANGELOG entry

* Also filter out Control when pressing AltGr
2019-03-05 17:55:01 -05:00
Riku Salminen
9ea2810b46 x11: thread safe replacement for XNextEvent (#782)
XNextEvent will block for input while holding the global Xlib mutex.

This will cause a deadlock in even the most trivial multi-threaded
application because OpenGL functions will need to hold the Xlib mutex
too.

Add EventsLoop::poll_one_event and EventsLoop::wait_for_input to provide
thread-safe functions to poll and wait events from the X11 event queue
using unix select(2) and XCheckIfEvent.

This is a somewhat ugly workaround to an ugly problem.

Fixes #779
2019-02-24 18:02:55 -05:00
Michael Palmos
9a23ec3c37 Fix incorrect keycodes when using a non-US keyboard layout. (#755)
* Fix incorrect keycodes when using a non-US keyboard layout.

This commit fixes the issue described in #752, and uses the advised
method to fix it.

* Style fixes

Co-Authored-By: Toqozz <toqoz@hotmail.com>

* Refactoring of macOS `virtualkeycode` fix (#752)

* Applies requested changes as per pull request discussion (#755).
2019-02-23 15:41:55 -05:00
Torkel Danielsson
84c812e568 Handle horizontal wheel input (Windows) (#792)
* add handler for horizontal wheel input

* add changlelog message re now handling horiz scroll on windows
2019-02-22 09:31:16 -05:00
trimental
f0ce5b0c8d On wayland, fix with_title() not setting the windows title (#770) 2019-02-22 09:30:59 -05:00
Osspial
7be1d16263 Refactor win32 window state code (#730)
* Overhaul win32 window state

* Fix warnings

* Add CHANGELOG entry

* Rephrase CHANGELOG entries

* Fix 1.28.0 build

* Remove WS_POPUP styling

* Slight style correction

* Make set_maximized work

* Fix rect restore not working after winit set_maximized call

* Add a few comments
2019-02-04 11:52:00 -05:00
trimental
c91dfdd6fe Wayland: add set_wayland_theme() to control client decoration color… (#775)
* Wayland: add `set_wayland_theme()` to control client decoration color theme

* Change &mut self to &self

* Remove endianness comment
2019-01-29 11:04:15 +01:00
Anthony Ramine
8e733543cd Update image to 0.21 (#758) 2019-01-16 14:36:46 -05:00
Sascha Grunert
26e37590e8 Allow serialization for WindowType (#762)
* Allow serialization for WindowType

* Update CHANGELOG.md

* Update CHANGELOG.md

Co-Authored-By: saschagrunert <sgrunert@suse.com>
2019-01-15 09:30:02 -08:00
Osspial
ddf133dd66 Version 0.18.1 (#749)
* Version 0.18.1

* Change to 0.18.1
2019-01-09 03:16:41 -11:00
Osspial
4584e7629a Remove MSRV guarantee (#746)
* Remove MSRV guarantee

* Update CHANGELOG
2018-12-29 15:02:02 -11:00
Francesca Plebani
139686ddce macOS: Improve set_cursor (#740)
* Improve set_cursor on macOS

* Check for nil
2018-12-28 15:29:29 -05:00
Francesca Plebani
5a0b4dba47 macOS: Implement Refresh (#742)
* macOS: Implement Refresh

* drawRect should take NSRect
2018-12-27 15:16:58 -05:00
Francesca Plebani
33c8aa660f macOS: Correct prepareForDragOperation: signature (#741) 2018-12-27 14:22:00 -05:00
Francesca Plebani
cb76dcae2a Change francesca64 to maintainer for macOS and Android (#744) 2018-12-24 13:08:33 -05:00
Bastien Orivel
d622de4797 Update parking_lot to 0.7 (#747) 2018-12-23 06:12:11 -11:00
Jacob Kiesel
9ae75c0c03 Add support for generating dummy DeviceIDs and WindowIDs (#738)
* Add support for generating dummy DeviceIDs and WindowIDs

* Fix linux

* Improve docs and move dummy to unsafe

* Strengthen guarantees a bit

* Add backticks to CHANGELOG.md

Co-Authored-By: Xaeroxe <xaeroxe@amethyst-engine.org>
2018-12-21 05:51:48 -11:00
Alisue
45a4281413 Support Yen in macOS (#739)
* Support Yen in macOS

* Add entry to CHANGELOG
2018-12-19 13:32:14 -05:00
acheronfail
bfbcab3a01 feat: add macos simple fullscreen (#692)
* feat: add macos simple fullscreen

* move impl to WindowExt

* feedback: remove warning, unused file and rename param

* feedback: combine fullscreen examples into one example

* fix: ensure decorations and maximize do not toggle while in fullscreen

* fix: prevent warning on non-macos platforms

* feedback: make changelog more explicit

* fix: prevent unconditional construction of NSRect

* fix: don't try to set_simple_fullscreen if already using native fullscreen

* fix: ensure set_simple_fullscreen plays nicely with set_fullscreen

* fix: do not enter native fullscreen if simple fullscreen is active
2018-12-18 23:07:33 -05:00
Jasper Mattsson
4b4c73cee4 Fix high CPU usage on tiling WMs when moving windows across monitors (#737)
This commit restricts an Xfwm4-specific DPI-preserving hack to Xfwm4
only. The hack saves and restores the DPI-adjusted size until the actual
size matches. On tiling WMs like i3 this fails, since the size is
constrained by the layout. This in turn causes a never-ending
XResizeWindow vs. XConfigureWindow fight between the WM and the client,
making the WM, winit client, and Xorg consume all CPU cycles available.
2018-12-18 22:20:31 -05:00
David Craven
fd349f1822 Use smithay-client-toolkit's dpi handling. (#724)
* Use smithay-client-toolkit's dpi handling.

* Add CHANGELOG entry.
2018-12-10 14:55:40 -05:00
Francesca Plebani
cb0a085968 X11: WindowBuilder min/max size accounts for DPI (#729) 2018-11-30 16:19:50 -05:00
Osspial
aabf0e13b7 On Windows, fix window shrinking when leaving fullscreen in some situations (#718)
* Fix resize border appearing in some cases after leaving fullscreen.

* On fullscreen, save client rect instead of window rect

* Add CHANGELOG entry

* Revert test changes to fullscreen example

* Update panic message when unable to get client area
2018-11-20 15:57:06 -05:00
Andreas Johansson
92873b06ed Handle removed wl_outputs (#719)
* Move the event managent to the closure

In preparation of more events not relating to the SeatManager being
captured.

* Handle wl_output remove events

In some cases, wl_outputs can be removed without the compositor
notifying the surfaces using leave/enter events. This breaks the DPI and
resize stuff since the windows' list of monitors were not updated.

Now, wl_output removals are handled and windows are updated accordingly.

* Add changelog entry for disappearing wl_outputs

* Clearer changelog message for wl_output removal changes
2018-11-20 15:21:58 -05:00
Artúr Kovács
04ca2cf9f4 Fix panic when dragging text onto a window on Windws (#697) (#711)
* Fix panic when dragging text onto a window on Windws (#697)

* Changed `panic` to `debug` (log) when unknow error occurs in `GetData` while processing a drag-drop / hover event. Plus added appropriate cursor effect if hovered item can not be processed.

* Improved code clarity.

* Add documentation to clarify behaviour of `DroppedFile`, `HoveredFile`, and `HoveredFileCancelled`

* Add period at the end of sentences in documentation.
2018-11-20 03:28:26 -05:00
Oskar Gustafsson
b049a4dc66 Add ordering traits to VirtualKeyCode (#713)
Motivation:
This allows VirtualKeyCode variants to be stored in a BTreeSet.
Unlike HashSets, BTreeSets implement Ord and Hash, allowing them to be
keys in a {Hash|BTree}Maps. This is nice, e.g. when implementing
keyboard shortcuts functionality, which maps a set of pressed keys to
some action.
2018-11-19 16:59:04 -05:00
Francesca Plebani
5be52c9753 Increase MSRV to 1.28.0 (#716) 2018-11-19 16:56:11 -05:00
Francesca Plebani
3c59283b3f X11: Check if XRRGetOutputInfo returned NULL (#709)
* X11: Check if XRRGetOutputInfo returned NULL

Fixes #693

* Change X11 error logging to actually use log
2018-11-17 15:51:39 -05:00
Osspial
3ba808e3c6 On Windows, catch window callback panics and forward them to the calling thread (#703)
* Catch windows callback panics

* Unwind through calling thread

* Add CHANGELOG entry

* Fix 1.24.1 builds

* Reformat CHANGELOG entry

* Make changes from review

* Wrap thread event target in panic catcher, reformat panic resume message

* Fix me being bad at git
2018-11-17 14:20:04 -05:00
Osspial
df5d66b5e8 Replace thread messages with messages to dummy window (#710)
* Replace thread messages with messages to dummy window

* Add CHANGELOG entry

* Style changes

* Make review changes
2018-11-16 22:17:32 -05:00
Victor Berger
7fe90e6c80 Introduce WindowBuilderExt::with_app_id for wayland (#700) 2018-11-15 16:59:56 -05:00
mtak-
8dcd514393 add mtak- to CONTRIBUTING.md as iOS maintainer, change francesca64 to a reviewer (#702) 2018-11-12 21:49:15 -05:00
Victor Berger
2c3e420f82 travis: freeze dependencies that silently broke 1.24.1 compat (#701) 2018-11-12 20:37:51 -05:00
Francesca Plebani
917db35a84 X11: Fix panic when dropping window before running event loop (#694)
Fixes #691

Dropping a window before running the `EventsLoop` results in events
still being queued when `XDestroyWindow` is called, so events like
`XI_Enter` (the culprit in this case) will still be processed.
Simply checking that the window still exists before calling
`query_pointer` was enough to solve the problem.
2018-11-10 13:54:50 -05:00
Victor Berger
dd52364d33 [META] Add a CONTRIBUTING.md (#674) 2018-11-10 11:56:40 +01:00
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
Francesca Frangipane
089816d9ba Release winit 0.16.0 (#578) 2018-06-25 16:47:10 -04:00
Francesca Frangipane
c873c2db15 Wayland: Fix window creation dimensions (#577)
* Wayland: Fix window creation dimensions

* Wayland: Fix window creation min/max
2018-06-24 08:28:57 -04:00
えちょ
047c67baf3 windows feature WS_EX_NOREDIRECTIONBITMAP (#575)
* set WS_EX_NOREDIRECTIONBITMAP

* add CHANGELOG.md

* more flexibility.

* Skip DwmEnableBlurBehindWindow if no_redirection_bitmap is enabled.
2018-06-21 21:33:29 -04:00
aloucks
8f394f117b Change set_cursor_position to return Result<(), String> (#562)
* Change set_cursor_position to return Result<(), String>

This is now consistent with `grab_cursor`, and
enables `window.set_cursor_position(x, y)?` in functions
that return `Result<_, Box<Error>>`.

* Adjust error handling of unimplemented cusor opertions in wayland

* The final nitpick

* Actually one more
2018-06-19 10:30:15 -04:00
Francesca Frangipane
fb7528c239 grab_cursor and hide_cursor (#571)
* Windows: Use new cursor state API

* X11: Use new cursor state API

* macOS: Use new cursor state API

* Android+iOS: Stubbed new cursor state API

* Emscripten: Use new cursor state API

* Prevent multiple inc/dec of display count on Windows

* Fixed missing imports (no idea where those went)

* Remove NoneCursor

* Improved documentation

* Fix Emscripten build

* Windows: Re-grab before and after fullscreen
2018-06-18 12:32:18 -04:00
Hal Gentz
042f5fe4b3 Shares the XConnection between all event loops instead of just all event (#572)
loops on the same thread.

This is needed for adding shared context support to glutin, as contexts
must be made with the same native display (and therefore the same
connection.)

Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
2018-06-17 20:44:38 -04:00
Francesca Frangipane
289fb47a34 macOS: Fix doubled key repeats (#570) 2018-06-17 15:08:26 -04:00
Nikolai Vazquez
38bc6babb7 Change Travis badge to SVG (#573)
PNG on GitHub does not render well on HiDPI screens.
2018-06-17 15:08:01 -04:00
Francesca Frangipane
e7a8efcfa0 Mirror monitor list methods on Window (#567)
* macOS: Monitor list methods on Window

* X11+Wayland: Monitor list methods on Window

* Windows: Monitor list methods on Window

* iOS: Monitor list methods on Window

* Android: Monitor list methods on Window

* Emscripten: Monitor list methods on Window

* Fixed Wayland implementation
2018-06-16 10:14:12 -04:00
Francesca Frangipane
1b74822cfc DPI for everyone (#548) 2018-06-14 19:42:18 -04:00
Lucas Kent
f083dae328 Windows creates Alt event instead of Menu event. (to match other platforms) (#551)
* Removed VirtualKeyCode::LMenu + VirtualKeyCode::RMenu, Windows now generates VirtualKeyCode::LAlt + VirtualKeyCode::RAlt instead.

* CHANGELOG nits
2018-06-13 13:24:33 -04:00
74 changed files with 7091 additions and 3233 deletions

View File

@@ -2,3 +2,4 @@
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

1
.gitignore vendored
View File

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

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "deps/apk-builder"]
path = deps/apk-builder
url = https://github.com/tomaka/android-rs-glue
url = https://github.com/rust-windowing/android-rs-glue

View File

@@ -19,12 +19,6 @@ matrix:
addons:
apt:
packages: *i686_packages
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: 1.24.1
addons:
apt:
packages: *i686_packages
# Linux 64bit
- env: TARGET=x86_64-unknown-linux-gnu
@@ -33,9 +27,6 @@ matrix:
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: stable
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: 1.24.1
# macOS
- env: TARGET=x86_64-apple-darwin
@@ -44,9 +35,6 @@ matrix:
- env: TARGET=x86_64-apple-darwin
os: osx
rust: stable
- env: TARGET=x86_64-apple-darwin
os: osx
rust: 1.24.1
# iOS
- env: TARGET=x86_64-apple-ios
@@ -55,9 +43,6 @@ matrix:
- env: TARGET=x86_64-apple-ios
os: osx
rust: stable
- env: TARGET=x86_64-apple-ios
os: osx
rust: 1.24.1
install:
- rustup self update
@@ -65,8 +50,12 @@ install:
script:
- cargo build --target $TARGET --verbose
- cargo build --target $TARGET --features serde --verbose
- cargo build --target $TARGET --features icon_loading --verbose
# Running iOS apps on OSX requires the simulator so we skip that for now
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features icon_loading --verbose; fi
after_success:
- |

View File

@@ -1,5 +1,159 @@
# Unreleased
# Version 0.19.5 (2019-11-04)
- On Android, fix the missing `raw-window-handle` support
# Version 0.19.4 (2019-10-16)
- Add support for `raw-window-handle` 0.3.
# Version 0.19.3 (2019-08-26)
- Update parking_lot version.
# Version 0.19.2 (2019-07-29)
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.
# Version 0.19.1 (2019-04-08)
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
- On macOS, fix command key event left and right reverse.
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
- On Windows, fix icon not showing up in corner of window.
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
# Version 0.19.0 (2019-03-06)
- On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available.
- On macOS, fix keycodes being incorrect when using a non-US keyboard layout.
- On Wayland, fix `with_title()` not setting the windows title
- On Wayland, add `set_wayland_theme()` to control client decoration color theme
- Added serde serialization to `os::unix::XWindowType`.
- **Breaking:** `image` crate upgraded to 0.21. This is exposed as part of the `icon_loading` API.
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
- Refactored Windows state/flag-setting code.
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
# Version 0.18.1 (2018-12-30)
- On macOS, fix `Yen` (JIS) so applications receive the event.
- On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors.
- On X11, fixed panic caused by dropping the window before running the event loop.
- on macOS, added `WindowExt::set_simple_fullscreen` which does not require a separate space
- Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland.
- On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code.
- On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program.
- On Windows, fix issue where resizing or moving window would eat `Awakened` events.
- On Windows, exiting fullscreen after entering fullscreen with disabled decorations no longer shrinks window.
- On X11, fixed a segfault when using virtual monitors with XRandR.
- Derive `Ord` and `PartialOrd` for `VirtualKeyCode` enum.
- On Windows, fix issue where hovering or dropping a non file item would create a panic.
- On Wayland, fix resizing and DPI calculation when a `wl_output` is removed without sending a `leave` event to the `wl_surface`, such as disconnecting a monitor from a laptop.
- On Wayland, DPI calculation is handled by smithay-client-toolkit.
- On X11, `WindowBuilder::with_min_dimensions` and `WindowBuilder::with_max_dimensions` now correctly account for DPI.
- Added support for generating dummy `DeviceId`s and `WindowId`s to better support unit testing.
- On macOS, fixed unsoundness in drag-and-drop that could result in drops being rejected.
- On macOS, implemented `WindowEvent::Refresh`.
- On macOS, all `MouseCursor` variants are now implemented and the cursor will no longer reset after unfocusing.
- Removed minimum supported Rust version guarantee.
# Version 0.18.0 (2018-11-07)
- **Breaking:** `image` crate upgraded to 0.20. This is exposed as part of the `icon_loading` API.
- On Wayland, pointer events will now provide the current modifiers state.
- On Wayland, titles will now be displayed in the window header decoration.
- On Wayland, key repetition is now ended when keyboard loses focus.
- On Wayland, windows will now use more stylish and modern client side decorations.
- On Wayland, windows will use server-side decorations when available.
- **Breaking:** Added support for F16-F24 keys (variants were added to the `VirtualKeyCode` enum).
- Fixed graphical glitches when resizing on Wayland.
- On Windows, fix freezes when performing certain actions after a window resize has been triggered. Reintroduces some visual artifacts when resizing.
- Updated window manager hints under X11 to v1.5 of [Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm140200472629520).
- Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions.
- Fixed UTF8 handling bug in X11 `set_title` function.
- On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first.
- On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented.
- On Windows, fix `Window::set_maximized`.
- On Windows 10, fix transparency (#260).
- On macOS, fix modifiers during key repeat.
- Implemented the `Debug` trait for `Window`, `EventsLoop`, `EventsLoopProxy` and `WindowBuilder`.
- On X11, now a `Resized` event will always be generated after a DPI change to ensure the window's logical size is consistent with the new DPI.
- Added further clarifications to the DPI docs.
- On Linux, if neither X11 nor Wayland manage to initialize, the corresponding panic now consists of a single line only.
- Add optional `serde` feature with implementations of `Serialize`/`Deserialize` for DPI types and various event types.
- Add `PartialEq`, `Eq`, and `Hash` implementations on public types that could have them but were missing them.
- On X11, drag-and-drop receiving an unsupported drop type can no longer cause the WM to freeze.
- Fix issue whereby the OpenGL context would not appear at startup on macOS Mojave (#1069).
- **Breaking:** Removed `From<NSApplicationActivationPolicy>` impl from `ActivationPolicy` on macOS.
- On macOS, the application can request the user's attention with `WindowExt::request_user_attention`.
# Version 0.17.2 (2018-08-19)
- On macOS, fix `<C-Tab>` so applications receive the event.
- On macOS, fix `<Cmd-{key}>` so applications receive the event.
- On Wayland, key press events will now be repeated.
# Version 0.17.1 (2018-08-05)
- On X11, prevent a compilation failure in release mode for versions of Rust greater than or equal to 1.30.
- Fixed deadlock that broke fullscreen mode on Windows.
# Version 0.17.0 (2018-08-02)
- Cocoa and core-graphics updates.
- Fixed thread-safety issues in several `Window` functions on Windows.
- On MacOS, the key state for modifiers key events is now properly set.
- On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again.
- Added NetBSD support.
- **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK).
- On iOS, the `UIApplication` is not started until `Window::new` is called.
- Fixed thread unsafety with cursor hiding on macOS.
- On iOS, fixed the size of the `JmpBuf` type used for `setjmp`/`longjmp` calls. Previously this was a buffer overflow on most architectures.
- On Windows, use cached window DPI instead of repeatedly querying the system. This fixes sporadic crashes on Windows 7.
# Version 0.16.2 (2018-07-07)
- On Windows, non-resizable windows now have the maximization button disabled. This is consistent with behavior on macOS and popular X11 WMs.
- Corrected incorrect `unreachable!` usage when guessing the DPI factor with no detected monitors.
# Version 0.16.1 (2018-07-02)
- Added logging through `log`. Logging will become more extensive over time.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
# Version 0.16.0 (2018-06-25)
- Windows additionally has `WindowBuilderExt::with_no_redirection_bitmap`.
- **Breaking:** Removed `VirtualKeyCode::LMenu` and `VirtualKeyCode::RMenu`; Windows now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead.
- On X11, exiting fullscreen no longer leaves the window in the monitor's top left corner.
- **Breaking:** `Window::hidpi_factor` has been renamed to `Window::get_hidpi_factor` for better consistency. `WindowEvent::HiDPIFactorChanged` has been renamed to `WindowEvent::HiDpiFactorChanged`. DPI factors are always represented as `f64` instead of `f32` now.
- The Windows backend is now DPI aware. `WindowEvent::HiDpiFactorChanged` is implemented, and `MonitorId::get_hidpi_factor` and `Window::hidpi_factor` return accurate values.
- Implemented `WindowEvent::HiDpiFactorChanged` on X11.
- On macOS, `Window::set_cursor_position` is now relative to the client area.
- On macOS, setting the maximum and minimum dimensions now applies to the client area dimensions rather than to the window dimensions.
- On iOS, `MonitorId::get_dimensions` has been implemented and both `MonitorId::get_hidpi_factor` and `Window::get_hidpi_factor` return accurate values.
- On Emscripten, `MonitorId::get_hidpi_factor` now returns the same value as `Window::get_hidpi_factor` (it previously would always return 1.0).
- **Breaking:** The entire API for sizes, positions, etc. has changed. In the majority of cases, winit produces and consumes positions and sizes as `LogicalPosition` and `LogicalSize`, respectively. The notable exception is `MonitorId` methods, which deal in `PhysicalPosition` and `PhysicalSize`. See the documentation for specifics and explanations of the types. Additionally, winit automatically conserves logical size when the DPI factor changes.
- **Breaking:** All deprecated methods have been removed. For `Window::platform_display` and `Window::platform_window`, switch to the appropriate platform-specific `WindowExt` methods. For `Window::get_inner_size_points` and `Window::get_inner_size_pixels`, use the `LogicalSize` returned by `Window::get_inner_size` and convert as needed.
- HiDPI support for Wayland.
- `EventsLoop::get_available_monitors` and `EventsLoop::get_primary_monitor` now have identical counterparts on `Window`, so this information can be acquired without an `EventsLoop` borrow.
- `AvailableMonitorsIter` now implements `Debug`.
- Fixed quirk on macOS where certain keys would generate characters at twice the normal rate when held down.
- On X11, all event loops now share the same `XConnection`.
- **Breaking:** `Window::set_cursor_state` and `CursorState` enum removed in favor of the more composable `Window::grab_cursor` and `Window::hide_cursor`. As a result, grabbing the cursor no longer automatically hides it; you must call both methods to retain the old behavior on Windows and macOS. `Cursor::NoneCursor` has been removed, as it's no longer useful.
- **Breaking:** `Window::set_cursor_position` now returns `Result<(), String>`, thus allowing for `Box<Error>` conversion via `?`.
# Version 0.15.1 (2018-06-13)
- On X11, the `Moved` event is no longer sent when the window is resized without changing position.

58
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,58 @@
# PLEASE MAKE PRs AGAINST THE `eventloop-2.0` BRANCH.
All development work for our next version is being done against that branch. Refer to [#459](https://github.com/rust-windowing/winit/issues/459) and [the associated milestone](https://github.com/rust-windowing/winit/milestone/2) for details on what that branch changes.
# Winit Contributing Guidelines
## Scope
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
consider whether or not it's directly related to window creation or input handling. If it isn't, it
may be worth creating a separate crate that extends Winit's API to add that functionality.
## Reporting an issue
When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible:
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
## Making a pull request
When making a code contribution to winit, before opening your pull request, please make sure that:
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
## Maintainers & Testers
Winit is managed by several people, each with their specialities, and each maintaining a subset of the
backends of winit. As such, depending on your platform of interest, your contacts will be different.
This table summarizes who can be contacted in which case, with the following legend:
- `M` - Maintainer: is a main maintainer for this platform
- `C` - Collaborator: can review code and address issues on this platform
- `T` - Tester: has the ability of testing the platform
- ` `: knows nothing of this platform
| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| @mitchmindtree | T | | T | T | | | |
| @Osspial | M | | T | T | T | | T |
| @vberger | | | T | M | | | |
| @mtak- | | T | | | T | M | |

View File

@@ -1,17 +1,17 @@
[package]
name = "winit"
version = "0.15.1"
authors = ["The winit contributors, Pierre Krieger <pierre.krieger1708@gmail.com>"]
version = "0.19.5"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/tomaka/winit"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
[package.metadata.docs.rs]
features = ["icon_loading"]
features = ["icon_loading", "serde"]
[features]
icon_loading = ["image"]
@@ -19,41 +19,53 @@ icon_loading = ["image"]
[dependencies]
lazy_static = "1"
libc = "0.2"
image = { version = "0.19", optional = true }
log = "0.4"
image = { version = "0.21", optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.3"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2"
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
cocoa = "0.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]
backtrace = "0.3"
bitflags = "1"
[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"
percent-encoding = "1.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.21", features = [ "dlopen", "egl", "cursor"] }
smithay-client-toolkit = "0.4.3"
x11-dl = "2.18.3"
parking_lot = "0.9"
percent-encoding = "2.0"

209
FEATURES.md Normal file
View File

@@ -0,0 +1,209 @@
# Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling, and can
be used to create both games and applications. It supports the main graphical platforms:
- Desktop
- Windows
- macOS
- Unix
- via X11
- via Wayland
- Mobile
- iOS
- Android
- Web
- via Emscripten
- via WASM
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features
available everywhere. In this context, APIs exposed in winit can be split into different "support tiers":
- **Core:** Features that are essential to providing a well-formed abstraction over each platform's
windowing and input APIs.
- **Platform:** Platform-specific features that can't be meaningfully exposed through a common API and
cannot be implemented outside of Winit without exposing a significant amount of Winit's internals
or interfering with Winit's abstractions.
- **Usability:** Features that are not strictly essential to Winit's functionality, but provide meaningful
usability improvements and cannot be reasonably implemented in an external crate. These are
generally optional and exposed through Cargo features.
Core features are taken care of by the core Winit maintainers. Platform features are not.
When a platform feature is submitted, the submitter is considered the expert in the
feature and may be asked to support the feature should it break in the future.
Winit ***does not*** directly expose functionality for drawing inside windows or creating native
menus, but ***does*** commit to providing APIs that higher-level crates can use to implement that
functionality.
## `1.0` and stability
When all core features are implemented to the satisfaction of the Winit maintainers, Winit 1.0 will
be released and the library will enter maintenance mode. For the most part, new core features will not
be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades
Some platform features could in theory be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions gets deprecated and become permanently
exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Touch events**: Single-touch events.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the taskbar icon
* Setting the parent window
* `WS_EX_NOREDIRECTIONBITMAP` support
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** |
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A** |
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ |
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❌ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❌ |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ |
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
[#242]: https://github.com/rust-windowing/winit/issues/242
[#306]: https://github.com/rust-windowing/winit/issues/306
[#315]: https://github.com/rust-windowing/winit/issues/315
[#319]: https://github.com/rust-windowing/winit/issues/319
[#33]: https://github.com/rust-windowing/winit/issues/33
[#459]: https://github.com/rust-windowing/winit/issues/459
[#5]: https://github.com/rust-windowing/winit/issues/5
[#63]: https://github.com/rust-windowing/winit/issues/63
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#804]: https://github.com/rust-windowing/winit/issues/804
[#812]: https://github.com/rust-windowing/winit/issues/812

11
HALL_OF_CHAMPIONS.md Normal file
View File

@@ -0,0 +1,11 @@
# Hall of Champions
The Winit maintainers would like to recognize the following former Winit
contributors, without whom Winit would not exist in its current form. We thank
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* @tomaka: For creating the Winit project and guiding it through its early
years of existence.
* @francesca64: For taking over the responsibility of maintaining almost every
Winit backend, and standardizing HiDPI support across all of them

View File

@@ -1,19 +1,35 @@
# This branch has been archived
Please direct all work against `master`.
--------
# winit - Cross-platform window creation and management in Rust
[![](http://meritbadge.herokuapp.com/winit)](https://crates.io/crates/winit)
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](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.png?branch=master)](https://travis-ci.org/tomaka/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/5h87hj0g4q2xe3j9/branch/master?svg=true)](https://ci.appveyor.com/project/tomaka/winit/branch/master)
[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master)
```toml
[dependencies]
winit = "0.15"
winit = "0.19.5"
```
## [Documentation](https://docs.rs/winit)
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
## Contact Us
Join us in any of these:
[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Usage
Winit is a window creation and management library. It can create windows and lets you handle
@@ -43,6 +59,14 @@ fn main() {
}
```
Winit is only officially supported on the latest stable version of the Rust compiler.
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `icon_loading`: Enables loading window icons directly from files. Depends on the [`image` crate](https://crates.io/crates/image).
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
### Platform-specific usage
#### Emscripten and WebAssembly

View File

@@ -4,8 +4,6 @@ environment:
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: 1.24.1
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: i686-pc-windows-gnu
@@ -22,3 +20,5 @@ build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo test --features icon_loading --verbose

View File

@@ -8,7 +8,7 @@ fn main() {
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
window.set_title("A fantastic window!");
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::NoneCursor, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
let mut cursor_idx = 0;
events_loop.run_forever(|event| {

38
examples/cursor_grab.rs Normal file
View File

@@ -0,0 +1,38 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
if let winit::Event::WindowEvent { event, .. } = event {
use winit::WindowEvent::*;
match event {
CloseRequested => return winit::ControlFlow::Break,
KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
..
} => {
use winit::VirtualKeyCode::*;
match key {
Escape => return winit::ControlFlow::Break,
G => window.grab_cursor(!modifiers.shift).unwrap(),
H => window.hide_cursor(!modifiers.shift),
_ => (),
}
}
_ => (),
}
}
winit::ControlFlow::Continue
});
}

View File

@@ -6,35 +6,46 @@ use winit::{ControlFlow, Event, WindowEvent};
fn main() {
let mut events_loop = winit::EventsLoop::new();
// enumerating monitors
#[cfg(target_os = "macos")]
let mut macos_use_simple_fullscreen = false;
let monitor = {
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.get_name());
// On macOS there are two fullscreen modes "native" and "simple"
#[cfg(target_os = "macos")]
{
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
io::stdout().flush().unwrap();
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
match num {
2 => macos_use_simple_fullscreen = true,
_ => {}
}
// Prompt for monitor when using native fullscreen
if !macos_use_simple_fullscreen {
Some(prompt_for_monitor(&events_loop))
} else {
None
}
}
print!("Please write the number of the monitor to use: ");
io::stdout().flush().unwrap();
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
println!("Using {:?}", monitor.get_name());
monitor
#[cfg(not(target_os = "macos"))]
Some(prompt_for_monitor(&events_loop))
};
let mut is_fullscreen = monitor.is_some();
let mut is_maximized = false;
let mut decorations = true;
let window = winit::WindowBuilder::new()
.with_title("Hello world!")
.with_fullscreen(Some(monitor))
.with_fullscreen(monitor)
.build(&events_loop)
.unwrap();
let mut is_fullscreen = true;
let mut is_maximized = false;
let mut decorations = true;
events_loop.run_forever(|event| {
println!("{:?}", event);
@@ -52,6 +63,18 @@ fn main() {
} => match (virtual_code, state) {
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
#[cfg(target_os = "macos")]
{
if macos_use_simple_fullscreen {
use winit::os::macos::WindowExt;
if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) {
is_fullscreen = !is_fullscreen;
}
return ControlFlow::Continue;
}
}
is_fullscreen = !is_fullscreen;
if !is_fullscreen {
window.set_fullscreen(None);
@@ -59,6 +82,15 @@ fn main() {
window.set_fullscreen(Some(window.get_current_monitor()));
}
}
(winit::VirtualKeyCode::S, winit::ElementState::Pressed) => {
println!("window.get_fullscreen {:?}", window.get_fullscreen());
#[cfg(target_os = "macos")]
{
use winit::os::macos::WindowExt;
println!("window.get_simple_fullscreen {:?}", WindowExt::get_simple_fullscreen(&window));
}
}
(winit::VirtualKeyCode::M, winit::ElementState::Pressed) => {
is_maximized = !is_maximized;
window.set_maximized(is_maximized);
@@ -77,3 +109,22 @@ fn main() {
ControlFlow::Continue
});
}
// Enumerate monitors and prompt user to choose one
fn prompt_for_monitor(events_loop: &winit::EventsLoop) -> winit::MonitorId {
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.get_name());
}
print!("Please write the number of the monitor to use: ");
io::stdout().flush().unwrap();
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
println!("Using {:?}", monitor.get_name());
monitor
}

View File

@@ -1,45 +0,0 @@
extern crate winit;
use winit::{ControlFlow, WindowEvent, ElementState, KeyboardInput};
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
window.set_title("winit - Cursor grabbing test");
let mut grabbed = false;
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
winit::Event::WindowEvent { event, .. } => {
match event {
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
if grabbed {
grabbed = false;
window.set_cursor_state(winit::CursorState::Normal)
.ok().expect("could not ungrab mouse cursor");
} else {
grabbed = true;
window.set_cursor_state(winit::CursorState::Grab)
.ok().expect("could not grab mouse cursor");
}
},
WindowEvent::CloseRequested => return ControlFlow::Break,
a @ WindowEvent::CursorMoved { .. } => {
println!("{:?}", a);
},
_ => (),
}
}
_ => {}
}
ControlFlow::Continue
});
}

View File

@@ -1,5 +1,7 @@
extern crate winit;
use winit::dpi::LogicalSize;
fn main() {
let mut events_loop = winit::EventsLoop::new();
@@ -7,8 +9,8 @@ fn main() {
.build(&events_loop)
.unwrap();
window.set_min_dimensions(Some((400, 200)));
window.set_max_dimensions(Some((800, 400)));
window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0)));
window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0)));
events_loop.run_forever(|event| {
println!("{:?}", event);

7
examples/monitor_list.rs Normal file
View File

@@ -0,0 +1,7 @@
extern crate winit;
fn main() {
let event_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new().build(&event_loop).unwrap();
println!("{:#?}\nPrimary: {:#?}", window.get_available_monitors(), window.get_primary_monitor());
}

View File

@@ -7,7 +7,7 @@ fn main() {
let window = winit::WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_dimensions(400, 200)
.with_dimensions((400, 200).into())
.with_resizable(resizable)
.build(&events_loop)
.unwrap();

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

331
src/dpi.rs Normal file
View File

@@ -0,0 +1,331 @@
//! DPI is important, so read the docs for this module if you don't want to be confused.
//!
//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all
//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical
//! pixels, as do any context-related functions in `glutin`.
//!
//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the
//! concepts.
//!
//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of
//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two
//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the
//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays.
//!
//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the
//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the
//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably
//! small.
//!
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
//! Ideally, the button now has approximately the same perceived size across varying displays.
//!
//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users
//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor), which is roughly equivalent
//! to `window.get_current_monitor().get_hidpi_factor()`.
//!
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings.
//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e.
//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out.
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
//! display to use that 2.0 DPI factor, given the use of the command line.
//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
//!
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
//! [`Resized`](../enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size.
//!
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! `winit` will send [`Resized`](../enum.WindowEvent.html#variant.Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! If you never received any [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
/// Checks that the DPI factor is a normal positive `f64`.
///
/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
#[inline]
pub fn validate_hidpi_factor(dpi_factor: f64) -> bool {
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
}
/// A position represented in logical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition {
pub x: f64,
pub y: f64,
}
impl LogicalPosition {
#[inline]
pub fn new(x: f64, y: f64) -> Self {
LogicalPosition { x, y }
}
#[inline]
pub fn from_physical<T: Into<PhysicalPosition>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x * dpi_factor;
let y = self.y * dpi_factor;
PhysicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for LogicalPosition {
#[inline]
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl From<(i32, i32)> for LogicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl Into<(f64, f64)> for LogicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl Into<(i32, i32)> for LogicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A position represented in physical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition {
pub x: f64,
pub y: f64,
}
impl PhysicalPosition {
#[inline]
pub fn new(x: f64, y: f64) -> Self {
PhysicalPosition { x, y }
}
#[inline]
pub fn from_logical<T: Into<LogicalPosition>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x / dpi_factor;
let y = self.y / dpi_factor;
LogicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for PhysicalPosition {
#[inline]
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl From<(i32, i32)> for PhysicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl Into<(f64, f64)> for PhysicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl Into<(i32, i32)> for PhysicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A size represented in logical pixels.
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize {
pub width: f64,
pub height: f64,
}
impl LogicalSize {
#[inline]
pub fn new(width: f64, height: f64) -> Self {
LogicalSize { width, height }
}
#[inline]
pub fn from_physical<T: Into<PhysicalSize>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width * dpi_factor;
let height = self.height * dpi_factor;
PhysicalSize::new(width, height)
}
}
impl From<(f64, f64)> for LogicalSize {
#[inline]
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl From<(u32, u32)> for LogicalSize {
#[inline]
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl Into<(f64, f64)> for LogicalSize {
#[inline]
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
impl Into<(u32, u32)> for LogicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}
/// A size represented in physical pixels.
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize {
pub width: f64,
pub height: f64,
}
impl PhysicalSize {
#[inline]
pub fn new(width: f64, height: f64) -> Self {
PhysicalSize { width, height }
}
#[inline]
pub fn from_logical<T: Into<LogicalSize>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width / dpi_factor;
let height = self.height / dpi_factor;
LogicalSize::new(width, height)
}
}
impl From<(f64, f64)> for PhysicalSize {
#[inline]
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl From<(u32, u32)> for PhysicalSize {
#[inline]
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl Into<(f64, f64)> for PhysicalSize {
#[inline]
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
impl Into<(u32, u32)> for PhysicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}

View File

@@ -1,8 +1,9 @@
use std::path::PathBuf;
use {WindowId, DeviceId};
use {DeviceId, LogicalPosition, LogicalSize, WindowId};
/// Describes a generic event.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
WindowEvent {
window_id: WindowId,
@@ -21,14 +22,13 @@ pub enum Event {
}
/// Describes an event from a `Window`.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum WindowEvent {
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(u32, u32),
Resized(LogicalSize),
/// The position of the window has changed. Contains the window's new position.
Moved(i32, i32),
Moved(LogicalPosition),
/// The window has been requested to close.
CloseRequested,
@@ -37,12 +37,21 @@ pub enum WindowEvent {
Destroyed,
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
DroppedFile(PathBuf),
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted for each file
/// separately.
HoveredFile(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
/// hovered.
HoveredFileCancelled,
/// The window received a unicode character.
@@ -63,7 +72,7 @@ pub enum WindowEvent {
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
position: (f64, f64),
position: LogicalPosition,
modifiers: ModifiersState
},
@@ -96,14 +105,16 @@ pub enum WindowEvent {
/// Touch event has been received
Touch(Touch),
/// DPI scaling factor of the window has changed.
/// The DPI factor of the window has changed.
///
/// The following actions cause DPI changes:
/// The following user actions can cause DPI changes:
///
/// * A user changes the resolution.
/// * A user changes the desktop scaling value (e.g. in Control Panel on Windows).
/// * A user moves the application window to a display with a different DPI.
HiDPIFactorChanged(f32),
/// * Changing the display's resolution.
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different DPI factor.
///
/// For more information about DPI in general, see the [`dpi`](dpi/index.html) module.
HiDpiFactorChanged(f64),
}
/// Represents raw hardware events that are not associated with any particular window.
@@ -114,7 +125,7 @@ pub enum WindowEvent {
/// may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
@@ -145,7 +156,8 @@ pub enum DeviceEvent {
}
/// Describes a keyboard input event.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
/// Identifies the physical key pressed
///
@@ -171,6 +183,7 @@ pub struct KeyboardInput {
/// Describes touch-screen input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
Started,
Moved,
@@ -193,11 +206,11 @@ pub enum TouchPhase {
/// as previously received End event is a new finger and has nothing to do with an old one.
///
/// Touch may be cancelled if for example window lost focus.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: (f64,f64),
pub location: LogicalPosition,
/// unique identifier of a finger.
pub id: u64
}
@@ -213,6 +226,7 @@ pub type ButtonId = u32;
/// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
@@ -220,6 +234,7 @@ pub enum ElementState {
/// Describes a button of a mouse controller.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
Right,
@@ -229,6 +244,7 @@ pub enum MouseButton {
/// Describes a difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
/// Amount in lines or rows to scroll in the horizontal
/// and vertical directions.
@@ -242,12 +258,13 @@ pub enum MouseScrollDelta {
/// Scroll events are expressed as a PixelDelta if
/// supported by the device (eg. a touchpad) and
/// platform.
PixelDelta(f32, f32)
PixelDelta(LogicalPosition),
}
/// Symbolic name for a keyboard key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum VirtualKeyCode {
/// The '1' key over the letters.
Key1,
@@ -315,6 +332,15 @@ pub enum VirtualKeyCode {
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq.
Snapshot,
@@ -383,7 +409,6 @@ pub enum VirtualKeyCode {
LAlt,
LBracket,
LControl,
LMenu,
LShift,
LWin,
Mail,
@@ -408,7 +433,6 @@ pub enum VirtualKeyCode {
RAlt,
RBracket,
RControl,
RMenu,
RShift,
RWin,
Semicolon,
@@ -440,6 +464,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,24 +25,28 @@
//! Once a window has been created, it will *generate events*. For example whenever the user moves
//! the window, resizes the window, moves the mouse, etc. an event is generated.
//!
//! The events generated by a window can be retreived from the `EventsLoop` the window was created
//! The events generated by a window can be retrieved from the `EventsLoop` the window was created
//! with.
//!
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
//! retreive all the events pending on the windows and immediately return after no new event is
//! retrieve all the events pending on the windows and immediately return after no new event is
//! available. You usually want to use this method in application that render continuously on the
//! screen, such as video games.
//!
//! ```no_run
//! use winit::{Event, WindowEvent};
//! use winit::dpi::LogicalSize;
//! # use winit::EventsLoop;
//! # let mut events_loop = EventsLoop::new();
//!
//! loop {
//! events_loop.poll_events(|event| {
//! match event {
//! Event::WindowEvent { event: WindowEvent::Resized(w, h), .. } => {
//! println!("The window was resized to {}x{}", w, h);
//! Event::WindowEvent {
//! event: WindowEvent::Resized(LogicalSize { width, height }),
//! ..
//! } => {
//! println!("The window was resized to {}x{}", width, height);
//! },
//! _ => ()
//! }
@@ -76,18 +80,29 @@
//! # Drawing on the window
//!
//! Winit doesn't provide any function that allows drawing on a window. However it allows you to
//! retreive the raw handle of the window (see the `os` module for that), which in turn allows you
//! retrieve the raw handle of the window (see the `os` module for that), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
//!
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
extern crate libc;
#[macro_use]
extern crate log;
#[cfg(feature = "icon_loading")]
extern crate image;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(target_os = "windows")]
extern crate backtrace;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
@@ -97,23 +112,26 @@ 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;
extern crate raw_window_handle;
pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase.
pub use events::*;
pub use window::{AvailableMonitorsIter, MonitorId};
pub use icon::*;
mod platform;
pub mod dpi;
mod events;
mod window;
mod icon;
mod platform;
mod window;
pub mod os;
@@ -140,6 +158,12 @@ pub struct Window {
window: platform::Window,
}
impl std::fmt::Debug for Window {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("Window { .. }")
}
}
/// Identifier of a window. Unique for each window.
///
/// Can be obtained with `window.id()`.
@@ -149,6 +173,17 @@ pub struct Window {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(platform::WindowId);
impl WindowId {
/// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `WindowId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
WindowId(platform::WindowId::dummy())
}
}
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
@@ -157,7 +192,18 @@ pub struct WindowId(platform::WindowId);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(platform::DeviceId);
/// Provides a way to retreive events from the system and from the windows that were registered to
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
DeviceId(platform::DeviceId::dummy())
}
}
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventsLoop` can be seen more or less as a "context". Calling `EventsLoop::new()`
@@ -175,10 +221,17 @@ pub struct EventsLoop {
_marker: ::std::marker::PhantomData<*mut ()> // Not Send nor Sync
}
impl std::fmt::Debug for EventsLoop {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("EventsLoop { .. }")
}
}
/// Returned by the user callback given to the `EventsLoop::run_forever` method.
///
/// Indicates whether the `run_forever` method should continue or complete.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ControlFlow {
/// Continue looping and waiting for events.
Continue,
@@ -227,6 +280,11 @@ impl EventsLoop {
/// Calls `callback` every time an event is received. If no event is available, sleeps the
/// current thread and waits for an event. If the callback returns `ControlFlow::Break` then
/// `run_forever` will immediately return.
///
/// # Danger!
///
/// The callback is run after *every* event, so if its execution time is non-trivial the event queue may not empty
/// at a sufficient rate. Rendering in the callback with vsync enabled **will** cause significant lag.
#[inline]
pub fn run_forever<F>(&mut self, callback: F)
where F: FnMut(Event) -> ControlFlow
@@ -249,6 +307,12 @@ pub struct EventsLoopProxy {
events_loop_proxy: platform::EventsLoopProxy,
}
impl std::fmt::Debug for EventsLoopProxy {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.pad("EventsLoopProxy { .. }")
}
}
impl EventsLoopProxy {
/// Wake up the `EventsLoop` from which this proxy was created.
///
@@ -287,6 +351,14 @@ pub struct WindowBuilder {
platform_specific: platform::PlatformSpecificWindowBuilderAttributes,
}
impl std::fmt::Debug for WindowBuilder {
fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result {
fmtr.debug_struct("WindowBuilder")
.field("window", &self.window)
.finish()
}
}
/// Error that can happen while creating a window or a headless renderer.
#[derive(Debug, Clone)]
pub enum CreationError {
@@ -317,7 +389,8 @@ impl std::error::Error for CreationError {
}
/// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseCursor {
/// The platform-dependent default cursor.
Default,
@@ -343,7 +416,6 @@ pub enum MouseCursor {
/// Cursor showing that something cannot be done.
NotAllowed,
ContextMenu,
NoneCursor,
Cell,
VerticalText,
Alias,
@@ -379,47 +451,24 @@ impl Default for MouseCursor {
}
}
/// Describes how winit handles the cursor.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CursorState {
/// Normal cursor behavior.
Normal,
/// The cursor will be invisible when over the window.
Hide,
/// Grabs the mouse cursor. The cursor's motion will be confined to this
/// window and the window has exclusive access to further events regarding
/// the cursor.
///
/// This is useful for first-person cameras for example.
Grab,
}
impl Default for CursorState {
fn default() -> Self {
CursorState::Normal
}
}
/// Attributes to use when creating a window.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct WindowAttributes {
/// The dimensions of the window. If this is `None`, some platform-specific dimensions will be
/// used.
///
/// The default is `None`.
pub dimensions: Option<(u32, u32)>,
pub dimensions: Option<LogicalSize>,
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
///
/// The default is `None`.
pub min_dimensions: Option<(u32, u32)>,
pub min_dimensions: Option<LogicalSize>,
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
///
/// The default is `None`.
pub max_dimensions: Option<(u32, u32)>,
pub max_dimensions: Option<LogicalSize>,
/// Whether the window is resizable or not.
///

59
src/os/ios.rs Normal file
View File

@@ -0,0 +1,59 @@
#![cfg(target_os = "ios")]
use std::os::raw::c_void;
use {MonitorId, Window, WindowBuilder};
/// Additional methods on `Window` that are specific to iOS.
pub trait WindowExt {
/// Returns a pointer to the `UIWindow` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiwindow(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiview(&self) -> *mut c_void;
}
impl WindowExt for Window {
#[inline]
fn get_uiwindow(&self) -> *mut c_void {
self.window.get_uiwindow() as _
}
#[inline]
fn get_uiview(&self) -> *mut c_void {
self.window.get_uiview() as _
}
}
/// Additional methods on `WindowBuilder` that are specific to iOS.
pub trait WindowBuilderExt {
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided.
///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
#[inline]
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self
}
}
/// Additional methods on `MonitorId` that are specific to iOS.
pub trait MonitorIdExt {
/// Returns a pointer to the `UIScreen` that is used by this monitor.
fn get_uiscreen(&self) -> *mut c_void;
}
impl MonitorIdExt for MonitorId {
#[inline]
fn get_uiscreen(&self) -> *mut c_void {
self.inner.get_uiscreen() as _
}
}

View File

@@ -1,9 +1,7 @@
#![cfg(target_os = "macos")]
use std::convert::From;
use std::os::raw::c_void;
use cocoa::appkit::NSApplicationActivationPolicy;
use {MonitorId, Window, WindowBuilder};
use {LogicalSize, MonitorId, Window, WindowBuilder};
/// Additional methods on `Window` that are specific to MacOS.
pub trait WindowExt {
@@ -16,6 +14,26 @@ pub trait WindowExt {
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nsview(&self) -> *mut c_void;
/// Request user attention, causing the application's dock icon to bounce.
/// Note that this has no effect if the application is already focused.
///
/// The `is_critical` flag has the following effects:
/// - `false`: the dock icon will only bounce once.
/// - `true`: the dock icon will bounce until the application is focused.
fn request_user_attention(&self, is_critical: bool);
/// Returns whether or not the window is in simple fullscreen mode.
fn get_simple_fullscreen(&self) -> bool;
/// Toggles a fullscreen mode that doesn't require a new macOS space.
/// Returns a boolean indicating whether the transition was successful (this
/// won't work if the window was already in the native fullscreen).
///
/// This is how fullscreen used to work on macOS in versions before Lion.
/// And allows the user to have a fullscreen window without using another
/// space or taking control over the entire monitor.
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
}
impl WindowExt for Window {
@@ -28,6 +46,21 @@ impl WindowExt for Window {
fn get_nsview(&self) -> *mut c_void {
self.window.get_nsview()
}
#[inline]
fn request_user_attention(&self, is_critical: bool) {
self.window.request_user_attention(is_critical)
}
#[inline]
fn get_simple_fullscreen(&self) -> bool {
self.window.get_simple_fullscreen()
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -47,19 +80,6 @@ impl Default for ActivationPolicy {
}
}
impl From<ActivationPolicy> for NSApplicationActivationPolicy {
fn from(activation_policy: ActivationPolicy) -> Self {
match activation_policy {
ActivationPolicy::Regular =>
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory =>
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited =>
NSApplicationActivationPolicy::NSApplicationActivationPolicyProhibited,
}
}
}
/// Additional methods on `WindowBuilder` that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
@@ -86,7 +106,7 @@ pub trait WindowBuilderExt {
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, width_inc: u32, height_inc: u32) -> WindowBuilder;
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
@@ -133,8 +153,8 @@ impl WindowBuilderExt for WindowBuilder {
}
#[inline]
fn with_resize_increments(mut self, width_inc: u32, height_inc: u32) -> WindowBuilder {
self.platform_specific.resize_increments = Some((width_inc, height_inc));
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
}

View File

@@ -3,6 +3,7 @@
//! Contains the follow modules:
//!
//! - `android`
//! - `ios`
//! - `macos`
//! - `unix`
//! - `windows`
@@ -10,6 +11,7 @@
//! However only the module corresponding to the platform you're compiling to will be available.
//!
pub mod android;
pub mod ios;
pub mod macos;
pub mod unix;
pub mod windows;

View File

@@ -1,14 +1,22 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::os::raw;
use std::sync::Arc;
use std::ptr;
use EventsLoop;
use MonitorId;
use Window;
use platform::EventsLoop as LinuxEventsLoop;
use platform::Window as LinuxWindow;
use WindowBuilder;
use std::sync::Arc;
use sctk::window::{ButtonState, Theme};
use {
EventsLoop,
LogicalSize,
MonitorId,
Window,
WindowBuilder,
};
use platform::{
EventsLoop as LinuxEventsLoop,
Window as LinuxWindow,
};
use platform::x11::XConnection;
use platform::x11::ffi::XVisualInfo;
@@ -19,6 +27,74 @@ pub use platform::x11;
pub use platform::XNotSupported;
pub use platform::x11::util::WindowType as XWindowType;
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
struct WaylandThemeObject(WaylandTheme);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on `EventsLoop` that are specific to Linux.
pub trait EventsLoopExt {
/// Builds a new `EventsLoop` that is forced to use X11.
@@ -37,6 +113,13 @@ pub trait EventsLoopExt {
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventsLoop`.
///
/// Returns `None` if the `EventsLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `EventsLoop` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl EventsLoopExt for EventsLoop {
@@ -72,9 +155,18 @@ impl EventsLoopExt for EventsLoop {
}
#[inline]
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
self.events_loop.x_connection().cloned()
}
#[inline]
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
match self.events_loop {
LinuxEventsLoop::Wayland(ref e) => Some(e.get_display().c_ptr() as *mut _),
_ => None
}
}
}
/// Additional methods on `Window` that are specific to Unix.
@@ -93,6 +185,7 @@ pub trait WindowExt {
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
@@ -119,6 +212,9 @@ pub trait WindowExt {
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme(&self, theme: WaylandTheme);
/// Check if the window is ready for drawing
///
/// It is a remnant of a previous implementation detail for the
@@ -155,6 +251,7 @@ impl WindowExt for Window {
}
#[inline]
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
@@ -193,6 +290,14 @@ impl WindowExt for Window {
}
}
#[inline]
fn set_wayland_theme(&self, theme: WaylandTheme) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
_ => {}
}
}
#[inline]
fn is_ready(&self) -> bool {
true
@@ -210,10 +315,19 @@ 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, width_inc: u32, height_inc: u32) -> WindowBuilder;
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
/// Build window with base size hint. Only implemented on X11.
fn with_base_size(self, base_width: u32, base_height: u32) -> WindowBuilder;
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_app_id(self, app_id: String) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
@@ -250,14 +364,26 @@ impl WindowBuilderExt for WindowBuilder {
}
#[inline]
fn with_resize_increments(mut self, width_inc: u32, height_inc: u32) -> WindowBuilder {
self.platform_specific.resize_increments = Some((width_inc, height_inc));
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_width: u32, base_height: u32) -> WindowBuilder {
self.platform_specific.base_size = Some((base_width, base_height));
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
self.platform_specific.app_id = Some(app_id);
self
}
}

View File

@@ -5,7 +5,25 @@ use std::os::raw::c_void;
use libc;
use winapi::shared::windef::HWND;
use {DeviceId, Icon, MonitorId, Window, WindowBuilder};
use {DeviceId, EventsLoop, Icon, MonitorId, Window, WindowBuilder};
use platform::EventsLoop as WindowsEventsLoop;
/// Additional methods on `EventsLoop` that are specific to Windows.
pub trait EventsLoopExt {
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
/// undesirable, you can create an `EventsLoop` using this function instead.
fn new_dpi_unaware() -> Self where Self: Sized;
}
impl EventsLoopExt for EventsLoop {
#[inline]
fn new_dpi_unaware() -> Self {
EventsLoop {
events_loop: WindowsEventsLoop::with_dpi_awareness(false),
_marker: ::std::marker::PhantomData,
}
}
}
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExt {
@@ -37,6 +55,9 @@ pub trait WindowBuilderExt {
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
@@ -51,6 +72,12 @@ impl WindowBuilderExt for WindowBuilder {
self.platform_specific.taskbar_icon = taskbar_icon;
self
}
#[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
self.platform_specific.no_redirection_bitmap = flag;
self
}
}
/// Additional methods on `MonitorId` that are specific to Windows.

View File

@@ -2,24 +2,34 @@
extern crate android_glue;
use libc;
use std::sync::mpsc::{Receiver, channel};
mod ffi;
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::os::raw::c_void;
use {CreationError, Event, WindowEvent, MouseCursor};
use std::sync::mpsc::{Receiver, channel};
use {
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
WindowEvent,
WindowId as RootWindowId,
};
use CreationError::OsError;
use WindowId as RootWindowId;
use events::{Touch, TouchPhase};
use window::MonitorId as RootMonitorId;
use std::collections::VecDeque;
use std::cell::RefCell;
use CursorState;
use WindowAttributes;
pub struct EventsLoop {
event_rx: Receiver<android_glue::Event>,
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>,
}
#[derive(Clone)]
@@ -31,13 +41,13 @@ impl EventsLoop {
android_glue::add_sender(tx);
EventsLoop {
event_rx: rx,
suspend_callback: RefCell::new(None),
suspend_callback: Default::default(),
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::new();
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
@@ -47,14 +57,17 @@ impl EventsLoop {
MonitorId
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
while let Ok(event) = self.event_rx.try_recv() {
let e = match event{
android_glue::Event::EventMotion(motion) => {
let dpi_factor = MonitorId.get_hidpi_factor();
let location = LogicalPosition::from_physical(
(motion.x as f64, motion.y as f64),
dpi_factor,
);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Touch(Touch {
@@ -64,7 +77,7 @@ impl EventsLoop {
android_glue::MotionAction::Up => TouchPhase::Ended,
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
},
location: (motion.x as f64, motion.y as f64),
location,
id: motion.pointer_id as u64,
device_id: DEVICE_ID,
}),
@@ -91,11 +104,12 @@ impl EventsLoop {
if native_window.is_null() {
None
} else {
let w = unsafe { ffi::ANativeWindow_getWidth(native_window as *const _) } as u32;
let h = unsafe { ffi::ANativeWindow_getHeight(native_window as *const _) } as u32;
let dpi_factor = MonitorId.get_hidpi_factor();
let physical_size = MonitorId.get_dimensions();
let size = LogicalSize::from_physical(physical_size, dpi_factor);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Resized(w, h),
event: WindowEvent::Resized(size),
})
}
},
@@ -157,17 +171,48 @@ impl EventsLoopProxy {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
pub struct Window {
native_window: *const c_void,
}
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct MonitorId;
mod ffi;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
@@ -176,21 +221,24 @@ impl MonitorId {
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
pub fn get_dimensions(&self) -> PhysicalSize {
unsafe {
let window = android_glue::get_native_window();
(ffi::ANativeWindow_getWidth(window) as u32, ffi::ANativeWindow_getHeight(window) as u32)
(
ffi::ANativeWindow_getWidth(window) as f64,
ffi::ANativeWindow_getHeight(window) as f64,
).into()
}
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
// Android assumes single screen
(0, 0)
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
1.0
}
}
@@ -205,10 +253,6 @@ impl Window {
_: PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
// not implemented
assert!(win_attribs.min_dimensions.is_none());
assert!(win_attribs.max_dimensions.is_none());
let native_window = unsafe { android_glue::get_native_window() };
if native_window.is_null() {
return Err(OsError(format!("Android's native window is null")));
@@ -228,98 +272,113 @@ impl Window {
#[inline]
pub fn set_title(&self, _: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _x: i32, _y: i32) {
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
pub fn get_inner_size(&self) -> Option<LogicalSize> {
if self.native_window.is_null() {
None
} else {
Some((
unsafe { ffi::ANativeWindow_getWidth(self.native_window as *const _) } as u32,
unsafe { ffi::ANativeWindow_getHeight(self.native_window as *const _) } as u32
))
let dpi_factor = self.get_hidpi_factor();
let physical_size = self.get_current_monitor().get_dimensions();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _x: u32, _y: u32) {
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!();
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
unimplemented!()
pub fn get_hidpi_factor(&self) -> f64 {
self.get_current_monitor().get_hidpi_factor()
}
#[inline]
pub fn set_cursor(&self, _: MouseCursor) {
// N/A
}
#[inline]
pub fn set_cursor_state(&self, _state: CursorState) -> Result<(), String> {
Ok(())
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on Android.".to_owned())
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
1.0
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
Ok(())
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on Android.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
// N/A
// Android has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// Android has single screen maximized apps so nothing to do
}
@@ -339,18 +398,39 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
pub fn set_ime_spot(&self, _spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId{inner: MonitorId}
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = AndroidHandle {
a_native_window: self.native_window as _,
..AndroidHandle::empty()
};
RawWindowHandle::Android(handle)
}
}
unsafe impl Send for Window {}

View File

@@ -1,6 +1,4 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(dead_code, non_camel_case_types, non_snake_case)]
use std::os::raw::{c_int, c_char, c_void, c_ulong, c_double, c_long, c_ushort};
#[cfg(test)]

View File

@@ -2,16 +2,22 @@
mod ffi;
use std::mem;
use std::os::raw::{c_char, c_void, c_double, c_ulong, c_int};
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, Arc};
use std::{mem, ptr, str};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::os::raw::{c_char, c_void, c_double, c_ulong, c_int};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, Arc};
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use window::MonitorId as RootMonitorId;
const DOCUMENT_NAME: &'static str = "#document\0";
fn get_hidpi_factor() -> f64 {
unsafe { ffi::emscripten_get_device_pixel_ratio() as f64 }
}
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes;
@@ -21,6 +27,12 @@ unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone, Default)]
pub struct PlatformSpecificHeadlessBuilderAttributes;
@@ -34,18 +46,18 @@ impl MonitorId {
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
unimplemented!()
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
(0, 0)
pub fn get_dimensions(&self) -> PhysicalSize {
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
1.0
pub fn get_hidpi_factor(&self) -> f64 {
get_hidpi_factor()
}
}
@@ -90,17 +102,19 @@ impl EventsLoop {
}
}
#[inline]
pub fn interrupt(&self) {
self.interrupted.store(true, Ordering::Relaxed);
}
#[inline]
pub fn create_proxy(&self) -> EventsLoopProxy {
unimplemented!()
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut list = VecDeque::new();
let mut list = VecDeque::with_capacity(1);
list.push_back(MonitorId);
list
}
@@ -115,7 +129,7 @@ impl EventsLoop {
{
let ref mut window = *self.window.lock().unwrap();
if let &mut Some(ref mut window) = window {
while let Some(event) = window.events.borrow_mut().pop_front() {
while let Some(event) = window.events.lock().unwrap().pop_front() {
callback(event)
}
}
@@ -141,10 +155,17 @@ impl EventsLoop {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
pub struct Window2 {
cursor_state: Mutex<::CursorState>,
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
is_fullscreen: bool,
events: Box<RefCell<VecDeque<::Event>>>,
events: Box<Mutex<VecDeque<::Event>>>,
}
pub struct Window {
@@ -176,7 +197,7 @@ extern "C" fn mouse_callback(
event_queue: *mut c_void) -> ffi::EM_BOOL
{
unsafe {
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
let modifiers = ::ModifiersState {
shift: (*event).shiftKey == ffi::EM_TRUE,
@@ -187,15 +208,20 @@ extern "C" fn mouse_callback(
match event_type {
ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => {
queue.borrow_mut().push_back(::Event::WindowEvent {
let dpi_factor = get_hidpi_factor();
let position = LogicalPosition::from_physical(
((*event).canvasX as f64, (*event).canvasY as f64),
dpi_factor,
);
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::CursorMoved {
device_id: ::DeviceId(DeviceId),
position: ((*event).canvasX as f64, (*event).canvasY as f64),
position,
modifiers: modifiers,
}
});
queue.borrow_mut().push_back(::Event::DeviceEvent {
queue.lock().unwrap().push_back(::Event::DeviceEvent {
device_id: ::DeviceId(DeviceId),
event: ::DeviceEvent::MouseMotion {
delta: ((*event).movementX as f64, (*event).movementY as f64),
@@ -215,7 +241,7 @@ extern "C" fn mouse_callback(
ffi::EMSCRIPTEN_EVENT_MOUSEUP => ::ElementState::Released,
_ => unreachable!(),
};
queue.borrow_mut().push_back(::Event::WindowEvent {
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::MouseInput {
device_id: ::DeviceId(DeviceId),
@@ -238,7 +264,7 @@ extern "C" fn keyboard_callback(
event_queue: *mut c_void) -> ffi::EM_BOOL
{
unsafe {
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
let modifiers = ::ModifiersState {
shift: (*event).shiftKey == ffi::EM_TRUE,
@@ -249,7 +275,7 @@ extern "C" fn keyboard_callback(
match event_type {
ffi::EMSCRIPTEN_EVENT_KEYDOWN => {
queue.borrow_mut().push_back(::Event::WindowEvent {
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::KeyboardInput {
device_id: ::DeviceId(DeviceId),
@@ -263,7 +289,7 @@ extern "C" fn keyboard_callback(
});
},
ffi::EMSCRIPTEN_EVENT_KEYUP => {
queue.borrow_mut().push_back(::Event::WindowEvent {
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::KeyboardInput {
device_id: ::DeviceId(DeviceId),
@@ -289,7 +315,7 @@ extern fn touch_callback(
event_queue: *mut c_void) -> ffi::EM_BOOL
{
unsafe {
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
let phase = match event_type {
ffi::EMSCRIPTEN_EVENT_TOUCHSTART => ::TouchPhase::Started,
@@ -302,13 +328,18 @@ extern fn touch_callback(
for touch in 0..(*event).numTouches as usize {
let touch = (*event).touches[touch];
if touch.isChanged == ffi::EM_TRUE {
queue.borrow_mut().push_back(::Event::WindowEvent {
let dpi_factor = get_hidpi_factor();
let location = LogicalPosition::from_physical(
(touch.canvasX as f64, touch.canvasY as f64),
dpi_factor,
);
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::Touch(::Touch {
device_id: ::DeviceId(DeviceId),
phase,
id: touch.identifier as u64,
location: (touch.canvasX as f64, touch.canvasY as f64),
location,
}),
});
}
@@ -356,8 +387,9 @@ impl Window {
}
let w = Window2 {
cursor_state: Mutex::new(::CursorState::Normal),
events: Box::new(RefCell::new(VecDeque::new())),
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
events: Default::default(),
is_fullscreen: attribs.fullscreen.is_some(),
};
@@ -395,8 +427,8 @@ impl Window {
em_try(ffi::emscripten_set_fullscreenchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(fullscreen_callback)))
.map_err(|e| ::CreationError::OsError(e))?;
}
} else if let Some((w, h)) = attribs.dimensions {
window.set_inner_size(w, h);
} else if let Some(size) = attribs.dimensions {
window.set_inner_size(size);
}
*events_loop.window.lock().unwrap() = Some(window.window.clone());
@@ -413,22 +445,22 @@ impl Window {
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
Some((0, 0))
pub fn get_position(&self) -> Option<LogicalPosition> {
Some((0, 0).into())
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
Some((0, 0))
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
Some((0, 0).into())
}
#[inline]
pub fn set_position(&self, _: i32, _: i32) {
pub fn set_position(&self, _: LogicalPosition) {
}
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
unsafe {
use std::{mem, ptr};
let mut width = 0;
let mut height = 0;
let mut fullscreen = 0;
@@ -438,99 +470,109 @@ impl Window {
{
None
} else {
Some((width as u32, height as u32))
let dpi_factor = self.get_hidpi_factor();
let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor);
Some(logical)
}
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, width: u32, height: u32) {
pub fn set_inner_size(&self, size: LogicalSize) {
unsafe {
use std::ptr;
ffi::emscripten_set_element_css_size(ptr::null(), width as c_double, height
as c_double);
let dpi_factor = self.get_hidpi_factor();
let physical = PhysicalSize::from_logical(size, dpi_factor);
let (width, height): (u32, u32) = physical.into();
ffi::emscripten_set_element_css_size(
ptr::null(),
width as c_double,
height as c_double,
);
}
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn show(&self) {}
#[inline]
pub fn hide(&self) {}
#[inline]
pub fn platform_display(&self) -> *mut ::libc::c_void {
unimplemented!()
pub fn show(&self) {
// N/A
}
#[inline]
pub fn platform_window(&self) -> *mut ::libc::c_void {
unimplemented!()
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: ::MouseCursor) {}
pub fn set_cursor(&self, _cursor: ::MouseCursor) {
// N/A
}
#[inline]
pub fn set_cursor_state(&self, state: ::CursorState) -> Result<(), String> {
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
use ::CursorState::*;
let mut old_state = self.window.cursor_state.lock().unwrap();
if state == *old_state {
return Ok(());
if grab {
em_try(ffi::emscripten_set_pointerlockchange_callback(
ptr::null(),
0 as *mut c_void,
ffi::EM_FALSE,
Some(pointerlockchange_callback),
))?;
em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?;
} else {
em_try(ffi::emscripten_set_pointerlockchange_callback(
ptr::null(),
0 as *mut c_void,
ffi::EM_FALSE,
None,
))?;
em_try(ffi::emscripten_exit_pointerlock())?;
}
// Set or unset grab callback
match state {
Hide | Normal => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, None))?,
Grab => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(pointerlockchange_callback)))?,
}
// Go back to normal cursor state
match *old_state {
Hide => show_mouse(),
Grab => em_try(ffi::emscripten_exit_pointerlock())?,
Normal => (),
}
// Set cursor from normal cursor state
match state {
Hide => ffi::emscripten_hide_mouse(),
Grab => em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?,
Normal => (),
}
// Update
*old_state = state;
Ok(())
}
*grabbed_lock = grab;
Ok(())
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
unsafe { ffi::emscripten_get_device_pixel_ratio() as f32 }
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.window.cursor_hidden.lock().unwrap();
if hide == *hidden_lock { return; }
if hide {
unsafe { ffi::emscripten_hide_mouse() };
} else {
show_mouse();
}
*hidden_lock = hide;
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
Err(())
pub fn get_hidpi_factor(&self) -> f64 {
get_hidpi_factor()
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on Emscripten.".to_owned())
}
#[inline]
@@ -538,6 +580,11 @@ impl Window {
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<::MonitorId> {
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<::MonitorId>) {
// iOS has single screen maximized apps so nothing to do
@@ -559,13 +606,25 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> ::MonitorId {
::MonitorId{inner: MonitorId}
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut list = VecDeque::with_capacity(1);
list.push_back(MonitorId);
list
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
}
@@ -579,7 +638,8 @@ impl Drop for Window {
unsafe {
// Return back to normal cursor state
let _ = self.set_cursor_state(::CursorState::Normal);
self.hide_cursor(false);
self.grab_cursor(false);
// Exit fullscreen if on
if self.window.is_fullscreen {
@@ -612,7 +672,6 @@ fn error_to_str(code: ffi::EMSCRIPTEN_RESULT) -> &'static str {
}
fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) -> u8 {
use std::str;
let slice = &input[0..input.iter().take_while(|x| **x != 0).count()];
let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) };
let key = match maybe_key {
@@ -629,7 +688,6 @@ fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) ->
fn key_translate_virt(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES],
location: c_ulong) -> Option<::VirtualKeyCode>
{
use std::str;
let slice = &input[0..input.iter().take_while(|x| **x != 0).count()];
let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) };
let key = match maybe_key {
@@ -769,11 +827,15 @@ fn key_translate_virt(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES
"F13" => Some(F13),
"F14" => Some(F14),
"F15" => Some(F15),
"F16" => None,
"F17" => None,
"F18" => None,
"F19" => None,
"F20" => None,
"F16" => Some(F16),
"F17" => Some(F17),
"F18" => Some(F18),
"F19" => Some(F19),
"F20" => Some(F20),
"F21" => Some(F21),
"F22" => Some(F22),
"F23" => Some(F23),
"F24" => Some(F24),
"Soft1" => None,
"Soft2" => None,
"Soft3" => None,

View File

@@ -1,20 +1,17 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::ffi::CString;
use std::os::raw::*;
use libc;
use objc::runtime::{ Object, Class };
use objc::runtime::Object;
#[allow(non_camel_case_types)]
pub type id = *mut Object;
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
pub const nil: id = 0 as id;
pub type CFStringRef = *const libc::c_void;
pub type CFStringRef = *const c_void;
pub type CFTimeInterval = f64;
pub type Boolean = u32;
#[allow(non_upper_case_globals)]
pub const kCFRunLoopRunHandledSource: i32 = 4;
#[cfg(target_pointer_width = "32")]
@@ -22,11 +19,6 @@ pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
#[cfg(target_pointer_width = "32")]
pub type NSUInteger = u32;
#[cfg(target_pointer_width = "64")]
pub type NSUInteger = u64;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGPoint {
@@ -38,14 +30,14 @@ pub struct CGPoint {
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize
pub size: CGSize,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat
pub height: CGFloat,
}
#[link(name = "UIKit", kind = "framework")]
@@ -55,33 +47,51 @@ extern {
pub static kCFRunLoopDefaultMode: CFStringRef;
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
pub fn UIApplicationMain(argc: libc::c_int, argv: *const libc::c_char, principalClassName: id, delegateClassName: id) -> libc::c_int;
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: id,
delegateClassName: id,
) -> c_int;
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
pub fn CFRunLoopRunInMode(mode: CFStringRef, seconds: CFTimeInterval, returnAfterSourceHandled: Boolean) -> i32;
pub fn CFRunLoopRunInMode(
mode: CFStringRef,
seconds: CFTimeInterval,
returnAfterSourceHandled: Boolean,
) -> i32;
}
extern {
pub fn setjmp(env: *mut libc::c_void) -> libc::c_int;
pub fn longjmp(env: *mut libc::c_void, val: libc::c_int);
pub fn setjmp(env: *mut c_void) -> c_int;
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
}
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
#[cfg(any(target_arch = "x86_64"))]
pub const JBLEN: usize = (9 * 2) + 3 + 16;
#[cfg(any(target_arch = "x86"))]
pub const JBLEN: usize = 18;
#[cfg(target_arch = "arm")]
pub const JBLEN: usize = 10 + 16 + 2;
#[cfg(target_arch = "aarch64")]
pub const JBLEN: usize = (14 + 8 + 2) * 2;
pub type JmpBuf = [c_int; JBLEN];
pub trait NSString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class("NSString"), alloc]
msg_send![class!(NSString), alloc]
}
#[allow(non_snake_case)]
unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id;
#[allow(non_snake_case)]
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
unsafe fn stringByAppendingString_(self, other: id) -> id;
unsafe fn init_str(self, string: &str) -> Self;
#[allow(non_snake_case)]
unsafe fn UTF8String(self) -> *const libc::c_char;
unsafe fn UTF8String(self) -> *const c_char;
}
impl NSString for id {
unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String:c_string as id]
}
@@ -94,14 +104,7 @@ impl NSString for id {
self.initWithUTF8String_(cstring.as_ptr())
}
unsafe fn UTF8String(self) -> *const libc::c_char {
unsafe fn UTF8String(self) -> *const c_char {
msg_send![self, UTF8String]
}
}
#[inline]
pub fn class(name: &str) -> *mut Class {
unsafe {
::std::mem::transmute(Class::get(name))
}
}

View File

@@ -19,7 +19,7 @@
//!
//! ```rust, ignore
//! #[no_mangle]
//! pub extern fn start_glutin_app() {
//! pub extern fn start_winit_app() {
//! start_inner()
//! }
//!
@@ -29,13 +29,13 @@
//!
//! ```
//!
//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode.
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
//!
//! ```ignore
//! void start_glutin_app();
//! void start_winit_app();
//! ```
//!
//! Use start_glutin_app inside your xcode's main function.
//! Use start_winit_app inside your xcode's main function.
//!
//!
//! # App lifecycle and events
@@ -45,7 +45,7 @@
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
//!
//!
//! This is how those event are represented in glutin:
//! This is how those event are represented in winit:
//!
//! - applicationDidBecomeActive is Focused(true)
//! - applicationWillResignActive is Focused(false)
@@ -60,100 +60,148 @@
#![cfg(target_os = "ios")]
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
use std::{fmt, mem, ptr};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ptr;
use std::mem;
use std::os::raw::c_void;
use std::os::raw::*;
use std::sync::Arc;
use libc;
use libc::c_int;
use objc::runtime::{Class, Object, Sel, BOOL, YES };
use objc::declare::{ ClassDecl };
use objc::declare::ClassDecl;
use objc::runtime::{BOOL, Class, Object, Sel, YES};
use { CreationError, CursorState, MouseCursor, WindowAttributes };
use WindowId as RootEventId;
use WindowEvent;
use Event;
use events::{ Touch, TouchPhase };
use {
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
WindowEvent,
WindowId as RootEventId,
};
use events::{Touch, TouchPhase};
use window::MonitorId as RootMonitorId;
mod ffi;
use self::ffi::{
setjmp,
UIApplicationMain,
CFTimeInterval,
CFRunLoopRunInMode,
CGFloat,
CGPoint,
CGRect,
id,
JBLEN,
JmpBuf,
kCFRunLoopDefaultMode,
kCFRunLoopRunHandledSource,
id,
longjmp,
nil,
NSString,
CGFloat,
longjmp,
CGRect,
CGPoint
setjmp,
UIApplicationMain,
};
static mut jmpbuf: [c_int;27] = [0;27];
#[derive(Debug, Clone)]
pub struct MonitorId;
static mut JMPBUF: Option<Box<JmpBuf>> = None;
pub struct Window {
delegate_state: *mut DelegateState
_events_queue: Arc<RefCell<VecDeque<Event>>>,
delegate_state: Box<DelegateState>,
}
#[derive(Clone)]
pub struct WindowProxy;
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
#[derive(Debug)]
struct DelegateState {
events_queue: VecDeque<Event>,
window: id,
controller: id,
size: (u32,u32),
scale: f32
view: id,
size: LogicalSize,
scale: f64,
}
impl DelegateState {
#[inline]
fn new(window: id, controller:id, size: (u32,u32), scale: f32) -> DelegateState {
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
DelegateState {
events_queue: VecDeque::new(),
window: window,
controller: controller,
size: size,
scale: scale
window,
controller,
view,
size,
scale,
}
}
}
impl Drop for DelegateState {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.window, release];
let _: () = msg_send![self.controller, release];
let _: () = msg_send![self.view, release];
}
}
}
#[derive(Clone)]
pub struct MonitorId;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
pub fn get_uiscreen(&self) -> id {
let class = class!(UIScreen);
unsafe { msg_send![class, mainScreen] }
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
unimplemented!()
pub fn get_dimensions(&self) -> PhysicalSize {
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
(bounds.size.width as f64, bounds.size.height as f64).into()
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
// iOS assumes single screen
(0, 0)
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
1.0
pub fn get_hidpi_factor(&self) -> f64 {
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
scale as f64
}
}
pub struct EventsLoop {
delegate_state: *mut DelegateState
events_queue: Arc<RefCell<VecDeque<Event>>>,
}
#[derive(Clone)]
@@ -162,30 +210,16 @@ pub struct EventsLoopProxy;
impl EventsLoop {
pub fn new() -> EventsLoop {
unsafe {
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("glutinState");
let state = state as *mut DelegateState;
let events_loop = EventsLoop {
delegate_state: state
};
return events_loop;
if !msg_send![class!(NSThread), isMainThread] {
panic!("`EventsLoop` can only be created on the main thread on iOS");
}
}
create_delegate_class();
create_view_class();
start_app();
panic!("Couldn't create UIApplication")
EventsLoop { events_queue: Default::default() }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::new();
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
@@ -198,29 +232,30 @@ impl EventsLoop {
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
unsafe {
let state = &mut *self.delegate_state;
if let Some(event) = state.events_queue.pop_front() {
callback(event);
return;
}
// jump hack, so we won't quit on willTerminate event before processing it
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
if let Some(event) = state.events_queue.pop_front() {
assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS");
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
}
}
unsafe {
// run runloop
let seconds: CFTimeInterval = 0.000002;
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
}
if let Some(event) = state.events_queue.pop_front() {
callback(event)
}
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event)
}
}
@@ -256,117 +291,199 @@ impl EventsLoopProxy {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
}
}
}
// TODO: AFAIK transparency is enabled by default on iOS,
// so to be consistent with other platforms we have to change that.
impl Window {
pub fn new(ev: &EventsLoop, _: WindowAttributes, _: PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
Ok(Window {
delegate_state: ev.delegate_state,
})
pub fn new(
ev: &EventsLoop,
_attributes: WindowAttributes,
pl_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
unsafe {
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
}
unsafe {
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
let app_class = class!(UIApplication);
let app: id = msg_send![app_class, sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
let events_queue = &*ev.events_queue;
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
let uiview_class = class!(UIView);
let root_view_class = pl_attributes.root_view_class;
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
delegate_state.view = msg_send![root_view_class, alloc];
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
return Ok(Window {
_events_queue: ev.events_queue.clone(),
delegate_state,
});
}
}
create_delegate_class();
start_app();
panic!("Couldn't create `UIApplication`!")
}
#[inline]
pub fn set_title(&self, _: &str) {
pub fn get_uiwindow(&self) -> id {
self.delegate_state.window
}
#[inline]
pub fn get_uiview(&self) -> id {
self.delegate_state.view
}
#[inline]
pub fn set_title(&self, _title: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _x: i32, _y: i32) {
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
unsafe { Some((&*self.delegate_state).size) }
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.delegate_state.size)
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _x: u32, _y: u32) {
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!();
pub fn set_cursor(&self, _cursor: MouseCursor) {
// N/A
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
unimplemented!()
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
#[inline]
pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) {
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _: MouseCursor) {
pub fn get_hidpi_factor(&self) -> f64 {
self.delegate_state.scale
}
#[inline]
pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> {
Ok(())
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
unsafe { (&*self.delegate_state) }.scale
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
unimplemented!();
}
#[inline]
pub fn create_window_proxy(&self) -> WindowProxy {
WindowProxy
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on iOS.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
// N/A
// iOS has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
@@ -386,58 +503,82 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId{inner: MonitorId}
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = IOSHandle {
ui_window: self.get_uiwindow() as *mut _,
ui_view: self.get_uiview() as *mut _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(handle)
}
}
fn create_delegate_class() {
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
let screen_class = class!(UIScreen);
let window_class = class!(UIWindow);
let controller_class = class!(UIViewController);
unsafe {
let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen];
let main_screen: id = msg_send![screen_class, mainScreen];
let bounds: CGRect = msg_send![main_screen, bounds];
let scale: CGFloat = msg_send![main_screen, nativeScale];
let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc];
let window: id = msg_send![window_class, alloc];
let window: id = msg_send![window, initWithFrame:bounds.clone()];
let size = (bounds.size.width as u32, bounds.size.height as u32);
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc];
let view_controller: id = msg_send![controller_class, alloc];
let view_controller: id = msg_send![view_controller, init];
let _: () = msg_send![window, setRootViewController:view_controller];
let _: () = msg_send![window, makeKeyAndVisible];
let state = Box::new(DelegateState::new(window, view_controller, size, scale as f32));
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
let state_ptr: *mut DelegateState = mem::transmute(state);
this.set_ivar("glutinState", state_ptr as *mut c_void);
this.set_ivar("winitState", state_ptr as *mut c_void);
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
// This makes it easier to configure the specific `UIView` type.
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
}
YES
}
extern fn post_launch(_: &Object, _: Sel, _: id) {
unsafe { longjmp(mem::transmute(&mut jmpbuf),1); }
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
}
extern fn did_become_active(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::WindowEvent {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(true),
});
@@ -446,9 +587,9 @@ fn create_delegate_class() {
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::WindowEvent {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(false),
});
@@ -457,38 +598,38 @@ fn create_delegate_class() {
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::Suspended(false));
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(false));
}
}
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::Suspended(true));
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(true));
}
}
extern fn will_terminate(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
// push event to the front to garantee that we'll process it
// immidiatly after jump
state.events_queue.push_front(Event::WindowEvent {
events_queue.borrow_mut().push_front(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Destroyed,
});
longjmp(mem::transmute(&mut jmpbuf),1);
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
}
}
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
let touches_enum: id = msg_send![touches, objectEnumerator];
@@ -501,12 +642,12 @@ fn create_delegate_class() {
let touch_id = touch as u64;
let phase: i32 = msg_send![touch, phase];
state.events_queue.push_back(Event::WindowEvent {
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID,
id: touch_id,
location: (location.x as f64, location.y as f64),
location: (location.x as f64, location.y as f64).into(),
phase: match phase {
0 => TouchPhase::Started,
1 => TouchPhase::Moved,
@@ -521,8 +662,8 @@ fn create_delegate_class() {
}
}
let ui_responder = Class::get("UIResponder").unwrap();
let mut decl = ClassDecl::new("AppDelegate", ui_responder).unwrap();
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
@@ -560,19 +701,13 @@ fn create_delegate_class() {
decl.add_method(sel!(postLaunch:),
post_launch as extern fn(&Object, Sel, id));
decl.add_ivar::<*mut c_void>("glutinState");
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<*mut c_void>("eventsQueue");
decl.register();
}
}
fn create_view_class() {
let ui_view_controller = Class::get("UIViewController").unwrap();
let decl = ClassDecl::new("MainViewController", ui_view_controller).unwrap();
decl.register();
}
#[inline]
fn start_app() {
unsafe {

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};
@@ -6,7 +6,7 @@ use std::os::raw::{c_void, c_char, c_int};
pub const RTLD_LAZY: c_int = 0x001;
pub const RTLD_NOW: c_int = 0x002;
#[link="dl"]
#[link(name = "dl")]
extern {
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
pub fn dlerror() -> *mut c_char;

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};
@@ -6,20 +6,19 @@ use std::ffi::CStr;
use std::os::raw::*;
use std::sync::Arc;
use parking_lot::Mutex;
use raw_window_handle::RawWindowHandle;
use sctk::reexports::client::ConnectError;
// `std::os::raw::c_void` and `libc::c_void` are NOT interchangeable!
use libc;
use {
CreationError,
CursorState,
EventsLoopClosed,
Icon,
MouseCursor,
ControlFlow,
WindowAttributes,
};
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use window::MonitorId as RootMonitorId;
use self::x11::{XConnection, XError};
use self::x11::ffi::XVisualInfo;
@@ -47,29 +46,43 @@ 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>,
pub app_id: Option<String>
}
thread_local!(
pub static X11_BACKEND: Result<Arc<XConnection>, XNotSupported> = {
XConnection::new(Some(x_error_callback)).map(Arc::new)
lazy_static!(
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
};
);
pub enum Window {
X(x11::Window),
Wayland(wayland::Window)
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WindowId {
X(x11::WindowId),
Wayland(wayland::WindowId)
Wayland(wayland::WindowId),
}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId::X(x11::WindowId::dummy())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
X(x11::DeviceId),
Wayland(wayland::DeviceId)
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId::X(x11::DeviceId::dummy())
}
}
#[derive(Debug, Clone)]
@@ -96,7 +109,7 @@ impl MonitorId {
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
pub fn get_dimensions(&self) -> PhysicalSize {
match self {
&MonitorId::X(ref m) => m.get_dimensions(),
&MonitorId::Wayland(ref m) => m.get_dimensions(),
@@ -104,7 +117,7 @@ impl MonitorId {
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
match self {
&MonitorId::X(ref m) => m.get_position(),
&MonitorId::Wayland(ref m) => m.get_position(),
@@ -112,10 +125,10 @@ impl MonitorId {
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
match self {
&MonitorId::X(ref m) => m.get_hidpi_factor(),
&MonitorId::Wayland(ref m) => m.get_hidpi_factor(),
&MonitorId::Wayland(ref m) => m.get_hidpi_factor() as f64,
}
}
}
@@ -129,7 +142,7 @@ impl Window {
) -> Result<Self, CreationError> {
match *events_loop {
EventsLoop::Wayland(ref events_loop) => {
wayland::Window::new(events_loop, attribs).map(Window::Wayland)
wayland::Window::new(events_loop, attribs, pl_attribs).map(Window::Wayland)
},
EventsLoop::X(ref events_loop) => {
x11::Window::new(events_loop, attribs, pl_attribs).map(Window::X)
@@ -141,7 +154,7 @@ impl Window {
pub fn id(&self) -> WindowId {
match self {
&Window::X(ref w) => WindowId::X(w.id()),
&Window::Wayland(ref w) => WindowId::Wayland(w.id())
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
}
}
@@ -149,7 +162,7 @@ impl Window {
pub fn set_title(&self, title: &str) {
match self {
&Window::X(ref w) => w.set_title(title),
&Window::Wayland(ref w) => w.set_title(title)
&Window::Wayland(ref w) => w.set_title(title),
}
}
@@ -157,7 +170,7 @@ impl Window {
pub fn show(&self) {
match self {
&Window::X(ref w) => w.show(),
&Window::Wayland(ref w) => w.show()
&Window::Wayland(ref w) => w.show(),
}
}
@@ -165,20 +178,20 @@ impl Window {
pub fn hide(&self) {
match self {
&Window::X(ref w) => w.hide(),
&Window::Wayland(ref w) => w.hide()
&Window::Wayland(ref w) => w.hide(),
}
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
match self {
&Window::X(ref w) => w.get_position(),
&Window::Wayland(ref w) => w.get_position()
&Window::Wayland(ref w) => w.get_position(),
}
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
match self {
&Window::X(ref m) => m.get_inner_position(),
&Window::Wayland(ref m) => m.get_inner_position(),
@@ -186,53 +199,53 @@ impl Window {
}
#[inline]
pub fn set_position(&self, x: i32, y: i32) {
pub fn set_position(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_position(x, y),
&Window::Wayland(ref w) => w.set_position(x, y)
&Window::X(ref w) => w.set_position(position),
&Window::Wayland(ref w) => w.set_position(position),
}
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
pub fn get_inner_size(&self) -> Option<LogicalSize> {
match self {
&Window::X(ref w) => w.get_inner_size(),
&Window::Wayland(ref w) => w.get_inner_size()
&Window::Wayland(ref w) => w.get_inner_size(),
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
match self {
&Window::X(ref w) => w.get_outer_size(),
&Window::Wayland(ref w) => w.get_outer_size()
&Window::Wayland(ref w) => w.get_outer_size(),
}
}
#[inline]
pub fn set_inner_size(&self, x: u32, y: u32) {
pub fn set_inner_size(&self, size: LogicalSize) {
match self {
&Window::X(ref w) => w.set_inner_size(x, y),
&Window::Wayland(ref w) => w.set_inner_size(x, y)
&Window::X(ref w) => w.set_inner_size(size),
&Window::Wayland(ref w) => w.set_inner_size(size),
}
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_min_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions)
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions),
}
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_max_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions)
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions),
}
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
match self {
@@ -250,42 +263,34 @@ impl Window {
}
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
match self {
&Window::X(ref w) => w.set_cursor_state(state),
&Window::Wayland(ref w) => w.set_cursor_state(state)
&Window::X(ref window) => window.grab_cursor(grab),
&Window::Wayland(ref window) => window.grab_cursor(grab),
}
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
pub fn hide_cursor(&self, hide: bool) {
match self {
&Window::X(ref window) => window.hide_cursor(hide),
&Window::Wayland(ref window) => window.hide_cursor(hide),
}
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
match self {
&Window::X(ref w) => w.hidpi_factor(),
&Window::Wayland(ref w) => w.hidpi_factor()
&Window::X(ref w) => w.get_hidpi_factor(),
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
}
}
#[inline]
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
match self {
&Window::X(ref w) => w.set_cursor_position(x, y),
&Window::Wayland(ref w) => w.set_cursor_position(x, y)
}
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
match self {
&Window::X(ref w) => w.platform_display(),
&Window::Wayland(ref w) => w.get_display().c_ptr() as *mut _
}
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
match self {
&Window::X(ref w) => w.platform_window(),
&Window::Wayland(ref w) => w.get_surface().c_ptr() as *mut _
&Window::X(ref w) => w.set_cursor_position(position),
&Window::Wayland(ref w) => w.set_cursor_position(position),
}
}
@@ -297,6 +302,15 @@ impl Window {
}
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
match self {
&Window::X(ref w) => w.get_fullscreen(),
&Window::Wayland(ref w) => w.get_fullscreen()
.map(|monitor_id| RootMonitorId { inner: MonitorId::Wayland(monitor_id) })
}
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
match self {
@@ -330,9 +344,9 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
pub fn set_ime_spot(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.send_xim_spot(x as i16, y as i16),
&Window::X(ref w) => w.set_ime_spot(position),
&Window::Wayland(_) => (),
}
}
@@ -340,8 +354,37 @@ impl Window {
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
match self {
&Window::X(ref w) => RootMonitorId{inner: MonitorId::X(w.get_current_monitor())},
&Window::Wayland(ref w) => RootMonitorId{inner: MonitorId::Wayland(w.get_current_monitor())},
&Window::X(ref window) => RootMonitorId { inner: MonitorId::X(window.get_current_monitor()) },
&Window::Wayland(ref window) => RootMonitorId { inner: MonitorId::Wayland(window.get_current_monitor()) },
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
match self {
&Window::X(ref window) => window.get_available_monitors()
.into_iter()
.map(MonitorId::X)
.collect(),
&Window::Wayland(ref window) => window.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
}
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
match self {
&Window::X(ref window) => MonitorId::X(window.get_primary_monitor()),
&Window::Wayland(ref window) => MonitorId::Wayland(window.get_primary_monitor()),
}
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
match self {
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
}
}
@@ -350,29 +393,28 @@ unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
X11_BACKEND.with(|result| {
if let &Ok(ref xconn) = result {
let mut buf: [c_char; 1024] = mem::uninitialized();
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr(),
buf.len() as c_int,
);
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
let xconn_lock = X11_BACKEND.lock();
if let Ok(ref xconn) = *xconn_lock {
let mut buf: [c_char; 1024] = mem::uninitialized();
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr(),
buf.len() as c_int,
);
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
let error = XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
};
let error = XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
};
eprintln!("[winit X11 error] {:#?}", error);
error!("X11 error: {:#?}", error);
*xconn.latest_error.lock() = Some(error);
}
});
*xconn.latest_error.lock() = Some(error);
}
// Fun fact: this return value is completely ignored.
0
}
@@ -418,10 +460,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,
);
@@ -434,27 +473,29 @@ r#"Failed to initialize any backend!
}
pub fn new_x11() -> Result<EventsLoop, XNotSupported> {
X11_BACKEND.with(|result| {
result
.as_ref()
.map(Arc::clone)
.map(x11::EventsLoop::new)
.map(EventsLoop::X)
.map_err(|err| err.clone())
})
X11_BACKEND
.lock()
.as_ref()
.map(Arc::clone)
.map(x11::EventsLoop::new)
.map(EventsLoop::X)
.map_err(|err| err.clone())
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
match *self {
EventsLoop::Wayland(ref evlp) => evlp.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
EventsLoop::X(ref evlp) => x11::get_available_monitors(evlp.x_connection())
.into_iter()
.map(MonitorId::X)
.collect(),
EventsLoop::Wayland(ref evlp) => evlp
.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
EventsLoop::X(ref evlp) => evlp
.x_connection()
.get_available_monitors()
.into_iter()
.map(MonitorId::X)
.collect(),
}
}
@@ -462,7 +503,7 @@ r#"Failed to initialize any backend!
pub fn get_primary_monitor(&self) -> MonitorId {
match *self {
EventsLoop::Wayland(ref evlp) => MonitorId::Wayland(evlp.get_primary_monitor()),
EventsLoop::X(ref evlp) => MonitorId::X(x11::get_primary_monitor(evlp.x_connection())),
EventsLoop::X(ref evlp) => MonitorId::X(evlp.x_connection().get_primary_monitor()),
}
}

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};
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,50 @@ 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| {
match event {
GlobalEvent::New { id, ref interface, version } => {
if interface == "wl_seat" {
seat_manager.add_seat(id, version, registry)
}
},
GlobalEvent::Removed { id, ref interface } => {
if interface == "wl_seat" {
seat_manager.remove_seat(id)
}
},
}
},
).unwrap();
Ok(EventsLoop {
display: 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,
})
}
@@ -199,27 +223,15 @@ impl EventsLoop {
}
pub fn get_primary_monitor(&self) -> MonitorId {
self.env.outputs.with_all(|list| {
if let Some(&(_, ref proxy, _)) = list.first() {
MonitorId {
proxy: proxy.clone(),
mgr: self.env.outputs.clone(),
}
} else {
panic!("No monitor is available.")
}
})
get_primary_monitor(&self.env.outputs)
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
self.env.outputs.with_all(|list| {
list.iter()
.map(|&(_, ref proxy, _)| MonitorId {
proxy: proxy.clone(),
mgr: self.env.outputs.clone(),
})
.collect()
})
get_available_monitors(&self.env.outputs)
}
pub fn get_display(&self) -> &Display {
&*self.display
}
}
@@ -248,16 +260,24 @@ impl EventsLoop {
}
// process pending resize/refresh
self.store.lock().unwrap().for_each(
|newsize, refresh, frame_refresh, closed, wid, frame| {
|newsize, size, new_dpi, refresh, frame_refresh, closed, wid, frame| {
if let Some(frame) = frame {
if let Some((w, h)) = newsize {
frame.resize(w as u32, h as u32);
frame.resize(w, h);
frame.refresh();
sink.send_event(::WindowEvent::Resized(w as u32, h as u32), wid);
let logical_size = ::LogicalSize::new(w as f64, h as f64);
sink.send_event(::WindowEvent::Resized(logical_size), wid);
*size = (w, h);
} else if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
}
}
if let Some(dpi) = new_dpi {
sink.send_event(::WindowEvent::HiDpiFactorChanged(dpi as f64), wid);
}
if refresh {
sink.send_event(::WindowEvent::Refresh, wid);
}
@@ -277,43 +297,42 @@ 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 {
fn receive(&mut self, evt: GlobalEvent, registry: Proxy<wl_registry::WlRegistry>) {
impl SeatManager {
fn add_seat(&mut self, id: u32, version: u32, registry: Proxy<wl_registry::WlRegistry>) {
use self::wl_registry::RequestsTrait as RegistryRequests;
use std::cmp::min;
let mut seat_data = SeatData {
sink: self.sink.clone(),
store: self.store.clone(),
pointer: None,
keyboard: None,
touch: None,
events_loop_proxy: self.events_loop_proxy.clone(),
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
};
let seat = registry
.bind(min(version, 5), id, move |seat| {
seat.implement(move |event, seat| {
seat_data.receive(event, seat)
}, ())
})
.unwrap();
self.store.lock().unwrap().new_seat(&seat);
self.seats.lock().unwrap().push((id, seat));
}
fn remove_seat(&mut self, id: u32) {
use self::wl_seat::RequestsTrait as SeatRequests;
match evt {
GlobalEvent::New {
id,
ref interface,
version,
} if interface == "wl_seat" =>
{
use std::cmp::min;
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,
});
self.store.lock().unwrap().new_seat(&seat);
self.seats.lock().unwrap().push((id, seat));
let mut seats = self.seats.lock().unwrap();
if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
let (_, seat) = seats.swap_remove(idx);
if seat.version() >= 5 {
seat.release();
}
GlobalEvent::Removed { id, ref interface } if interface == "wl_seat" => {
let mut seats = self.seats.lock().unwrap();
if let Some(idx) = seats.iter().position(|&(i, _)| i == id) {
let (_, seat) = seats.swap_remove(idx);
if seat.version() >= 5 {
seat.release();
}
}
}
_ => (),
}
}
}
@@ -324,20 +343,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
@@ -352,8 +373,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
@@ -368,7 +391,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(),
))
@@ -434,9 +457,9 @@ impl fmt::Debug for MonitorId {
struct MonitorId {
name: Option<String>,
native_identifier: u32,
dimensions: (u32, u32),
position: (i32, i32),
hidpi_factor: f32,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: i32,
}
let monitor_id_proxy = MonitorId {
@@ -463,7 +486,7 @@ impl MonitorId {
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
}
pub fn get_dimensions(&self) -> (u32, u32) {
pub fn get_dimensions(&self) -> PhysicalSize {
match self.mgr.with_info(&self.proxy, |_, info| {
info.modes
.iter()
@@ -472,19 +495,44 @@ impl MonitorId {
}) {
Some(Some((w, h))) => (w as u32, h as u32),
_ => (0, 0),
}
}.into()
}
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
self.mgr
.with_info(&self.proxy, |_, info| info.location)
.unwrap_or((0, 0))
.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> i32 {
self.mgr
.with_info(&self.proxy, |_, info| info.scale_factor as f32)
.unwrap_or(1.0)
.with_info(&self.proxy, |_, info| info.scale_factor)
.unwrap_or(1)
}
}
pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorId {
outputs.with_all(|list| {
if let Some(&(_, ref proxy, _)) = list.first() {
MonitorId {
proxy: proxy.clone(),
mgr: outputs.clone(),
}
} else {
panic!("No monitor is available.")
}
})
}
pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorId> {
outputs.with_all(|list| {
list.iter()
.map(|&(_, ref proxy, _)| MonitorId {
proxy: proxy.clone(),
mgr: outputs.clone(),
})
.collect()
})
}

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),
@@ -247,7 +298,6 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
// => Some(VirtualKeyCode::LBracket),
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
// => Some(VirtualKeyCode::LMenu),
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
// => Some(VirtualKeyCode::LWin),
// => Some(VirtualKeyCode::Mail),
@@ -262,6 +312,13 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
// => Some(VirtualKeyCode::OEM102),
// => Some(VirtualKeyCode::Period),
// => Some(VirtualKeyCode::Playpause),
@@ -270,7 +327,6 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
// => Some(VirtualKeyCode::RBracket),
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
// => Some(VirtualKeyCode::RMenu),
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
// => Some(VirtualKeyCode::RWin),
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),

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),
// 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),
// 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 f32, y as f32),
phase: TouchPhase::Moved,
// TODO: replace dummy value with actual modifier state
modifiers: ModifiersState::default(),
},
wid,
);
} else {
let (mut x, mut y) = 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 f32, y as f32),
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,
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 as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid: wid,
location: (x, y),
id: id,
});
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,
location: pt.location,
id: id as u64,
phase: TouchPhase::Cancelled,
location: pt.location.into(),
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),
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,
id: pt.id as u64,
}),
pt.wid,
);
},
}
})
}, ())
}).unwrap()
}

View File

@@ -1,66 +1,62 @@
use raw_window_handle::unix::WaylandHandle;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use {CreationError, CursorState, MouseCursor, WindowAttributes};
use platform::MonitorId as PlatformMonitorId;
use {CreationError, MouseCursor, WindowAttributes};
use dpi::{LogicalPosition, LogicalSize};
use platform::{MonitorId as PlatformMonitorId, PlatformSpecificWindowBuilderAttributes as PlAttributes};
use window::MonitorId as RootMonitorId;
use sctk::window::{BasicFrame, Event as WEvent, Window as SWindow};
use sctk::surface::{get_dpi_factor, get_outputs};
use sctk::window::{ConceptFrame, Event as WEvent, State as WState, Window as SWindow, Theme};
use sctk::reexports::client::{Display, Proxy};
use sctk::reexports::client::protocol::{wl_seat, wl_surface};
use sctk::reexports::client::protocol::wl_compositor::RequestsTrait as CompositorRequests;
use sctk::reexports::client::protocol::wl_surface::RequestsTrait as SurfaceRequests;
use sctk::output::OutputMgr;
use super::{make_wid, EventsLoop, MonitorId, WindowId};
use platform::platform::wayland::event_loop::{get_available_monitors, get_primary_monitor};
pub struct Window {
surface: Proxy<wl_surface::WlSurface>,
frame: Arc<Mutex<SWindow<BasicFrame>>>,
monitors: Arc<Mutex<Vec<MonitorId>>>,
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
outputs: OutputMgr, // Access to info for all monitors
size: Arc<Mutex<(u32, u32)>>,
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
display: Arc<Display>,
need_frame_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
}
impl Window {
pub fn new(evlp: &EventsLoop, attributes: WindowAttributes) -> Result<Window, CreationError> {
let (width, height) = attributes.dimensions.unwrap_or((800, 600));
pub fn new(evlp: &EventsLoop, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result<Window, CreationError> {
let (width, height) = attributes.dimensions.map(Into::into).unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
// monitor tracking
let monitor_list = Arc::new(Mutex::new(Vec::new()));
let surface = evlp.env.compositor.create_surface().unwrap().implement({
let list = monitor_list.clone();
let omgr = evlp.env.outputs.clone();
move |event, _| match event {
wl_surface::Event::Enter { output } => list.lock().unwrap().push(MonitorId {
proxy: output,
mgr: omgr.clone(),
}),
wl_surface::Event::Leave { output } => {
list.lock().unwrap().retain(|m| !m.proxy.equals(&output));
}
}
let window_store = evlp.store.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
window_store.lock().unwrap().dpi_change(&surface, dpi);
surface.set_buffer_scale(dpi);
});
let window_store = evlp.store.clone();
let my_surface = surface.clone();
let mut frame = SWindow::<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 {
WEvent::Configure { new_size, .. } => {
move |event| match event {
WEvent::Configure { new_size, states } => {
let mut store = window_store.lock().unwrap();
let is_fullscreen = states.contains(&WState::Fullscreen);
for window in &mut store.windows {
if window.surface.equals(&my_surface) {
window.newsize = new_size.map(|(w, h)| (w as i32, h as i32));
window.newsize = new_size;
window.need_refresh = true;
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
@@ -87,6 +83,12 @@ impl Window {
},
).unwrap();
if let Some(app_id) = pl_attribs.app_id {
frame.set_app_id(app_id);
}
frame.set_title(attributes.title);
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
frame.new_seat(seat);
}
@@ -107,8 +109,8 @@ impl Window {
frame.set_decorate(attributes.decorations);
// min-max dimensions
frame.set_min_size(attributes.min_dimensions);
frame.set_max_size(attributes.max_dimensions);
frame.set_min_size(attributes.min_dimensions.map(Into::into));
frame.set_max_size(attributes.max_dimensions.map(Into::into));
let kill_switch = Arc::new(Mutex::new(false));
let need_frame_refresh = Arc::new(Mutex::new(true));
@@ -117,11 +119,15 @@ impl Window {
evlp.store.lock().unwrap().windows.push(InternalWindow {
closed: false,
newsize: None,
size: size.clone(),
fullscreen: fullscreen.clone(),
need_refresh: false,
need_frame_refresh: need_frame_refresh.clone(),
surface: surface.clone(),
kill_switch: kill_switch.clone(),
frame: Arc::downgrade(&frame),
current_dpi: 1,
new_dpi: None,
});
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
@@ -129,10 +135,11 @@ impl Window {
display: evlp.display.clone(),
surface: surface,
frame: frame,
monitors: monitor_list,
outputs: evlp.env.outputs.clone(),
size: size,
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
need_frame_refresh: need_frame_refresh,
fullscreen: fullscreen,
})
}
@@ -156,48 +163,49 @@ impl Window {
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn set_position(&self, _x: i32, _y: i32) {
pub fn set_position(&self, _pos: LogicalPosition) {
// Not possible with wayland
}
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
Some(self.size.lock().unwrap().clone())
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.size.lock().unwrap().clone().into())
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
Some((w as u32, h as u32))
Some((w, h).into())
}
#[inline]
// NOTE: This will only resize the borders, the contents must be updated by the user
pub fn set_inner_size(&self, x: u32, y: u32) {
self.frame.lock().unwrap().resize(x, y);
*(self.size.lock().unwrap()) = (x, y);
pub fn set_inner_size(&self, size: LogicalSize) {
let (w, h) = size.into();
self.frame.lock().unwrap().resize(w, h);
*(self.size.lock().unwrap()) = (w, h);
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
self.frame.lock().unwrap().set_min_size(dimensions);
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
self.frame.lock().unwrap().set_max_size(dimensions);
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into));
}
#[inline]
@@ -206,30 +214,8 @@ impl Window {
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
// TODO
}
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
use CursorState::{Grab, Hide, Normal};
// TODO : not yet possible on wayland to grab cursor
match state {
Grab => Err("Cursor cannot be grabbed on wayland yet.".to_string()),
Hide => Err("Cursor cannot be hidden on wayland yet.".to_string()),
Normal => Ok(()),
}
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
let mut factor: f32 = 1.0;
let guard = self.monitors.lock().unwrap();
for monitor_id in guard.iter() {
let hidpif = monitor_id.get_hidpi_factor();
factor = factor.max(hidpif);
}
factor
pub fn hidpi_factor(&self) -> i32 {
get_dpi_factor(&self.surface)
}
pub fn set_decorations(&self, decorate: bool) {
@@ -245,6 +231,14 @@ impl Window {
}
}
pub fn get_fullscreen(&self) -> Option<MonitorId> {
if *(self.fullscreen.lock().unwrap()) {
Some(self.get_current_monitor())
} else {
None
}
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
if let Some(RootMonitorId {
inner: PlatformMonitorId::Wayland(ref monitor_id),
@@ -259,10 +253,29 @@ impl Window {
}
}
pub fn set_theme<T: Theme>(&self, theme: T) {
self.frame.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
// TODO: not yet possible on wayland
Err(())
pub fn set_cursor(&self, _cursor: MouseCursor) {
// TODO
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// TODO: This isn't possible on Wayland yet
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not yet possible on Wayland.".to_owned())
}
#[inline]
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), String> {
Err("Setting the cursor position is not yet possible on Wayland.".to_owned())
}
pub fn get_display(&self) -> &Display {
@@ -274,10 +287,27 @@ impl Window {
}
pub fn get_current_monitor(&self) -> MonitorId {
// we don't know how much each monitor sees us so...
// just return the most recent one ?
let guard = self.monitors.lock().unwrap();
guard.last().unwrap().clone()
let output = get_outputs(&self.surface).last().unwrap().clone();
MonitorId {
proxy: output,
mgr: self.outputs.clone(),
}
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors(&self.outputs)
}
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor(&self.outputs)
}
pub fn raw_window_handle(&self) -> WaylandHandle {
WaylandHandle {
surface: self.get_surface().c_ptr() as *mut _,
display: self.get_display().c_ptr() as *mut _,
..WaylandHandle::empty()
}
}
}
@@ -294,12 +324,16 @@ impl Drop for Window {
struct InternalWindow {
surface: Proxy<wl_surface::WlSurface>,
newsize: Option<(i32, i32)>,
newsize: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
fullscreen: Arc<Mutex<bool>>,
need_refresh: bool,
need_frame_refresh: Arc<Mutex<bool>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<BasicFrame>>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>,
}
pub struct WindowStore {
@@ -345,21 +379,34 @@ impl WindowStore {
}
}
fn dpi_change(&mut self, surface: &Proxy<wl_surface::WlSurface>, new: i32) {
for window in &mut self.windows {
if surface.equals(&window.surface) {
window.new_dpi = Some(new);
}
}
}
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(Option<(i32, i32)>, bool, bool, bool, WindowId, Option<&mut 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();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
window.newsize.take(),
&mut *(window.size.lock().unwrap()),
window.new_dpi,
window.need_refresh,
::std::mem::replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
window.closed,
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
window.need_refresh = false;
// avoid re-spamming the event
window.closed = false;

View File

@@ -82,11 +82,11 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
ffi::XK_KP_Delete => events::VirtualKeyCode::Delete,
ffi::XK_KP_Equal => events::VirtualKeyCode::NumpadEquals,
//ffi::XK_KP_Multiply => events::VirtualKeyCode::NumpadMultiply,
//ffi::XK_KP_Add => events::VirtualKeyCode::NumpadAdd,
ffi::XK_KP_Add => events::VirtualKeyCode::Add,
//ffi::XK_KP_Separator => events::VirtualKeyCode::Kp_separator,
//ffi::XK_KP_Subtract => events::VirtualKeyCode::NumpadSubtract,
ffi::XK_KP_Subtract => events::VirtualKeyCode::Subtract,
//ffi::XK_KP_Decimal => events::VirtualKeyCode::Kp_decimal,
//ffi::XK_KP_Divide => events::VirtualKeyCode::NumpadDivide,
ffi::XK_KP_Divide => events::VirtualKeyCode::Divide,
ffi::XK_KP_0 => events::VirtualKeyCode::Numpad0,
ffi::XK_KP_1 => events::VirtualKeyCode::Numpad1,
ffi::XK_KP_2 => events::VirtualKeyCode::Numpad2,
@@ -117,23 +117,23 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_L4 => events::VirtualKeyCode::L4,
ffi::XK_F15 => events::VirtualKeyCode::F15,
//ffi::XK_L5 => events::VirtualKeyCode::L5,
//ffi::XK_F16 => events::VirtualKeyCode::F16,
ffi::XK_F16 => events::VirtualKeyCode::F16,
//ffi::XK_L6 => events::VirtualKeyCode::L6,
//ffi::XK_F17 => events::VirtualKeyCode::F17,
ffi::XK_F17 => events::VirtualKeyCode::F17,
//ffi::XK_L7 => events::VirtualKeyCode::L7,
//ffi::XK_F18 => events::VirtualKeyCode::F18,
ffi::XK_F18 => events::VirtualKeyCode::F18,
//ffi::XK_L8 => events::VirtualKeyCode::L8,
//ffi::XK_F19 => events::VirtualKeyCode::F19,
ffi::XK_F19 => events::VirtualKeyCode::F19,
//ffi::XK_L9 => events::VirtualKeyCode::L9,
//ffi::XK_F20 => events::VirtualKeyCode::F20,
ffi::XK_F20 => events::VirtualKeyCode::F20,
//ffi::XK_L10 => events::VirtualKeyCode::L10,
//ffi::XK_F21 => events::VirtualKeyCode::F21,
ffi::XK_F21 => events::VirtualKeyCode::F21,
//ffi::XK_R1 => events::VirtualKeyCode::R1,
//ffi::XK_F22 => events::VirtualKeyCode::F22,
ffi::XK_F22 => events::VirtualKeyCode::F22,
//ffi::XK_R2 => events::VirtualKeyCode::R2,
//ffi::XK_F23 => events::VirtualKeyCode::F23,
ffi::XK_F23 => events::VirtualKeyCode::F23,
//ffi::XK_R3 => events::VirtualKeyCode::R3,
//ffi::XK_F24 => events::VirtualKeyCode::F24,
ffi::XK_F24 => events::VirtualKeyCode::F24,
//ffi::XK_R4 => events::VirtualKeyCode::R4,
//ffi::XK_F25 => events::VirtualKeyCode::F25,
//ffi::XK_R5 => events::VirtualKeyCode::R5,

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;
@@ -9,13 +9,7 @@ mod dnd;
mod ime;
pub mod util;
pub use self::monitor::{
MonitorId,
get_available_monitors,
get_monitor_for_window,
get_primary_monitor,
invalidate_cached_monitor_list,
};
pub use self::monitor::MonitorId;
pub use self::window::UnownedWindow;
pub use self::xdisplay::{XConnection, XNotSupported, XError};
@@ -25,11 +19,17 @@ use std::collections::HashMap;
use std::ffi::CStr;
use std::ops::Deref;
use std::os::raw::*;
use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF};
#[cfg(target_os = "linux")]
use libc::__errno_location;
#[cfg(target_os = "freebsd")]
use libc::__error as __errno_location;
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
use libc::__errno as __errno_location;
use std::sync::{Arc, mpsc, Weak};
use std::sync::atomic::{self, AtomicBool};
use libc::{self, setlocale, LC_CTYPE};
use parking_lot::Mutex;
use {
ControlFlow,
@@ -38,6 +38,8 @@ use {
Event,
EventsLoopClosed,
KeyboardInput,
LogicalPosition,
LogicalSize,
WindowAttributes,
WindowEvent,
};
@@ -92,7 +94,7 @@ impl EventsLoop {
result.expect("Failed to set input method destruction callback")
});
let randr_event_offset = monitor::select_input(&xconn, root)
let randr_event_offset = xconn.select_xrandr_input(root)
.expect("Failed to query XRandR extension");
let xi2ext = unsafe {
@@ -190,6 +192,70 @@ impl EventsLoop {
}
}
unsafe fn poll_one_event(&mut self, event_ptr : *mut ffi::XEvent) -> bool {
// This function is used to poll and remove a single event
// from the Xlib event queue in a non-blocking, atomic way.
// XCheckIfEvent is non-blocking and removes events from queue.
// XNextEvent can't be used because it blocks while holding the
// global Xlib mutex.
// XPeekEvent does not remove events from the queue.
unsafe extern "C" fn predicate(
_display: *mut ffi::Display,
_event: *mut ffi::XEvent,
_arg : *mut c_char) -> c_int {
// This predicate always returns "true" (1) to accept all events
1
}
let result = (self.xconn.xlib.XCheckIfEvent)(
self.xconn.display,
event_ptr,
Some(predicate),
std::ptr::null_mut());
result != 0
}
unsafe fn wait_for_input(&mut self) {
// XNextEvent can not be used in multi-threaded applications
// because it is blocking for input while holding the global
// Xlib mutex.
// To work around this issue, first flush the X11 display, then
// use select(2) to wait for input to arrive
loop {
// First use XFlush to flush any buffered x11 requests
(self.xconn.xlib.XFlush)(self.xconn.display);
// Then use select(2) to wait for input data
let mut fds : fd_set = mem::uninitialized();
FD_ZERO(&mut fds);
FD_SET(self.xconn.x11_fd, &mut fds);
let err = select(
self.xconn.x11_fd + 1,
&mut fds, // read fds
std::ptr::null_mut(), // write fds
std::ptr::null_mut(), // except fds (could be used to detect errors)
std::ptr::null_mut()); // timeout
if err < 0 {
let errno_ptr = __errno_location();
let errno = *errno_ptr;
if errno == EINTR {
// try again if errno is EINTR
continue;
}
assert!(errno == EBADF || errno == EINVAL || errno == ENOMEM);
panic!("select(2) returned fatal error condition");
}
if FD_ISSET(self.xconn.x11_fd, &mut fds) {
break;
}
}
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(Event)
{
@@ -197,13 +263,9 @@ impl EventsLoop {
loop {
// Get next event
unsafe {
// Ensure XNextEvent won't block
let count = (self.xconn.xlib.XPending)(self.xconn.display);
if count == 0 {
if !self.poll_one_event(&mut xev) {
break;
}
(self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev);
}
self.process_event(&mut xev, &mut callback);
}
@@ -215,7 +277,12 @@ impl EventsLoop {
let mut xev = unsafe { mem::uninitialized() };
loop {
unsafe { (self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev) }; // Blocks as necessary
unsafe {
while !self.poll_one_event(&mut xev) {
// block until input is available
self.wait_for_input();
}
};
let mut control_flow = ControlFlow::Continue;
@@ -283,25 +350,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.
@@ -323,23 +390,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 {
@@ -348,10 +413,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 {
@@ -394,6 +465,13 @@ impl EventsLoop {
}
ffi::ConfigureNotify => {
#[derive(Debug, Default)]
struct Events {
resized: Option<WindowEvent>,
moved: Option<WindowEvent>,
dpi_changed: Option<WindowEvent>,
}
let xev: &ffi::XConfigureEvent = xev.as_ref();
let xwindow = xev.window;
let events = self.with_window(xwindow, |window| {
@@ -406,12 +484,14 @@ impl EventsLoop {
// that has a position relative to the parent window.
let is_synthetic = xev.send_event == ffi::True;
// These are both in physical space.
let new_inner_size = (xev.width as u32, xev.height as u32);
let new_inner_position = (xev.x as i32, xev.y as i32);
let mut monitor = window.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)
@@ -431,14 +511,9 @@ impl EventsLoop {
(resized, moved)
};
let capacity = resized as usize + moved as usize;
let mut events = Vec::with_capacity(capacity);
let mut events = Events::default();
if resized {
events.push(WindowEvent::Resized(new_inner_size.0, new_inner_size.1));
}
if moved || shared_state_lock.position.is_none() {
let new_outer_position = if moved || shared_state_lock.position.is_none() {
// We need to convert client area position to window position.
let frame_extents = shared_state_lock.frame_extents
.as_ref()
@@ -451,19 +526,89 @@ impl EventsLoop {
let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
shared_state_lock.position = Some(outer);
if moved {
events.push(WindowEvent::Moved(outer.0, outer.1));
let logical_position = LogicalPosition::from_physical(outer, monitor.hidpi_factor);
events.moved = Some(WindowEvent::Moved(logical_position));
}
outer
} else {
shared_state_lock.position.unwrap()
};
if is_synthetic {
// If we don't use the existing adjusted value when available, then the user can screw up the
// resizing by dragging across monitors *without* dropping the window.
let (width, height) = shared_state_lock.dpi_adjusted
.unwrap_or_else(|| (xev.width as f64, xev.height as f64));
let last_hidpi_factor = shared_state_lock.guessed_dpi
.take()
.unwrap_or_else(|| {
shared_state_lock.last_monitor
.as_ref()
.map(|last_monitor| last_monitor.hidpi_factor)
.unwrap_or(1.0)
});
let new_hidpi_factor = {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
monitor = self.xconn.get_monitor_for_window(Some(window_rect));
let new_hidpi_factor = monitor.hidpi_factor;
shared_state_lock.last_monitor = Some(monitor.clone());
new_hidpi_factor
};
if last_hidpi_factor != new_hidpi_factor {
events.dpi_changed = Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor));
let (new_width, new_height, flusher) = window.adjust_for_dpi(
last_hidpi_factor,
new_hidpi_factor,
width,
height,
);
flusher.queue();
shared_state_lock.dpi_adjusted = Some((new_width, new_height));
// if the DPI factor changed, force a resize event to ensure the logical
// size is computed with the right DPI factor
resized = true;
}
}
// This is a hack to ensure that the DPI adjusted resize is actually applied on all WMs. KWin
// doesn't need this, but Xfwm does. The hack should not be run on other WMs, since tiling
// WMs constrain the window size, making the resize fail. This would cause an endless stream of
// XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU.
if let Some(adjusted_size) = shared_state_lock.dpi_adjusted {
let rounded_size = (adjusted_size.0.round() as u32, adjusted_size.1.round() as u32);
if new_inner_size == rounded_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
// When this finally happens, the event will not be synthetic.
shared_state_lock.dpi_adjusted = None;
} else {
unsafe {
(self.xconn.xlib.XResizeWindow)(
self.xconn.display,
xwindow,
rounded_size.0 as c_uint,
rounded_size.1 as c_uint,
);
}
}
}
if resized {
let logical_size = LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor);
events.resized = Some(WindowEvent::Resized(logical_size));
}
events
});
if let Some(events) = events {
for event in events {
callback(Event::WindowEvent {
window_id: mkwid(xwindow),
event,
});
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 });
}
}
}
@@ -529,7 +674,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
@@ -694,14 +839,25 @@ impl EventsLoop {
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
});
if cursor_moved == Some(true) {
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id,
position: new_cursor_pos,
modifiers,
},
let dpi_factor = self.with_window(xev.event, |window| {
window.get_hidpi_factor()
});
if let Some(dpi_factor) = dpi_factor {
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id,
position,
modifiers,
},
});
} else {
return;
}
} else if cursor_moved.is_none() {
return;
}
@@ -782,19 +938,35 @@ impl EventsLoop {
event: CursorEntered { device_id },
});
let new_cursor_pos = (xev.event_x, xev.event_y);
if let Some(dpi_factor) = self.with_window(xev.event, |window| {
window.get_hidpi_factor()
}) {
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
// The mods field on this event isn't actually populated, so query the
// pointer device. In the future, we can likely remove this round-trip by
// relying on Xkb for modifier values.
let modifiers = self.xconn.query_pointer(xev.event, xev.deviceid)
.expect("Failed to query pointer device").get_modifier_state();
// The mods field on this event isn't actually populated, so query the
// pointer device. In the future, we can likely remove this round-trip by
// relying on `Xkb` for modifier values.
//
// This needs to only be done after confirming the window still exists,
// since otherwise we risk getting a `BadWindow` error if the window was
// dropped with queued events.
let modifiers = self.xconn
.query_pointer(xev.event, xev.deviceid)
.expect("Failed to query pointer device")
.get_modifier_state();
callback(Event::WindowEvent { window_id, event: CursorMoved {
device_id,
position: new_cursor_pos,
modifiers,
}})
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id,
position,
modifiers,
},
});
}
}
ffi::XI_Leave => {
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
@@ -812,7 +984,12 @@ impl EventsLoop {
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
if !self.window_exists(xev.event) { return; }
let dpi_factor = match self.with_window(xev.event, |window| {
window.get_hidpi_factor()
}) {
Some(dpi_factor) => dpi_factor,
None => return,
};
let window_id = mkwid(xev.event);
self.ime
@@ -830,11 +1007,15 @@ impl EventsLoop {
.map(|device| device.attachment)
.unwrap_or(2);
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position: (xev.event_x, xev.event_y),
position,
modifiers: ModifiersState::from(xev.mods),
}
});
@@ -861,15 +1042,24 @@ impl EventsLoop {
ffi::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!()
};
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid),
phase,
location: (xev.event_x, xev.event_y),
id: xev.detail as u64,
},
)})
let dpi_factor = self.with_window(xev.event, |window| {
window.get_hidpi_factor()
});
if let Some(dpi_factor) = dpi_factor {
let location = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid),
phase,
location,
id: xev.detail as u64,
}),
})
}
}
ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => {
@@ -986,7 +1176,44 @@ impl EventsLoop {
_ => {
if event_type == self.randr_event_offset {
// In the future, it would be quite easy to emit monitor hotplug events.
monitor::invalidate_cached_monitor_list();
let prev_list = monitor::invalidate_cached_monitor_list();
if let Some(prev_list) = prev_list {
let new_list = self.xconn.get_available_monitors();
for new_monitor in new_list {
prev_list
.iter()
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
.map(|prev_monitor| {
if new_monitor.hidpi_factor != prev_monitor.hidpi_factor {
for (window_id, window) in self.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.get_current_monitor();
if monitor.name == new_monitor.name {
callback(Event::WindowEvent {
window_id: mkwid(window_id.0),
event: WindowEvent::HiDpiFactorChanged(
new_monitor.hidpi_factor
),
});
let (width, height) = match window.get_inner_size_physical() {
Some(result) => result,
None => continue,
};
let (_, _, flusher) = window.adjust_for_dpi(
prev_monitor.hidpi_factor,
new_monitor.hidpi_factor,
width as f64,
height as f64,
);
flusher.queue();
}
}
}
}
});
}
}
}
},
}
@@ -1107,19 +1334,28 @@ impl<'a> Deref for DeviceInfo<'a> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
pub struct Window {
pub window: Arc<UnownedWindow>,
ime_sender: Mutex<ImeSender>,
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
pub struct Window(Arc<UnownedWindow>);
impl Deref for Window {
type Target = UnownedWindow;
#[inline]
fn deref(&self) -> &UnownedWindow {
&*self.window
&*self.0
}
}
@@ -1130,35 +1366,19 @@ impl Window {
pl_attribs: PlatformSpecificWindowBuilderAttributes
) -> Result<Self, CreationError> {
let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?);
event_loop.windows
.borrow_mut()
.insert(window.id(), Arc::downgrade(&window));
event_loop.ime
.borrow_mut()
.create_context(window.id().0)
.expect("Failed to create input context");
Ok(Window {
window,
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
})
}
#[inline]
pub fn send_xim_spot(&self, x: i16, y: i16) {
let _ = self.ime_sender
.lock()
.send((self.window.id().0, x, y));
Ok(Window(window))
}
}
impl Drop for Window {
fn drop(&mut self) {
let xconn = &self.window.xconn;
let window = self.deref();
let xconn = &window.xconn;
unsafe {
(xconn.xlib.XDestroyWindow)(xconn.display, self.window.id().0);
(xconn.xlib.XDestroyWindow)(xconn.display, window.id().0);
// If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about.
let _ = xconn.check_errors();
}

View File

@@ -1,8 +1,9 @@
use std::os::raw::*;
use std::sync::Arc;
use parking_lot::Mutex;
use {PhysicalPosition, PhysicalSize};
use super::{util, XConnection, XError};
use super::ffi::{
RRCrtcChangeNotifyMask,
RROutputPropertyNotifyMask,
@@ -11,7 +12,6 @@ use super::ffi::{
Window,
XRRScreenResources,
};
use super::{util, XConnection, XError};
// Used to test XRandR < 1.5 code path. This should always be committed as false.
const FORCE_RANDR_COMPAT: bool = false;
@@ -45,7 +45,7 @@ pub struct MonitorId {
/// The actual id
id: u32,
/// The name of the monitor
name: String,
pub(crate) name: String,
/// The size of the monitor
dimensions: (u32, u32),
/// The position of the monitor in the X screen
@@ -53,23 +53,23 @@ pub struct MonitorId {
/// If the monitor is the primary one
primary: bool,
/// The DPI scale factor
pub(crate) hidpi_factor: f32,
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 {
fn from_repr(
xconn: &Arc<XConnection>,
xconn: &XConnection,
resources: *mut XRRScreenResources,
id: u32,
repr: util::MonitorRepr,
primary: bool,
) -> Self {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr) };
) -> Option<Self> {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
let rect = util::Rect::new(position, dimensions);
MonitorId {
let rect = util::AaRect::new(position, dimensions);
Some(MonitorId {
id,
name,
hidpi_factor,
@@ -77,7 +77,7 @@ impl MonitorId {
position,
primary,
rect,
}
})
}
pub fn get_name(&self) -> Option<String> {
@@ -89,182 +89,186 @@ impl MonitorId {
self.id as u32
}
pub fn get_dimensions(&self) -> (u32, u32) {
self.dimensions
pub fn get_dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
pub fn get_position(&self) -> (i32, i32) {
self.position
pub fn get_position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}
pub fn get_monitor_for_window(
xconn: &Arc<XConnection>,
window_rect: Option<util::Rect>,
) -> MonitorId {
let monitors = get_available_monitors(xconn);
let default = monitors
.get(0)
.expect("[winit] Failed to find any monitors using XRandR.");
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorId {
let monitors = self.get_available_monitors();
let default = monitors
.get(0)
.expect("[winit] Failed to find any monitors using XRandR.");
let window_rect = match window_rect {
Some(rect) => rect,
None => return default.to_owned(),
};
let window_rect = match window_rect {
Some(rect) => rect,
None => return default.to_owned(),
};
let mut largest_overlap = 0;
let mut matched_monitor = default;
for monitor in &monitors {
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
if overlapping_area > largest_overlap {
largest_overlap = overlapping_area;
matched_monitor = &monitor;
let mut largest_overlap = 0;
let mut matched_monitor = default;
for monitor in &monitors {
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
if overlapping_area > largest_overlap {
largest_overlap = overlapping_area;
matched_monitor = &monitor;
}
}
matched_monitor.to_owned()
}
matched_monitor.to_owned()
}
fn query_monitor_list(xconn: &Arc<XConnection>) -> Vec<MonitorId> {
unsafe {
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
let resources = (xconn.xrandr.XRRGetScreenResources)(xconn.display, root);
if resources.is_null() {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
let mut available;
let mut has_primary = false;
if xconn.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
// videowalls.
let xrandr_1_5 = xconn.xrandr_1_5.as_ref().unwrap();
let mut monitor_count = 0;
let monitors = (xrandr_1_5.XRRGetMonitors)(xconn.display, root, 1, &mut monitor_count);
assert!(monitor_count >= 0);
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
has_primary |= is_primary;
available.push(MonitorId::from_repr(
xconn,
resources,
monitor_index as u32,
monitor.into(),
is_primary,
));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (xconn.xrandr.XRRGetOutputPrimary)(xconn.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (xconn.xrandr.XRRGetCrtcInfo)(xconn.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
available.push(MonitorId::from_repr(
xconn,
resources,
crtc_id as u32,
crtc,
is_primary,
));
}
(xconn.xrandr.XRRFreeCrtcInfo)(crtc);
}
}
// If no monitors were detected as being primary, we just pick one ourselves!
if !has_primary {
if let Some(ref mut fallback) = available.first_mut() {
// Setting this here will come in handy if we ever add an `is_primary` method.
fallback.primary = true;
}
}
(xconn.xrandr.XRRFreeScreenResources)(resources);
available
}
}
pub fn get_available_monitors(xconn: &Arc<XConnection>) -> Vec<MonitorId> {
let mut monitors_lock = MONITORS.lock();
(*monitors_lock)
.as_ref()
.cloned()
.or_else(|| {
let monitors = Some(query_monitor_list(xconn));
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
})
.unwrap()
}
#[inline]
pub fn get_primary_monitor(xconn: &Arc<XConnection>) -> MonitorId {
get_available_monitors(xconn)
.into_iter()
.find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.")
}
pub fn select_input(xconn: &Arc<XConnection>, root: Window) -> Result<c_int, XError> {
{
let mut version_lock = XRANDR_VERSION.lock();
if version_lock.is_none() {
let mut major = 0;
let mut minor = 0;
let has_extension = unsafe {
(xconn.xrandr.XRRQueryVersion)(
xconn.display,
&mut major,
&mut minor,
)
fn query_monitor_list(&self) -> Vec<MonitorId> {
unsafe {
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if version_is_at_least(1, 3) {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
if has_extension != True {
panic!("[winit] XRandR extension not available.");
if resources.is_null() {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
*version_lock = Some((major, minor));
let mut available;
let mut has_primary = false;
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
// videowalls.
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
let mut monitor_count = 0;
let monitors = (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
assert!(monitor_count >= 0);
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
has_primary |= is_primary;
MonitorId::from_repr(
self,
resources,
monitor_index as u32,
monitor.into(),
is_primary,
).map(|monitor_id| available.push(monitor_id));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
MonitorId::from_repr(
self,
resources,
crtc_id as u32,
crtc,
is_primary,
).map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}
}
// If no monitors were detected as being primary, we just pick one ourselves!
if !has_primary {
if let Some(ref mut fallback) = available.first_mut() {
// Setting this here will come in handy if we ever add an `is_primary` method.
fallback.primary = true;
}
}
(self.xrandr.XRRFreeScreenResources)(resources);
available
}
}
let mut event_offset = 0;
let mut error_offset = 0;
let status = unsafe {
(xconn.xrandr.XRRQueryExtension)(
xconn.display,
&mut event_offset,
&mut error_offset,
)
};
if status != True {
xconn.check_errors()?;
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
pub fn get_available_monitors(&self) -> Vec<MonitorId> {
let mut monitors_lock = MONITORS.lock();
(*monitors_lock)
.as_ref()
.cloned()
.or_else(|| {
let monitors = Some(self.query_monitor_list());
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
})
.unwrap()
}
let mask = RRCrtcChangeNotifyMask
| RROutputPropertyNotifyMask
| RRScreenChangeNotifyMask;
unsafe { (xconn.xrandr.XRRSelectInput)(xconn.display, root, mask) };
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
self.get_available_monitors()
.into_iter()
.find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.")
}
Ok(event_offset)
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
{
let mut version_lock = XRANDR_VERSION.lock();
if version_lock.is_none() {
let mut major = 0;
let mut minor = 0;
let has_extension = unsafe {
(self.xrandr.XRRQueryVersion)(
self.display,
&mut major,
&mut minor,
)
};
if has_extension != True {
panic!("[winit] XRandR extension not available.");
}
*version_lock = Some((major, minor));
}
}
let mut event_offset = 0;
let mut error_offset = 0;
let status = unsafe {
(self.xrandr.XRRQueryExtension)(
self.display,
&mut event_offset,
&mut error_offset,
)
};
if status != True {
self.check_errors()?;
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
}
let mask = RRCrtcChangeNotifyMask
| RROutputPropertyNotifyMask
| RRScreenChangeNotifyMask;
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
Ok(event_offset)
}
}

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

@@ -1,35 +1,36 @@
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
}
@@ -78,6 +79,24 @@ impl FrameExtents {
pub fn from_border(border: c_ulong) -> Self {
Self::new(border, border, border, border)
}
pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
let logicalize = |value: c_ulong| value as f64 / factor;
LogicalFrameExtents {
left: logicalize(self.left),
right: logicalize(self.right),
top: logicalize(self.top),
bottom: logicalize(self.bottom),
}
}
}
#[derive(Debug, Clone)]
pub struct LogicalFrameExtents {
pub left: f64,
pub right: f64,
pub top: f64,
pub bottom: f64,
}
#[derive(Debug, Clone, PartialEq)]
@@ -103,6 +122,16 @@ impl FrameExtentsHeuristic {
}
}
pub fn inner_pos_to_outer_logical(&self, mut logical: LogicalPosition, factor: f64) -> LogicalPosition {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
let frame_extents = self.frame_extents.as_logical(factor);
logical.x -= frame_extents.left;
logical.y -= frame_extents.top;
}
logical
}
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
@@ -113,6 +142,13 @@ impl FrameExtentsHeuristic {
),
)
}
pub fn inner_size_to_outer_logical(&self, mut logical: LogicalSize, factor: f64) -> LogicalSize {
let frame_extents = self.frame_extents.as_logical(factor);
logical.width += frame_extents.left + frame_extents.right;
logical.height += frame_extents.top + frame_extents.bottom;
logical
}
}
impl XConnection {

View File

@@ -22,8 +22,9 @@ 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)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
@@ -41,6 +42,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,12 +81,111 @@ 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) }
}
}
pub struct NormalHints<'a> {
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
}
impl<'a> NormalHints<'a> {
pub fn new(xconn: &'a XConnection) -> Self {
NormalHints { size_hints: xconn.alloc_size_hints() }
}
pub fn has_flag(&self, flag: c_long) -> bool {
has_flag(self.size_hints.flags, flag)
}
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
if self.has_flag(flag) {
Some((*field1 as _, *field2 as _))
} else {
None
}
}
pub fn get_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
self.size_hints.flags |= ffi::PSize;
self.size_hints.width = width as c_int;
self.size_hints.height = height as c_int;
} else {
self.size_hints.flags &= !ffi::PSize;
}
}
pub fn get_max_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PMaxSize, &self.size_hints.max_width, &self.size_hints.max_height)
}
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
if let Some((max_width, max_height)) = max_size {
self.size_hints.flags |= ffi::PMaxSize;
self.size_hints.max_width = max_width as c_int;
self.size_hints.max_height = max_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMaxSize;
}
}
pub fn get_min_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PMinSize, &self.size_hints.min_width, &self.size_hints.min_height)
}
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
if let Some((min_width, min_height)) = min_size {
self.size_hints.flags |= ffi::PMinSize;
self.size_hints.min_width = min_width as c_int;
self.size_hints.min_height = min_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMinSize;
}
}
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
self.getter(ffi::PResizeInc, &self.size_hints.width_inc, &self.size_hints.height_inc)
}
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
if let Some((width_inc, height_inc)) = resize_increments {
self.size_hints.flags |= ffi::PResizeInc;
self.size_hints.width_inc = width_inc as c_int;
self.size_hints.height_inc = height_inc as c_int;
} else {
self.size_hints.flags &= !ffi::PResizeInc;
}
}
pub fn get_base_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PBaseSize, &self.size_hints.base_width, &self.size_hints.base_height)
}
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
if let Some((base_width, base_height)) = base_size {
self.size_hints.flags |= ffi::PBaseSize;
self.size_hints.base_width = base_width as c_int;
self.size_hints.base_height = base_height as c_int;
} else {
self.size_hints.flags &= !ffi::PBaseSize;
}
}
}
impl XConnection {
pub fn get_wm_hints(&self, window: ffi::Window) -> Result<XSmartPointer<ffi::XWMHints>, XError> {
let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
@@ -90,4 +208,29 @@ impl XConnection {
}
Flusher::new(self)
}
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints, XError> {
let size_hints = self.alloc_size_hints();
let mut supplied_by_user: c_long = unsafe { mem::uninitialized() };
unsafe {
(self.xlib.XGetWMNormalHints)(
self.display,
window,
size_hints.ptr,
&mut supplied_by_user,
);
}
self.check_errors().map(|_| NormalHints { size_hints })
}
pub fn set_normal_hints(&self, window: ffi::Window, normal_hints: NormalHints) -> Flusher {
unsafe {
(self.xlib.XSetWMNormalHints)(
self.display,
window,
normal_hints.size_hints.ptr,
);
}
Flusher::new(self)
}
}

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

@@ -27,10 +27,16 @@ pub use self::wm::*;
use std::mem;
use std::ptr;
use std::ops::BitAnd;
use std::os::raw::*;
use super::{ffi, XConnection, XError};
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
let b_ptr = a as *const _ as *const B;
unsafe { &*b_ptr }
}
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
let wrapped = Some(value);
if *field != wrapped {
@@ -41,6 +47,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where T:
Copy + PartialEq + BitAnd<T, Output = T>
{
bitset & flag == flag
}
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
pub struct Flusher<'a> {
xconn: &'a XConnection,

View File

@@ -1,21 +1,49 @@
use std::{env, slice};
use std::str::FromStr;
use validate_hidpi_factor;
use super::*;
use super::ffi::{
RROutput,
XRRCrtcInfo,
XRRMonitorInfo,
XRRScreenResources,
};
pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32),
(width_mm, height_mm): (u64, u64),
) -> f64 {
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
let dpi_override = env::var("WINIT_HIDPI_FACTOR")
.ok()
.and_then(|var| f64::from_str(&var).ok());
if let Some(dpi_override) = dpi_override {
if !validate_hidpi_factor(dpi_override) {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`",
dpi_override,
);
}
return dpi_override;
}
// See http://xpra.org/trac/ticket/728 for more information.
if width_mm == 0 || height_mm == 0 {
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
return 1.0;
}
let ppmm = (
(width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)
).sqrt();
// Quantize 1/12 step size
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
assert!(validate_hidpi_factor(dpi_factor));
dpi_factor
}
pub enum MonitorRepr {
Monitor(*mut XRRMonitorInfo),
Crtc(*mut XRRCrtcInfo),
Monitor(*mut ffi::XRRMonitorInfo),
Crtc(*mut ffi::XRRCrtcInfo),
}
impl MonitorRepr {
pub unsafe fn get_output(&self) -> RROutput {
pub unsafe fn get_output(&self) -> ffi::RROutput {
match *self {
// Same member names, but different locations within the struct...
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
@@ -38,62 +66,69 @@ impl MonitorRepr {
}
}
impl From<*mut XRRMonitorInfo> for MonitorRepr {
fn from(monitor: *mut XRRMonitorInfo) -> Self {
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
MonitorRepr::Monitor(monitor)
}
}
impl From<*mut XRRCrtcInfo> for MonitorRepr {
fn from(crtc: *mut XRRCrtcInfo) -> Self {
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
MonitorRepr::Crtc(crtc)
}
}
pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32),
(width_mm, height_mm): (u64, u64),
) -> f64 {
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
if let Ok(dpi_factor_str) = env::var("WINIT_HIDPI_FACTOR") {
if let Ok(dpi_factor) = f64::from_str(&dpi_factor_str) {
if dpi_factor <= 0. {
panic!("Expected `WINIT_HIDPI_FACTOR` to be bigger than 0, got '{}'", dpi_factor);
}
return dpi_factor;
}
}
// See http://xpra.org/trac/ticket/728 for more information
if width_mm == 0 || width_mm == 0 {
return 1.0;
}
let ppmm = (
(width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)
).sqrt();
// Quantize 1/12 step size
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
}
impl XConnection {
pub unsafe fn get_output_info(&self, resources: *mut XRRScreenResources, repr: &MonitorRepr) -> (String, f32) {
// Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)();
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
if resource_manager_str == ptr::null_mut() {
return None;
}
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
let name : &str = "Xft.dpi:\t";
for pair in res.split("\n") {
if pair.starts_with(&name) {
let res = &pair[name.len()..];
return f64::from_str(&res).ok();
}
}
}
None
}
pub unsafe fn get_output_info(
&self,
resources: *mut ffi::XRRScreenResources,
repr: &MonitorRepr,
) -> Option<(String, f64)> {
let output_info = (self.xrandr.XRRGetOutputInfo)(
self.display,
resources,
repr.get_output(),
);
if output_info.is_null() {
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
// it's possible for it to return null.
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596
let _ = self.check_errors(); // discard `BadRROutput` error
return None;
}
let name_slice = slice::from_raw_parts(
(*output_info).name as *mut u8,
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
let hidpi_factor = calc_dpi_factor(
repr.get_dimensions(),
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
) as f32;
let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
repr.get_dimensions(),
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
)
};
(self.xrandr.XRRFreeOutputInfo)(output_info);
(name, hidpi_factor)
Some((name, hidpi_factor))
}
}

View File

@@ -1,3 +1,4 @@
use raw_window_handle::unix::XlibHandle;
use std::{cmp, env, mem};
use std::ffi::CString;
use std::os::raw::*;
@@ -7,15 +8,15 @@ use std::sync::Arc;
use libc;
use parking_lot::Mutex;
use {CursorState, Icon, MouseCursor, WindowAttributes};
use {Icon, MouseCursor, WindowAttributes};
use CreationError::{self, OsError};
use dpi::{LogicalPosition, LogicalSize};
use platform::MonitorId as PlatformMonitorId;
use platform::PlatformSpecificWindowBuilderAttributes;
use platform::x11::MonitorId as X11MonitorId;
use platform::x11::monitor::get_monitor_for_window;
use window::MonitorId as RootMonitorId;
use super::{ffi, util, XConnection, XError, WindowId, EventsLoop};
use super::{ffi, util, ImeSender, XConnection, XError, WindowId, EventsLoop};
unsafe extern "C" fn visibility_predicate(
_display: *mut ffi::Display,
@@ -29,17 +30,28 @@ unsafe extern "C" fn visibility_predicate(
#[derive(Debug, Default)]
pub struct SharedState {
pub multitouch: 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)>,
pub fullscreen: Option<RootMonitorId>,
// Used to restore position after exiting fullscreen.
pub restore_position: Option<(i32, i32)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_dimensions: Option<(u32, u32)>,
pub max_dimensions: Option<(u32, u32)>,
pub min_dimensions: Option<LogicalSize>,
pub max_dimensions: Option<LogicalSize>,
}
impl SharedState {
fn new(dpi_factor: f64) -> Mutex<Self> {
let mut shared_state = SharedState::default();
shared_state.guessed_dpi = Some(dpi_factor);
Mutex::new(shared_state)
}
}
unsafe impl Send for UnownedWindow {}
@@ -51,7 +63,9 @@ pub struct UnownedWindow {
root: ffi::Window, // never changes
screen_id: i32, // never changes
cursor: Mutex<MouseCursor>,
cursor_state: Mutex<CursorState>,
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
ime_sender: Mutex<ImeSender>,
pub multitouch: bool, // never changes
pub shared_state: Mutex<SharedState>,
}
@@ -65,18 +79,60 @@ impl UnownedWindow {
let xconn = &event_loop.xconn;
let root = event_loop.root;
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.unwrap_or((800, 600));
if let Some(max) = window_attrs.max_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();
if let Some(max) = max_dimensions {
dimensions.0 = cmp::min(dimensions.0, max.0);
dimensions.1 = cmp::min(dimensions.1, max.1);
}
if let Some(min) = window_attrs.min_dimensions {
if let Some(min) = min_dimensions {
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
};
@@ -144,9 +200,11 @@ impl UnownedWindow {
root,
screen_id,
cursor: Default::default(),
cursor_state: Default::default(),
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
multitouch: window_attrs.multitouch,
shared_state: Default::default(),
shared_state: SharedState::new(dpi_factor),
};
// Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
@@ -216,48 +274,36 @@ 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
{
(*window.shared_state.lock()).min_dimensions = window_attrs.min_dimensions;
(*window.shared_state.lock()).max_dimensions = window_attrs.max_dimensions;
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);
min_dimensions = Some(dimensions);
let mut min_dimensions = window_attrs.min_dimensions
.map(|size| size.to_physical(dpi_factor));
let mut max_dimensions = window_attrs.max_dimensions
.map(|size| size.to_physical(dpi_factor));
if !window_attrs.resizable {
if util::wm_name_is_one_of(&["Xfwm4"]) {
warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
} else {
max_dimensions = Some(dimensions.into());
min_dimensions = Some(dimensions.into());
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 size_hints = xconn.alloc_size_hints();
(*size_hints).flags = ffi::PSize;
(*size_hints).width = dimensions.0 as c_int;
(*size_hints).height = dimensions.1 as c_int;
if let Some((min_width, min_height)) = min_dimensions {
(*size_hints).flags |= ffi::PMinSize;
(*size_hints).min_width = min_width as c_int;
(*size_hints).min_height = min_height as c_int;
}
if let Some((max_width, max_height)) = max_dimensions {
(*size_hints).flags |= ffi::PMaxSize;
(*size_hints).max_width = max_width as c_int;
(*size_hints).max_height = max_height as c_int;
}
if let Some((width_inc, height_inc)) = pl_attribs.resize_increments {
(*size_hints).flags |= ffi::PResizeInc;
(*size_hints).width_inc = width_inc as c_int;
(*size_hints).height_inc = height_inc as c_int;
}
if let Some((base_width, base_height)) = pl_attribs.base_size {
(*size_hints).flags |= ffi::PBaseSize;
(*size_hints).base_width = base_width as c_int;
(*size_hints).base_height = base_height as c_int;
}
unsafe {
(xconn.xlib.XSetWMNormalHints)(
xconn.display,
window.xwindow,
size_hints.ptr,
);
}//.queue();
let mut normal_hints = util::NormalHints::new(xconn);
normal_hints.set_size(Some(dimensions));
normal_hints.set_min_size(min_dimensions.map(Into::into));
normal_hints.set_max_size(max_dimensions.map(Into::into));
normal_hints.set_resize_increments(pl_attribs.resize_increments);
normal_hints.set_base_size(pl_attribs.base_size);
xconn.set_normal_hints(window.xwindow, normal_hints).queue();
}
// Set window icons
@@ -291,7 +337,7 @@ impl UnownedWindow {
&mut supported_ptr,
);
if supported_ptr == ffi::False {
return Err(OsError(format!("XkbSetDetectableAutoRepeat failed")));
return Err(OsError(format!("`XkbSetDetectableAutoRepeat` failed")));
}
}
@@ -315,6 +361,15 @@ impl UnownedWindow {
};
xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask).queue();
{
let result = event_loop.ime
.borrow_mut()
.create_context(window.xwindow);
if let Err(err) = result {
return Err(OsError(format!("Failed to create input context: {:?}", err)));
}
}
// These properties must be set after mapping
if window_attrs.maximized {
window.set_maximized_inner(window_attrs.maximized).queue();
@@ -355,6 +410,16 @@ impl UnownedWindow {
))
}
fn logicalize_coords(&self, (x, y): (i32, i32)) -> LogicalPosition {
let dpi = self.get_hidpi_factor();
LogicalPosition::from_physical((x, y), dpi)
}
fn logicalize_size(&self, (width, height): (u32, u32)) -> LogicalSize {
let dpi = self.get_hidpi_factor();
LogicalSize::from_physical((width, height), dpi)
}
fn set_pid(&self) -> Option<util::Flusher> {
let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") };
let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") };
@@ -400,6 +465,20 @@ 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");
if is_urgent {
@@ -439,35 +518,67 @@ impl UnownedWindow {
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorId>) -> util::Flusher {
match monitor {
None => {
self.set_fullscreen_hint(false)
let flusher = self.set_fullscreen_hint(false);
if let Some(position) = self.shared_state.lock().restore_position.take() {
self.set_position_inner(position.0, position.1).queue();
}
flusher
},
Some(RootMonitorId { inner: PlatformMonitorId::X(monitor) }) => {
let screenpos = monitor.get_position();
self.set_position(screenpos.0 as i32, screenpos.1 as i32);
let window_position = self.get_position_physical();
self.shared_state.lock().restore_position = window_position;
let monitor_origin: (i32, i32) = monitor.get_position().into();
self.set_position_inner(monitor_origin.0, monitor_origin.1).queue();
self.set_fullscreen_hint(true)
}
_ => unreachable!(),
}
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
self.shared_state.lock().fullscreen.clone()
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
self.shared_state.lock().fullscreen = monitor.clone();
self.set_fullscreen_inner(monitor)
.flush()
.expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents();
}
fn get_rect(&self) -> Option<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(), self.get_outer_size()) {
Some(util::Rect::new(position, size))
if let (Some(position), Some(size)) = (self.get_position_physical(), self.get_outer_size_physical()) {
Some(util::AaRect::new(position, size))
} else {
None
}
}
#[inline]
pub fn get_current_monitor(&self) -> X11MonitorId {
get_monitor_for_window(&self.xconn, self.get_rect()).to_owned()
let monitor = self.shared_state
.lock()
.last_monitor
.as_ref()
.cloned();
monitor
.unwrap_or_else(|| {
let monitor = self.xconn.get_monitor_for_window(self.get_rect()).to_owned();
self.shared_state.lock().last_monitor = Some(monitor.clone());
monitor
})
}
pub fn get_available_monitors(&self) -> Vec<X11MonitorId> {
self.xconn.get_available_monitors()
}
pub fn get_primary_monitor(&self) -> X11MonitorId {
self.xconn.get_primary_monitor()
}
fn set_maximized_inner(&self, maximized: bool) -> util::Flusher {
@@ -476,6 +587,7 @@ impl UnownedWindow {
self.set_netwm(maximized.into(), (horz_atom as c_long, vert_atom as c_long, 0, 0))
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.set_maximized_inner(maximized)
.flush()
@@ -498,11 +610,12 @@ impl UnownedWindow {
wm_name_atom,
utf8_atom,
util::PropMode::Replace,
title.as_bytes_with_nul(),
title.as_bytes(),
)
}
}
#[inline]
pub fn set_title(&self, title: &str) {
self.set_title_inner(title)
.flush()
@@ -526,6 +639,7 @@ impl UnownedWindow {
)
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
self.set_decorations_inner(decorations)
.flush()
@@ -538,6 +652,7 @@ impl UnownedWindow {
self.set_netwm(always_on_top.into(), (above_atom as c_long, 0, 0, 0))
}
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
self.set_always_on_top_inner(always_on_top)
.flush()
@@ -568,6 +683,7 @@ impl UnownedWindow {
)
}
#[inline]
pub fn set_window_icon(&self, icon: Option<Icon>) {
match icon {
Some(icon) => self.set_icon_inner(icon),
@@ -575,6 +691,7 @@ impl UnownedWindow {
}.flush().expect("Failed to set icons");
}
#[inline]
pub fn show(&self) {
unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
@@ -583,6 +700,7 @@ impl UnownedWindow {
}
}
#[inline]
pub fn hide(&self) {
unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
@@ -596,31 +714,46 @@ impl UnownedWindow {
(*self.shared_state.lock()).frame_extents = Some(extents);
}
pub fn invalidate_cached_frame_extents(&self) {
pub(crate) fn invalidate_cached_frame_extents(&self) {
(*self.shared_state.lock()).frame_extents.take();
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> {
let extents = (*self.shared_state.lock()).frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_position().map(|(x, y)|
extents.inner_pos_to_outer(x, y)
)
self.get_inner_position_physical()
.map(|(x, y)| extents.inner_pos_to_outer(x, y))
} else {
self.update_cached_frame_extents();
self.get_position_physical()
}
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
let extents = (*self.shared_state.lock()).frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_position()
.map(|logical| extents.inner_pos_to_outer_logical(logical, self.get_hidpi_factor()))
} else {
self.update_cached_frame_extents();
self.get_position()
}
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
self.xconn.translate_coords(self.xwindow, self.root )
pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> {
self.xconn.translate_coords(self.xwindow, self.root)
.ok()
.map(|coords| (coords.x_rel_root, coords.y_rel_root))
}
pub fn set_position(&self, mut x: i32, mut y: i32) {
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.get_inner_position_physical()
.map(|coords| self.logicalize_coords(coords))
}
pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher {
// There are a few WMs that set client area position rather than window position, so
// we'll translate for consistency.
if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) {
@@ -630,7 +763,7 @@ impl UnownedWindow {
y += extents.frame_extents.top as i32;
} else {
self.update_cached_frame_extents();
self.set_position(x, y)
return self.set_position_inner(x, y);
}
}
unsafe {
@@ -640,32 +773,58 @@ impl UnownedWindow {
x as c_int,
y as c_int,
);
self.xconn.flush_requests()
}.expect("Failed to call XMoveWindow");
}
util::Flusher::new(&self.xconn)
}
pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
self.set_position_inner(x, y)
.flush()
.expect("Failed to call `XMoveWindow`");
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
pub fn set_position(&self, logical_position: LogicalPosition) {
let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into();
self.set_position_physical(x, y);
}
pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> {
self.xconn.get_geometry(self.xwindow)
.ok()
.map(|geo| (geo.width, geo.height))
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
let extents = (*self.shared_state.lock()).frame_extents.clone();
pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.get_inner_size_physical()
.map(|size| self.logicalize_size(size))
}
pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_size().map(|(w, h)|
extents.inner_size_to_outer(w, h)
)
self.get_inner_size_physical()
.map(|(w, h)| extents.inner_size_to_outer(w, h))
} else {
self.update_cached_frame_extents();
self.get_outer_size_physical()
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_size()
.map(|logical| extents.inner_size_to_outer_logical(logical, self.get_hidpi_factor()))
} else {
self.update_cached_frame_extents();
self.get_outer_size()
}
}
#[inline]
pub fn set_inner_size(&self, width: u32, height: u32) {
pub(crate) fn set_inner_size_physical(&self, width: u32, height: u32) {
unsafe {
(self.xconn.xlib.XResizeWindow)(
self.xconn.display,
@@ -674,62 +833,86 @@ impl UnownedWindow {
height as c_uint,
);
self.xconn.flush_requests()
}.expect("Failed to call XResizeWindow");
}.expect("Failed to call `XResizeWindow`");
}
unsafe fn update_normal_hints<F>(&self, callback: F) -> Result<(), XError>
where F: FnOnce(*mut ffi::XSizeHints) -> ()
#[inline]
pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.get_hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into();
self.set_inner_size_physical(width, height);
}
fn update_normal_hints<F>(&self, callback: F) -> Result<(), XError>
where F: FnOnce(&mut util::NormalHints) -> ()
{
let size_hints = self.xconn.alloc_size_hints();
let mut flags: c_long = mem::uninitialized();
(self.xconn.xlib.XGetWMNormalHints)(
self.xconn.display,
self.xwindow,
size_hints.ptr,
&mut flags,
);
self.xconn.check_errors()?;
callback(size_hints.ptr);
(self.xconn.xlib.XSetWMNormalHints)(
self.xconn.display,
self.xwindow,
size_hints.ptr,
);
self.xconn.flush_requests()?;
Ok(())
let mut normal_hints = self.xconn.get_normal_hints(self.xwindow)?;
callback(&mut normal_hints);
self.xconn.set_normal_hints(self.xwindow, normal_hints).flush()
}
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
(*self.shared_state.lock()).min_dimensions = dimensions;
unsafe {
self.update_normal_hints(|size_hints| {
if let Some((width, height)) = dimensions {
(*size_hints).flags |= ffi::PMinSize;
(*size_hints).min_width = width as c_int;
(*size_hints).min_height = height as c_int;
} else {
(*size_hints).flags &= !ffi::PMinSize;
}
})
}.expect("Failed to call XSetWMNormalHints");
pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
self.update_normal_hints(|normal_hints| normal_hints.set_min_size(dimensions))
.expect("Failed to call `XSetWMNormalHints`");
}
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
(*self.shared_state.lock()).max_dimensions = dimensions;
#[inline]
pub fn set_min_dimensions(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().min_dimensions = logical_dimensions;
let physical_dimensions = logical_dimensions.map(|logical_dimensions| {
logical_dimensions.to_physical(self.get_hidpi_factor()).into()
});
self.set_min_dimensions_physical(physical_dimensions);
}
pub(crate) fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
self.update_normal_hints(|normal_hints| normal_hints.set_max_size(dimensions))
.expect("Failed to call `XSetWMNormalHints`");
}
#[inline]
pub fn set_max_dimensions(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().max_dimensions = logical_dimensions;
let physical_dimensions = logical_dimensions.map(|logical_dimensions| {
logical_dimensions.to_physical(self.get_hidpi_factor()).into()
});
self.set_max_dimensions_physical(physical_dimensions);
}
pub(crate) fn adjust_for_dpi(
&self,
old_dpi_factor: f64,
new_dpi_factor: f64,
width: f64,
height: f64,
) -> (f64, f64, util::Flusher) {
let scale_factor = new_dpi_factor / old_dpi_factor;
let new_width = width * scale_factor;
let new_height = height * scale_factor;
self.update_normal_hints(|normal_hints| {
let dpi_adjuster = |(width, height): (u32, u32)| -> (u32, u32) {
let new_width = width as f64 * scale_factor;
let new_height = height as f64 * scale_factor;
(new_width.round() as u32, new_height.round() as u32)
};
let max_size = normal_hints.get_max_size().map(&dpi_adjuster);
let min_size = normal_hints.get_min_size().map(&dpi_adjuster);
let resize_increments = normal_hints.get_resize_increments().map(&dpi_adjuster);
let base_size = normal_hints.get_base_size().map(&dpi_adjuster);
normal_hints.set_max_size(max_size);
normal_hints.set_min_size(min_size);
normal_hints.set_resize_increments(resize_increments);
normal_hints.set_base_size(base_size);
}).expect("Failed to update normal hints");
unsafe {
self.update_normal_hints(|size_hints| {
if let Some((width, height)) = dimensions {
(*size_hints).flags |= ffi::PMaxSize;
(*size_hints).max_width = width as c_int;
(*size_hints).max_height = height as c_int;
} else {
(*size_hints).flags &= !ffi::PMaxSize;
}
})
}.expect("Failed to call XSetWMNormalHints");
(self.xconn.xlib.XResizeWindow)(
self.xconn.display,
self.xwindow,
new_width.round() as c_uint,
new_height.round() as c_uint,
);
}
(new_width, new_height, util::Flusher::new(&self.xconn))
}
pub fn set_resizable(&self, resizable: bool) {
@@ -737,26 +920,29 @@ 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;
}
if resizable {
let min_dimensions = (*self.shared_state.lock()).min_dimensions;
let max_dimensions = (*self.shared_state.lock()).max_dimensions;
self.set_min_dimensions(min_dimensions);
self.set_max_dimensions(max_dimensions);
let (logical_min, logical_max) = if resizable {
let shared_state_lock = self.shared_state.lock();
(shared_state_lock.min_dimensions, shared_state_lock.max_dimensions)
} else {
unsafe {
self.update_normal_hints(|size_hints| {
(*size_hints).flags |= ffi::PMinSize | ffi::PMaxSize;
if let Some((width, height)) = self.get_inner_size() {
(*size_hints).min_width = width as c_int;
(*size_hints).min_height = height as c_int;
(*size_hints).max_width = width as c_int;
(*size_hints).max_height = height as c_int;
}
})
}.expect("Failed to call XSetWMNormalHints");
}
let window_size = self.get_inner_size();
(window_size.clone(), window_size)
};
let dpi_factor = self.get_hidpi_factor();
let min_dimensions = logical_min
.map(|logical_size| logical_size.to_physical(dpi_factor))
.map(Into::into);
let max_dimensions = logical_max
.map(|logical_size| logical_size.to_physical(dpi_factor))
.map(Into::into);
self.update_normal_hints(|normal_hints| {
normal_hints.set_min_size(min_dimensions);
normal_hints.set_max_size(max_dimensions);
}).expect("Failed to call `XSetWMNormalHints`");
}
#[inline]
@@ -774,21 +960,12 @@ impl UnownedWindow {
Arc::clone(&self.xconn)
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
self.xconn.display as _
}
#[inline]
pub fn get_xlib_window(&self) -> c_ulong {
self.xwindow
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
self.xwindow as _
}
pub fn get_xcb_connection(&self) -> *mut c_void {
unsafe {
(self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _
@@ -870,9 +1047,6 @@ impl UnownedWindow {
MouseCursor::ZoomIn => load(b"zoom-in\0"),
MouseCursor::ZoomOut => load(b"zoom-out\0"),
MouseCursor::NoneCursor => self.create_empty_cursor()
.expect("Failed to create empty cursor"),
}
}
@@ -886,9 +1060,10 @@ impl UnownedWindow {
}
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
*self.cursor.lock() = cursor;
if *self.cursor_state.lock() != CursorState::Hide {
if !*self.cursor_hidden.lock() {
self.update_cursor(self.get_cursor(cursor));
}
}
@@ -931,74 +1106,82 @@ impl UnownedWindow {
Some(cursor)
}
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
use CursorState::*;
let mut cursor_state_lock = self.cursor_state.lock();
match (state, *cursor_state_lock) {
(Normal, Normal) | (Hide, Hide) | (Grab, Grab) => return Ok(()),
_ => {},
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let mut grabbed_lock = self.cursor_grabbed.lock();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
// We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
// Therefore, this is common to both codepaths.
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
let result = if grab {
let result = unsafe {
(self.xconn.xlib.XGrabPointer)(
self.xconn.display,
self.xwindow,
ffi::True,
(
ffi::ButtonPressMask
| ffi::ButtonReleaseMask
| ffi::EnterWindowMask
| ffi::LeaveWindowMask
| ffi::PointerMotionMask
| ffi::PointerMotionHintMask
| ffi::Button1MotionMask
| ffi::Button2MotionMask
| ffi::Button3MotionMask
| ffi::Button4MotionMask
| ffi::Button5MotionMask
| ffi::ButtonMotionMask
| ffi::KeymapStateMask
) as c_uint,
ffi::GrabModeAsync,
ffi::GrabModeAsync,
self.xwindow,
0,
ffi::CurrentTime,
)
};
match *cursor_state_lock {
Grab => {
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
self.xconn.flush_requests().expect("Failed to call XUngrabPointer");
}
},
Normal => {},
Hide => self.update_cursor(self.get_cursor(*self.cursor.lock())),
}
match state {
Normal => {
*cursor_state_lock = state;
Ok(())
},
Hide => {
*cursor_state_lock = state;
self.update_cursor(
self.create_empty_cursor().expect("Failed to create empty cursor")
);
Ok(())
},
Grab => {
unsafe {
// Ungrab before grabbing to prevent passive grabs
// from causing AlreadyGrabbed
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
match (self.xconn.xlib.XGrabPointer)(
self.xconn.display, self.xwindow, ffi::True,
(ffi::ButtonPressMask | ffi::ButtonReleaseMask | ffi::EnterWindowMask |
ffi::LeaveWindowMask | ffi::PointerMotionMask | ffi::PointerMotionHintMask |
ffi::Button1MotionMask | ffi::Button2MotionMask | ffi::Button3MotionMask |
ffi::Button4MotionMask | ffi::Button5MotionMask | ffi::ButtonMotionMask |
ffi::KeymapStateMask) as c_uint,
ffi::GrabModeAsync, ffi::GrabModeAsync,
self.xwindow, 0, ffi::CurrentTime
) {
ffi::GrabSuccess => {
*cursor_state_lock = state;
Ok(())
},
ffi::AlreadyGrabbed | ffi::GrabInvalidTime |
ffi::GrabNotViewable | ffi::GrabFrozen
=> Err("cursor could not be grabbed".to_string()),
_ => unreachable!(),
}
}
},
match result {
ffi::GrabSuccess => Ok(()),
ffi::AlreadyGrabbed => Err("Cursor could not be grabbed: already grabbed by another client"),
ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"),
ffi::GrabNotViewable => Err("Cursor could not be grabbed: grab location not viewable"),
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
_ => unreachable!(),
}.map_err(|err| err.to_owned())
} else {
self.xconn.flush_requests()
.map_err(|err| format!("Failed to call `XUngrabPointer`: {:?}", err))
};
if result.is_ok() {
*grabbed_lock = grab;
}
result
}
pub fn hidpi_factor(&self) -> f32 {
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.cursor_hidden.lock();
if hide == *hidden_lock {return; }
let cursor = if hide {
self.create_empty_cursor().expect("Failed to create empty cursor")
} else {
self.get_cursor(*self.cursor.lock())
};
*hidden_lock = hide;
drop(hidden_lock);
self.update_cursor(cursor);
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.get_current_monitor().hidpi_factor
}
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
unsafe {
(self.xconn.xlib.XWarpPointer)(
self.xconn.display,
@@ -1011,10 +1194,37 @@ impl UnownedWindow {
x,
y,
);
self.xconn.flush_requests().map_err(|_| ())
self.xconn.flush_requests().map_err(|e| format!("`XWarpPointer` failed: {:?}", e))
}
}
#[inline]
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> {
let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into();
self.set_cursor_position_physical(x, y)
}
pub(crate) fn set_ime_spot_physical(&self, x: i32, y: i32) {
let _ = self.ime_sender
.lock()
.send((self.xwindow, x as i16, y as i16));
}
#[inline]
pub fn set_ime_spot(&self, logical_spot: LogicalPosition) {
let (x, y) = logical_spot.to_physical(self.get_hidpi_factor()).into();
self.set_ime_spot_physical(x, y);
}
#[inline]
pub fn id(&self) -> WindowId { WindowId(self.xwindow) }
#[inline]
pub fn raw_window_handle(&self) -> XlibHandle {
XlibHandle {
window: self.xwindow,
display: self.xconn.display as _,
..XlibHandle::empty()
}
}
}

View File

@@ -1,6 +1,7 @@
use std::ptr;
use std::fmt;
use std::error::Error;
use std::os::raw::c_int;
use libc;
use parking_lot::Mutex;
@@ -18,6 +19,7 @@ pub struct XConnection {
pub xinput2: ffi::XInput2,
pub xlib_xcb: ffi::Xlib_xcb,
pub display: *mut ffi::Display,
pub x11_fd: c_int,
pub latest_error: Mutex<Option<XError>>,
}
@@ -48,6 +50,11 @@ impl XConnection {
display
};
// Get X11 socket file descriptor
let fd = unsafe {
(xlib.XConnectionNumber)(display)
};
Ok(XConnection {
xlib,
xrandr,
@@ -56,6 +63,7 @@ impl XConnection {
xinput2,
xlib_xcb,
display,
x11_fd: fd,
latest_error: Mutex::new(None),
})
}

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();
@@ -377,14 +398,16 @@ impl EventsLoop {
} else {
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
};
let view_rect = NSView::frame(*window.view);
let scale_factor = window.hidpi_factor();
let x = (scale_factor * view_point.x as f32) as f64;
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
let window_event = WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(ns_event),
};
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
self.shared.pending_events.lock().unwrap().push_back(event);
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
},
@@ -397,32 +420,30 @@ impl EventsLoop {
// If the mouse movement was on one of our windows, use it.
// Otherwise, if one of our windows is the key window (receiving input), use it.
// Otherwise, return `None`.
let window = match maybe_window.or_else(maybe_key_window) {
Some(window) => window,
match maybe_window.or_else(maybe_key_window) {
Some(_window) => (),
None => return None,
};
let scale_factor = window.hidpi_factor();
}
let mut events = std::collections::VecDeque::with_capacity(3);
let delta_x = (scale_factor * ns_event.deltaX() as f32) as f64;
let delta_x = ns_event.deltaX() as f64;
if delta_x != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let delta_y = (scale_factor * ns_event.deltaY() as f32) as f64;
let delta_y = ns_event.deltaY() as f64;
if delta_y != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
if delta_x != 0.0 || delta_y != 0.0 {
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
@@ -433,19 +454,22 @@ impl EventsLoop {
appkit::NSScrollWheel => {
// If none of the windows received the scroll, return `None`.
let window = match maybe_window {
Some(window) => window,
None => return None,
};
if maybe_window.is_none() {
return None;
}
use events::MouseScrollDelta::{LineDelta, PixelDelta};
let scale_factor = window.hidpi_factor();
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
scale_factor * ns_event.scrollingDeltaY() as f32)
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
LineDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
scale_factor * ns_event.scrollingDeltaY() as f32)
// TODO: This is probably wrong
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
};
let phase = match ns_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
@@ -456,11 +480,15 @@ impl EventsLoop {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel {
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta(ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32)
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
LineDelta(ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32)
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
},
}
});
@@ -516,7 +544,67 @@ impl Proxy {
}
}
pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
pub fn char_to_keycode(c: char) -> Option<events::VirtualKeyCode> {
// We only translate keys that are affected by keyboard layout.
//
// Note that since keys are translated in a somewhat "dumb" way (reading character)
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
// letter to be received, and so we receive the wrong key.
//
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
Some(match c {
'a' | 'A' => events::VirtualKeyCode::A,
'b' | 'B' => events::VirtualKeyCode::B,
'c' | 'C' => events::VirtualKeyCode::C,
'd' | 'D' => events::VirtualKeyCode::D,
'e' | 'E' => events::VirtualKeyCode::E,
'f' | 'F' => events::VirtualKeyCode::F,
'g' | 'G' => events::VirtualKeyCode::G,
'h' | 'H' => events::VirtualKeyCode::H,
'i' | 'I' => events::VirtualKeyCode::I,
'j' | 'J' => events::VirtualKeyCode::J,
'k' | 'K' => events::VirtualKeyCode::K,
'l' | 'L' => events::VirtualKeyCode::L,
'm' | 'M' => events::VirtualKeyCode::M,
'n' | 'N' => events::VirtualKeyCode::N,
'o' | 'O' => events::VirtualKeyCode::O,
'p' | 'P' => events::VirtualKeyCode::P,
'q' | 'Q' => events::VirtualKeyCode::Q,
'r' | 'R' => events::VirtualKeyCode::R,
's' | 'S' => events::VirtualKeyCode::S,
't' | 'T' => events::VirtualKeyCode::T,
'u' | 'U' => events::VirtualKeyCode::U,
'v' | 'V' => events::VirtualKeyCode::V,
'w' | 'W' => events::VirtualKeyCode::W,
'x' | 'X' => events::VirtualKeyCode::X,
'y' | 'Y' => events::VirtualKeyCode::Y,
'z' | 'Z' => events::VirtualKeyCode::Z,
'1' | '!' => events::VirtualKeyCode::Key1,
'2' | '@' => events::VirtualKeyCode::Key2,
'3' | '#' => events::VirtualKeyCode::Key3,
'4' | '$' => events::VirtualKeyCode::Key4,
'5' | '%' => events::VirtualKeyCode::Key5,
'6' | '^' => events::VirtualKeyCode::Key6,
'7' | '&' => events::VirtualKeyCode::Key7,
'8' | '*' => events::VirtualKeyCode::Key8,
'9' | '(' => events::VirtualKeyCode::Key9,
'0' | ')' => events::VirtualKeyCode::Key0,
'=' | '+' => events::VirtualKeyCode::Equals,
'-' | '_' => events::VirtualKeyCode::Minus,
']' | '}' => events::VirtualKeyCode::RBracket,
'[' | '{' => events::VirtualKeyCode::LBracket,
'\''| '"' => events::VirtualKeyCode::Apostrophe,
';' | ':' => events::VirtualKeyCode::Semicolon,
'\\'| '|' => events::VirtualKeyCode::Backslash,
',' | '<' => events::VirtualKeyCode::Comma,
'/' | '?' => events::VirtualKeyCode::Slash,
'.' | '>' => events::VirtualKeyCode::Period,
'`' | '~' => events::VirtualKeyCode::Grave,
_ => return None,
})
}
pub fn scancode_to_keycode(code: c_ushort) -> Option<events::VirtualKeyCode> {
Some(match code {
0x00 => events::VirtualKeyCode::A,
0x01 => events::VirtualKeyCode::S,
@@ -572,8 +660,8 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x33 => events::VirtualKeyCode::Back,
//0x34 => unkown,
0x35 => events::VirtualKeyCode::Escape,
0x36 => events::VirtualKeyCode::LWin,
0x37 => events::VirtualKeyCode::RWin,
0x36 => events::VirtualKeyCode::RWin,
0x37 => events::VirtualKeyCode::LWin,
0x38 => events::VirtualKeyCode::LShift,
//0x39 => Caps lock,
0x3a => events::VirtualKeyCode::LAlt,
@@ -582,7 +670,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,
@@ -595,10 +683,11 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x4a => events::VirtualKeyCode::VolumeDown,
0x4b => events::VirtualKeyCode::Divide,
0x4c => events::VirtualKeyCode::NumpadEnter,
0x4e => events::VirtualKeyCode::Subtract,
//0x4d => unkown,
0x4e => events::VirtualKeyCode::Subtract,
//0x4f => F18 key,
//0x50 => F19 Key,
0x4f => events::VirtualKeyCode::F18,
0x50 => events::VirtualKeyCode::F19,
0x51 => events::VirtualKeyCode::NumpadEquals,
0x52 => events::VirtualKeyCode::Numpad0,
0x53 => events::VirtualKeyCode::Numpad1,
@@ -608,11 +697,11 @@ 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,
//0x5e => unkown,
0x5d => events::VirtualKeyCode::Yen,
//0x5e => JIS Ro,
//0x5f => unkown,
0x60 => events::VirtualKeyCode::F5,
0x61 => events::VirtualKeyCode::F6,
@@ -620,11 +709,11 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
0x63 => events::VirtualKeyCode::F3,
0x64 => events::VirtualKeyCode::F8,
0x65 => events::VirtualKeyCode::F9,
//0x66 => unkown,
//0x66 => JIS Eisuu (macOS),
0x67 => events::VirtualKeyCode::F11,
//0x68 => unkown,
//0x68 => JIS Kana (macOS),
0x69 => events::VirtualKeyCode::F13,
//0x6a => F16 Key,
0x6a => events::VirtualKeyCode::F16,
0x6b => events::VirtualKeyCode::F14,
//0x6c => unkown,
0x6d => events::VirtualKeyCode::F10,
@@ -652,6 +741,22 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
})
}
pub fn check_function_keys(
s: &String
) -> Option<events::VirtualKeyCode> {
if let Some(ch) = s.encode_utf16().next() {
return Some(match ch {
0xf718 => events::VirtualKeyCode::F21,
0xf719 => events::VirtualKeyCode::F22,
0xf71a => events::VirtualKeyCode::F23,
0xf71b => events::VirtualKeyCode::F24,
_ => return None,
})
}
None
}
pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
let flags = unsafe {
NSEvent::modifierFlags(event)
@@ -664,22 +769,36 @@ pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
}
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
unsafe {
msg_send![event, keyCode]
}
}
unsafe fn modifier_event(
ns_event: cocoa::base::id,
keymask: NSEventModifierFlags,
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;
let keycode = NSEvent::keyCode(ns_event);
let scancode = keycode as u32;
let virtual_keycode = to_virtual_key_code(keycode);
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = if was_key_pressed {
ElementState::Released
} else {
ElementState::Pressed
};
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode,
scancode: scancode as u32,
virtual_keycode,
modifiers: event_mods(ns_event),
},

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

@@ -8,6 +8,12 @@ use std::sync::Arc;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
use {CreationError};
pub struct Window {

View File

@@ -1,30 +1,44 @@
use std::collections::VecDeque;
use std::fmt;
use cocoa::appkit::NSScreen;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use std::collections::VecDeque;
use std::fmt;
use {PhysicalPosition, PhysicalSize};
use super::EventsLoop;
use super::window::IdRef;
use super::window::{IdRef, Window2};
#[derive(Clone, PartialEq)]
pub struct MonitorId(CGDirectDisplayID);
impl EventsLoop {
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut monitors = VecDeque::new();
if let Ok(displays) = CGDisplay::active_displays() {
for d in displays {
monitors.push_back(MonitorId(d));
}
fn get_available_monitors() -> VecDeque<MonitorId> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for d in displays {
monitors.push_back(MonitorId(d));
}
monitors
} else {
VecDeque::with_capacity(0)
}
}
pub fn get_primary_monitor() -> MonitorId {
let id = MonitorId(CGDisplay::main().id);
id
}
impl EventsLoop {
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
let id = MonitorId(CGDisplay::main().id);
id
get_primary_monitor()
}
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorId {
@@ -33,15 +47,27 @@ impl EventsLoop {
}
}
impl Window2 {
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor()
}
}
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
native_identifier: u32,
dimensions: (u32, u32),
position: (i32, i32),
hidpi_factor: f32,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
@@ -68,30 +94,32 @@ impl MonitorId {
self.0
}
pub fn get_dimensions(&self) -> (u32, u32) {
pub fn get_dimensions(&self) -> PhysicalSize {
let MonitorId(display_id) = *self;
let display = CGDisplay::new(display_id);
let dimension = {
let height = display.pixels_high();
let width = display.pixels_wide();
(width as u32, height as u32)
};
dimension
let height = display.pixels_high();
let width = display.pixels_wide();
PhysicalSize::from_logical(
(width as f64, height as f64),
self.get_hidpi_factor(),
)
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) };
(bounds.origin.x as i32, bounds.origin.y as i32)
PhysicalPosition::from_logical(
(bounds.origin.x as f64, bounds.origin.y as f64),
self.get_hidpi_factor(),
)
}
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
let screen = match self.get_nsscreen() {
Some(screen) => screen,
None => return 1.0, // default to 1.0 when we can't find the screen
};
unsafe { NSScreen::backingScaleFactor(screen) as f32 }
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
}
pub(crate) fn get_nsscreen(&self) -> Option<id> {

View File

@@ -0,0 +1,149 @@
use cocoa::{
appkit::NSImage, base::{id, nil, YES},
foundation::{NSDictionary, NSPoint, NSString},
};
use objc::runtime::Sel;
use super::IntoOption;
use MouseCursor;
pub enum Cursor {
Native(&'static str),
Undocumented(&'static str),
WebKit(&'static str),
}
impl From<MouseCursor> for Cursor {
fn from(cursor: MouseCursor) -> Self {
match cursor {
MouseCursor::Arrow | MouseCursor::Default => Cursor::Native("arrowCursor"),
MouseCursor::Hand => Cursor::Native("pointingHandCursor"),
MouseCursor::Grabbing | MouseCursor::Grab => Cursor::Native("closedHandCursor"),
MouseCursor::Text => Cursor::Native("IBeamCursor"),
MouseCursor::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
MouseCursor::Copy => Cursor::Native("dragCopyCursor"),
MouseCursor::Alias => Cursor::Native("dragLinkCursor"),
MouseCursor::NotAllowed | MouseCursor::NoDrop => Cursor::Native("operationNotAllowedCursor"),
MouseCursor::ContextMenu => Cursor::Native("contextualMenuCursor"),
MouseCursor::Crosshair => Cursor::Native("crosshairCursor"),
MouseCursor::EResize => Cursor::Native("resizeRightCursor"),
MouseCursor::NResize => Cursor::Native("resizeUpCursor"),
MouseCursor::WResize => Cursor::Native("resizeLeftCursor"),
MouseCursor::SResize => Cursor::Native("resizeDownCursor"),
MouseCursor::EwResize | MouseCursor::ColResize => Cursor::Native("resizeLeftRightCursor"),
MouseCursor::NsResize | MouseCursor::RowResize => Cursor::Native("resizeUpDownCursor"),
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
MouseCursor::Help => Cursor::Undocumented("_helpCursor"),
MouseCursor::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
MouseCursor::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
MouseCursor::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
MouseCursor::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
MouseCursor::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
MouseCursor::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
MouseCursor::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
MouseCursor::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
// While these are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// MouseCursor::Move => Cursor::Undocumented("_moveCursor"),
// MouseCursor::Wait => Cursor::Undocumented("_waitCursor"),
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
MouseCursor::Wait | MouseCursor::Progress => Cursor::Undocumented("busyButClickableCursor"),
// For the rest, we can just snatch the cursors from WebKit...
// They fit the style of the native cursors, and will seem
// completely standard to macOS users.
// https://stackoverflow.com/a/21786835/5435443
MouseCursor::Move | MouseCursor::AllScroll => Cursor::WebKit("move"),
MouseCursor::Cell => Cursor::WebKit("cell"),
}
}
}
impl Default for Cursor {
fn default() -> Self {
Cursor::Native("arrowCursor")
}
}
impl Cursor {
pub unsafe fn load(&self) -> id {
match self {
Cursor::Native(cursor_name) => {
let sel = Sel::register(cursor_name);
msg_send![class!(NSCursor), performSelector:sel]
},
Cursor::Undocumented(cursor_name) => {
let class = class!(NSCursor);
let sel = Sel::register(cursor_name);
let sel = if msg_send![class, respondsToSelector:sel] {
sel
} else {
warn!("Cursor `{}` appears to be invalid", cursor_name);
sel!(arrowCursor)
};
msg_send![class, performSelector:sel]
},
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name)
.unwrap_or_else(|message| {
warn!("{}", message);
Self::default().load()
}),
}
}
}
// Note that loading `busybutclickable` with this code won't animate the frames;
// instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result<id, String> {
static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
let cursor_name = NSString::alloc(nil).init_str(cursor_name_str);
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
let key_x = NSString::alloc(nil).init_str("hotx");
let key_y = NSString::alloc(nil).init_str("hoty");
let cursor_path: id = msg_send![cursor_root,
stringByAppendingPathComponent:cursor_name
];
let pdf_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_pdf
];
let info_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_plist
];
let image = NSImage::alloc(nil)
.initByReferencingFile_(pdf_path)
// This will probably never be `None`, since images are loaded lazily...
.into_option()
// because of that, we need to check for validity.
.filter(|image| image.isValid() == YES)
.ok_or_else(||
format!("Failed to read image for `{}` cursor", cursor_name_str)
)?;
let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path)
.into_option()
.ok_or_else(||
format!("Failed to read info for `{}` cursor", cursor_name_str)
)?;
let x = info.valueForKey_(key_x);
let y = info.valueForKey_(key_y);
let point = NSPoint::new(
msg_send![x, doubleValue],
msg_send![y, doubleValue],
);
let cursor: id = msg_send![class!(NSCursor), alloc];
let cursor: id = msg_send![cursor, initWithImage:image hotSpot:point];
cursor
.into_option()
.ok_or_else(||
format!("Failed to initialize `{}` cursor", cursor_name_str)
)
}

View File

@@ -0,0 +1,14 @@
use cocoa::base::{id, nil};
pub trait IntoOption: Sized {
fn into_option(self) -> Option<Self>;
}
impl IntoOption for id {
fn into_option(self) -> Option<Self> {
match self != nil {
true => Some(self),
false => None,
}
}
}

View File

@@ -1,7 +1,13 @@
mod cursor;
mod into_option;
pub use self::{cursor::Cursor, into_option::IntoOption};
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;
use objc::runtime::{Class, Object};
use platform::platform::ffi;
use platform::platform::window::IdRef;
@@ -14,8 +20,8 @@ pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> i32 {
(CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)) as _
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
@@ -25,14 +31,33 @@ pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
window.makeFirstResponder_(view);
}
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
use cocoa::appkit::NSWindow;
let current_style_mask = window.styleMask();
if on {
window.setStyleMask_(current_style_mask | mask);
} else {
window.setStyleMask_(current_style_mask & (!mask));
}
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class("NSTextInputContext"), alloc];
let input_context: id = msg_send![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

@@ -5,45 +5,50 @@ use std::{slice, str};
use std::boxed::Box;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::Weak;
use std::sync::{Arc, Mutex, 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, scancode_to_keycode, char_to_keycode, check_function_keys, get_scancode};
use platform::platform::util;
use platform::platform::ffi::*;
use platform::platform::window::{get_window_id, IdRef};
use events;
struct ViewState {
window: id,
shared: Weak<Shared>,
ime_spot: Option<(i32, i32)>,
cursor: Arc<Mutex<util::Cursor>>,
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 {
pub fn new_view(window: id, shared: Weak<Shared>) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
let state = ViewState {
window,
shared,
cursor,
ime_spot: None,
raw_characters: None,
last_insert: None,
is_key_down: false,
};
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let view: id = msg_send![VIEW_CLASS.0, alloc];
IdRef::new(msg_send![view, initWithWinit:state_ptr])
(IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access)
}
}
pub fn set_ime_spot(view: id, input_context: id, x: i32, y: i32) {
pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) {
unsafe {
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
@@ -51,8 +56,8 @@ pub fn set_ime_spot(view: id, input_context: id, x: i32, y: i32) {
state.window,
NSWindow::frame(state.window),
);
let base_x = content_rect.origin.x as i32;
let base_y = (content_rect.origin.y + content_rect.size.height) as i32;
let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
state.ime_spot = Some((base_x + x, base_y - y));
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
@@ -64,13 +69,21 @@ 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(
sel!(initWithWinit:),
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
);
decl.add_method(
sel!(drawRect:),
draw_rect as extern fn(&Object, Sel, NSRect),
);
decl.add_method(
sel!(resetCursorRects),
reset_cursor_rects as extern fn(&Object, Sel),
);
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
decl.add_method(
sel!(markedRange),
@@ -122,6 +135,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();
@@ -153,6 +167,41 @@ extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
}
}
extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(shared) = state.shared.upgrade() {
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::Refresh,
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
let superclass = util::superclass(this);
let () = msg_send![super(this, superclass), drawRect:rect];
}
}
extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let bounds: NSRect = msg_send![this, bounds];
let cursor = state.cursor.lock().unwrap().load();
let _: () = msg_send![this,
addCursorRect:bounds
cursor:cursor
];
}
}
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
//println!("hasMarkedText");
unsafe {
@@ -191,7 +240,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 +263,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(
@@ -249,7 +298,7 @@ extern fn first_rect_for_character_range(
);
let x = content_rect.origin.x;
let y = util::bottom_left_to_top_left(content_rect);
(x as i32, y as i32)
(x, y)
});
NSRect::new(
@@ -265,7 +314,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 +328,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,16 +392,68 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
}
}
fn get_characters(event: id, ignore_modifiers: bool) -> String {
unsafe {
let characters: id = if ignore_modifiers {
msg_send![event, charactersIgnoringModifiers]
} else {
msg_send![event, characters]
};
assert_ne!(characters, nil);
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
string.to_owned()
}
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<events::VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<events::VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters.chars().next().map_or(None, |c| char_to_keycode(c))
}
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
// If we don't get a match, then we fall back to unmodified characters.
let code = get_code(event, false)
.or_else(|| {
get_code(event, true)
});
// We've checked all layout related keys, so fall through to scancode.
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
//
// We're additionally checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
code.or_else(|| {
let scancode = get_scancode(event);
scancode_to_keycode(scancode)
.or_else(|| {
check_function_keys(&get_characters(event, true))
})
})
}
extern fn key_down(this: &Object, _sel: Sel, event: id) {
//println!("keyDown");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_id = WindowId(get_window_id(state.window));
let characters = get_characters(event, false);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(keycode);
let scancode = keycode as u32;
state.raw_characters = Some(characters.clone());
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let is_repeat = msg_send![event, isARepeat];
let window_event = Event::WindowEvent {
@@ -368,25 +469,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 string = str::from_utf8_unchecked(slice);
Some(string.to_owned())
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.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 characters.chars() {
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
@@ -396,11 +486,14 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
.unwrap()
.push_back(window_event);
}
} else {
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
// keys to generate twice as many characters.
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
}
}
let array: id = msg_send![class("NSArray"), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
}
}
@@ -410,11 +503,11 @@ 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;
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(keycode);
let scancode = keycode as u32;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::KeyboardInput {
@@ -527,15 +620,14 @@ fn mouse_motion(this: &Object, event: id) {
return;
}
let scale_factor = NSWindow::backingScaleFactor(state.window) as f64;
let x = scale_factor * view_point.x as f64;
let y = scale_factor * (view_rect.size.height as f64 - view_point.y as f64);
let x = view_point.x as f64;
let y = view_rect.size.height as f64 - view_point.y as f64;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y),
position: (x, y).into(),
modifiers: event_mods(event),
},
};
@@ -564,3 +656,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,54 +1,83 @@
use {CreationError, Event, WindowEvent, WindowId, MouseCursor, CursorState};
use CreationError::OsError;
use libc;
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
use std;
use std::cell::{Cell, RefCell};
use std::f64;
use std::ops::Deref;
use std::os::raw::c_void;
use std::sync::{Mutex, Weak};
use std::sync::atomic::{Ordering, AtomicBool};
use WindowAttributes;
use os::macos::ActivationPolicy;
use os::macos::WindowExt;
use cocoa::appkit::{
self,
CGFloat,
NSApp,
NSApplication,
NSColor,
NSRequestUserAttentionType,
NSScreen,
NSView,
NSWindow,
NSWindowButton,
NSWindowStyleMask,
NSApplicationActivationPolicy,
NSApplicationPresentationOptions,
};
use cocoa::base::{id, nil};
use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString};
use core_graphics::display::CGDisplay;
use objc;
use objc::runtime::{Class, Object, Sel, BOOL, YES, NO};
use objc::declare::ClassDecl;
use cocoa;
use cocoa::appkit::{self, NSApplication, NSColor, NSScreen, NSView, NSWindow, NSWindowButton,
NSWindowStyleMask};
use cocoa::base::{id, nil};
use cocoa::foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString, NSAutoreleasePool};
use core_graphics::display::CGDisplay;
use std;
use std::ops::Deref;
use std::os::raw::c_void;
use std::sync::Weak;
use std::cell::{Cell, RefCell};
use super::events_loop::{EventsLoop, Shared};
use platform::platform::ffi;
use platform::platform::util;
use {
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
WindowAttributes,
WindowEvent,
WindowId,
};
use CreationError::OsError;
use os::macos::{ActivationPolicy, WindowExt};
use platform::platform::{ffi, util};
use platform::platform::events_loop::{EventsLoop, Shared};
use platform::platform::view::{new_view, set_ime_spot};
use window::MonitorId as RootMonitorId;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub usize);
struct DelegateState {
impl Id {
pub unsafe fn dummy() -> Self {
Id(0)
}
}
// TODO: It's possible for delegate methods to be called asynchronously, causing data races / `RefCell` panics.
pub struct DelegateState {
view: IdRef,
window: IdRef,
shared: Weak<Shared>,
win_attribs: RefCell<WindowAttributes>,
standard_frame: Cell<Option<NSRect>>,
is_simple_fullscreen: Cell<bool>,
save_style_mask: Cell<Option<NSWindowStyleMask>>,
save_presentation_opts: Cell<Option<NSApplicationPresentationOptions>>,
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
handle_with_fullscreen: bool,
// During windowDidResize, we use this to only send Moved if the position changed.
previous_position: Option<(i32, i32)>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
previous_position: Option<(f64, f64)>,
// Used to prevent redundant events.
previous_dpi_factor: f64,
}
impl DelegateState {
@@ -75,22 +104,30 @@ impl DelegateState {
}
}
unsafe fn saved_style_mask(&self, resizable: bool) -> NSWindowStyleMask {
let base_mask = self.save_style_mask
.take()
.unwrap_or_else(|| self.window.styleMask());
if resizable {
base_mask | NSWindowStyleMask::NSResizableWindowMask
} else {
base_mask & !NSWindowStyleMask::NSResizableWindowMask
}
}
fn saved_standard_frame(&self) -> NSRect {
self.standard_frame.get().unwrap_or_else(|| NSRect::new(
NSPoint::new(50.0, 50.0),
NSSize::new(800.0, 600.0),
))
}
fn restore_state_from_fullscreen(&mut self) {
let maximized = unsafe {
let mut win_attribs = self.win_attribs.borrow_mut();
win_attribs.fullscreen = None;
let mask = {
let base_mask = self.save_style_mask
.take()
.unwrap_or_else(|| self.window.styleMask());
if win_attribs.resizable {
base_mask | NSWindowStyleMask::NSResizableWindowMask
} else {
base_mask & !NSWindowStyleMask::NSResizableWindowMask
}
};
let mask = self.saved_style_mask(win_attribs.resizable);
util::set_style_mask(*self.window, *self.view, mask);
win_attribs.maximized
@@ -132,10 +169,7 @@ impl DelegateState {
let screen = NSScreen::mainScreen(nil);
NSScreen::visibleFrame(screen)
} else {
self.standard_frame.get().unwrap_or(NSRect::new(
NSPoint::new(50.0, 50.0),
NSSize::new(800.0, 600.0),
))
self.saved_standard_frame()
};
self.window.setFrame_display_(new_rect, 0);
@@ -150,48 +184,44 @@ pub struct WindowDelegate {
}
impl WindowDelegate {
// Emits an event via the `EventsLoop`'s callback or stores it in the pending queue.
pub fn emit_event(state: &mut DelegateState, window_event: WindowEvent) {
let window_id = get_window_id(*state.window);
let event = Event::WindowEvent {
window_id: WindowId(window_id),
event: window_event,
};
if let Some(shared) = state.shared.upgrade() {
shared.call_user_callback_with_event_or_store_in_pending(event);
}
}
pub fn emit_resize_event(state: &mut DelegateState) {
let rect = unsafe { NSView::frame(*state.view) };
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
WindowDelegate::emit_event(state, WindowEvent::Resized(size));
}
pub fn emit_move_event(state: &mut DelegateState) {
let rect = unsafe { NSWindow::frame(*state.window) };
let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect);
let moved = state.previous_position != Some((x, y));
if moved {
state.previous_position = Some((x, y));
WindowDelegate::emit_event(state, WindowEvent::Moved((x, y).into()));
}
}
/// Get the delegate class, initiailizing it neccessary
fn class() -> *const Class {
use std::os::raw::c_void;
// Emits an event via the `EventsLoop`'s callback or stores it in the pending queue.
unsafe fn emit_event(state: &mut DelegateState, window_event: WindowEvent) {
let window_id = get_window_id(*state.window);
let event = Event::WindowEvent {
window_id: WindowId(window_id),
event: window_event,
};
if let Some(shared) = state.shared.upgrade() {
shared.call_user_callback_with_event_or_store_in_pending(event);
}
}
// Called when the window is resized or when the window was moved to a different screen.
unsafe fn emit_resize_event(state: &mut DelegateState) {
let rect = NSView::frame(*state.view);
let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32;
let width = (scale_factor * rect.size.width as f32) as u32;
let height = (scale_factor * rect.size.height as f32) as u32;
emit_event(state, WindowEvent::Resized(width, height));
}
unsafe fn emit_move_event(state: &mut DelegateState) {
let frame_rect = NSWindow::frame(*state.window);
let x = frame_rect.origin.x as _;
let y = util::bottom_left_to_top_left(frame_rect);
let moved = state.previous_position != Some((x, y));
if moved {
state.previous_position = Some((x, y));
emit_event(state, WindowEvent::Moved(x, y));
}
}
extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::CloseRequested);
WindowDelegate::emit_event(state, WindowEvent::CloseRequested);
}
NO
}
@@ -201,7 +231,7 @@ impl WindowDelegate {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::Destroyed);
WindowDelegate::emit_event(state, WindowEvent::Destroyed);
// Remove the window from the shared state.
if let Some(shared) = state.shared.upgrade() {
@@ -215,8 +245,8 @@ impl WindowDelegate {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_resize_event(state);
emit_move_event(state);
WindowDelegate::emit_resize_event(state);
WindowDelegate::emit_move_event(state);
}
}
@@ -225,7 +255,7 @@ impl WindowDelegate {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_move_event(state);
WindowDelegate::emit_move_event(state);
}
}
@@ -233,18 +263,26 @@ impl WindowDelegate {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_resize_event(state);
let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32;
emit_event(state, WindowEvent::HiDPIFactorChanged(scale_factor));
let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64;
if state.previous_dpi_factor != dpi_factor {
state.previous_dpi_factor = dpi_factor;
WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor));
WindowDelegate::emit_resize_event(state);
}
}
}
// This will always be called before `window_did_change_screen`.
extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32;
emit_event(state, WindowEvent::HiDPIFactorChanged(scale_factor));
let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64;
if state.previous_dpi_factor != dpi_factor {
state.previous_dpi_factor = dpi_factor;
WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor));
WindowDelegate::emit_resize_event(state);
}
}
}
@@ -254,7 +292,7 @@ impl WindowDelegate {
// lost focus
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::Focused(true));
WindowDelegate::emit_event(state, WindowEvent::Focused(true));
}
}
@@ -262,7 +300,7 @@ impl WindowDelegate {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::Focused(false));
WindowDelegate::emit_event(state, WindowEvent::Focused(false));
}
}
@@ -285,7 +323,7 @@ impl WindowDelegate {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::HoveredFile(PathBuf::from(path)));
WindowDelegate::emit_event(state, WindowEvent::HoveredFile(PathBuf::from(path)));
}
};
@@ -293,7 +331,9 @@ impl WindowDelegate {
}
/// Invoked when the image is released
extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) {}
extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL {
YES
}
/// Invoked after the released image has been removed from the screen
extern fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL {
@@ -314,7 +354,7 @@ impl WindowDelegate {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::DroppedFile(PathBuf::from(path)));
WindowDelegate::emit_event(state, WindowEvent::DroppedFile(PathBuf::from(path)));
}
};
@@ -329,7 +369,7 @@ impl WindowDelegate {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state as *mut DelegateState);
emit_event(state, WindowEvent::HoveredFileCancelled);
WindowDelegate::emit_event(state, WindowEvent::HoveredFileCancelled);
}
}
@@ -403,7 +443,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
@@ -428,7 +468,7 @@ impl WindowDelegate {
decl.add_method(sel!(draggingEntered:),
dragging_entered as extern fn(&Object, Sel, id) -> BOOL);
decl.add_method(sel!(prepareForDragOperation:),
prepare_for_drag_operation as extern fn(&Object, Sel, id));
prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL);
decl.add_method(sel!(performDragOperation:),
perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL);
decl.add_method(sel!(concludeDragOperation:),
@@ -500,7 +540,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub titlebar_hidden: bool,
pub titlebar_buttons_hidden: bool,
pub fullsize_content_view: bool,
pub resize_increments: Option<(u32, u32)>,
pub resize_increments: Option<LogicalSize>,
}
pub struct Window2 {
@@ -508,6 +548,8 @@ pub struct Window2 {
pub window: IdRef,
pub delegate: WindowDelegate,
pub input_context: IdRef,
cursor: Weak<Mutex<util::Cursor>>,
cursor_hidden: AtomicBool,
}
unsafe impl Send for Window2 {}
@@ -563,6 +605,86 @@ 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);
}
}
#[inline]
fn get_simple_fullscreen(&self) -> bool {
self.delegate.state.is_simple_fullscreen.get()
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
let state = &self.delegate.state;
unsafe {
let app = NSApp();
let win_attribs = state.win_attribs.borrow_mut();
let is_native_fullscreen = win_attribs.fullscreen.is_some();
let is_simple_fullscreen = state.is_simple_fullscreen.get();
// Do nothing if native fullscreen is active.
if is_native_fullscreen || (fullscreen && is_simple_fullscreen) || (!fullscreen && !is_simple_fullscreen) {
return false;
}
if fullscreen {
// Remember the original window's settings
state.standard_frame.set(Some(NSWindow::frame(*self.window)));
state.save_style_mask.set(Some(self.window.styleMask()));
state.save_presentation_opts.set(Some(app.presentationOptions_()));
// Tell our window's state that we're in fullscreen
state.is_simple_fullscreen.set(true);
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock |
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
app.setPresentationOptions_(presentation_options);
// Hide the titlebar
util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSTitledWindowMask, false);
// Set the window frame to the screen frame size
let screen = self.window.screen();
let screen_frame = NSScreen::frame(screen);
NSWindow::setFrame_display_(*self.window, screen_frame, YES);
// Fullscreen windows can't be resized, minimized, or moved
util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSMiniaturizableWindowMask, false);
util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSResizableWindowMask, false);
NSWindow::setMovable_(*self.window, NO);
true
} else {
let saved_style_mask = state.saved_style_mask(win_attribs.resizable);
util::set_style_mask(*self.window, *self.view, saved_style_mask);
state.is_simple_fullscreen.set(false);
if let Some(presentation_opts) = state.save_presentation_opts.get() {
app.setPresentationOptions_(presentation_opts);
}
let frame = state.saved_standard_frame();
NSWindow::setFrame_display_(*self.window, frame, YES);
NSWindow::setMovable_(*self.window, YES);
true
}
}
}
}
impl Window2 {
@@ -572,7 +694,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");
}
}
@@ -599,7 +721,7 @@ impl Window2 {
return Err(OsError(format!("Couldn't create NSWindow")));
},
};
let view = match Window2::create_view(*window, Weak::clone(&shared)) {
let (view, cursor) = match Window2::create_view(*window, Weak::clone(&shared)) {
Some(view) => view,
None => {
let _: () = unsafe { msg_send![autoreleasepool, drain] };
@@ -617,12 +739,11 @@ impl Window2 {
app.activateIgnoringOtherApps_(YES);
if let Some((width, height)) = win_attribs.min_dimensions {
nswindow_set_min_dimensions(window.0, width.into(), height.into());
if let Some(dimensions) = win_attribs.min_dimensions {
nswindow_set_min_dimensions(window.0, dimensions);
}
if let Some((width, height)) = win_attribs.max_dimensions {
nswindow_set_max_dimensions(window.0, width.into(), height.into());
if let Some(dimensions) = win_attribs.max_dimensions {
nswindow_set_max_dimensions(window.0, dimensions);
}
use cocoa::foundation::NSArray;
@@ -631,23 +752,35 @@ impl Window2 {
registerForDraggedTypes:NSArray::arrayWithObject(nil, appkit::NSFilenamesPboardType)];
}
let ds = DelegateState {
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*window) as f64 };
let mut delegate_state = DelegateState {
view: view.clone(),
window: window.clone(),
shared,
win_attribs: RefCell::new(win_attribs.clone()),
standard_frame: Cell::new(None),
is_simple_fullscreen: Cell::new(false),
save_style_mask: Cell::new(None),
save_presentation_opts: Cell::new(None),
handle_with_fullscreen: win_attribs.fullscreen.is_some(),
previous_position: None,
previous_dpi_factor: dpi_factor,
};
ds.win_attribs.borrow_mut().fullscreen = None;
delegate_state.win_attribs.borrow_mut().fullscreen = None;
if dpi_factor != 1.0 {
WindowDelegate::emit_event(&mut delegate_state, WindowEvent::HiDpiFactorChanged(dpi_factor));
WindowDelegate::emit_resize_event(&mut delegate_state);
}
let window = Window2 {
view: view,
window: window,
delegate: WindowDelegate::new(ds),
delegate: WindowDelegate::new(delegate_state),
input_context,
cursor,
cursor_hidden: Default::default(),
};
// Set fullscreen mode after we setup everything
@@ -689,7 +822,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)
}
@@ -701,7 +842,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);
@@ -729,8 +870,10 @@ impl Window2 {
let frame = match screen {
Some(screen) => appkit::NSScreen::frame(screen),
None => {
let (width, height) = attrs.dimensions.unwrap_or((800, 600));
NSRect::new(NSPoint::new(0., 0.), NSSize::new(width as f64, height as f64))
let (width, height) = attrs.dimensions
.map(|logical| (logical.width, logical.height))
.unwrap_or((800.0, 600.0));
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height))
}
};
@@ -799,9 +942,10 @@ impl Window2 {
let _: () = msg_send![*window, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel];
}
if let Some((x, y)) = pl_attrs.resize_increments {
if x >= 1 && y >= 1 {
let size = NSSize::new(x as _, y as _);
if let Some(increments) = pl_attrs.resize_increments {
let (x, y) = (increments.width, increments.height);
if x >= 1.0 && y >= 1.0 {
let size = NSSize::new(x as CGFloat, y as CGFloat);
window.setResizeIncrements_(size);
}
}
@@ -814,14 +958,24 @@ impl Window2 {
}
}
fn create_view(window: id, shared: Weak<Shared>) -> Option<IdRef> {
fn create_view(window: id, shared: Weak<Shared>) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
unsafe {
let view = new_view(window, shared);
let (view, cursor) = new_view(window, shared);
view.non_nil().map(|view| {
view.setWantsBestResolutionOpenGLSurface_(YES);
// On Mojave, views automatically become layer-backed shortly after being added to
// a window. Changing the layer-backedness of a view breaks the association between
// the view and its associated OpenGL context. To work around this, on Mojave we
// explicitly make the view layer-backed up front so that AppKit doesn't do it
// itself and break the association with its context.
if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 {
view.setWantsLayer(YES);
}
window.setContentView_(*view);
window.makeFirstResponder_(*view);
view
(view, cursor)
})
}
}
@@ -843,15 +997,15 @@ impl Window2 {
unsafe { NSWindow::orderOut_(*self.window, nil); }
}
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
let frame_rect = unsafe { NSWindow::frame(*self.window) };
Some((
frame_rect.origin.x as i32,
frame_rect.origin.x as f64,
util::bottom_left_to_top_left(frame_rect),
))
).into())
}
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
let content_rect = unsafe {
NSWindow::contentRectForFrameRect_(
*self.window,
@@ -859,18 +1013,18 @@ impl Window2 {
)
};
Some((
content_rect.origin.x as i32,
content_rect.origin.x as f64,
util::bottom_left_to_top_left(content_rect),
))
).into())
}
pub fn set_position(&self, x: i32, y: i32) {
pub fn set_position(&self, position: LogicalPosition) {
let dummy = NSRect::new(
NSPoint::new(
x as f64,
position.x,
// While it's true that we're setting the top-left position, it still needs to be
// in a bottom-left coordinate system.
CGDisplay::main().pixels_high() as f64 - y as f64,
CGDisplay::main().pixels_high() as f64 - position.y,
),
NSSize::new(0f64, 0f64),
);
@@ -880,42 +1034,35 @@ impl Window2 {
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
let factor = self.hidpi_factor() as f64; // API convention is that size is in physical pixels
unsafe {
let view_frame = NSView::frame(*self.view);
Some(((view_frame.size.width*factor) as u32, (view_frame.size.height*factor) as u32))
}
pub fn get_inner_size(&self) -> Option<LogicalSize> {
let view_frame = unsafe { NSView::frame(*self.view) };
Some((view_frame.size.width as f64, view_frame.size.height as f64).into())
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
let factor = self.hidpi_factor() as f64; // API convention is that size is in physical pixels
unsafe {
let window_frame = NSWindow::frame(*self.window);
Some(((window_frame.size.width*factor) as u32, (window_frame.size.height*factor) as u32))
}
pub fn get_outer_size(&self) -> Option<LogicalSize> {
let view_frame = unsafe { NSWindow::frame(*self.window) };
Some((view_frame.size.width as f64, view_frame.size.height as f64).into())
}
#[inline]
pub fn set_inner_size(&self, width: u32, height: u32) {
let factor = self.hidpi_factor() as f64; // API convention is that size is in physical pixels
pub fn set_inner_size(&self, size: LogicalSize) {
unsafe {
NSWindow::setContentSize_(*self.window, NSSize::new((width as f64)/factor, (height as f64)/factor));
NSWindow::setContentSize_(*self.window, NSSize::new(size.width as CGFloat, size.height as CGFloat));
}
}
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
unsafe {
let (width, height) = dimensions.unwrap_or((0, 0));
nswindow_set_min_dimensions(self.window.0, width.into(), height.into());
let dimensions = dimensions.unwrap_or_else(|| (0, 0).into());
nswindow_set_min_dimensions(self.window.0, dimensions);
}
}
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
unsafe {
let (width, height) = dimensions.unwrap_or((!0, !0));
nswindow_set_max_dimensions(self.window.0, width.into(), height.into());
let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into());
nswindow_set_max_dimensions(self.window.0, dimensions);
}
}
@@ -934,94 +1081,59 @@ impl Window2 {
} // Otherwise, we don't change the mask until we exit fullscreen.
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!()
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
*self.window as *mut libc::c_void
}
pub fn set_cursor(&self, cursor: MouseCursor) {
let cursor_name = match cursor {
MouseCursor::Arrow | MouseCursor::Default => "arrowCursor",
MouseCursor::Hand => "pointingHandCursor",
MouseCursor::Grabbing | MouseCursor::Grab => "closedHandCursor",
MouseCursor::Text => "IBeamCursor",
MouseCursor::VerticalText => "IBeamCursorForVerticalLayout",
MouseCursor::Copy => "dragCopyCursor",
MouseCursor::Alias => "dragLinkCursor",
MouseCursor::NotAllowed | MouseCursor::NoDrop => "operationNotAllowedCursor",
MouseCursor::ContextMenu => "contextualMenuCursor",
MouseCursor::Crosshair => "crosshairCursor",
MouseCursor::EResize => "resizeRightCursor",
MouseCursor::NResize => "resizeUpCursor",
MouseCursor::WResize => "resizeLeftCursor",
MouseCursor::SResize => "resizeDownCursor",
MouseCursor::EwResize | MouseCursor::ColResize => "resizeLeftRightCursor",
MouseCursor::NsResize | MouseCursor::RowResize => "resizeUpDownCursor",
// TODO: Find appropriate OSX cursors
MouseCursor::NeResize | MouseCursor::NwResize |
MouseCursor::SeResize | MouseCursor::SwResize |
MouseCursor::NwseResize | MouseCursor::NeswResize |
MouseCursor::Cell | MouseCursor::NoneCursor |
MouseCursor::Wait | MouseCursor::Progress | MouseCursor::Help |
MouseCursor::Move | MouseCursor::AllScroll | MouseCursor::ZoomIn |
MouseCursor::ZoomOut => "arrowCursor",
};
let sel = Sel::register(cursor_name);
let cls = Class::get("NSCursor").unwrap();
let cursor = util::Cursor::from(cursor);
if let Some(cursor_access) = self.cursor.upgrade() {
*cursor_access.lock().unwrap() = cursor;
}
unsafe {
use objc::Message;
let cursor: id = cls.send_message(sel, ()).unwrap();
let _: () = msg_send![cursor, set];
let _: () = msg_send![*self.window,
invalidateCursorRectsForView:*self.view
];
}
}
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
let cls = Class::get("NSCursor").unwrap();
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
CGDisplay::associate_mouse_and_mouse_cursor_position(!grab)
.map_err(|status| format!("Failed to grab cursor: `CGError` {:?}", status))
}
// TODO: Check for errors.
match state {
CursorState::Normal => {
let _: () = unsafe { msg_send![cls, unhide] };
let _ = CGDisplay::associate_mouse_and_mouse_cursor_position(true);
Ok(())
},
CursorState::Hide => {
let _: () = unsafe { msg_send![cls, hide] };
Ok(())
},
CursorState::Grab => {
let _: () = unsafe { msg_send![cls, hide] };
let _ = CGDisplay::associate_mouse_and_mouse_cursor_position(false);
Ok(())
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let cursor_class = class!(NSCursor);
// macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once.
// (otherwise, `hide_cursor(false)` would need to be called n times!)
if hide != self.cursor_hidden.load(Ordering::Acquire) {
if hide {
let _: () = unsafe { msg_send![cursor_class, hide] };
} else {
let _: () = unsafe { msg_send![cursor_class, unhide] };
}
self.cursor_hidden.store(hide, Ordering::Release);
}
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
unsafe {
NSWindow::backingScaleFactor(*self.window) as f32
NSWindow::backingScaleFactor(*self.window) as f64
}
}
#[inline]
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
let (window_x, window_y) = self.get_position().unwrap_or((0, 0));
let (cursor_x, cursor_y) = (window_x + x, window_y + y);
// TODO: Check for errors.
let _ = CGDisplay::warp_mouse_cursor_position(appkit::CGPoint {
x: cursor_x as appkit::CGFloat,
y: cursor_y as appkit::CGFloat,
});
let _ = CGDisplay::associate_mouse_and_mouse_cursor_position(true);
pub fn set_cursor_position(&self, cursor_position: LogicalPosition) -> Result<(), String> {
let window_position = self.get_inner_position()
.ok_or("`get_inner_position` failed".to_owned())?;
let point = appkit::CGPoint {
x: (cursor_position.x + window_position.x) as CGFloat,
y: (cursor_position.y + window_position.y) as CGFloat,
};
CGDisplay::warp_mouse_cursor_position(point)
.map_err(|e| format!("`CGWarpMouseCursorPosition` failed: {:?}", e))?;
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
.map_err(|e| format!("`CGAssociateMouseAndMouseCursorPosition` failed: {:?}", e))?;
Ok(())
}
@@ -1031,11 +1143,25 @@ impl Window2 {
self.delegate.state.perform_maximized(maximized)
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
let state = &self.delegate.state;
let win_attribs = state.win_attribs.borrow();
win_attribs.fullscreen.clone()
}
#[inline]
/// TODO: Right now set_fullscreen do not work on switching monitors
/// in fullscreen mode
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
let state = &self.delegate.state;
// Do nothing if simple fullscreen is active.
if state.is_simple_fullscreen.get() {
return
}
let current = {
let win_attribs = state.win_attribs.borrow_mut();
@@ -1131,8 +1257,8 @@ impl Window2 {
}
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
set_ime_spot(*self.view, *self.input_context, x, y);
pub fn set_ime_spot(&self, logical_spot: LogicalPosition) {
set_ime_spot(*self.view, *self.input_context, logical_spot.x, logical_spot.y);
}
#[inline]
@@ -1141,6 +1267,16 @@ impl Window2 {
self::get_current_monitor(*self.window)
}
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = MacOSHandle {
ns_window: self.get_nswindow(),
ns_view: self.get_nsview(),
..MacOSHandle::empty()
};
RawWindowHandle::MacOS(handle)
}
}
// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier
@@ -1149,51 +1285,50 @@ pub fn get_window_id(window_cocoa_id: id) -> Id {
Id(window_cocoa_id as *const objc::runtime::Object as usize)
}
unsafe fn nswindow_set_min_dimensions<V: NSWindow + Copy>(
window: V, min_width: f64, min_height: f64)
{
unsafe fn nswindow_set_min_dimensions<V: NSWindow + Copy>(window: V, mut min_size: LogicalSize) {
let mut current_rect = NSWindow::frame(window);
let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window));
// Convert from client area size to window size
min_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0
min_size.height += (current_rect.size.height - content_rect.size.height) as f64;
window.setMinSize_(NSSize {
width: min_width,
height: min_height,
width: min_size.width as CGFloat,
height: min_size.height as CGFloat,
});
// If necessary, resize the window to match constraint
let mut current_rect = NSWindow::frame(window);
if current_rect.size.width < min_width {
current_rect.size.width = min_width;
if current_rect.size.width < min_size.width {
current_rect.size.width = min_size.width;
window.setFrame_display_(current_rect, 0)
}
if current_rect.size.height < min_height {
// The origin point of a rectangle is at its bottom left in Cocoa. To
// ensure the window's top-left point remains the same:
current_rect.origin.y +=
current_rect.size.height - min_height;
current_rect.size.height = min_height;
if current_rect.size.height < min_size.height {
// The origin point of a rectangle is at its bottom left in Cocoa.
// To ensure the window's top-left point remains the same:
current_rect.origin.y += current_rect.size.height - min_size.height;
current_rect.size.height = min_size.height;
window.setFrame_display_(current_rect, 0)
}
}
unsafe fn nswindow_set_max_dimensions<V: NSWindow + Copy>(
window: V, max_width: f64, max_height: f64)
{
unsafe fn nswindow_set_max_dimensions<V: NSWindow + Copy>(window: V, mut max_size: LogicalSize) {
let mut current_rect = NSWindow::frame(window);
let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window));
// Convert from client area size to window size
max_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0
max_size.height += (current_rect.size.height - content_rect.size.height) as f64;
window.setMaxSize_(NSSize {
width: max_width,
height: max_height,
width: max_size.width as CGFloat,
height: max_size.height as CGFloat,
});
// If necessary, resize the window to match constraint
let mut current_rect = NSWindow::frame(window);
if current_rect.size.width > max_width {
current_rect.size.width = max_width;
if current_rect.size.width > max_size.width {
current_rect.size.width = max_size.width;
window.setFrame_display_(current_rect, 0)
}
if current_rect.size.height > max_height {
// The origin point of a rectangle is at its bottom left in
// Cocoa. To ensure the window's top-left point remains the
// same:
current_rect.origin.y +=
current_rect.size.height - max_height;
current_rect.size.height = max_height;
if current_rect.size.height > max_size.height {
// The origin point of a rectangle is at its bottom left in Cocoa.
// To ensure the window's top-left point remains the same:
current_rect.origin.y += current_rect.size.height - max_size.height;
current_rect.size.height = max_size.height;
window.setFrame_display_(current_rect, 0)
}
}

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

189
src/platform/windows/dpi.rs Normal file
View File

@@ -0,0 +1,189 @@
#![allow(non_snake_case, unused_unsafe)]
use std::mem;
use std::os::raw::c_void;
use std::sync::{Once, ONCE_INIT};
use winapi::shared::minwindef::{BOOL, UINT, FALSE};
use winapi::shared::windef::{
DPI_AWARENESS_CONTEXT,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
HMONITOR,
HWND,
};
use winapi::shared::winerror::S_OK;
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
use winapi::um::shellscalingapi::{
MDT_EFFECTIVE_DPI,
MONITOR_DPI_TYPE,
PROCESS_DPI_AWARENESS,
PROCESS_PER_MONITOR_DPI_AWARE,
};
use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX};
use winapi::um::winnt::{HRESULT, LPCSTR};
use winapi::um::winuser::{self, MONITOR_DEFAULTTONEAREST};
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
type SetProcessDPIAware = unsafe extern "system" fn () -> BOOL;
type SetProcessDpiAwareness = unsafe extern "system" fn (
value: PROCESS_DPI_AWARENESS,
) -> HRESULT;
type SetProcessDpiAwarenessContext = unsafe extern "system" fn (
value: DPI_AWARENESS_CONTEXT,
) -> BOOL;
type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT;
type GetDpiForMonitor = unsafe extern "system" fn (
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut UINT,
dpi_y: *mut UINT,
) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn (hwnd: HWND) -> BOOL;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
}
}
lazy_static! {
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> = get_function!(
"user32.dll",
GetDpiForWindow
);
static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> = get_function!(
"shcore.dll",
GetDpiForMonitor
);
static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> = get_function!(
"user32.dll",
EnableNonClientDpiScaling
);
}
pub fn become_dpi_aware(enable: bool) {
if !enable { return; }
static ENABLE_DPI_AWARENESS: Once = ONCE_INIT;
ENABLE_DPI_AWARENESS.call_once(|| { unsafe {
if let Some(SetProcessDpiAwarenessContext) = get_function!(
"user32.dll",
SetProcessDpiAwarenessContext
) {
// We are on Windows 10 Anniversary Update (1607) or later.
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
== FALSE {
// V2 only works with Windows 10 Creators Update (1703). Try using the older
// V1 if we can't set V2.
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
}
} else if let Some(SetProcessDpiAwareness) = get_function!(
"shcore.dll",
SetProcessDpiAwareness
) {
// We are on Windows 8.1 or later.
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
} else if let Some(SetProcessDPIAware) = get_function!(
"user32.dll",
SetProcessDPIAware
) {
// We are on Vista or later.
SetProcessDPIAware();
}
} });
}
pub fn enable_non_client_dpi_scaling(hwnd: HWND) {
unsafe {
if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING {
EnableNonClientDpiScaling(hwnd);
}
}
}
pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option<u32> {
unsafe {
if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let mut dpi_x = 0;
let mut dpi_y = 0;
if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
// MSDN says that "the values of *dpiX and *dpiY are identical. You only need to
// record one of the values to determine the DPI and respond appropriately".
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
return Some(dpi_x as u32)
}
}
}
None
}
pub const BASE_DPI: u32 = 96;
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
let hdc = winuser::GetDC(hwnd);
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
}
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
// We are on Windows 10 Anniversary Update (1607) or later.
match GetDpiForWindow(hwnd) {
0 => BASE_DPI, // 0 is returned if hwnd is invalid
dpi => dpi as u32,
}
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if monitor.is_null() {
return BASE_DPI;
}
let mut dpi_x = 0;
let mut dpi_y = 0;
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
dpi_x as u32
} else {
BASE_DPI
}
} else {
// We are on Vista or later.
if winuser::IsProcessDPIAware() != FALSE {
// If the process is DPI aware, then scaling must be handled by the application using
// this DPI value.
GetDeviceCaps(hdc, LOGPIXELSX) as u32
} else {
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
// application and the WM.
BASE_DPI
}
}
}
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
}

View File

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

View File

@@ -1,33 +1,87 @@
use std::char;
use std::{char, ptr};
use std::os::raw::c_int;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use events::VirtualKeyCode;
use events::ModifiersState;
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT};
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT, HKL, HKL__};
use winapi::um::winuser;
use ScanCode;
fn key_pressed(vkey: c_int) -> bool {
unsafe {
(winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15)
}
}
pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
unsafe {
if winuser::GetKeyState(winuser::VK_SHIFT) & (1 << 15) == (1 << 15) {
mods.shift = true;
}
if winuser::GetKeyState(winuser::VK_CONTROL) & (1 << 15) == (1 << 15) {
mods.ctrl = true;
}
if winuser::GetKeyState(winuser::VK_MENU) & (1 << 15) == (1 << 15) {
mods.alt = true;
}
if (winuser::GetKeyState(winuser::VK_LWIN) | winuser::GetKeyState(winuser::VK_RWIN)) & (1 << 15) == (1 << 15) {
mods.logo = true;
}
}
let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU);
mods.shift = key_pressed(winuser::VK_SHIFT);
mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr;
mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr;
mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN);
mods
}
unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<char> {
let mut unicode_bytes = [0u16; 5];
let len = winuser::ToUnicodeEx(v_key, 0, keyboard_state.as_ptr(), unicode_bytes.as_mut_ptr(), unicode_bytes.len() as _, 0, hkl);
if len >= 1 {
char::decode_utf16(unicode_bytes.into_iter().cloned()).next().and_then(|c| c.ok())
} else {
None
}
}
/// Figures out if the keyboard layout has an AltGr key instead of an Alt key.
///
/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So,
/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every
/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If
/// pressing AltGr outputs characters that are different from the standard characters, the layout
/// uses AltGr. Otherwise, it doesn't.
///
/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416
fn layout_uses_altgr() -> bool {
unsafe {
static ACTIVE_LAYOUT: AtomicPtr<HKL__> = AtomicPtr::new(ptr::null_mut());
static USES_ALTGR: AtomicBool = AtomicBool::new(false);
let hkl = winuser::GetKeyboardLayout(0);
let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst);
if hkl == old_hkl {
return USES_ALTGR.load(Ordering::SeqCst);
}
let mut keyboard_state_altgr = [0u8; 256];
// AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses
// we have to emulate to do an AltGr test.
keyboard_state_altgr[winuser::VK_MENU as usize] = 0x80;
keyboard_state_altgr[winuser::VK_CONTROL as usize] = 0x80;
let keyboard_state_empty = [0u8; 256];
for v_key in 0..=255 {
let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl);
let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl);
if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) {
if noaltgr != altgr {
USES_ALTGR.store(true, Ordering::SeqCst);
return true;
}
}
}
USES_ALTGR.store(false, Ordering::SeqCst);
false
}
}
pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
match vkey {
@@ -45,8 +99,8 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
winuser::VK_RSHIFT => Some(VirtualKeyCode::RShift),
winuser::VK_LCONTROL => Some(VirtualKeyCode::LControl),
winuser::VK_RCONTROL => Some(VirtualKeyCode::RControl),
winuser::VK_LMENU => Some(VirtualKeyCode::LMenu),
winuser::VK_RMENU => Some(VirtualKeyCode::RMenu),
winuser::VK_LMENU => Some(VirtualKeyCode::LAlt),
winuser::VK_RMENU => Some(VirtualKeyCode::RAlt),
winuser::VK_PAUSE => Some(VirtualKeyCode::Pause),
winuser::VK_CAPITAL => Some(VirtualKeyCode::Capital),
winuser::VK_KANA => Some(VirtualKeyCode::Kana),
@@ -148,7 +202,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 +210,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),

File diff suppressed because it is too large Load Diff

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

@@ -11,13 +11,14 @@ pub use self::window::Window;
pub struct PlatformSpecificWindowBuilderAttributes {
pub parent: Option<HWND>,
pub taskbar_icon: Option<::Icon>,
pub no_redirection_bitmap: bool,
}
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
// 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 {}
@@ -25,6 +26,12 @@ unsafe impl Sync for Cursor {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u32);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
impl DeviceId {
pub fn get_persistent_identifier(&self) -> Option<String> {
if self.0 != 0 {
@@ -47,6 +54,16 @@ pub struct WindowId(HWND);
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl WindowId {
pub unsafe fn dummy() -> Self {
use std::ptr::null_mut;
WindowId(null_mut())
}
}
mod dpi;
mod drop_handler;
mod event;
mod events_loop;
mod icon;
@@ -54,3 +71,4 @@ mod monitor;
mod raw_input;
mod util;
mod window;
mod window_state;

View File

@@ -1,38 +1,33 @@
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::{DWORD, LPARAM, BOOL, TRUE};
use winapi::shared::windef::{HMONITOR, HDC, LPRECT, HWND};
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
use winapi::um::winnt::LONG;
use winapi::um::winuser;
use std::collections::VecDeque;
use std::{mem, ptr};
use std::collections::VecDeque;
use super::{EventsLoop, util};
use dpi::{PhysicalPosition, PhysicalSize};
use platform::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
use platform::platform::window::Window;
/// Win32 implementation of the main `MonitorId` object.
#[derive(Debug, Clone)]
pub struct MonitorId {
/// The system name of the adapter.
adapter_name: [wchar_t; 32],
/// Monitor handle.
hmonitor: HMonitor,
/// The system name of the monitor.
monitor_name: String,
/// True if this is the primary monitor.
primary: bool,
/// The position of the monitor in pixels on the desktop.
///
/// A window that is positioned at these coordinates will overlap the monitor.
position: (i32, i32),
/// The current resolution in pixels on the monitor.
dimensions: (u32, u32),
/// DPI scaling factor.
hidpi_factor: f32,
/// DPI scale factor.
hidpi_factor: f64,
}
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
@@ -44,122 +39,135 @@ struct HMonitor(HMONITOR);
unsafe impl Send for HMonitor {}
unsafe extern "system" fn monitor_enum_proc(hmonitor: HMONITOR, _: HDC, place: LPRECT, data: LPARAM) -> BOOL {
unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR,
_hdc: HDC,
_place: LPRECT,
data: LPARAM,
) -> BOOL {
let monitors = data as *mut VecDeque<MonitorId>;
(*monitors).push_back(MonitorId::from_hmonitor(hmonitor));
TRUE // continue enumeration
}
let place = *place;
let position = (place.left as i32, place.top as i32);
let dimensions = ((place.right - place.left) as u32, (place.bottom - place.top) as u32);
let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed();
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
if winuser::GetMonitorInfoW(hmonitor, &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO) == 0 {
// Some error occurred, just skip this monitor and go on.
return TRUE;
pub fn get_available_monitors() -> VecDeque<MonitorId> {
let mut monitors: VecDeque<MonitorId> = VecDeque::new();
unsafe {
winuser::EnumDisplayMonitors(
ptr::null_mut(),
ptr::null_mut(),
Some(monitor_enum_proc),
&mut monitors as *mut _ as LPARAM,
);
}
monitors
}
(*monitors).push_back(MonitorId {
adapter_name: monitor_info.szDevice,
hmonitor: HMonitor(hmonitor),
monitor_name: util::wchar_to_string(&monitor_info.szDevice),
primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0,
position,
dimensions,
hidpi_factor: 1.0,
});
// TRUE means continue enumeration.
TRUE
pub fn get_primary_monitor() -> MonitorId {
const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe {
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
};
MonitorId::from_hmonitor(hmonitor)
}
impl EventsLoop {
// TODO: Investigate opportunities for caching
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
unsafe {
let mut result: VecDeque<MonitorId> = VecDeque::new();
winuser::EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(monitor_enum_proc), &mut result as *mut _ as LPARAM);
result
}
get_available_monitors()
}
pub fn get_current_monitor(handle: HWND) -> MonitorId {
unsafe {
let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed();
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
let hmonitor = winuser::MonitorFromWindow(handle, winuser::MONITOR_DEFAULTTONEAREST);
winuser::GetMonitorInfoW(
hmonitor,
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
);
let place = monitor_info.rcMonitor;
let position = (place.left as i32, place.top as i32);
let dimensions = (
(place.right - place.left) as u32,
(place.bottom - place.top) as u32,
);
MonitorId {
adapter_name: monitor_info.szDevice,
hmonitor: super::monitor::HMonitor(hmonitor),
monitor_name: util::wchar_to_string(&monitor_info.szDevice),
primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0,
position,
dimensions,
hidpi_factor: 1.0,
}
}
pub fn get_current_monitor(hwnd: HWND) -> MonitorId {
let hmonitor = unsafe {
winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST)
};
MonitorId::from_hmonitor(hmonitor)
}
pub fn get_primary_monitor(&self) -> MonitorId {
// we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag
// TODO: it is possible to query the win32 API for the primary monitor, this should be done
// instead
for monitor in self.get_available_monitors().into_iter() {
if monitor.primary {
return monitor;
}
}
get_primary_monitor()
}
}
panic!("Failed to find the primary monitor")
impl Window {
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
get_available_monitors()
}
pub fn get_primary_monitor(&self) -> MonitorId {
get_primary_monitor()
}
}
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, util::WinError> {
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
let status = unsafe {
winuser::GetMonitorInfoW(
hmonitor,
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
)
};
if status == 0 {
Err(util::WinError::from_last_error())
} else {
Ok(monitor_info)
}
}
impl MonitorId {
/// See the docs if the crate root file.
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
let place = monitor_info.rcMonitor;
let dimensions = (
(place.right - place.left) as u32,
(place.bottom - place.top) as u32,
);
MonitorId {
hmonitor: HMonitor(hmonitor),
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
position: (place.left as i32, place.top as i32),
dimensions,
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
}
}
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
let left = self.position.0 as LONG;
let right = left + self.dimensions.0 as LONG;
let top = self.position.1 as LONG;
let bottom = top + self.dimensions.1 as LONG;
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some(self.monitor_name.clone())
}
/// See the docs of the crate root file.
#[inline]
pub fn get_native_identifier(&self) -> String {
self.monitor_name.clone()
}
/// See the docs of the crate root file.
#[inline]
pub fn get_hmonitor(&self) -> HMONITOR {
self.hmonitor.0
}
/// See the docs of the crate root file.
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
// TODO: retrieve the dimensions every time this is called
self.dimensions
}
/// A window that is positioned at these coordinates will overlap the monitor.
#[inline]
pub fn get_position(&self) -> (i32, i32) {
self.position
pub fn get_dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}

View File

@@ -1,9 +1,11 @@
use std::{self, mem, ptr};
use std::{self, mem, ptr, slice, io};
use std::ops::BitAnd;
use std::sync::atomic::{AtomicBool, Ordering};
use MouseCursor;
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::DWORD;
use winapi::shared::windef::RECT;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winbase::{
FormatMessageW,
@@ -19,6 +21,7 @@ use winapi::um::winnt::{
LANG_NEUTRAL,
SUBLANG_DEFAULT,
};
use winapi::um::winuser;
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where T:
@@ -28,18 +31,90 @@ where T:
}
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
String::from_utf16_lossy(wchar)
.trim_right_matches(0 as char)
.to_string()
String::from_utf16_lossy(wchar).to_string()
}
// 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;
let right_eq = a.right == b.right;
let top_eq = a.top == b.top;
let bottom_eq = a.bottom == b.bottom;
left_eq && right_eq && top_eq && bottom_eq
pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
let len = unsafe { lstrlenW(wchar) } as usize;
let wchar_slice = unsafe { slice::from_raw_parts(wchar, len) };
wchar_to_string(wchar_slice)
}
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
let mut data: T = mem::uninitialized();
if fun(&mut data) != 0 {
Some(data)
} else {
None
}
}
fn win_to_err<F: FnOnce() -> BOOL>(f: F) -> Result<(), io::Error> {
if f() != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn get_cursor_pos() -> Option<POINT> {
unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) }
}
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) }
}
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
unsafe {
let mut rect = mem::uninitialized();
let mut top_left = mem::zeroed();
win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?;
win_to_err(|| winuser::GetClientRect(hwnd, &mut rect))?;
rect.left += top_left.x;
rect.top += top_left.y;
rect.right += top_left.x;
rect.bottom += top_left.y;
Ok(rect)
}
}
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
unsafe {
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
let style_ex = winuser::GetWindowLongW(hwnd, winuser::GWL_EXSTYLE);
adjust_window_rect_with_styles(hwnd, style as _, style_ex as _, rect)
}
}
pub fn adjust_window_rect_with_styles(hwnd: HWND, style: DWORD, style_ex: DWORD, rect: RECT) -> Option<RECT> {
unsafe { status_map(|r| {
*r = rect;
let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL;
winuser::AdjustWindowRectEx(r, style as _ , b_menu, style_ex as _)
}) }
}
pub fn set_cursor_hidden(hidden: bool) {
static HIDDEN: AtomicBool = AtomicBool::new(false);
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
if changed {
unsafe{ winuser::ShowCursor(!hidden as BOOL) };
}
}
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
unsafe {
let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null());
win_to_err(|| winuser::ClipCursor(rect_ptr))
}
}
pub fn is_focused(window: HWND) -> bool {
window == unsafe{ winuser::GetActiveWindow() }
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -80,3 +155,29 @@ pub unsafe fn get_last_error() -> Option<String> {
}
None
}
impl MouseCursor {
pub(crate) fn to_windows_cursor(self) -> *const wchar_t {
match self {
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
MouseCursor::Hand => winuser::IDC_HAND,
MouseCursor::Crosshair => winuser::IDC_CROSS,
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
MouseCursor::Grab | MouseCursor::Grabbing |
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
MouseCursor::EResize | MouseCursor::WResize |
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
MouseCursor::NResize | MouseCursor::SResize |
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
MouseCursor::NeResize | MouseCursor::SwResize |
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
MouseCursor::NwResize | MouseCursor::SeResize |
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
MouseCursor::Wait => winuser::IDC_WAIT,
MouseCursor::Progress => winuser::IDC_APPSTARTING,
MouseCursor::Help => winuser::IDC_HELP,
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,329 @@
use {MouseCursor, WindowAttributes};
use std::{io, ptr};
use std::sync::MutexGuard;
use dpi::LogicalSize;
use platform::platform::{util, events_loop};
use platform::platform::icon::WinIcon;
use winapi::shared::windef::{RECT, HWND};
use winapi::shared::minwindef::DWORD;
use winapi::um::winuser;
/// Contains information about states and the window that the callback is going to use.
#[derive(Clone)]
pub struct WindowState {
pub mouse: MouseProperties,
/// Used by `WM_GETMINMAXINFO`.
pub min_size: Option<LogicalSize>,
pub max_size: Option<LogicalSize>,
pub window_icon: Option<WinIcon>,
pub taskbar_icon: Option<WinIcon>,
pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64,
pub fullscreen: Option<::MonitorId>,
window_flags: WindowFlags,
}
#[derive(Clone)]
pub struct SavedWindow {
pub client_rect: RECT,
pub dpi_factor: f64,
}
#[derive(Clone)]
pub struct MouseProperties {
pub cursor: MouseCursor,
cursor_flags: CursorFlags,
}
bitflags! {
pub struct CursorFlags: u8 {
const GRABBED = 1 << 0;
const HIDDEN = 1 << 1;
const IN_WINDOW = 1 << 2;
}
}
bitflags! {
pub struct WindowFlags: u32 {
const RESIZABLE = 1 << 0;
const DECORATIONS = 1 << 1;
const VISIBLE = 1 << 2;
const ON_TASKBAR = 1 << 3;
const ALWAYS_ON_TOP = 1 << 4;
const NO_BACK_BUFFER = 1 << 5;
const TRANSPARENT = 1 << 6;
const CHILD = 1 << 7;
const MAXIMIZED = 1 << 8;
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
/// included here to make masking easier.
const MARKER_FULLSCREEN = 1 << 9;
/// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`.
/// In most cases, it's okay to let those parameters change the state. However, when we're
/// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to
/// effect our stored state, because the purpose of `apply_diff` is to update the actual
/// window's state to match our stored state. This controls whether to accept those changes.
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10;
const FULLSCREEN_AND_MASK = !(
WindowFlags::DECORATIONS.bits |
WindowFlags::RESIZABLE.bits |
WindowFlags::MAXIMIZED.bits
);
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
}
}
impl WindowState {
pub fn new(
attributes: &WindowAttributes,
window_icon: Option<WinIcon>,
taskbar_icon: Option<WinIcon>,
dpi_factor: f64
) -> WindowState {
WindowState {
mouse: MouseProperties {
cursor: MouseCursor::default(),
cursor_flags: CursorFlags::empty(),
},
min_size: attributes.min_dimensions,
max_size: attributes.max_dimensions,
window_icon,
taskbar_icon,
saved_window: None,
dpi_factor,
fullscreen: None,
window_flags: WindowFlags::empty()
}
}
pub fn window_flags(&self) -> WindowFlags {
self.window_flags
}
pub fn set_window_flags<F>(mut this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>, f: F)
where F: FnOnce(&mut WindowFlags)
{
let old_flags = this.window_flags;
f(&mut this.window_flags);
let is_fullscreen = this.fullscreen.is_some();
this.window_flags.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
let new_flags = this.window_flags;
drop(this);
old_flags.apply_diff(window, new_flags, set_client_rect);
}
pub fn refresh_window_state(this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>) {
Self::set_window_flags(this, window, set_client_rect, |_| ());
}
pub fn set_window_flags_in_place<F>(&mut self, f: F)
where F: FnOnce(&mut WindowFlags)
{
f(&mut self.window_flags);
}
}
impl MouseProperties {
pub fn cursor_flags(&self) -> CursorFlags {
self.cursor_flags
}
pub fn set_cursor_flags<F>(&mut self, window: HWND, f: F) -> Result<(), io::Error>
where F: FnOnce(&mut CursorFlags)
{
let old_flags = self.cursor_flags;
f(&mut self.cursor_flags);
match self.cursor_flags.refresh_os_cursor(window) {
Ok(()) => (),
Err(e) => {
self.cursor_flags = old_flags;
return Err(e);
}
}
Ok(())
}
}
impl WindowFlags {
fn mask(mut self) -> WindowFlags {
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK;
}
if !self.contains(WindowFlags::VISIBLE) {
self &= WindowFlags::INVISIBLE_AND_MASK;
}
if !self.contains(WindowFlags::DECORATIONS) {
self &= WindowFlags::NO_DECORATIONS_AND_MASK;
}
self
}
pub fn to_window_styles(self) -> (DWORD, DWORD) {
use winapi::um::winuser::*;
let (mut style, mut style_ex) = (0, 0);
if self.contains(WindowFlags::RESIZABLE) {
style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
}
if self.contains(WindowFlags::DECORATIONS) {
style |= WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER;
style_ex = WS_EX_WINDOWEDGE;
}
if self.contains(WindowFlags::VISIBLE) {
style |= WS_VISIBLE;
}
if self.contains(WindowFlags::ON_TASKBAR) {
style_ex |= WS_EX_APPWINDOW;
}
if self.contains(WindowFlags::ALWAYS_ON_TOP) {
style_ex |= WS_EX_TOPMOST;
}
if self.contains(WindowFlags::NO_BACK_BUFFER) {
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
}
if self.contains(WindowFlags::TRANSPARENT) {
// Is this necessary? The docs say that WS_EX_LAYERED requires a windows class without
// CS_OWNDC, and Winit windows have that flag set.
style_ex |= WS_EX_LAYERED;
}
if self.contains(WindowFlags::CHILD) {
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
}
if self.contains(WindowFlags::MAXIMIZED) {
style |= WS_MAXIMIZE;
}
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
style_ex |= WS_EX_ACCEPTFILES;
(style, style_ex)
}
/// Adjust the window client rectangle to the return value, if present.
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
self = self.mask();
new = new.mask();
let diff = self ^ new;
if diff == WindowFlags::empty() {
return;
}
if diff.contains(WindowFlags::VISIBLE) {
unsafe {
winuser::ShowWindow(
window,
match new.contains(WindowFlags::VISIBLE) {
true => winuser::SW_SHOW,
false => winuser::SW_HIDE
}
);
}
}
if diff.contains(WindowFlags::ALWAYS_ON_TOP) {
unsafe {
winuser::SetWindowPos(
window,
match new.contains(WindowFlags::ALWAYS_ON_TOP) {
true => winuser::HWND_TOPMOST,
false => winuser::HWND_NOTOPMOST,
},
0, 0, 0, 0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
winuser::UpdateWindow(window);
}
}
if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) {
unsafe {
winuser::ShowWindow(
window,
match new.contains(WindowFlags::MAXIMIZED) {
true => winuser::SW_MAXIMIZE,
false => winuser::SW_RESTORE
}
);
}
}
if diff != WindowFlags::empty() {
let (style, style_ex) = new.to_window_styles();
unsafe {
winuser::SendMessageW(window, *events_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
match set_client_rect.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) {
Some(client_rect) => {
let (x, y, w, h) = (
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
);
winuser::SetWindowPos(
window,
ptr::null_mut(),
x, y, w, h,
winuser::SWP_NOZORDER
| winuser::SWP_FRAMECHANGED,
);
},
None => {
// Refresh the window frame.
winuser::SetWindowPos(
window,
ptr::null_mut(),
0, 0, 0, 0,
winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_FRAMECHANGED,
);
}
}
winuser::SendMessageW(window, *events_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
}
}
}
}
impl CursorFlags {
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> {
let client_rect = util::get_client_rect(window)?;
if util::is_focused(window) {
if self.contains(CursorFlags::GRABBED) {
util::set_cursor_clip(Some(client_rect))?;
} else {
util::set_cursor_clip(None)?;
}
}
let cursor_in_client = self.contains(CursorFlags::IN_WINDOW);
if cursor_in_client {
util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN));
} else {
util::set_cursor_hidden(false);
}
Ok(())
}
}

View File

@@ -1,16 +1,19 @@
use std::collections::vec_deque::IntoIter as VecDequeIter;
use CreationError;
use CursorState;
use EventsLoop;
use Icon;
use MouseCursor;
use Window;
use WindowBuilder;
use WindowId;
use libc;
use platform;
use {
CreationError,
EventsLoop,
Icon,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
platform,
Window,
WindowBuilder,
WindowId,
};
impl WindowBuilder {
/// Initializes a new `WindowBuilder` with default values.
@@ -23,29 +26,23 @@ impl WindowBuilder {
}
/// Requests the window to be of specific dimensions.
///
/// Width and height are in pixels.
#[inline]
pub fn with_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
self.window.dimensions = Some((width, height));
pub fn with_dimensions(mut self, size: LogicalSize) -> WindowBuilder {
self.window.dimensions = Some(size);
self
}
/// Sets a minimum dimension size for the window
///
/// Width and height are in pixels.
#[inline]
pub fn with_min_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
self.window.min_dimensions = Some((width, height));
pub fn with_min_dimensions(mut self, min_size: LogicalSize) -> WindowBuilder {
self.window.min_dimensions = Some(min_size);
self
}
/// Sets a maximum dimension size for the window
///
/// Width and height are in pixels.
#[inline]
pub fn with_max_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
self.window.max_dimensions = Some((width, height));
pub fn with_max_dimensions(mut self, max_size: LogicalSize) -> WindowBuilder {
self.window.max_dimensions = Some(max_size);
self
}
@@ -144,14 +141,15 @@ impl WindowBuilder {
///
/// Error should be very rare and only occur in case of permission denied, incompatible system,
/// out of memory, etc.
#[inline]
pub fn build(mut self, events_loop: &EventsLoop) -> Result<Window, CreationError> {
self.window.dimensions = Some(self.window.dimensions.unwrap_or_else(|| {
if let Some(ref monitor) = self.window.fullscreen {
// resizing the window to the dimensions of the monitor when fullscreen
monitor.get_dimensions()
LogicalSize::from_physical(monitor.get_dimensions(), 1.0)
} else {
// default dimensions
(1024, 768)
(1024, 768).into()
}
}));
@@ -219,7 +217,7 @@ impl Window {
///
/// Returns `None` if the window no longer exists.
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
self.window.get_position()
}
@@ -228,7 +226,7 @@ impl Window {
///
/// The same conditions that apply to `get_position` apply to this method.
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.window.get_inner_position()
}
@@ -238,61 +236,30 @@ impl Window {
///
/// This is a no-op if the window has already been closed.
#[inline]
pub fn set_position(&self, x: i32, y: i32) {
self.window.set_position(x, y)
pub fn set_position(&self, position: LogicalPosition) {
self.window.set_position(position)
}
/// Returns the size in pixels of the client area of the window.
/// Returns the logical size of the window's client area.
///
/// The client area is the content of the window, excluding the title bar and borders.
/// These are the dimensions that need to be supplied to `glViewport`.
///
/// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be.
///
/// Returns `None` if the window no longer exists.
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.window.get_inner_size()
}
/// Returns the size in points of the client area of the window.
/// Returns the logical size of the entire window.
///
/// The client area is the content of the window, excluding the title bar and borders.
/// To get the dimensions of the frame buffer when calling `glViewport`, multiply with hidpi factor.
///
/// Returns `None` if the window no longer exists.
///
/// DEPRECATED
#[inline]
#[deprecated]
pub fn get_inner_size_points(&self) -> Option<(u32, u32)> {
self.window.get_inner_size().map(|(x, y)| {
let hidpi = self.hidpi_factor();
((x as f32 / hidpi) as u32, (y as f32 / hidpi) as u32)
})
}
/// Returns the size in pixels of the client area of the window.
///
/// The client area is the content of the window, excluding the title bar and borders.
/// These are the dimensions of the frame buffer, and the dimensions that you should use
/// when you call `glViewport`.
///
/// Returns `None` if the window no longer exists.
///
/// DEPRECATED
#[inline]
#[deprecated]
pub fn get_inner_size_pixels(&self) -> Option<(u32, u32)> {
self.window.get_inner_size()
}
/// Returns the size in pixels of the window.
///
/// These dimensions include title bar and borders. If you don't want these, you should use
/// use `get_inner_size` instead.
/// These dimensions include the title bar and borders. If you don't want that (and you usually don't),
/// use `get_inner_size` instead.
///
/// Returns `None` if the window no longer exists.
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.window.get_outer_size()
}
@@ -302,23 +269,19 @@ impl Window {
///
/// This is a no-op if the window has already been closed.
#[inline]
pub fn set_inner_size(&self, x: u32, y: u32) {
self.window.set_inner_size(x, y)
pub fn set_inner_size(&self, size: LogicalSize) {
self.window.set_inner_size(size)
}
/// Sets a minimum dimension size for the window.
///
/// Width and height are in pixels.
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_min_dimensions(dimensions)
}
/// Sets a maximum dimension size for the window.
///
/// Width and height are in pixels.
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_max_dimensions(dimensions)
}
@@ -337,54 +300,61 @@ impl Window {
self.window.set_resizable(resizable)
}
/// DEPRECATED. Gets the native platform specific display for this window.
/// This is typically only required when integrating with
/// other libraries that need this information.
#[deprecated]
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
/// the most robust way to track the DPI you need to use to draw.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
#[inline]
pub unsafe fn platform_display(&self) -> *mut libc::c_void {
self.window.platform_display()
}
/// DEPRECATED. Gets the native platform specific window handle. This is
/// typically only required when integrating with other libraries
/// that need this information.
#[deprecated]
#[inline]
pub unsafe fn platform_window(&self) -> *mut libc::c_void {
self.window.platform_window()
pub fn get_hidpi_factor(&self) -> f64 {
self.window.get_hidpi_factor()
}
/// Modifies the mouse cursor of the window.
/// Has no effect on Android.
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
self.window.set_cursor(cursor);
}
/// Returns the ratio between the backing framebuffer resolution and the
/// window size in screen pixels. This is typically one for a normal display
/// and two for a retina display.
///
/// ## Platform-specific
/// On X11 the DPI factor can be overridden using the `WINIT_HIDPI_FACTOR` environment
/// variable.
#[inline]
pub fn hidpi_factor(&self) -> f32 {
self.window.hidpi_factor()
}
/// Changes the position of the cursor in window coordinates.
#[inline]
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
self.window.set_cursor_position(x, y)
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
self.window.set_cursor_position(position)
}
/// Sets how winit handles the cursor. See the documentation of `CursorState` for details.
/// Grabs the cursor, preventing it from leaving the window.
///
/// Has no effect on Android.
/// ## Platform-specific
///
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
self.window.set_cursor_state(state)
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
self.window.grab_cursor(grab)
}
/// Hides the cursor, making it invisible but still usable.
///
/// ## Platform-specific
///
/// On Windows and X11, the cursor is only hidden within the confines of the window.
///
/// On macOS, the cursor is hidden as long as the window has input focus, even if the cursor is outside of the
/// window.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn hide_cursor(&self, hide: bool) {
self.window.hide_cursor(hide)
}
/// Sets the window to maximized or back
@@ -399,6 +369,12 @@ impl Window {
self.window.set_fullscreen(monitor)
}
/// Gets the window's current fullscreen state.
#[inline]
pub fn get_fullscreen(&self) -> Option<MonitorId> {
self.window.get_fullscreen()
}
/// Turn window decorations on or off.
#[inline]
pub fn set_decorations(&self, decorations: bool) {
@@ -426,24 +402,49 @@ impl Window {
/// Sets location of IME candidate box in client area coordinates relative to the top left.
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
self.window.set_ime_spot(x, y)
pub fn set_ime_spot(&self, position: LogicalPosition) {
self.window.set_ime_spot(position)
}
/// Returns the monitor on which the window currently resides
#[inline]
pub fn get_current_monitor(&self) -> MonitorId {
self.window.get_current_monitor()
}
/// Returns the list of all the monitors available on the system.
///
/// This is the same as `EventsLoop::get_available_monitors`, and is provided for convenience.
#[inline]
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
let data = self.window.get_available_monitors();
AvailableMonitorsIter { data: data.into_iter() }
}
/// Returns the primary monitor of the system.
///
/// This is the same as `EventsLoop::get_primary_monitor`, and is provided for convenience.
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId { inner: self.window.get_primary_monitor() }
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.window.id())
}
}
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
self.window.raw_window_handle()
}
}
/// An iterator for the list of available monitors.
// Implementation note: we retrieve the list once, then serve each element by one by one.
// This may change in the future.
#[derive(Debug)]
pub struct AvailableMonitorsIter {
pub(crate) data: VecDequeIter<platform::MonitorId>,
}
@@ -477,26 +478,29 @@ impl MonitorId {
self.inner.get_name()
}
/// Returns the number of pixels currently displayed on the monitor.
/// Returns the monitor's resolution.
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
pub fn get_dimensions(&self) -> PhysicalSize {
self.inner.get_dimensions()
}
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
self.inner.get_position()
}
/// Returns the ratio between the monitor's physical pixels and logical pixels.
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// ## Platform-specific
/// On X11 the DPI factor can be overridden using the `WINIT_HIDPI_FACTOR` environment
/// variable.
///
/// - **X11:** This respects Xft.dpi XResource, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
self.inner.get_hidpi_factor()
}
}

39
tests/serde_objects.rs Normal file
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>();
}