Compare commits

...

165 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
Francesca Frangipane
23c384bd30 Release winit 0.15.1 (#564) 2018-06-13 12:06:22 -04:00
Victor Berger
ced1616e51 wayland: implement set_resizable (#565) 2018-06-13 11:18:44 -04:00
Peter Atashian
233ac4aed2 Update to winapi 0.3.5 (#563) 2018-06-12 11:58:18 -04:00
Danny Fritz
be5a2b0e87 Windows & X11: Window::set_resizable (#558)
* Windows: Window::set_resizable

* X11: Window::set_resizable

* Code style regarding resizable

* X11: set_resizable remember max/min window size

* Stub out set_resizable on Android, iOS, and emscripten

* remove comment block from docs

* Windows: set_resizable in fullscreen

* Special case Xfwm

* Added fun provisos to docs
2018-06-11 18:47:50 -04:00
Francesca Frangipane
2b4b64f499 macOS: Only detect clicks+motion within client area (#561)
* macOS: Only detect clicks within client area

* macOS: Only track mouse motion within client area

* Add CHANGELOG entry about #463 fix
2018-06-11 11:16:39 -04:00
Francesca Frangipane
262490d074 X11: Fix super fun race conditions (#554)
* X11: Fix super fun race conditions

* Fix build on rustc<1.26
2018-06-07 14:08:19 -04:00
Francesca Frangipane
8891cfd85e macOS: Resizable without decorations (#553)
* macOS: Resizable without decorations

* Fix style mask regressions
2018-06-07 13:29:23 -04:00
Danny Fritz
2cc8fa1eac X11: implement with_resizable (#540) (#556) 2018-06-07 12:46:15 -04:00
Francesca Frangipane
79aebf06dc macOS: Fix alt and win keycodes (#552)
* macOS: Generate LAlt/RAlt VirtualKeyCode

* macOS: Correct RWin/LWin
2018-06-06 11:30:26 -04:00
Francesca Frangipane
19dd961752 X11: Fix flickering when resizing with transparency enabled (#546)
* X11: Fix flickering when resizing with transparency enabled

* X11: Fix with_override_redirect
2018-06-03 13:11:54 -04:00
Christian Duerr
bf413ecb83 Fix DPI with 0 width/hight reported by xorg (#544)
* Fix DPI with 0 width/hight reported by xorg

* Add `WINIT_HIDPI_FACTOR` env variable

It is now possible to override the DPI factor using the
`WINIT_HIDPI_FACTOR` environment variable on X11.

The changelog also has been updated to introduce all current changes
made.

* Add documentation for the environment variable

* Fix nitpicks

* Learning the alphabet

* Panic with error message if DPI env var is <= 0
2018-06-03 12:41:47 -04:00
Francesca Frangipane
fd1a3eda1c Test against rustc 1.24.1 on Travis (#547) 2018-06-02 22:59:59 -04:00
Danny Fritz
0e2488db32 Added a GitHub PULL_REQUEST_TEMPLATE (#542)
* Added a GitHub PULL_REQUEST_TEMPLATE

* Updated to better reflect my dictatorial demands
2018-06-02 11:04:08 -04:00
Danny Fritz
58a00bffbb Windows: implement with_resizable (#540) (#541)
* Windows: implement with_resizable (#540)

* Fixed typo
2018-06-02 10:51:24 -04:00
Johannes Hofmann
bbfe57400d appveyor.yml: Test additional Rust channels (#539)
* In addition to nightly, also test the current stable version and Rust
  1.24.1
* Use rustup-init.exe to install the different versions
2018-05-30 07:57:40 -04:00
Francesca Frangipane
4372f6fdac X11: Flatten window model (#536) 2018-05-29 07:48:47 -04:00
Francesca Frangipane
30f798b246 X11: util design improvements (#534) 2018-05-27 08:49:35 -04:00
Francesca Frangipane
282770f11a Release winit 0.15.0 (#530) 2018-05-22 14:17:41 -04:00
Francesca Frangipane
17373a4e91 X11: Fix primary monitor fallback regression (#532) 2018-05-22 09:07:46 -04:00
Francesca Frangipane
a34147b602 macOS: Fix keyboard regressions (#533)
* Emit `ReceivedCharacter` for key repeats
* Enter emits `\r` instead of `\n`
2018-05-22 09:05:33 -04:00
Francesca Frangipane
cebd15bfd1 X11: Improve hint support (#529)
Fixes #257
2018-05-20 10:47:22 -04:00
Francesca Frangipane
f51f7c0ca8 Add option to make window "always on top" (#528)
* macOS: always_on_top

* Windows: always_on_top

* X11: always_on_top

* Stub set_always_on_top on other platforms
2018-05-20 10:24:05 -04:00
Francesca Frangipane
f6d26df64d Windows: CursorState improvements (#526)
* Windows: CursorState improvements

Fixes #523

Prior to changing the cursor state, we now check the current grab
state, since it can be invalidated by alt-tabbing and other things.

`CursorState::Hide` is also implemented now.

The cursor name is now wrapped in a `Cursor` struct to allow
multithreaded access.

`Window::set_cursor_state` has been reworked to use
`execute_in_thread`. Two unneeded `transmute` calls were also
removed.

The `WM_SETCURSOR` handler is much more readable now.

`MonitorId::get_adapter_name` has been removed, since it's dead
code and appears to be a relic from 4 years ago.

* Windows: CursorState::Grab no longer hides cursor

`MouseCursor::NoneCursor` has been implemented to allow for
equivalent behavior to the older implementation.

Windows and X11 now have consistent cursor grabbing behavior.
macOS still needs to be updated.

* Windows: Grabbing auto-hides again (for now)

This API needs more work, so let's stick to a bug fix and some
refactoring. However, it now hides using a different technique
than it did originally, which applies instantly instead of after
mouse movement.
2018-05-19 12:02:57 -04:00
Francesca Frangipane
fddfb2e2d6 Windows: Fix detection of Pause and Scroll keys (#525)
Fixes #524
2018-05-18 18:48:19 -04:00
Francesca Frangipane
dec728cfa2 macOS: Implement NSTextInputClient (#518)
Fixes #263
2018-05-17 21:28:30 -04:00
Francesca Frangipane
8440091a4e macOS: Implement with_resize_increments (#519)
Fixes #135
2018-05-16 10:16:36 -04:00
Francesca Frangipane
2464a135b3 macOS: Fix Window::get_current_monitor (#521)
* macOS: Implement MonitorId::get_position

* macOS: Fix Window::get_current_monitor
2018-05-16 09:41:45 -04:00
Francesca Frangipane
87fa120ebb macOS: Fix re-enabling decorations after the window is built without them (#520)
Fixes #517
2018-05-16 08:51:56 -04:00
Francesca Frangipane
d86f53a02c X11: Fix get_current_monitor (#515)
* X11: Fix get_current_monitor

Fixes #64

* impl Debug for MonitorId on all platforms
2018-05-14 08:14:57 -04:00
Francesca Frangipane
15a4fec3d9 X11: Fix scroll wheel delta on i3/etc. (#514)
Fixes #447
2018-05-13 08:44:23 -04:00
OJ Kwon
1819be1173 fix(mac_platform): forward keyevent to system (#511)
* fix(mac_platform): forward keyevent to system

* doc(changelog): update changelog
2018-05-12 22:10:57 -04:00
Victor Berger
ffa9b51d27 wayland: improve diagnostic of failed init (#512) 2018-05-12 07:58:11 -04:00
tinaun
b4a8c08f43 compile with icon_loading feature on docs.rs (#509)
* compile with icon_loading feature on docs.rs

these functions should be more visible now.

* Explicitly document which functions require icon_loading
2018-05-11 10:33:06 -04:00
Francesca Frangipane
741bcc4672 Correct privacy for Icon::to_cardinals (#510) 2018-05-10 18:42:41 -04:00
82 changed files with 9892 additions and 4533 deletions

5
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

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

2
.gitignore vendored
View File

@@ -1,4 +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

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

View File

@@ -1,5 +1,202 @@
# 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.
- `MouseCursor` and `CursorState` now implement `Default`.
- `WindowBuilder::with_resizable` implemented for Windows, X11, Wayland, and macOS.
- `Window::set_resizable` implemented for Windows, X11, Wayland, and macOS.
- On X11, if the monitor's width or height in millimeters is reported as 0, the DPI is now 1.0 instead of +inf.
- On X11, the environment variable `WINIT_HIDPI_FACTOR` has been added for overriding DPI factor.
- On X11, enabling transparency no longer causes the window contents to flicker when resizing.
- On X11, `with_override_redirect` now actually enables override redirect.
- macOS now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead of `None` for both.
- On macOS, `VirtualKeyCode::RWin` and `VirtualKeyCode::LWin` are no longer switched.
- On macOS, windows without decorations can once again be resized.
- Fixed race conditions when creating an `EventsLoop` on X11, most commonly manifesting as "[xcb] Unknown sequence number while processing queue".
- On macOS, `CursorMoved` and `MouseInput` events are only generated if they occurs within the window's client area.
- On macOS, resizing the window no longer generates a spurious `MouseInput` event.
# Version 0.15.0 (2018-05-22)
- `Icon::to_cardinals` is no longer public, since it was never supposed to be.
- Wayland: improve diagnostics if initialization fails
- Fix some system event key doesn't work when focused, do not block keyevent forward to system on macOS
- On X11, the scroll wheel position is now correctly reset on i3 and other WMs that have the same quirk.
- On X11, `Window::get_current_monitor` now reliably returns the correct monitor.
- On X11, `Window::hidpi_factor` returns values from XRandR rather than the inaccurate values previously queried from the core protocol.
- On X11, the primary monitor is detected correctly even when using versions of XRandR less than 1.5.
- `MonitorId` now implements `Debug`.
- Fixed bug on macOS where using `with_decorations(false)` would cause `set_decorations(true)` to produce a transparent titlebar with no title.
- Implemented `MonitorId::get_position` on macOS.
- On macOS, `Window::get_current_monitor` now returns accurate values.
- Added `WindowBuilderExt::with_resize_increments` to macOS.
- **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`.
- macOS keyboard handling has been overhauled, allowing for the use of dead keys, IME, etc. Right modifier keys are also no longer reported as being left.
- Added the `Window::set_ime_spot(x: i32, y: i32)` method, which is implemented on X11 and macOS.
- **Breaking**: `os::unix::WindowExt::send_xim_spot(x: i16, y: i16)` no longer exists. Switch to the new `Window::set_ime_spot(x: i32, y: i32)`, which has equivalent functionality.
- Fixed detection of `Pause` and `Scroll` keys on Windows.
- On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the cursor.
- On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first.
- Implemented `MouseCursor::NoneCursor` on Windows.
- Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11.
- On X11, `WindowBuilderExt` now has `with_class`, `with_override_redirect`, and `with_x11_window_type` to allow for more control over window creation. `WindowExt` additionally has `set_urgent`.
- More hints are set by default on X11, including `_NET_WM_PID` and `WM_CLIENT_MACHINE`. Note that prior to this, the `WM_CLASS` hint was automatically set to whatever value was passed to `with_title`. It's now set to the executable name to better conform to expectations and the specification; if this is undesirable, you must explicitly use `WindowBuilderExt::with_class`.
# Version 0.14.0 (2018-05-09)
- Created the `Copy`, `Paste` and `Cut` `VirtualKeyCode`s and added support for them on X11 and Wayland

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,55 +1,71 @@
[package]
name = "winit"
version = "0.14.0"
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", "serde"]
[features]
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.14"
core-foundation = "0.5"
core-graphics = "0.13"
objc = "0.2.3"
cocoa = "0.18.4"
core-foundation = "0.6"
core-graphics = "0.17.3"
[target.'cfg(target_os = "windows")'.dependencies]
backtrace = "0.3"
bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3"
version = "0.3.6"
features = [
"combaseapi",
"dwmapi",
"errhandlingapi",
"hidusage",
"libloaderapi",
"objbase",
"ole2",
"processthreadsapi",
"shellapi",
"shellscalingapi",
"shobjidl_core",
"unknwnbase",
"winbase",
"windowsx",
"winerror",
"wingdi",
"winnt",
"winuser",
"wingdi",
"shellapi",
"dwmapi",
"processthreadsapi",
"libloaderapi",
"windowsx",
"hidusage",
"combaseapi",
"objbase",
"unknwnbase",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies]
wayland-client = { version = "0.20.4", features = [ "dlopen", "egl", "cursor"] }
smithay-client-toolkit = "0.2.1"
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.14"
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

@@ -1,12 +1,17 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
install:
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe"
- rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
- SET PATH=%PATH%;%USERPROFILE%\.cargo\bin
- SET PATH=%PATH%;C:\MinGW\bin
- rustc -V
- cargo -V
@@ -15,3 +20,5 @@ build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo test --features icon_loading --verbose

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

38
examples/resizable.rs Normal file
View File

@@ -0,0 +1,38 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let mut resizable = false;
let window = winit::WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_dimensions((400, 200).into())
.with_resizable(resizable)
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
match event {
winit::Event::WindowEvent { event, .. } => match event {
winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break,
winit::WindowEvent::KeyboardInput {
input:
winit::KeyboardInput {
virtual_keycode: Some(winit::VirtualKeyCode::Space),
state: winit::ElementState::Released,
..
},
..
} => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);
}
_ => (),
},
_ => (),
};
winit::ControlFlow::Continue
});
}

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

@@ -104,12 +104,16 @@ impl Icon {
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from the path of an image on the filesystem.
///
/// Requires the `icon_loading` feature.
pub fn from_path<P: AsRef<Path>>(path: P) -> image::ImageResult<Self> {
image::open(path).map(Into::into)
}
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from anything implementing `BufRead` and `Seek`.
///
/// Requires the `icon_loading` feature.
pub fn from_reader<R: BufRead + Seek>(
reader: R,
format: image::ImageFormat,
@@ -120,12 +124,16 @@ impl Icon {
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from the unprocessed bytes of an image file.
/// Uses heuristics to determine format.
///
/// Requires the `icon_loading` feature.
pub fn from_bytes(bytes: &[u8]) -> image::ImageResult<Self> {
image::load_from_memory(bytes).map(Into::into)
}
#[cfg(feature = "icon_loading")]
/// Loads an `Icon` from the unprocessed bytes of an image.
///
/// Requires the `icon_loading` feature.
pub fn from_bytes_with_format(
bytes: &[u8],
format: image::ImageFormat,
@@ -135,9 +143,10 @@ impl Icon {
}
#[cfg(feature = "icon_loading")]
/// 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() {
@@ -148,6 +157,7 @@ impl From<image::DynamicImage> for Icon {
}
#[cfg(feature = "icon_loading")]
/// Requires the `icon_loading` feature.
impl From<image::RgbaImage> for Icon {
fn from(buf: image::RgbaImage) -> Self {
let (width, height) = buf.dimensions();

View File

@@ -15,7 +15,7 @@
//! - Calling `Window::new(&events_loop)`.
//! - Calling `let builder = WindowBuilder::new()` then `builder.build(&events_loop)`.
//!
//! The first way is the simpliest way and will give you default values for everything.
//! The first way is the simplest way and will give you default values for everything.
//!
//! The second way allows you to customize the way your window will look and behave by modifying
//! the fields of the `WindowBuilder` object before you create the window.
@@ -25,24 +25,28 @@
//! Once a window has been created, it will *generate events*. For example whenever the user moves
//! the window, resizes the window, moves the mouse, etc. an event is generated.
//!
//! The events generated by a window can be retreived from the `EventsLoop` the window was created
//! The events generated by a window can be retrieved from the `EventsLoop` the window was created
//! with.
//!
//! There are two ways to do so. The first is to call `events_loop.poll_events(...)`, which will
//! retreive all the events pending on the windows and immediately return after no new event is
//! retrieve all the events pending on the windows and immediately return after no new event is
//! available. You usually want to use this method in application that render continuously on the
//! screen, such as video games.
//!
//! ```no_run
//! use winit::{Event, WindowEvent};
//! use winit::dpi::LogicalSize;
//! # use winit::EventsLoop;
//! # let mut events_loop = EventsLoop::new();
//!
//! loop {
//! events_loop.poll_events(|event| {
//! match event {
//! Event::WindowEvent { event: WindowEvent::Resized(w, h), .. } => {
//! println!("The window was resized to {}x{}", w, h);
//! Event::WindowEvent {
//! event: WindowEvent::Resized(LogicalSize { width, height }),
//! ..
//! } => {
//! println!("The window was resized to {}x{}", width, height);
//! },
//! _ => ()
//! }
@@ -76,20 +80,29 @@
//! # Drawing on the window
//!
//! Winit doesn't provide any function that allows drawing on a window. However it allows you to
//! retreive the raw handle of the window (see the `os` module for that), which in turn allows you
//! retrieve the raw handle of the window (see the `os` module for that), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
//!
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))]
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
extern crate libc;
#[macro_use]
extern crate log;
#[cfg(feature = "icon_loading")]
extern crate image;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(target_os = "windows")]
#[macro_use]
extern crate winapi;
#[cfg(target_os = "windows")]
extern crate backtrace;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
@@ -99,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;
@@ -142,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()`.
@@ -151,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
@@ -159,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()`
@@ -177,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,
@@ -229,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
@@ -251,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.
///
@@ -289,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 {
@@ -319,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,
@@ -345,7 +416,6 @@ pub enum MouseCursor {
/// Cursor showing that something cannot be done.
NotAllowed,
ContextMenu,
NoneCursor,
Cell,
VerticalText,
Alias,
@@ -375,41 +445,35 @@ pub enum MouseCursor {
RowResize,
}
/// Describes how winit handles the cursor.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CursorState {
/// Normal cursor behavior.
Normal,
/// The cursor will be invisible when over the window.
Hide,
/// Grabs the mouse cursor. The cursor's motion will be confined to this
/// window and the window has exclusive access to further events regarding
/// the cursor.
///
/// This is useful for first-person cameras for example.
Grab,
impl Default for MouseCursor {
fn default() -> Self {
MouseCursor::Default
}
}
/// Attributes to use when creating a window.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct WindowAttributes {
/// The dimensions of the window. If this is `None`, some platform-specific dimensions will be
/// used.
///
/// The default is `None`.
pub dimensions: Option<(u32, u32)>,
pub dimensions: Option<LogicalSize>,
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
///
/// The default is `None`.
pub min_dimensions: Option<(u32, u32)>,
pub min_dimensions: Option<LogicalSize>,
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
///
/// The default is `None`.
pub max_dimensions: Option<(u32, u32)>,
pub max_dimensions: Option<LogicalSize>,
/// Whether the window is resizable or not.
///
/// The default is `true`.
pub resizable: bool,
/// Whether the window should be set as fullscreen upon creation.
///
@@ -442,6 +506,11 @@ pub struct WindowAttributes {
/// The default is `true`.
pub decorations: bool,
/// Whether the window should always be on top of other windows.
///
/// The default is `false`.
pub always_on_top: bool,
/// The window icon.
///
/// The default is `None`.
@@ -459,12 +528,14 @@ impl Default for WindowAttributes {
dimensions: None,
min_dimensions: None,
max_dimensions: None,
resizable: true,
title: "winit window".to_owned(),
maximized: false,
fullscreen: None,
visible: true,
transparent: false,
decorations: true,
always_on_top: false,
window_icon: None,
multitouch: false,
}

59
src/os/ios.rs Normal file
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
@@ -71,64 +91,72 @@ impl From<ActivationPolicy> for NSApplicationActivationPolicy {
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowBuilderExt {
/// Sets the activation policy for the window being built.
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> WindowBuilder;
/// Makes the titlebar transparent and allows the content to appear behind it.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
/// Hides the window title.
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
/// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
/// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
}
impl WindowBuilderExt for WindowBuilder {
/// Sets the activation policy for the window being built
#[inline]
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
self.platform_specific.activation_policy = activation_policy;
self
}
/// Enables click-and-drag behavior for the entire window, not just the titlebar
#[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> WindowBuilder {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self
}
/// Makes the titlebar transparent and allows the content to appear behind it
#[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
self.platform_specific.titlebar_transparent = titlebar_transparent;
self
}
/// Hides the window titlebar
#[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
/// Hides the window titlebar buttons
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
/// Hides the window title
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
self.platform_specific.title_hidden = title_hidden;
self
}
/// Makes the window content appear behind the titlebar
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
}
/// Additional methods on `MonitorId` that are specific to MacOS.

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;
@@ -17,6 +25,75 @@ use platform::x11::ffi::XVisualInfo;
pub use platform::x11;
pub use platform::XNotSupported;
pub use platform::x11::util::WindowType as XWindowType;
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
struct WaylandThemeObject(WaylandTheme);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on `EventsLoop` that are specific to Linux.
pub trait EventsLoopExt {
@@ -36,6 +113,13 @@ pub trait EventsLoopExt {
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventsLoop`.
///
/// Returns `None` if the `EventsLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `EventsLoop` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl EventsLoopExt for EventsLoop {
@@ -71,9 +155,18 @@ impl EventsLoopExt for EventsLoop {
}
#[inline]
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
self.events_loop.x_connection().cloned()
}
#[inline]
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
match self.events_loop {
LinuxEventsLoop::Wayland(ref e) => Some(e.get_display().c_ptr() as *mut _),
_ => None
}
}
}
/// Additional methods on `Window` that are specific to Unix.
@@ -92,9 +185,11 @@ pub trait WindowExt {
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
fn send_xim_spot(&self, x: i16, y: i16);
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
fn set_urgent(&self, is_urgent: bool);
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
@@ -117,6 +212,9 @@ pub trait WindowExt {
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme(&self, theme: WaylandTheme);
/// Check if the window is ready for drawing
///
/// It is a remnant of a previous implementation detail for the
@@ -144,6 +242,7 @@ impl WindowExt for Window {
}
}
#[inline]
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
@@ -151,6 +250,8 @@ impl WindowExt for Window {
}
}
#[inline]
#[doc(hidden)]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
@@ -158,6 +259,7 @@ impl WindowExt for Window {
}
}
#[inline]
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
@@ -165,9 +267,10 @@ impl WindowExt for Window {
}
}
fn send_xim_spot(&self, x: i16, y: i16) {
#[inline]
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.send_xim_spot(x, y);
w.set_urgent(is_urgent);
}
}
@@ -187,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
@@ -198,10 +309,25 @@ pub trait WindowBuilderExt {
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
fn with_class(self, class: String, instance: String) -> WindowBuilder;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, width_inc: i32, height_inc: i32) -> 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: i32, base_height: i32) -> 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 {
@@ -220,14 +346,44 @@ impl WindowBuilderExt for WindowBuilder {
}
#[inline]
fn with_resize_increments(mut self, width_inc: i32, height_inc: i32) -> WindowBuilder {
self.platform_specific.resize_increments = Some((width_inc, height_inc));
fn with_class(mut self, instance: String, class: String) -> WindowBuilder {
self.platform_specific.class = Some((instance, class));
self
}
#[inline]
fn with_base_size(mut self, base_width: i32, base_height: i32) -> WindowBuilder {
self.platform_specific.base_size = Some((base_width, base_height));
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
self.platform_specific.x11_window_type = x11_window_type;
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
self.platform_specific.app_id = Some(app_id);
self
}
}

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,9 +171,21 @@ impl EventsLoopProxy {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
pub struct Window {
native_window: *const c_void,
}
@@ -167,7 +193,26 @@ pub struct Window {
#[derive(Clone)]
pub struct MonitorId;
mod ffi;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
@@ -176,21 +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,93 +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 get_inner_size(&self) -> Option<(u32, u32)> {
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
if self.native_window.is_null() {
None
} else {
Some((
unsafe { ffi::ANativeWindow_getWidth(self.native_window as *const _) } as u32,
unsafe { ffi::ANativeWindow_getHeight(self.native_window as *const _) } as u32
))
let dpi_factor = self.get_hidpi_factor();
let physical_size = self.get_current_monitor().get_dimensions();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _x: u32, _y: u32) {
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!();
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
unimplemented!()
pub fn get_hidpi_factor(&self) -> f64 {
self.get_current_monitor().get_hidpi_factor()
}
#[inline]
pub fn set_cursor(&self, _: MouseCursor) {
// N/A
}
#[inline]
pub fn set_cursor_state(&self, _state: CursorState) -> Result<(), String> {
Ok(())
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on Android.".to_owned())
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
1.0
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
Ok(())
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on Android.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
// N/A
// Android has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// Android has single screen maximized apps so nothing to do
}
@@ -323,19 +387,50 @@ impl Window {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId{inner: MonitorId}
pub fn set_ime_spot(&self, _spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
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,10 +27,16 @@ unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone, Default)]
pub struct PlatformSpecificHeadlessBuilderAttributes;
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct MonitorId;
impl MonitorId {
@@ -34,18 +46,18 @@ impl MonitorId {
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
unimplemented!()
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
(0, 0)
pub fn get_dimensions(&self) -> PhysicalSize {
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
1.0
pub fn get_hidpi_factor(&self) -> f64 {
get_hidpi_factor()
}
}
@@ -90,17 +102,19 @@ impl EventsLoop {
}
}
#[inline]
pub fn interrupt(&self) {
self.interrupted.store(true, Ordering::Relaxed);
}
#[inline]
pub fn create_proxy(&self) -> EventsLoopProxy {
unimplemented!()
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut list = VecDeque::new();
let mut list = VecDeque::with_capacity(1);
list.push_back(MonitorId);
list
}
@@ -115,7 +129,7 @@ impl EventsLoop {
{
let ref mut window = *self.window.lock().unwrap();
if let &mut Some(ref mut window) = window {
while let Some(event) = window.events.borrow_mut().pop_front() {
while let Some(event) = window.events.lock().unwrap().pop_front() {
callback(event)
}
}
@@ -141,10 +155,17 @@ impl EventsLoop {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
pub struct Window2 {
cursor_state: Mutex<::CursorState>,
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
is_fullscreen: bool,
events: Box<RefCell<VecDeque<::Event>>>,
events: Box<Mutex<VecDeque<::Event>>>,
}
pub struct Window {
@@ -176,7 +197,7 @@ extern "C" fn mouse_callback(
event_queue: *mut c_void) -> ffi::EM_BOOL
{
unsafe {
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
let modifiers = ::ModifiersState {
shift: (*event).shiftKey == ffi::EM_TRUE,
@@ -187,15 +208,20 @@ extern "C" fn mouse_callback(
match event_type {
ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => {
queue.borrow_mut().push_back(::Event::WindowEvent {
let dpi_factor = get_hidpi_factor();
let position = LogicalPosition::from_physical(
((*event).canvasX as f64, (*event).canvasY as f64),
dpi_factor,
);
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::CursorMoved {
device_id: ::DeviceId(DeviceId),
position: ((*event).canvasX as f64, (*event).canvasY as f64),
position,
modifiers: modifiers,
}
});
queue.borrow_mut().push_back(::Event::DeviceEvent {
queue.lock().unwrap().push_back(::Event::DeviceEvent {
device_id: ::DeviceId(DeviceId),
event: ::DeviceEvent::MouseMotion {
delta: ((*event).movementX as f64, (*event).movementY as f64),
@@ -215,7 +241,7 @@ extern "C" fn mouse_callback(
ffi::EMSCRIPTEN_EVENT_MOUSEUP => ::ElementState::Released,
_ => unreachable!(),
};
queue.borrow_mut().push_back(::Event::WindowEvent {
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::MouseInput {
device_id: ::DeviceId(DeviceId),
@@ -238,7 +264,7 @@ extern "C" fn keyboard_callback(
event_queue: *mut c_void) -> ffi::EM_BOOL
{
unsafe {
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
let modifiers = ::ModifiersState {
shift: (*event).shiftKey == ffi::EM_TRUE,
@@ -249,7 +275,7 @@ extern "C" fn keyboard_callback(
match event_type {
ffi::EMSCRIPTEN_EVENT_KEYDOWN => {
queue.borrow_mut().push_back(::Event::WindowEvent {
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::KeyboardInput {
device_id: ::DeviceId(DeviceId),
@@ -263,7 +289,7 @@ extern "C" fn keyboard_callback(
});
},
ffi::EMSCRIPTEN_EVENT_KEYUP => {
queue.borrow_mut().push_back(::Event::WindowEvent {
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::KeyboardInput {
device_id: ::DeviceId(DeviceId),
@@ -289,7 +315,7 @@ extern fn touch_callback(
event_queue: *mut c_void) -> ffi::EM_BOOL
{
unsafe {
let queue: &RefCell<VecDeque<::Event>> = mem::transmute(event_queue);
let queue: &Mutex<VecDeque<::Event>> = mem::transmute(event_queue);
let phase = match event_type {
ffi::EMSCRIPTEN_EVENT_TOUCHSTART => ::TouchPhase::Started,
@@ -302,13 +328,18 @@ extern fn touch_callback(
for touch in 0..(*event).numTouches as usize {
let touch = (*event).touches[touch];
if touch.isChanged == ffi::EM_TRUE {
queue.borrow_mut().push_back(::Event::WindowEvent {
let dpi_factor = get_hidpi_factor();
let location = LogicalPosition::from_physical(
(touch.canvasX as f64, touch.canvasY as f64),
dpi_factor,
);
queue.lock().unwrap().push_back(::Event::WindowEvent {
window_id: ::WindowId(WindowId(0)),
event: ::WindowEvent::Touch(::Touch {
device_id: ::DeviceId(DeviceId),
phase,
id: touch.identifier as u64,
location: (touch.canvasX as f64, touch.canvasY as f64),
location,
}),
});
}
@@ -356,8 +387,9 @@ impl Window {
}
let w = Window2 {
cursor_state: Mutex::new(::CursorState::Normal),
events: Box::new(RefCell::new(VecDeque::new())),
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
events: Default::default(),
is_fullscreen: attribs.fullscreen.is_some(),
};
@@ -395,8 +427,8 @@ impl Window {
em_try(ffi::emscripten_set_fullscreenchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(fullscreen_callback)))
.map_err(|e| ::CreationError::OsError(e))?;
}
} else if let Some((w, h)) = attribs.dimensions {
window.set_inner_size(w, h);
} else if let Some(size) = attribs.dimensions {
window.set_inner_size(size);
}
*events_loop.window.lock().unwrap() = Some(window.window.clone());
@@ -413,22 +445,22 @@ impl Window {
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
Some((0, 0))
pub fn get_position(&self) -> Option<LogicalPosition> {
Some((0, 0).into())
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
Some((0, 0))
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
Some((0, 0).into())
}
#[inline]
pub fn set_position(&self, _: i32, _: i32) {
pub fn set_position(&self, _: LogicalPosition) {
}
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
unsafe {
use std::{mem, ptr};
let mut width = 0;
let mut height = 0;
let mut fullscreen = 0;
@@ -438,94 +470,109 @@ impl Window {
{
None
} else {
Some((width as u32, height as u32))
let dpi_factor = self.get_hidpi_factor();
let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor);
Some(logical)
}
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, width: u32, height: u32) {
pub fn set_inner_size(&self, size: LogicalSize) {
unsafe {
use std::ptr;
ffi::emscripten_set_element_css_size(ptr::null(), width as c_double, height
as c_double);
let dpi_factor = self.get_hidpi_factor();
let physical = PhysicalSize::from_logical(size, dpi_factor);
let (width, height): (u32, u32) = physical.into();
ffi::emscripten_set_element_css_size(
ptr::null(),
width as c_double,
height as c_double,
);
}
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
#[inline]
pub fn show(&self) {}
#[inline]
pub fn hide(&self) {}
#[inline]
pub fn platform_display(&self) -> *mut ::libc::c_void {
unimplemented!()
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn platform_window(&self) -> *mut ::libc::c_void {
unimplemented!()
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: ::MouseCursor) {}
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn set_cursor_state(&self, state: ::CursorState) -> Result<(), String> {
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: ::MouseCursor) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
use ::CursorState::*;
let mut old_state = self.window.cursor_state.lock().unwrap();
if state == *old_state {
return Ok(());
if grab {
em_try(ffi::emscripten_set_pointerlockchange_callback(
ptr::null(),
0 as *mut c_void,
ffi::EM_FALSE,
Some(pointerlockchange_callback),
))?;
em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?;
} else {
em_try(ffi::emscripten_set_pointerlockchange_callback(
ptr::null(),
0 as *mut c_void,
ffi::EM_FALSE,
None,
))?;
em_try(ffi::emscripten_exit_pointerlock())?;
}
// Set or unset grab callback
match state {
Hide | Normal => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, None))?,
Grab => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(pointerlockchange_callback)))?,
}
// Go back to normal cursor state
match *old_state {
Hide => show_mouse(),
Grab => em_try(ffi::emscripten_exit_pointerlock())?,
Normal => (),
}
// Set cursor from normal cursor state
match state {
Hide => ffi::emscripten_hide_mouse(),
Grab => em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?,
Normal => (),
}
// Update
*old_state = state;
Ok(())
}
*grabbed_lock = grab;
Ok(())
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
unsafe { ffi::emscripten_get_device_pixel_ratio() as f32 }
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.window.cursor_hidden.lock().unwrap();
if hide == *hidden_lock { return; }
if hide {
unsafe { ffi::emscripten_hide_mouse() };
} else {
show_mouse();
}
*hidden_lock = hide;
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
Err(())
pub fn get_hidpi_factor(&self) -> f64 {
get_hidpi_factor()
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on Emscripten.".to_owned())
}
#[inline]
@@ -533,6 +580,11 @@ impl Window {
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<::MonitorId> {
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<::MonitorId>) {
// iOS has single screen maximized apps so nothing to do
@@ -543,14 +595,36 @@ impl Window {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> ::MonitorId {
::MonitorId{inner: MonitorId}
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut list = VecDeque::with_capacity(1);
list.push_back(MonitorId);
list
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
}
@@ -564,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 {
@@ -597,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 {
@@ -614,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 {
@@ -754,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(Clone)]
pub struct MonitorId;
static mut JMPBUF: Option<Box<JmpBuf>> = None;
pub struct Window {
delegate_state: *mut DelegateState
_events_queue: Arc<RefCell<VecDeque<Event>>>,
delegate_state: Box<DelegateState>,
}
#[derive(Clone)]
pub struct WindowProxy;
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
#[derive(Debug)]
struct DelegateState {
events_queue: VecDeque<Event>,
window: id,
controller: id,
size: (u32,u32),
scale: f32
view: id,
size: LogicalSize,
scale: f64,
}
impl DelegateState {
#[inline]
fn new(window: id, controller:id, size: (u32,u32), scale: f32) -> DelegateState {
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
DelegateState {
events_queue: VecDeque::new(),
window: window,
controller: controller,
size: size,
scale: scale
window,
controller,
view,
size,
scale,
}
}
}
impl Drop for DelegateState {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.window, release];
let _: () = msg_send![self.controller, release];
let _: () = msg_send![self.view, release];
}
}
}
#[derive(Clone)]
pub struct MonitorId;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
pub fn get_uiscreen(&self) -> id {
let class = class!(UIScreen);
unsafe { msg_send![class, mainScreen] }
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
unimplemented!()
pub fn get_dimensions(&self) -> PhysicalSize {
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
(bounds.size.width as f64, bounds.size.height as f64).into()
}
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
// iOS assumes single screen
(0, 0)
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
1.0
pub fn get_hidpi_factor(&self) -> f64 {
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
scale as f64
}
}
pub struct EventsLoop {
delegate_state: *mut DelegateState
events_queue: Arc<RefCell<VecDeque<Event>>>,
}
#[derive(Clone)]
@@ -162,30 +210,16 @@ pub struct EventsLoopProxy;
impl EventsLoop {
pub fn new() -> EventsLoop {
unsafe {
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("glutinState");
let state = state as *mut DelegateState;
let events_loop = EventsLoop {
delegate_state: state
};
return events_loop;
if !msg_send![class!(NSThread), isMainThread] {
panic!("`EventsLoop` can only be created on the main thread on iOS");
}
}
create_delegate_class();
create_view_class();
start_app();
panic!("Couldn't create UIApplication")
EventsLoop { events_queue: Default::default() }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::new();
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
@@ -198,29 +232,30 @@ impl EventsLoop {
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
unsafe {
let state = &mut *self.delegate_state;
if let Some(event) = state.events_queue.pop_front() {
callback(event);
return;
}
// jump hack, so we won't quit on willTerminate event before processing it
if setjmp(mem::transmute(&mut jmpbuf)) != 0 {
if let Some(event) = state.events_queue.pop_front() {
assert!(JMPBUF.is_some(), "`EventsLoop::poll_events` must be called after window creation on iOS");
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
}
}
unsafe {
// run runloop
let seconds: CFTimeInterval = 0.000002;
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
}
if let Some(event) = state.events_queue.pop_front() {
callback(event)
}
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event)
}
}
@@ -256,112 +291,199 @@ impl EventsLoopProxy {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
}
}
}
// TODO: AFAIK transparency is enabled by default on iOS,
// so to be consistent with other platforms we have to change that.
impl Window {
pub fn new(ev: &EventsLoop, _: WindowAttributes, _: PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
Ok(Window {
delegate_state: ev.delegate_state,
})
pub fn new(
ev: &EventsLoop,
_attributes: WindowAttributes,
pl_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
unsafe {
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
}
unsafe {
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
let app_class = class!(UIApplication);
let app: id = msg_send![app_class, sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
let events_queue = &*ev.events_queue;
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
let uiview_class = class!(UIView);
let root_view_class = pl_attributes.root_view_class;
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
delegate_state.view = msg_send![root_view_class, alloc];
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
return Ok(Window {
_events_queue: ev.events_queue.clone(),
delegate_state,
});
}
}
create_delegate_class();
start_app();
panic!("Couldn't create `UIApplication`!")
}
#[inline]
pub fn set_title(&self, _: &str) {
pub fn get_uiwindow(&self) -> id {
self.delegate_state.window
}
#[inline]
pub fn get_uiview(&self) -> id {
self.delegate_state.view
}
#[inline]
pub fn set_title(&self, _title: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _x: i32, _y: i32) {
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
unsafe { Some((&*self.delegate_state).size) }
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.delegate_state.size)
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _x: u32, _y: u32) {
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { }
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!();
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
unimplemented!()
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) {
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _: MouseCursor) {
pub fn set_cursor(&self, _cursor: MouseCursor) {
// N/A
}
#[inline]
pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> {
Ok(())
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
unsafe { (&*self.delegate_state) }.scale
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> {
unimplemented!();
pub fn get_hidpi_factor(&self) -> f64 {
self.delegate_state.scale
}
#[inline]
pub fn create_window_proxy(&self) -> WindowProxy {
WindowProxy
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on iOS.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn get_fullscreen(&self) -> Option<RootMonitorId> {
// N/A
// iOS has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
@@ -370,59 +492,93 @@ impl Window {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId{inner: MonitorId}
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = IOSHandle {
ui_window: self.get_uiwindow() as *mut _,
ui_view: self.get_uiview() as *mut _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(handle)
}
}
fn create_delegate_class() {
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
let screen_class = class!(UIScreen);
let window_class = class!(UIWindow);
let controller_class = class!(UIViewController);
unsafe {
let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen];
let main_screen: id = msg_send![screen_class, mainScreen];
let bounds: CGRect = msg_send![main_screen, bounds];
let scale: CGFloat = msg_send![main_screen, nativeScale];
let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc];
let window: id = msg_send![window_class, alloc];
let window: id = msg_send![window, initWithFrame:bounds.clone()];
let size = (bounds.size.width as u32, bounds.size.height as u32);
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc];
let view_controller: id = msg_send![controller_class, alloc];
let view_controller: id = msg_send![view_controller, init];
let _: () = msg_send![window, setRootViewController:view_controller];
let _: () = msg_send![window, makeKeyAndVisible];
let state = Box::new(DelegateState::new(window, view_controller, size, scale as f32));
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
let state_ptr: *mut DelegateState = mem::transmute(state);
this.set_ivar("glutinState", state_ptr as *mut c_void);
this.set_ivar("winitState", state_ptr as *mut c_void);
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
// This makes it easier to configure the specific `UIView` type.
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
}
YES
}
extern fn post_launch(_: &Object, _: Sel, _: id) {
unsafe { longjmp(mem::transmute(&mut jmpbuf),1); }
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
}
extern fn did_become_active(this: &Object, _: Sel, _: id) {
unsafe {
let state: *mut c_void = *this.get_ivar("glutinState");
let state = &mut *(state as *mut DelegateState);
state.events_queue.push_back(Event::WindowEvent {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(true),
});
@@ -431,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),
});
@@ -442,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];
@@ -486,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,
@@ -506,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:),
@@ -545,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,18 +6,19 @@ use std::ffi::CStr;
use std::os::raw::*;
use std::sync::Arc;
// `std::os::raw::c_void` and `libc::c_void` are NOT interchangeable!
use libc;
use parking_lot::Mutex;
use raw_window_handle::RawWindowHandle;
use sctk::reexports::client::ConnectError;
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;
@@ -40,34 +41,51 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
pub struct PlatformSpecificWindowBuilderAttributes {
pub visual_infos: Option<XVisualInfo>,
pub screen_id: Option<i32>,
pub resize_increments: Option<(i32, i32)>,
pub base_size: Option<(i32, i32)>,
pub resize_increments: Option<(u32, u32)>,
pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>,
pub override_redirect: bool,
pub x11_window_type: x11::util::WindowType,
pub gtk_theme_variant: Option<String>,
pub app_id: Option<String>
}
lazy_static!(
pub static ref X11_BACKEND: Result<Arc<XConnection>, XNotSupported> = {
XConnection::new(Some(x_error_callback)).map(Arc::new)
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
};
);
pub enum Window {
X(x11::Window),
Wayland(wayland::Window)
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WindowId {
X(x11::WindowId),
Wayland(wayland::WindowId)
Wayland(wayland::WindowId),
}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId::X(x11::WindowId::dummy())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
X(x11::DeviceId),
Wayland(wayland::DeviceId)
Wayland(wayland::DeviceId),
}
#[derive(Clone)]
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId::X(x11::DeviceId::dummy())
}
}
#[derive(Debug, Clone)]
pub enum MonitorId {
X(x11::MonitorId),
Wayland(wayland::MonitorId),
@@ -91,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(),
@@ -99,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(),
@@ -107,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,
}
}
}
@@ -124,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)
@@ -136,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()),
}
}
@@ -144,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),
}
}
@@ -152,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(),
}
}
@@ -160,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(),
@@ -181,50 +199,58 @@ impl Window {
}
#[inline]
pub fn set_position(&self, x: i32, y: i32) {
pub fn set_position(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_position(x, y),
&Window::Wayland(ref w) => w.set_position(x, y)
&Window::X(ref w) => w.set_position(position),
&Window::Wayland(ref w) => w.set_position(position),
}
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
pub fn get_inner_size(&self) -> Option<LogicalSize> {
match self {
&Window::X(ref w) => w.get_inner_size(),
&Window::Wayland(ref w) => w.get_inner_size()
&Window::Wayland(ref w) => w.get_inner_size(),
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
match self {
&Window::X(ref w) => w.get_outer_size(),
&Window::Wayland(ref w) => w.get_outer_size()
&Window::Wayland(ref w) => w.get_outer_size(),
}
}
#[inline]
pub fn set_inner_size(&self, x: u32, y: u32) {
pub fn set_inner_size(&self, size: LogicalSize) {
match self {
&Window::X(ref w) => w.set_inner_size(x, y),
&Window::Wayland(ref w) => w.set_inner_size(x, y)
&Window::X(ref w) => w.set_inner_size(size),
&Window::Wayland(ref w) => w.set_inner_size(size),
}
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_min_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions)
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions),
}
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_max_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions)
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions),
}
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
match self {
&Window::X(ref w) => w.set_resizable(resizable),
&Window::Wayland(ref w) => w.set_resizable(resizable),
}
}
@@ -237,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),
}
}
@@ -284,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 {
@@ -300,6 +327,14 @@ impl Window {
}
}
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
match self {
&Window::X(ref w) => w.set_always_on_top(always_on_top),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
match self {
@@ -308,11 +343,48 @@ impl Window {
}
}
#[inline]
pub fn set_ime_spot(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_ime_spot(position),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
match self {
&Window::X(ref w) => RootMonitorId{inner: MonitorId::X(w.get_current_monitor())},
&Window::Wayland(ref w) => RootMonitorId{inner: MonitorId::Wayland(w.get_current_monitor())},
&Window::X(ref window) => RootMonitorId { inner: MonitorId::X(window.get_current_monitor()) },
&Window::Wayland(ref window) => RootMonitorId { inner: MonitorId::Wayland(window.get_current_monitor()) },
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
match self {
&Window::X(ref window) => window.get_available_monitors()
.into_iter()
.map(MonitorId::X)
.collect(),
&Window::Wayland(ref window) => window.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
}
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
match self {
&Window::X(ref window) => MonitorId::X(window.get_primary_monitor()),
&Window::Wayland(ref window) => MonitorId::Wayland(window.get_primary_monitor()),
}
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
match self {
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
}
}
@@ -321,7 +393,8 @@ unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
if let Ok(ref xconn) = *X11_BACKEND {
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,
@@ -338,6 +411,8 @@ unsafe extern "C" fn x_error_callback(
minor_code: (*event).minor_code,
};
error!("X11 error: {:#?}", error);
*xconn.latest_error.lock() = Some(error);
}
// Fun fact: this return value is completely ignored.
@@ -385,40 +460,42 @@ 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,
);
panic!(err_string);
}
pub fn new_wayland() -> Result<EventsLoop, ()> {
pub fn new_wayland() -> Result<EventsLoop, ConnectError> {
wayland::EventsLoop::new()
.map(EventsLoop::Wayland)
.ok_or(())
}
pub fn new_x11() -> Result<EventsLoop, XNotSupported> {
match *X11_BACKEND {
Ok(ref x) => Ok(EventsLoop::X(x11::EventsLoop::new(x.clone()))),
Err(ref err) => Err(err.clone()),
}
X11_BACKEND
.lock()
.as_ref()
.map(Arc::clone)
.map(x11::EventsLoop::new)
.map(EventsLoop::X)
.map_err(|err| err.clone())
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
match *self {
EventsLoop::Wayland(ref evlp) => evlp.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
EventsLoop::X(ref evlp) => x11::get_available_monitors(evlp.x_connection())
.into_iter()
.map(MonitorId::X)
.collect(),
EventsLoop::Wayland(ref evlp) => evlp
.get_available_monitors()
.into_iter()
.map(MonitorId::Wayland)
.collect(),
EventsLoop::X(ref evlp) => evlp
.x_connection()
.get_available_monitors()
.into_iter()
.map(MonitorId::X)
.collect(),
}
}
@@ -426,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,21 +1,25 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use {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};
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>,
@@ -90,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(())
}
@@ -100,35 +104,53 @@ impl EventsLoopProxy {
}
impl EventsLoop {
pub fn new() -> Option<EventsLoop> {
let (display, mut event_queue) = match Display::connect_to_env() {
Ok(ret) => ret,
Err(_) => return None,
};
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();
Some(EventsLoop {
display: Arc::new(display),
Ok(EventsLoop {
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,
})
}
@@ -201,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
}
}
@@ -250,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);
}
@@ -279,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();
}
}
}
_ => (),
}
}
}
@@ -326,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
@@ -354,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
@@ -370,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(),
))
@@ -430,6 +451,29 @@ impl Clone for MonitorId {
}
}
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
native_identifier: u32,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: i32,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
native_identifier: self.get_native_identifier(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
pub fn get_name(&self) -> Option<String> {
self.mgr.with_info(&self.proxy, |_, info| {
@@ -442,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()
@@ -451,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);
}
@@ -101,12 +103,14 @@ impl Window {
frame.set_maximized();
}
frame.set_resizable(attributes.resizable);
// set decorations
frame.set_decorate(attributes.decorations);
// min-max dimensions
frame.set_min_size(attributes.min_dimensions);
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));
@@ -115,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();
@@ -127,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,
})
}
@@ -154,75 +163,59 @@ impl Window {
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
pub fn get_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn set_position(&self, _x: i32, _y: i32) {
pub fn set_position(&self, _pos: LogicalPosition) {
// Not possible with wayland
}
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
Some(self.size.lock().unwrap().clone())
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.size.lock().unwrap().clone().into())
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
pub fn get_outer_size(&self) -> Option<LogicalSize> {
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
Some((w as u32, h as u32))
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]
pub fn set_cursor(&self, _cursor: MouseCursor) {
// TODO
pub fn set_resizable(&self, resizable: bool) {
self.frame.lock().unwrap().set_resizable(resizable);
}
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
use CursorState::{Grab, 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) {
@@ -238,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),
@@ -252,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 {
@@ -267,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()
}
}
}
@@ -287,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 {
@@ -338,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

@@ -40,7 +40,7 @@ impl DndAtoms {
b"text/uri-list\0".as_ptr() as *mut c_char,
b"None\0".as_ptr() as *mut c_char,
];
let atoms = unsafe { util::get_atoms(xconn, &names) }?;
let atoms = unsafe { xconn.get_atoms(&names) }?;
Ok(DndAtoms {
aware: atoms[0],
enter: atoms[1],
@@ -127,13 +127,12 @@ impl Dnd {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
};
util::send_client_msg(
&self.xconn,
self.xconn.send_client_msg(
target_window,
target_window,
self.atoms.status,
None,
(this_window as c_long, accepted, 0, 0, action),
[this_window as c_long, accepted, 0, 0, action],
).flush()
}
@@ -147,13 +146,12 @@ impl Dnd {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
};
util::send_client_msg(
&self.xconn,
self.xconn.send_client_msg(
target_window,
target_window,
self.atoms.finished,
None,
(this_window as c_long, accepted, action, 0, 0),
[this_window as c_long, accepted, action, 0, 0],
).flush()
}
@@ -161,8 +159,7 @@ impl Dnd {
&self,
source_window: c_ulong,
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
util::get_property(
&self.xconn,
self.xconn.get_property(
source_window,
self.atoms.type_list,
ffi::XA_ATOM,
@@ -184,8 +181,7 @@ impl Dnd {
&self,
window: c_ulong,
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
util::get_property(
&self.xconn,
self.xconn.get_property(
window,
self.atoms.selection,
self.atoms.uri_list,

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

@@ -5,12 +5,20 @@ use std::sync::Arc;
use std::os::raw::c_char;
use std::ffi::{CStr, CString, IntoStringError};
use parking_lot::Mutex;
use super::{ffi, util, XConnection, XError};
lazy_static! {
static ref GLOBAL_LOCK: Mutex<()> = Default::default();
}
unsafe fn open_im(
xconn: &Arc<XConnection>,
locale_modifiers: &CStr,
) -> Option<ffi::XIM> {
let _lock = GLOBAL_LOCK.lock();
// XSetLocaleModifiers returns...
// * The current locale modifiers if it's given a NULL pointer.
// * The new locale modifiers if we succeeded in setting them.
@@ -85,13 +93,11 @@ enum GetXimServersError {
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
// XMODIFIERS to `@server=ibus`?!?"
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
let servers_atom = util::get_atom(&xconn, b"XIM_SERVERS\0")
.map_err(GetXimServersError::XError)?;
let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0");
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
let mut atoms: Vec<ffi::Atom> = util::get_property(
&xconn,
let mut atoms: Vec<ffi::Atom> = xconn.get_property(
root,
servers_atom,
ffi::XA_ATOM,

File diff suppressed because it is too large Load Diff

View File

@@ -1,110 +1,85 @@
use std::sync::Arc;
use std::slice;
use std::os::raw::*;
use super::XConnection;
use parking_lot::Mutex;
#[derive(Clone)]
use {PhysicalPosition, PhysicalSize};
use super::{util, XConnection, XError};
use super::ffi::{
RRCrtcChangeNotifyMask,
RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask,
True,
Window,
XRRScreenResources,
};
// Used to test XRandR < 1.5 code path. This should always be committed as false.
const FORCE_RANDR_COMPAT: bool = false;
// Also used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
lazy_static! {
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
static ref MONITORS: Mutex<Option<Vec<MonitorId>>> = Mutex::default();
}
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
if avail_major == major {
avail_minor >= minor
} else {
avail_major > major
}
} else {
unreachable!();
}
}
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorId>> {
// We update this lazily.
(*MONITORS.lock()).take()
}
#[derive(Debug, Clone)]
pub struct MonitorId {
/// The actual id
id: u32,
/// The name of the monitor
name: String,
pub(crate) name: String,
/// The size of the monitor
dimensions: (u32, u32),
/// The position of the monitor in the X screen
position: (i32, i32),
/// If the monitor is the primary one
primary: bool,
/// The DPI scaling factor
hidpi_factor: f32,
}
pub fn get_available_monitors(x: &Arc<XConnection>) -> Vec<MonitorId> {
let mut available = Vec::new();
unsafe {
let root = (x.xlib.XDefaultRootWindow)(x.display);
let resources = (x.xrandr.XRRGetScreenResources)(x.display, root);
if let Some(ref xrandr_1_5) = x.xrandr_1_5 {
// We're in XRandR >= 1.5, enumerate Monitors to handle things like MST and videowalls
let mut nmonitors = 0;
let monitors = (xrandr_1_5.XRRGetMonitors)(x.display, root, 1, &mut nmonitors);
for i in 0..nmonitors {
let monitor = *(monitors.offset(i as isize));
let output = (xrandr_1_5.XRRGetOutputInfo)(x.display, resources, *(monitor.outputs.offset(0)));
let nameslice = slice::from_raw_parts((*output).name as *mut u8, (*output).nameLen as usize);
let name = String::from_utf8_lossy(nameslice).into_owned();
let hidpi_factor = {
let x_mm = (*output).mm_width as f32;
let y_mm = (*output).mm_height as f32;
let x_px = monitor.width as f32;
let y_px = monitor.height as f32;
let ppmm = ((x_px * y_px) / (x_mm * y_mm)).sqrt();
// Quantize 1/12 step size
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
};
(xrandr_1_5.XRRFreeOutputInfo)(output);
available.push(MonitorId{
id: i as u32,
name,
hidpi_factor,
dimensions: (monitor.width as u32, monitor.height as u32),
position: (monitor.x as i32, monitor.y as i32),
primary: (monitor.primary != 0),
});
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work but MST and
// videowall setups will show more monitors than the logical groups the user
// cares about
for i in 0..(*resources).ncrtc {
let crtcid = *((*resources).crtcs.offset(i as isize));
let crtc = (x.xrandr.XRRGetCrtcInfo)(x.display, resources, crtcid);
if (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0 {
let output = (x.xrandr.XRRGetOutputInfo)(x.display, resources, *((*crtc).outputs.offset(0)));
let nameslice = slice::from_raw_parts((*output).name as *mut u8, (*output).nameLen as usize);
let name = String::from_utf8_lossy(nameslice).into_owned();
let hidpi_factor = {
let x_mm = (*output).mm_width as f32;
let y_mm = (*output).mm_height as f32;
let x_px = (*crtc).width as f32;
let y_px = (*crtc).height as f32;
let ppmm = ((x_px * y_px) / (x_mm * y_mm)).sqrt();
// Quantize 1/12 step size
((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0)
};
(x.xrandr.XRRFreeOutputInfo)(output);
available.push(MonitorId{
id: crtcid as u32,
name,
hidpi_factor,
dimensions: ((*crtc).width as u32, (*crtc).height as u32),
position: ((*crtc).x as i32, (*crtc).y as i32),
primary: true,
});
}
(x.xrandr.XRRFreeCrtcInfo)(crtc);
}
}
(x.xrandr.XRRFreeScreenResources)(resources);
}
available
}
#[inline]
pub fn get_primary_monitor(x: &Arc<XConnection>) -> MonitorId {
get_available_monitors(x).into_iter().find(|m| m.primary)
// 'no primary' case is better handled picking some existing monitor
.or_else(|| get_available_monitors(x).into_iter().next())
.expect("[winit] Failed to find any x11 monitor")
/// The DPI scale factor
pub(crate) hidpi_factor: f64,
/// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect,
}
impl MonitorId {
fn from_repr(
xconn: &XConnection,
resources: *mut XRRScreenResources,
id: u32,
repr: util::MonitorRepr,
primary: bool,
) -> Option<Self> {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
let rect = util::AaRect::new(position, dimensions);
Some(MonitorId {
id,
name,
hidpi_factor,
dimensions,
position,
primary,
rect,
})
}
pub fn get_name(&self) -> Option<String> {
Some(self.name.clone())
}
@@ -114,16 +89,186 @@ impl MonitorId {
self.id as u32
}
pub fn get_dimensions(&self) -> (u32, u32) {
self.dimensions
pub fn get_dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
pub fn get_position(&self) -> (i32, i32) {
self.position
pub fn get_position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorId {
let monitors = self.get_available_monitors();
let default = monitors
.get(0)
.expect("[winit] Failed to find any monitors using XRandR.");
let window_rect = match window_rect {
Some(rect) => rect,
None => return default.to_owned(),
};
let mut largest_overlap = 0;
let mut matched_monitor = default;
for monitor in &monitors {
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
if overlapping_area > largest_overlap {
largest_overlap = overlapping_area;
matched_monitor = &monitor;
}
}
matched_monitor.to_owned()
}
fn query_monitor_list(&self) -> Vec<MonitorId> {
unsafe {
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if version_is_at_least(1, 3) {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
if resources.is_null() {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
let mut available;
let mut has_primary = false;
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
// videowalls.
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
let mut monitor_count = 0;
let monitors = (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
assert!(monitor_count >= 0);
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
has_primary |= is_primary;
MonitorId::from_repr(
self,
resources,
monitor_index as u32,
monitor.into(),
is_primary,
).map(|monitor_id| available.push(monitor_id));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
MonitorId::from_repr(
self,
resources,
crtc_id as u32,
crtc,
is_primary,
).map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}
}
// If no monitors were detected as being primary, we just pick one ourselves!
if !has_primary {
if let Some(ref mut fallback) = available.first_mut() {
// Setting this here will come in handy if we ever add an `is_primary` method.
fallback.primary = true;
}
}
(self.xrandr.XRRFreeScreenResources)(resources);
available
}
}
pub fn get_available_monitors(&self) -> Vec<MonitorId> {
let mut monitors_lock = MONITORS.lock();
(*monitors_lock)
.as_ref()
.cloned()
.or_else(|| {
let monitors = Some(self.query_monitor_list());
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
})
.unwrap()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
self.get_available_monitors()
.into_iter()
.find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.")
}
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
{
let mut version_lock = XRANDR_VERSION.lock();
if version_lock.is_none() {
let mut major = 0;
let mut minor = 0;
let has_extension = unsafe {
(self.xrandr.XRRQueryVersion)(
self.display,
&mut major,
&mut minor,
)
};
if has_extension != True {
panic!("[winit] XRandR extension not available.");
}
*version_lock = Some((major, minor));
}
}
let mut event_offset = 0;
let mut error_offset = 0;
let status = unsafe {
(self.xrandr.XRRQueryExtension)(
self.display,
&mut event_offset,
&mut error_offset,
)
};
if status != True {
self.check_errors()?;
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
}
let mask = RRCrtcChangeNotifyMask
| RROutputPropertyNotifyMask
| RRScreenChangeNotifyMask;
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
Ok(event_offset)
}
}

View File

@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt::Debug;
use std::os::raw::*;
use parking_lot::Mutex;
@@ -12,48 +13,60 @@ lazy_static! {
static ref ATOM_CACHE: Mutex<AtomCache> = Mutex::new(HashMap::with_capacity(2048));
}
pub unsafe fn get_atom(xconn: &Arc<XConnection>, name: &[u8]) -> Result<ffi::Atom, XError> {
let name = CStr::from_bytes_with_nul_unchecked(name); // I trust you. Don't let me down.
let mut atom_cache_lock = ATOM_CACHE.lock();
let cached_atom = (*atom_cache_lock).get(name).cloned();
if let Some(atom) = cached_atom {
Ok(atom)
} else {
let atom = (xconn.xlib.XInternAtom)(
xconn.display,
name.as_ptr() as *const c_char,
impl XConnection {
pub fn get_atom<T: AsRef<CStr> + Debug>(&self, name: T) -> ffi::Atom {
let name = name.as_ref();
let mut atom_cache_lock = ATOM_CACHE.lock();
let cached_atom = (*atom_cache_lock).get(name).cloned();
if let Some(atom) = cached_atom {
atom
} else {
let atom = unsafe { (self.xlib.XInternAtom)(
self.display,
name.as_ptr() as *const c_char,
ffi::False,
) };
if atom == 0 {
let msg = format!(
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
name,
self.check_errors(),
);
panic!(msg);
}
/*println!(
"XInternAtom name:{:?} atom:{:?}",
name,
atom,
);*/
(*atom_cache_lock).insert(name.to_owned(), atom);
atom
}
}
pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom {
debug_assert!(CStr::from_bytes_with_nul(name).is_ok());
let name = CStr::from_bytes_with_nul_unchecked(name);
self.get_atom(name)
}
// Note: this doesn't use caching, for the sake of simplicity.
// If you're dealing with this many atoms, you'll usually want to cache them locally anyway.
pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result<Vec<ffi::Atom>, XError> {
let mut atoms = Vec::with_capacity(names.len());
(self.xlib.XInternAtoms)(
self.display,
names.as_ptr() as *mut _,
names.len() as c_int,
ffi::False,
atoms.as_mut_ptr(),
);
self.check_errors()?;
atoms.set_len(names.len());
/*println!(
"XInternAtom name:{:?} atom:{:?}",
name,
atom,
"XInternAtoms atoms:{:?}",
atoms,
);*/
xconn.check_errors()?;
(*atom_cache_lock).insert(name.to_owned(), atom);
Ok(atom)
Ok(atoms)
}
}
// Note: this doesn't use caching, for the sake of simplicity.
// If you're dealing with this many atoms, you'll usually want to cache them locally anyway.
pub unsafe fn get_atoms(
xconn: &Arc<XConnection>,
names: &[*mut c_char],
) -> Result<Vec<ffi::Atom>, XError> {
let mut atoms = Vec::with_capacity(names.len());
(xconn.xlib.XInternAtoms)(
xconn.display,
names.as_ptr() as *mut _,
names.len() as c_int,
ffi::False,
atoms.as_mut_ptr(),
);
xconn.check_errors()?;
atoms.set_len(names.len());
/*println!(
"XInternAtoms atoms:{:?}",
atoms,
);*/
Ok(atoms)
}

View File

@@ -0,0 +1,95 @@
use super::*;
pub type ClientMsgPayload = [c_long; 5];
impl XConnection {
pub fn send_event<T: Into<ffi::XEvent>>(
&self,
target_window: c_ulong,
event_mask: Option<c_long>,
event: T,
) -> Flusher {
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
unsafe {
(self.xlib.XSendEvent)(
self.display,
target_window,
ffi::False,
event_mask,
&mut event.into(),
);
}
Flusher::new(self)
}
pub fn send_client_msg(
&self,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: ClientMsgPayload,
) -> Flusher {
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
event.type_ = ffi::ClientMessage;
event.display = self.display;
event.window = window;
event.message_type = message_type;
event.format = c_long::FORMAT as c_int;
event.data = unsafe { mem::transmute(data) };
self.send_event(target_window, event_mask, event)
}
// Prepare yourself for the ultimate in unsafety!
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
// to send more than one message worth of data.
pub fn send_client_msg_multi<T: Formattable>(
&self,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: &[T],
) -> Flusher {
let format = T::FORMAT;
let size_of_t = mem::size_of::<T>();
debug_assert_eq!(size_of_t, format.get_actual_size());
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
event.type_ = ffi::ClientMessage;
event.display = self.display;
event.window = window;
event.message_type = message_type;
event.format = format as c_int;
let t_per_payload = format.get_payload_size() / size_of_t;
assert!(t_per_payload > 0);
let payload_count = data.len() / t_per_payload;
let payload_remainder = data.len() % t_per_payload;
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
let mut payload_index = 0;
while payload_index < payload_count {
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
payload_index += 1;
event.data = unsafe { mem::transmute(*payload) };
self.send_event(target_window, event_mask, &event).queue();
}
if payload_remainder > 0 {
let mut payload: ClientMsgPayload = [0; 5];
let t_payload = payload.as_mut_ptr() as *mut T;
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
let invalid_t_payload = invalid_payload as *const T;
let mut t_index = 0;
while t_index < payload_remainder {
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
t_index += 1;
}
event.data = unsafe { mem::transmute(payload) };
self.send_event(target_window, event_mask, &event).queue();
}
Flusher::new(self)
}
}

View File

@@ -0,0 +1,58 @@
use std::fmt::Debug;
use std::mem;
use std::os::raw::*;
// This isn't actually the number of the bits in the format.
// X11 does a match on this value to determine which type to call sizeof on.
// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64.
// ...if that sounds confusing, then you know why this enum is here.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Format {
Char = 8,
Short = 16,
Long = 32,
}
impl Format {
pub fn from_format(format: usize) -> Option<Self> {
match format {
8 => Some(Format::Char),
16 => Some(Format::Short),
32 => Some(Format::Long),
_ => None,
}
}
pub fn is_same_size_as<T>(&self) -> bool {
mem::size_of::<T>() == self.get_actual_size()
}
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
&Format::Short => mem::size_of::<c_short>(),
&Format::Long => mem::size_of::<c_long>(),
}
}
pub fn get_payload_size(&self) -> usize {
match self {
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
&Format::Char => mem::size_of::<c_char>() * 20,
&Format::Short => mem::size_of::<c_short>() * 10,
&Format::Long => mem::size_of::<c_long>() * 5,
}
}
}
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
const FORMAT: Format;
}
// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it.
impl Formattable for c_schar { const FORMAT: Format = Format::Char; }
impl Formattable for c_uchar { const FORMAT: Format = Format::Char; }
impl Formattable for c_short { const FORMAT: Format = Format::Short; }
impl Formattable for c_ushort { const FORMAT: Format = Format::Short; }
impl Formattable for c_long { const FORMAT: Format = Format::Long; }
impl Formattable for c_ulong { const FORMAT: Format = Format::Long; }

View File

@@ -1,4 +1,40 @@
use std::cmp;
use super::*;
use {LogicalPosition, LogicalSize};
// Friendly neighborhood axis-aligned rectangle
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AaRect {
x: i64,
y: i64,
width: i64,
height: i64,
}
impl AaRect {
pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
let (x, y) = (x as i64, y as i64);
let (width, height) = (width as i64, height as i64);
AaRect { x, y, width, height }
}
pub fn contains_point(&self, x: i64, y: i64) -> bool {
x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
}
pub fn get_overlapping_area(&self, other: &Self) -> i64 {
let x_overlap = cmp::max(
0,
cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
);
let y_overlap = cmp::max(
0,
cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
);
x_overlap * y_overlap
}
}
#[derive(Debug)]
pub struct TranslatedCoords {
@@ -7,30 +43,6 @@ pub struct TranslatedCoords {
pub child: ffi::Window,
}
// This is adequate for get_inner_position
pub unsafe fn translate_coords(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = mem::uninitialized();
(xconn.xlib.XTranslateCoordinates)(
xconn.display,
window,
root,
0,
0,
&mut translated_coords.x_rel_root,
&mut translated_coords.y_rel_root,
&mut translated_coords.child,
);
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
xconn.check_errors().map(|_| translated_coords)
}
#[derive(Debug)]
pub struct Geometry {
pub root: ffi::Window,
@@ -51,30 +63,6 @@ pub struct Geometry {
pub depth: c_uint,
}
// This is adequate for get_inner_size
pub unsafe fn get_geometry(
xconn: &Arc<XConnection>,
window: ffi::Window,
) -> Result<Geometry, XError> {
let mut geometry: Geometry = mem::uninitialized();
let _status = (xconn.xlib.XGetGeometry)(
xconn.display,
window,
&mut geometry.root,
&mut geometry.x_rel_parent,
&mut geometry.y_rel_parent,
&mut geometry.width,
&mut geometry.height,
&mut geometry.border,
&mut geometry.depth,
);
//println!("XGetGeometry geo:{:?}", geometry);
xconn.check_errors().map(|_| geometry)
}
#[derive(Debug, Clone)]
pub struct FrameExtents {
pub left: c_ulong,
@@ -91,109 +79,24 @@ impl FrameExtents {
pub fn from_border(border: c_ulong) -> Self {
Self::new(border, border, border, border)
}
}
fn get_frame_extents(
xconn: &Arc<XConnection>,
window: ffi::Window,
) -> Option<self::FrameExtents> {
let extents_atom = unsafe { self::get_atom(xconn, b"_NET_FRAME_EXTENTS\0") }
.expect("Failed to call XInternAtom (_NET_FRAME_EXTENTS)");
if !self::hint_is_supported(extents_atom) {
return None;
}
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
// support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
// be unsupported by many smaller WMs.
let extents: Option<Vec<c_ulong>> = unsafe {
self::get_property(
xconn,
window,
extents_atom,
ffi::XA_CARDINAL,
)
}.ok();
extents.and_then(|extents| {
if extents.len() >= 4 {
Some(self::FrameExtents {
left: extents[0],
right: extents[1],
top: extents[2],
bottom: extents[3],
})
} else {
None
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),
}
})
}
}
pub fn is_top_level(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> Option<bool> {
let client_list_atom = unsafe { self::get_atom(xconn, b"_NET_CLIENT_LIST\0") }
.expect("Failed to call XInternAtom (_NET_CLIENT_LIST)");
if !self::hint_is_supported(client_list_atom) {
return None;
}
let client_list: Option<Vec<ffi::Window>> = unsafe {
self::get_property(
xconn,
root,
client_list_atom,
ffi::XA_WINDOW,
)
}.ok();
client_list.map(|client_list| client_list.contains(&window))
}
unsafe fn get_parent_window(
xconn: &Arc<XConnection>,
window: ffi::Window,
) -> Result<ffi::Window, XError> {
let mut root: ffi::Window = mem::uninitialized();
let mut parent: ffi::Window = mem::uninitialized();
let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: c_uint = mem::uninitialized();
let _status = (xconn.xlib.XQueryTree)(
xconn.display,
window,
&mut root,
&mut parent,
&mut children,
&mut nchildren,
);
// The list of children isn't used
if children != ptr::null_mut() {
(xconn.xlib.XFree)(children as *mut _);
}
xconn.check_errors().map(|_| parent)
}
fn climb_hierarchy(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> Result<ffi::Window, XError> {
let mut outer_window = window;
loop {
let candidate = unsafe { get_parent_window(xconn, outer_window) }?;
if candidate == root {
break;
}
outer_window = candidate;
}
Ok(outer_window)
#[derive(Debug, Clone)]
pub struct LogicalFrameExtents {
pub left: f64,
pub right: f64,
pub top: f64,
pub bottom: f64,
}
#[derive(Debug, Clone, PartialEq)]
@@ -219,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(
@@ -229,122 +142,246 @@ 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
}
}
pub fn get_frame_extents_heuristic(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> FrameExtentsHeuristic {
use self::FrameExtentsHeuristicPath::*;
impl XConnection {
// This is adequate for get_inner_position
pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
unsafe {
(self.xlib.XTranslateCoordinates)(
self.display,
window,
root,
0,
0,
&mut translated_coords.x_rel_root,
&mut translated_coords.y_rel_root,
&mut translated_coords.child,
);
}
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
self.check_errors().map(|_| translated_coords)
}
// Position relative to root window.
// With rare exceptions, this is the position of a nested window. Cases where the window
// isn't nested are outlined in the comments throghout this function, but in addition to
// that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = {
let coords = unsafe { translate_coords(xconn, window, root) }
.expect("Failed to translate window coordinates");
(
coords.y_rel_root,
coords.child,
)
};
// This is adequate for get_inner_size
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
let mut geometry: Geometry = unsafe { mem::uninitialized() };
let _status = unsafe {
(self.xlib.XGetGeometry)(
self.display,
window,
&mut geometry.root,
&mut geometry.x_rel_parent,
&mut geometry.y_rel_parent,
&mut geometry.width,
&mut geometry.height,
&mut geometry.border,
&mut geometry.depth,
)
};
//println!("XGetGeometry geo:{:?}", geometry);
self.check_errors().map(|_| geometry)
}
let (width, height, border) = {
let inner_geometry = unsafe { get_geometry(xconn, window) }
.expect("Failed to get inner window geometry");
(
inner_geometry.width,
inner_geometry.height,
inner_geometry.border,
)
};
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") };
// The first condition is only false for un-nested windows, but isn't always false for
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
// when y is on the range [0, 2] and if the window has been unfocused since being
// undecorated (or was undecorated upon construction), the first condition is true,
// requiring us to rely on the second condition.
let nested = !(window == child || is_top_level(xconn, child, root) == Some(true));
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
if let Some(mut frame_extents) = get_frame_extents(xconn, window) {
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
// decorations are disabled, but since the window becomes un-nested, it's easy to
// catch.
if !nested {
frame_extents = FrameExtents::new(0, 0, 0, 0);
if !hint_is_supported(extents_atom) {
return None;
}
// The difference between the nested window's position and the outermost window's
// position is equivalent to the frame size. In most scenarios, this is equivalent to
// manually climbing the hierarchy as is done in the case below. Here's a list of
// known discrepancies:
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
// addition to a 1px semi-transparent border. The margin can be easily observed by
// using a screenshot tool to get a screenshot of a selected window, and is
// presumably used for drawing drop shadows. Getting window geometry information
// via hierarchy-climbing results in this margin being included in both the
// position and outer size, so a window positioned at (0, 0) would be reported as
// having a position (-10, -8).
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
// on all sides, and there's no additional border.
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
// Without decorations, there's no difference. This is presumably related to
// Enlightenment's fairly unique concept of window position; it interprets
// positions given to XMoveWindow as a client area position rather than a position
// of the overall window.
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
// support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
// be unsupported by many smaller WMs.
let extents: Option<Vec<c_ulong>> = self.get_property(
window,
extents_atom,
ffi::XA_CARDINAL,
).ok();
FrameExtentsHeuristic {
frame_extents,
heuristic_path: Supported,
extents.and_then(|extents| {
if extents.len() >= 4 {
Some(FrameExtents {
left: extents[0],
right: extents[1],
top: extents[2],
bottom: extents[3],
})
} else {
None
}
})
}
pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool> {
let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") };
if !hint_is_supported(client_list_atom) {
return None;
}
} else if nested {
// If the position value we have is for a nested window used as the client area, we'll
// just climb up the hierarchy and get the geometry of the outermost window we're
// nested in.
let outer_window = climb_hierarchy(xconn, window, root)
.expect("Failed to climb window hierarchy");
let (outer_y, outer_width, outer_height) = {
let outer_geometry = unsafe { get_geometry(xconn, outer_window) }
.expect("Failed to get outer window geometry");
let client_list: Option<Vec<ffi::Window>> = self.get_property(
root,
client_list_atom,
ffi::XA_WINDOW,
).ok();
client_list.map(|client_list| client_list.contains(&window))
}
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
let parent = unsafe {
let mut root: ffi::Window = mem::uninitialized();
let mut parent: ffi::Window = mem::uninitialized();
let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: c_uint = mem::uninitialized();
// What's filled into `parent` if `window` is the root window?
let _status = (self.xlib.XQueryTree)(
self.display,
window,
&mut root,
&mut parent,
&mut children,
&mut nchildren,
);
// The list of children isn't used
if children != ptr::null_mut() {
(self.xlib.XFree)(children as *mut _);
}
parent
};
self.check_errors().map(|_| parent)
}
fn climb_hierarchy(&self, window: ffi::Window, root: ffi::Window) -> Result<ffi::Window, XError> {
let mut outer_window = window;
loop {
let candidate = self.get_parent_window(outer_window)?;
if candidate == root {
break;
}
outer_window = candidate;
}
Ok(outer_window)
}
pub fn get_frame_extents_heuristic(&self, window: ffi::Window, root: ffi::Window) -> FrameExtentsHeuristic {
use self::FrameExtentsHeuristicPath::*;
// Position relative to root window.
// With rare exceptions, this is the position of a nested window. Cases where the window
// isn't nested are outlined in the comments throghout this function, but in addition to
// that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = {
let coords = self.translate_coords(window, root).expect("Failed to translate window coordinates");
(
outer_geometry.y_rel_parent,
outer_geometry.width,
outer_geometry.height,
coords.y_rel_root,
coords.child,
)
};
// Since we have the geometry of the outermost window and the geometry of the client
// area, we can figure out what's in between.
let diff_x = outer_width.saturating_sub(width);
let diff_y = outer_height.saturating_sub(height);
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
let (width, height, border) = {
let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry");
(
inner_geometry.width,
inner_geometry.height,
inner_geometry.border,
)
};
let left = diff_x / 2;
let right = left;
let top = offset_y;
let bottom = diff_y.saturating_sub(offset_y);
// The first condition is only false for un-nested windows, but isn't always false for
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
// when y is on the range [0, 2] and if the window has been unfocused since being
// undecorated (or was undecorated upon construction), the first condition is true,
// requiring us to rely on the second condition.
let nested = !(window == child || self.is_top_level(child, root) == Some(true));
let frame_extents = FrameExtents::new(
left.into(),
right.into(),
top.into(),
bottom.into(),
);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedNested,
}
} else {
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
// border value. This is convenient, since we can use it to get an accurate frame.
let frame_extents = FrameExtents::from_border(border.into());
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedBordered,
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
if let Some(mut frame_extents) = self.get_frame_extents(window) {
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
// decorations are disabled, but since the window becomes un-nested, it's easy to
// catch.
if !nested {
frame_extents = FrameExtents::new(0, 0, 0, 0);
}
// The difference between the nested window's position and the outermost window's
// position is equivalent to the frame size. In most scenarios, this is equivalent to
// manually climbing the hierarchy as is done in the case below. Here's a list of
// known discrepancies:
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
// addition to a 1px semi-transparent border. The margin can be easily observed by
// using a screenshot tool to get a screenshot of a selected window, and is
// presumably used for drawing drop shadows. Getting window geometry information
// via hierarchy-climbing results in this margin being included in both the
// position and outer size, so a window positioned at (0, 0) would be reported as
// having a position (-10, -8).
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
// on all sides, and there's no additional border.
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
// Without decorations, there's no difference. This is presumably related to
// Enlightenment's fairly unique concept of window position; it interprets
// positions given to XMoveWindow as a client area position rather than a position
// of the overall window.
FrameExtentsHeuristic {
frame_extents,
heuristic_path: Supported,
}
} else if nested {
// If the position value we have is for a nested window used as the client area, we'll
// just climb up the hierarchy and get the geometry of the outermost window we're
// nested in.
let outer_window = self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy");
let (outer_y, outer_width, outer_height) = {
let outer_geometry = self.get_geometry(outer_window).expect("Failed to get outer window geometry");
(
outer_geometry.y_rel_parent,
outer_geometry.width,
outer_geometry.height,
)
};
// Since we have the geometry of the outermost window and the geometry of the client
// area, we can figure out what's in between.
let diff_x = outer_width.saturating_sub(width);
let diff_y = outer_height.saturating_sub(height);
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
let left = diff_x / 2;
let right = left;
let top = offset_y;
let bottom = diff_y.saturating_sub(offset_y);
let frame_extents = FrameExtents::new(
left.into(),
right.into(),
top.into(),
bottom.into(),
);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedNested,
}
} else {
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
// border value. This is convenient, since we can use it to get an accurate frame.
let frame_extents = FrameExtents::from_border(border.into());
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedBordered,
}
}
}
}

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::*;
pub const MWM_HINTS_DECORATIONS: c_ulong = 2;
@@ -6,15 +8,229 @@ pub const MWM_HINTS_DECORATIONS: c_ulong = 2;
pub enum StateOperation {
Remove = 0, // _NET_WM_STATE_REMOVE
Add = 1, // _NET_WM_STATE_ADD
_Toggle = 2, // _NET_WM_STATE_TOGGLE
Toggle = 2, // _NET_WM_STATE_TOGGLE
}
impl From<bool> for StateOperation {
fn from(b: bool) -> Self {
if b {
fn from(op: bool) -> Self {
if op {
StateOperation::Add
} else {
StateOperation::Remove
}
}
}
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
/// root window clicks.
Desktop,
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
Dock,
/// Toolbar windows. "Torn off" from the main application.
Toolbar,
/// Pinnable menu windows. "Torn off" from the main application.
Menu,
/// A small persistent utility window, such as a palette or toolbox.
Utility,
/// The window is a splash screen displayed as an application is starting up.
Splash,
/// This is a dialog window.
Dialog,
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
/// This property is typically used on override-redirect windows.
DropdownMenu,
/// A popup menu that usually appears when the user right clicks on an object.
/// This property is typically used on override-redirect windows.
PopupMenu,
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
/// This property is typically used on override-redirect windows.
Tooltip,
/// The window is a notification.
/// This property is typically used on override-redirect windows.
Notification,
/// This should be used on the windows that are popped up by combo boxes.
/// This property is typically used on override-redirect windows.
Combo,
/// This indicates the the window is being dragged.
/// This property is typically used on override-redirect windows.
Dnd,
/// This is a normal, top-level window.
Normal,
}
impl Default for WindowType {
fn default() -> Self {
WindowType::Normal
}
}
impl WindowType {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
use self::WindowType::*;
let atom_name: &[u8] = match self {
&Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
&Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
&Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
&Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
&Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
&Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
&Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
&DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
&PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
&Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
&Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
&Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
&Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
&Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
};
unsafe { xconn.get_atom_unchecked(atom_name) }
}
}
pub struct NormalHints<'a> {
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
}
impl<'a> NormalHints<'a> {
pub fn new(xconn: &'a XConnection) -> Self {
NormalHints { size_hints: xconn.alloc_size_hints() }
}
pub fn has_flag(&self, flag: c_long) -> bool {
has_flag(self.size_hints.flags, flag)
}
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
if self.has_flag(flag) {
Some((*field1 as _, *field2 as _))
} else {
None
}
}
pub fn get_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
self.size_hints.flags |= ffi::PSize;
self.size_hints.width = width as c_int;
self.size_hints.height = height as c_int;
} else {
self.size_hints.flags &= !ffi::PSize;
}
}
pub fn get_max_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PMaxSize, &self.size_hints.max_width, &self.size_hints.max_height)
}
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
if let Some((max_width, max_height)) = max_size {
self.size_hints.flags |= ffi::PMaxSize;
self.size_hints.max_width = max_width as c_int;
self.size_hints.max_height = max_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMaxSize;
}
}
pub fn get_min_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PMinSize, &self.size_hints.min_width, &self.size_hints.min_height)
}
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
if let Some((min_width, min_height)) = min_size {
self.size_hints.flags |= ffi::PMinSize;
self.size_hints.min_width = min_width as c_int;
self.size_hints.min_height = min_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMinSize;
}
}
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
self.getter(ffi::PResizeInc, &self.size_hints.width_inc, &self.size_hints.height_inc)
}
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
if let Some((width_inc, height_inc)) = resize_increments {
self.size_hints.flags |= ffi::PResizeInc;
self.size_hints.width_inc = width_inc as c_int;
self.size_hints.height_inc = height_inc as c_int;
} else {
self.size_hints.flags &= !ffi::PResizeInc;
}
}
pub fn get_base_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PBaseSize, &self.size_hints.base_width, &self.size_hints.base_height)
}
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
if let Some((base_width, base_height)) = base_size {
self.size_hints.flags |= ffi::PBaseSize;
self.size_hints.base_width = base_width as c_int;
self.size_hints.base_height = base_height as c_int;
} else {
self.size_hints.flags &= !ffi::PBaseSize;
}
}
}
impl XConnection {
pub fn get_wm_hints(&self, window: ffi::Window) -> Result<XSmartPointer<ffi::XWMHints>, XError> {
let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
self.check_errors()?;
let wm_hints = if wm_hints.is_null() {
self.alloc_wm_hints()
} else {
XSmartPointer::new(self, wm_hints).unwrap()
};
Ok(wm_hints)
}
pub fn set_wm_hints(&self, window: ffi::Window, wm_hints: XSmartPointer<ffi::XWMHints>) -> Flusher {
unsafe {
(self.xlib.XSetWMHints)(
self.display,
window,
wm_hints.ptr,
);
}
Flusher::new(self)
}
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints, XError> {
let size_hints = self.alloc_size_hints();
let mut supplied_by_user: c_long = unsafe { mem::uninitialized() };
unsafe {
(self.xlib.XGetWMNormalHints)(
self.display,
window,
size_hints.ptr,
&mut supplied_by_user,
);
}
self.check_errors().map(|_| NormalHints { size_hints })
}
pub fn set_normal_hints(&self, window: ffi::Window, normal_hints: NormalHints) -> Flusher {
unsafe {
(self.xlib.XSetWMNormalHints)(
self.display,
window,
normal_hints.size_hints.ptr,
);
}
Flusher::new(self)
}
}

View File

@@ -17,7 +17,7 @@ impl Pixel {
}
impl Icon {
pub fn to_cardinals(&self) -> Vec<Cardinal> {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (self.width * self.height) as usize);

View File

@@ -1,44 +1,15 @@
use std::str;
use super::*;
use events::ModifiersState;
pub unsafe fn select_xinput_events(
xconn: &Arc<XConnection>,
window: c_ulong,
device_id: c_int,
mask: i32,
) -> Flusher {
let mut event_mask = ffi::XIEventMask {
deviceid: device_id,
mask: &mask as *const _ as *mut c_uchar,
mask_len: mem::size_of_val(&mask) as c_int,
};
(xconn.xinput2.XISelectEvents)(
xconn.display,
window,
&mut event_mask as *mut ffi::XIEventMask,
1, // number of masks to read from pointer above
);
Flusher::new(xconn)
}
pub const VIRTUAL_CORE_POINTER: c_int = 2;
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
#[allow(dead_code)]
pub unsafe fn select_xkb_events(
xconn: &Arc<XConnection>,
device_id: c_uint,
mask: c_ulong,
) -> Option<Flusher> {
let status = (xconn.xlib.XkbSelectEvents)(
xconn.display,
device_id,
mask,
mask,
);
if status == ffi::True {
Some(Flusher::new(xconn))
} else {
None
}
}
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.
// To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
impl From<ffi::XIModifierState> for ModifiersState {
fn from(mods: ffi::XIModifierState) -> Self {
@@ -53,17 +24,17 @@ impl From<ffi::XIModifierState> for ModifiersState {
}
pub struct PointerState<'a> {
xconn: &'a Arc<XConnection>,
_root: ffi::Window,
_child: ffi::Window,
_root_x: c_double,
_root_y: c_double,
_win_x: c_double,
_win_y: c_double,
_buttons: ffi::XIButtonState,
xconn: &'a XConnection,
root: ffi::Window,
child: ffi::Window,
pub root_x: c_double,
pub root_y: c_double,
win_x: c_double,
win_y: c_double,
buttons: ffi::XIButtonState,
modifiers: ffi::XIModifierState,
_group: ffi::XIGroupState,
_relative_to_window: bool,
group: ffi::XIGroupState,
relative_to_window: bool,
}
impl<'a> PointerState<'a> {
@@ -74,114 +45,115 @@ impl<'a> PointerState<'a> {
impl<'a> Drop for PointerState<'a> {
fn drop(&mut self) {
unsafe {
// This is why you need to read the docs carefully...
(self.xconn.xlib.XFree)(self._buttons.mask as _);
if !self.buttons.mask.is_null() {
unsafe {
// This is why you need to read the docs carefully...
(self.xconn.xlib.XFree)(self.buttons.mask as _);
}
}
}
}
pub unsafe fn query_pointer(
xconn: &Arc<XConnection>,
window: ffi::Window,
device_id: c_int,
) -> Result<PointerState, XError> {
let mut root_return = mem::uninitialized();
let mut child_return = mem::uninitialized();
let mut root_x_return = mem::uninitialized();
let mut root_y_return = mem::uninitialized();
let mut win_x_return = mem::uninitialized();
let mut win_y_return = mem::uninitialized();
let mut buttons_return = mem::uninitialized();
let mut modifiers_return = mem::uninitialized();
let mut group_return = mem::uninitialized();
impl XConnection {
pub fn select_xinput_events(&self, window: c_ulong, device_id: c_int, mask: i32) -> Flusher {
let mut event_mask = ffi::XIEventMask {
deviceid: device_id,
mask: &mask as *const _ as *mut c_uchar,
mask_len: mem::size_of_val(&mask) as c_int,
};
unsafe {
(self.xinput2.XISelectEvents)(
self.display,
window,
&mut event_mask as *mut ffi::XIEventMask,
1, // number of masks to read from pointer above
);
}
Flusher::new(self)
}
let relative_to_window = (xconn.xinput2.XIQueryPointer)(
xconn.display,
device_id,
window,
&mut root_return,
&mut child_return,
&mut root_x_return,
&mut root_y_return,
&mut win_x_return,
&mut win_y_return,
&mut buttons_return,
&mut modifiers_return,
&mut group_return,
) == ffi::True;
#[allow(dead_code)]
pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option<Flusher> {
let status = unsafe {
(self.xlib.XkbSelectEvents)(
self.display,
device_id,
mask,
mask,
)
};
if status == ffi::True {
Some(Flusher::new(self))
} else {
None
}
}
xconn.check_errors()?;
pub fn query_pointer(&self, window: ffi::Window, device_id: c_int) -> Result<PointerState, XError> {
unsafe {
let mut pointer_state: PointerState = mem::uninitialized();
pointer_state.xconn = self;
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)(
self.display,
device_id,
window,
&mut pointer_state.root,
&mut pointer_state.child,
&mut pointer_state.root_x,
&mut pointer_state.root_y,
&mut pointer_state.win_x,
&mut pointer_state.win_y,
&mut pointer_state.buttons,
&mut pointer_state.modifiers,
&mut pointer_state.group,
) == ffi::True;
if let Err(err) = self.check_errors() {
// Running the destrutor would be bad news for us...
mem::forget(pointer_state);
Err(err)
} else {
Ok(pointer_state)
}
}
}
Ok(PointerState {
xconn,
_root: root_return,
_child: child_return,
_root_x: root_x_return,
_root_y: root_y_return,
_win_x: win_x_return,
_win_y: win_y_return,
_buttons: buttons_return,
modifiers: modifiers_return,
_group: group_return,
_relative_to_window: relative_to_window,
})
}
fn lookup_utf8_inner(
&self,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
buffer: &mut [u8],
) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
let count = unsafe {
(self.xlib.Xutf8LookupString)(
ic,
key_event,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
&mut keysym,
&mut status,
)
};
(keysym, status, count)
}
unsafe fn lookup_utf8_inner(
xconn: &Arc<XConnection>,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
buffer: &mut [u8],
) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
let count = (xconn.xlib.Xutf8LookupString)(
ic,
key_event,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
&mut keysym,
&mut status,
);
(keysym, status, count)
}
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.
// To test if lookup_utf8 works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
pub unsafe fn lookup_utf8(
xconn: &Arc<XConnection>,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
) -> String {
let mut buffer: [u8; TEXT_BUFFER_SIZE] = mem::uninitialized();
let (_, status, count) = lookup_utf8_inner(
xconn,
ic,
key_event,
&mut buffer,
);
// The buffer overflowed, so we'll make a new one on the heap.
if status == ffi::XBufferOverflow {
let mut buffer = Vec::with_capacity(count as usize);
buffer.set_len(count as usize);
let (_, _, new_count) = lookup_utf8_inner(
xconn,
ic,
key_event,
&mut buffer,
);
debug_assert_eq!(count, new_count);
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
} else {
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() };
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
// The buffer overflowed, so we'll make a new one on the heap.
if status == ffi::XBufferOverflow {
let mut buffer = Vec::with_capacity(count as usize);
unsafe { buffer.set_len(count as usize) };
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
debug_assert_eq!(count, new_count);
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
} else {
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
}
}
}

View File

@@ -0,0 +1,62 @@
use std::ops::{Deref, DerefMut};
use super::*;
pub struct XSmartPointer<'a, T> {
xconn: &'a XConnection,
pub ptr: *mut T,
}
impl<'a, T> XSmartPointer<'a, T> {
// You're responsible for only passing things to this that should be XFree'd.
// Returns None if ptr is null.
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
if !ptr.is_null() {
Some(XSmartPointer {
xconn,
ptr,
})
} else {
None
}
}
}
impl<'a, T> Deref for XSmartPointer<'a, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.ptr }
}
}
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl<'a, T> Drop for XSmartPointer<'a, T> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFree)(self.ptr as *mut _);
}
}
}
impl XConnection {
pub fn alloc_class_hint(&self) -> XSmartPointer<ffi::XClassHint> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocClassHint)() })
.expect("`XAllocClassHint` returned null; out of memory")
}
pub fn alloc_size_hints(&self) -> XSmartPointer<ffi::XSizeHints> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocSizeHints)() })
.expect("`XAllocSizeHints` returned null; out of memory")
}
pub fn alloc_wm_hints(&self) -> XSmartPointer<ffi::XWMHints> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocWMHints)() })
.expect("`XAllocWMHints` returned null; out of memory")
}
}

View File

@@ -2,178 +2,103 @@
// *results may vary
mod atom;
mod client_msg;
mod format;
mod geometry;
mod hint;
mod icon;
mod input;
mod memory;
mod randr;
mod window_property;
mod wm;
pub use self::atom::*;
pub use self::client_msg::*;
pub use self::format::*;
pub use self::geometry::*;
pub use self::hint::*;
pub use self::icon::*;
pub use self::input::*;
pub use self::memory::*;
pub use self::randr::*;
pub use self::window_property::*;
pub use self::wm::*;
use std::mem;
use std::ptr;
use std::str;
use std::sync::Arc;
use std::ops::{Deref, DerefMut};
use std::ops::BitAnd;
use std::os::raw::*;
use super::{ffi, XConnection, XError};
// This isn't actually the number of the bits in the format.
// X11 does a match on this value to determine which type to call sizeof on.
// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64.
// ...if that sounds confusing, then you know why this enum is here.
#[derive(Debug, Copy, Clone)]
pub enum Format {
Char = 8,
Short = 16,
Long = 32,
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
let b_ptr = a as *const _ as *const B;
unsafe { &*b_ptr }
}
impl Format {
pub fn from_format(format: usize) -> Option<Self> {
match format {
8 => Some(Format::Char),
16 => Some(Format::Short),
32 => Some(Format::Long),
_ => None,
}
}
pub fn is_same_size_as<T>(&self) -> bool {
mem::size_of::<T>() == self.get_actual_size()
}
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
&Format::Short => mem::size_of::<c_short>(),
&Format::Long => mem::size_of::<c_long>(),
}
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
let wrapped = Some(value);
if *field != wrapped {
*field = wrapped;
true
} else {
false
}
}
pub struct XSmartPointer<'a, T> {
xconn: &'a Arc<XConnection>,
pub ptr: *mut T,
}
impl<'a, T> XSmartPointer<'a, T> {
// You're responsible for only passing things to this that should be XFree'd.
// Returns None if ptr is null.
pub fn new(xconn: &'a Arc<XConnection>, ptr: *mut T) -> Option<Self> {
if !ptr.is_null() {
Some(XSmartPointer {
xconn,
ptr,
})
} else {
None
}
}
}
impl<'a, T> Deref for XSmartPointer<'a, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.ptr }
}
}
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl<'a, T> Drop for XSmartPointer<'a, T> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFree)(self.ptr as *mut _);
}
}
}
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.
// This buffer contains the requests you make, and is flushed under various circumstances:
// 1. XPending, XNextEvent, and XWindowEvent flush "as needed"
// 2. XFlush explicitly flushes
// 3. XSync flushes and blocks until all requests are responded to
// 4. Calls that have a return dependent on a response (i.e. XGetWindowProperty) sync internally.
// When in doubt, check the X11 source; if a function calls _XReply, it flushes and waits.
// All util functions that abstract an async function will return a Flusher.
pub unsafe fn flush_requests(xconn: &Arc<XConnection>) -> Result<(), XError> {
(xconn.xlib.XFlush)(xconn.display);
//println!("XFlush");
// This isn't necessarily a useful time to check for errors (since our request hasn't
// necessarily been processed yet)
xconn.check_errors()
}
pub unsafe fn sync_with_server(xconn: &Arc<XConnection>) -> Result<(), XError> {
(xconn.xlib.XSync)(xconn.display, ffi::False);
//println!("XSync");
xconn.check_errors()
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 Arc<XConnection>,
xconn: &'a XConnection,
}
impl<'a> Flusher<'a> {
pub fn new(xconn: &'a Arc<XConnection>) -> Self {
pub fn new(xconn: &'a XConnection) -> Self {
Flusher { xconn }
}
// "I want this request sent now!"
pub fn flush(self) -> Result<(), XError> {
unsafe { flush_requests(self.xconn) }
self.xconn.flush_requests()
}
// "I want the response now too!"
pub fn sync(self) -> Result<(), XError> {
self.xconn.sync_with_server()
}
// "I'm aware that this request hasn't been sent, and I'm okay with waiting."
pub fn queue(self) {}
}
pub unsafe fn send_client_msg(
xconn: &Arc<XConnection>,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: (c_long, c_long, c_long, c_long, c_long),
) -> Flusher {
let mut event: ffi::XClientMessageEvent = mem::uninitialized();
event.type_ = ffi::ClientMessage;
event.display = xconn.display;
event.window = window;
event.message_type = message_type;
event.format = Format::Long as c_int;
event.data = ffi::ClientMessageData::new();
event.data.set_long(0, data.0);
event.data.set_long(1, data.1);
event.data.set_long(2, data.2);
event.data.set_long(3, data.3);
event.data.set_long(4, data.4);
impl XConnection {
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.
// This buffer contains the requests you make, and is flushed under various circumstances:
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"
// 2. `XFlush` explicitly flushes
// 3. `XSync` flushes and blocks until all requests are responded to
// 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync internally.
// When in doubt, check the X11 source; if a function calls `_XReply`, it flushes and waits.
// All util functions that abstract an async function will return a `Flusher`.
pub fn flush_requests(&self) -> Result<(), XError> {
unsafe { (self.xlib.XFlush)(self.display) };
//println!("XFlush");
// This isn't necessarily a useful time to check for errors (since our request hasn't
// necessarily been processed yet)
self.check_errors()
}
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
(xconn.xlib.XSendEvent)(
xconn.display,
target_window,
ffi::False,
event_mask,
&mut event.into(),
);
Flusher::new(xconn)
pub fn sync_with_server(&self) -> Result<(), XError> {
unsafe { (self.xlib.XSync)(self.display, ffi::False) };
//println!("XSync");
self.check_errors()
}
}

View File

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

View File

@@ -1,5 +1,4 @@
use std;
use std::fmt::Debug;
use super::*;
@@ -25,139 +24,121 @@ impl GetPropertyError {
}
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
// To test if get_property works correctly, set this to 1.
// To test if `get_property` works correctly, set this to 1.
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
pub unsafe fn get_property<T: Debug + Clone>(
xconn: &Arc<XConnection>,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
) -> Result<Vec<T>, GetPropertyError> {
let mut data = Vec::new();
let mut offset = 0;
let mut done = false;
while !done {
let mut actual_type: ffi::Atom = mem::uninitialized();
let mut actual_format: c_int = mem::uninitialized();
let mut quantity_returned: c_ulong = mem::uninitialized();
let mut bytes_after: c_ulong = mem::uninitialized();
let mut buf: *mut c_uchar = ptr::null_mut();
(xconn.xlib.XGetWindowProperty)(
xconn.display,
window,
property,
// This offset is in terms of 32-bit chunks.
offset,
// This is the quanity of 32-bit chunks to receive at once.
PROPERTY_BUFFER_SIZE,
ffi::False,
property_type,
&mut actual_type,
&mut actual_format,
// This is the quantity of items we retrieved in our format, NOT of 32-bit chunks!
&mut quantity_returned,
// ...and this is a quantity of bytes. So, this function deals in 3 different units.
&mut bytes_after,
&mut buf,
);
if let Err(e) = xconn.check_errors() {
return Err(GetPropertyError::XError(e));
}
if actual_type != property_type {
return Err(GetPropertyError::TypeMismatch(actual_type));
}
let format_mismatch = Format::from_format(actual_format as _)
.map(|actual_format| !actual_format.is_same_size_as::<T>())
// This won't actually be reached; the XError condition above is triggered first.
.unwrap_or(true);
if format_mismatch {
return Err(GetPropertyError::FormatMismatch(actual_format));
}
if !buf.is_null() {
offset += PROPERTY_BUFFER_SIZE;
let new_data = std::slice::from_raw_parts(
buf as *mut T,
quantity_returned as usize,
);
/*println!(
"XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}",
property,
mem::size_of::<T>() * 8,
data.len(),
offset,
quantity_returned,
new_data,
);*/
data.extend_from_slice(&new_data);
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
(xconn.xlib.XFree)(buf as _); // Don't try to access new_data after this.
} else {
return Err(GetPropertyError::NothingAllocated);
}
done = bytes_after == 0;
}
Ok(data)
}
#[derive(Debug)]
pub enum PropMode {
Replace = ffi::PropModeReplace as isize,
_Prepend = ffi::PropModePrepend as isize,
_Append = ffi::PropModeAppend as isize,
Prepend = ffi::PropModePrepend as isize,
Append = ffi::PropModeAppend as isize,
}
#[derive(Debug, Clone)]
pub struct InvalidFormat {
format_used: Format,
size_passed: usize,
size_expected: usize,
}
impl XConnection {
pub fn get_property<T: Formattable>(
&self,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
) -> Result<Vec<T>, GetPropertyError> {
let mut data = Vec::new();
let mut offset = 0;
pub unsafe fn change_property<'a, T: Debug>(
xconn: &'a Arc<XConnection>,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
format: Format,
mode: PropMode,
new_value: &[T],
) -> Flusher<'a> {
if !format.is_same_size_as::<T>() {
panic!(format!(
"[winit developer error] Incorrect usage of `util::change_property`: {:#?}",
InvalidFormat {
format_used: format,
size_passed: mem::size_of::<T>() * 8,
size_expected: format.get_actual_size() * 8,
},
));
let mut done = false;
while !done {
unsafe {
let mut actual_type: ffi::Atom = mem::uninitialized();
let mut actual_format: c_int = mem::uninitialized();
let mut quantity_returned: c_ulong = mem::uninitialized();
let mut bytes_after: c_ulong = mem::uninitialized();
let mut buf: *mut c_uchar = ptr::null_mut();
(self.xlib.XGetWindowProperty)(
self.display,
window,
property,
// This offset is in terms of 32-bit chunks.
offset,
// This is the quanity of 32-bit chunks to receive at once.
PROPERTY_BUFFER_SIZE,
ffi::False,
property_type,
&mut actual_type,
&mut actual_format,
// This is the quantity of items we retrieved in our format, NOT of 32-bit chunks!
&mut quantity_returned,
// ...and this is a quantity of bytes. So, this function deals in 3 different units.
&mut bytes_after,
&mut buf,
);
if let Err(e) = self.check_errors() {
return Err(GetPropertyError::XError(e));
}
if actual_type != property_type {
return Err(GetPropertyError::TypeMismatch(actual_type));
}
let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT);
if format_mismatch {
return Err(GetPropertyError::FormatMismatch(actual_format));
}
if !buf.is_null() {
offset += PROPERTY_BUFFER_SIZE;
let new_data = std::slice::from_raw_parts(
buf as *mut T,
quantity_returned as usize,
);
/*println!(
"XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}",
property,
mem::size_of::<T>() * 8,
data.len(),
offset,
quantity_returned,
new_data,
);*/
data.extend_from_slice(&new_data);
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
} else {
return Err(GetPropertyError::NothingAllocated);
}
done = bytes_after == 0;
}
}
Ok(data)
}
(xconn.xlib.XChangeProperty)(
xconn.display,
window,
property,
property_type,
format as c_int,
mode as c_int,
new_value.as_ptr() as *const c_uchar,
new_value.len() as c_int,
);
/*println!(
"XChangeProperty prop:{:?} val:{:?}",
property,
new_value,
);*/
Flusher::new(xconn)
pub fn change_property<'a, T: Formattable>(
&'a self,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
mode: PropMode,
new_value: &[T],
) -> Flusher<'a> {
debug_assert_eq!(mem::size_of::<T>(), T::FORMAT.get_actual_size());
unsafe {
(self.xlib.XChangeProperty)(
self.display,
window,
property,
property_type,
T::FORMAT as c_int,
mode as c_int,
new_value.as_ptr() as *const c_uchar,
new_value.len() as c_int,
);
}
/*println!(
"XChangeProperty prop:{:?} val:{:?}",
property,
new_value,
);*/
Flusher::new(self)
}
}

View File

@@ -20,139 +20,122 @@ pub fn wm_name_is_one_of(names: &[&str]) -> bool {
}
}
pub fn update_cached_wm_info(xconn: &Arc<XConnection>, root: ffi::Window) {
*SUPPORTED_HINTS.lock() = self::get_supported_hints(xconn, root);
*WM_NAME.lock() = self::get_wm_name(xconn, root);
}
impl XConnection {
pub fn update_cached_wm_info(&self, root: ffi::Window) {
*SUPPORTED_HINTS.lock() = self.get_supported_hints(root);
*WM_NAME.lock() = self.get_wm_name(root);
}
fn get_supported_hints(xconn: &Arc<XConnection>, root: ffi::Window) -> Vec<ffi::Atom> {
let supported_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTED\0") }
.expect("Failed to call XInternAtom (_NET_SUPPORTED)");
unsafe {
self::get_property(
xconn,
fn get_supported_hints(&self, root: ffi::Window) -> Vec<ffi::Atom> {
let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") };
self.get_property(
root,
supported_atom,
ffi::XA_ATOM,
)
}.unwrap_or_else(|_| Vec::with_capacity(0))
}
).unwrap_or_else(|_| Vec::with_capacity(0))
}
fn get_wm_name(xconn: &Arc<XConnection>, root: ffi::Window) -> Option<String> {
let check_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTING_WM_CHECK\0") }
.expect("Failed to call XInternAtom (_NET_SUPPORTING_WM_CHECK)");
let wm_name_atom = unsafe { self::get_atom(xconn, b"_NET_WM_NAME\0") }
.expect("Failed to call XInternAtom (_NET_WM_NAME)");
fn get_wm_name(&self, root: ffi::Window) -> Option<String> {
let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") };
let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") };
// Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
// it working and being supported. This has been reported upstream, but due to the
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
// regardless of whether or not the WM claims to support it.
//
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
// in 0.72.
/*if !supported_hints.contains(&check_atom) {
return None;
}*/
// Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
// it working and being supported. This has been reported upstream, but due to the
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
// regardless of whether or not the WM claims to support it.
//
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
// in 0.72.
/*if !supported_hints.contains(&check_atom) {
return None;
}*/
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
/*if !supported_hints.contains(&wm_name_atom) {
return None;
}*/
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
/*if !supported_hints.contains(&wm_name_atom) {
return None;
}*/
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
// Querying this property on the root window will give us the ID of a child window created by
// the WM.
let root_window_wm_check = {
let result = unsafe {
self::get_property(
xconn,
// Querying this property on the root window will give us the ID of a child window created by
// the WM.
let root_window_wm_check = {
let result = self.get_property(
root,
check_atom,
ffi::XA_WINDOW,
)
);
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// Querying the same property on the child window we were given, we should get this child
// window's ID again.
let child_window_wm_check = {
let result = unsafe {
self::get_property(
xconn,
// Querying the same property on the child window we were given, we should get this child
// window's ID again.
let child_window_wm_check = {
let result = self.get_property(
root_window_wm_check,
check_atom,
ffi::XA_WINDOW,
)
);
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
// These values should be the same.
if root_window_wm_check != child_window_wm_check {
return None;
}
};
// These values should be the same.
if root_window_wm_check != child_window_wm_check {
return None;
}
// All of that work gives us a window ID that we can get the WM name from.
let wm_name = {
let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") };
// All of that work gives us a window ID that we can get the WM name from.
let wm_name = {
let utf8_string_atom = unsafe { self::get_atom(xconn, b"UTF8_STRING\0") }
.expect("Failed to call XInternAtom (UTF8_STRING)");
let result = unsafe {
self::get_property(
xconn,
let result = self.get_property(
root_window_wm_check,
wm_name_atom,
utf8_string_atom,
)
};
);
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
// string. For more fun trivia, IceWM is also unique in including version and uname
// information in this string (this means you'll have to be careful if you want to match
// against it, though).
// The unofficial 1.4 fork of IceWM still includes the extra details, but properly
// returns a UTF8 string that isn't null-terminated.
let no_utf8 = if let Err(ref err) = result {
err.is_actual_property_type(ffi::XA_STRING)
} else {
false
};
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
// string. For more fun trivia, IceWM is also unique in including version and uname
// information in this string (this means you'll have to be careful if you want to match
// against it, though).
// The unofficial 1.4 fork of IceWM still includes the extra details, but properly
// returns a UTF8 string that isn't null-terminated.
let no_utf8 = if let Err(ref err) = result {
err.is_actual_property_type(ffi::XA_STRING)
} else {
false
};
if no_utf8 {
unsafe {
self::get_property(
xconn,
if no_utf8 {
self.get_property(
root_window_wm_check,
wm_name_atom,
ffi::XA_STRING,
)
} else {
result
}
} else {
result
}
}.ok();
}.ok();
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
}
}

File diff suppressed because it is too large Load Diff

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

@@ -1,14 +1,14 @@
use {ControlFlow, EventsLoopClosed};
use cocoa::{self, appkit, foundation};
use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow};
use events::{self, ElementState, Event, MouseButton, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
use events::{self, ElementState, Event, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use super::window::Window2;
use std;
use std::os::raw::*;
use super::DeviceId;
pub struct EventsLoop {
modifiers: Modifiers,
pub shared: Arc<Shared>,
@@ -180,7 +180,7 @@ impl EventsLoop {
where F: FnMut(Event),
{
unsafe {
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
@@ -221,7 +221,7 @@ impl EventsLoop {
where F: FnMut(Event) -> ControlFlow
{
unsafe {
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
@@ -294,10 +294,7 @@ impl EventsLoop {
// FIXME: Document this. Why do we do this? Seems like it passes on events to window/app.
// If we don't do this, window does not become main for some reason.
match event_type {
appkit::NSKeyDown => (),
_ => appkit::NSApp().sendEvent_(ns_event),
}
appkit::NSApp().sendEvent_(ns_event);
let windows = self.shared.windows.lock().unwrap();
let maybe_window = windows.iter()
@@ -318,137 +315,74 @@ impl EventsLoop {
});
match event_type {
appkit::NSKeyDown => {
let mut events = std::collections::VecDeque::new();
let received_c_str = foundation::NSString::UTF8String(ns_event.characters());
let received_str = std::ffi::CStr::from_ptr(received_c_str);
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
let state = ElementState::Pressed;
let code = NSEvent::keyCode(ns_event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: vkey,
modifiers: event_mods(ns_event),
},
};
for received_char in std::str::from_utf8(received_str.to_bytes()).unwrap().chars() {
let window_event = WindowEvent::ReceivedCharacter(received_char);
events.push_back(into_event(window_event));
}
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
Some(into_event(window_event))
},
appkit::NSKeyUp => {
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
let state = ElementState::Released;
let code = NSEvent::keyCode(ns_event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: vkey,
modifiers: event_mods(ns_event),
},
};
Some(into_event(window_event))
},
appkit::NSFlagsChanged => {
unsafe fn modifier_event(event: cocoa::base::id,
keymask: NSEventModifierFlags,
key: events::VirtualKeyCode,
key_pressed: bool) -> Option<WindowEvent>
{
if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) {
let state = ElementState::Pressed;
let code = NSEvent::keyCode(event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: Some(key),
modifiers: event_mods(event),
},
};
Some(window_event)
} else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) {
let state = ElementState::Released;
let code = NSEvent::keyCode(event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: Some(key),
modifiers: event_mods(event),
},
};
Some(window_event)
} else {
None
// https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881
appkit::NSKeyUp => {
if let Some(key_window) = maybe_key_window() {
if event_mods(ns_event).logo {
let _: () = msg_send![*key_window.window, sendEvent:ns_event];
}
}
None
},
// similar to above, but for `<Cmd-.>`, the keyDown is suppressed instead of the
// KeyUp, and the above trick does not appear to work.
appkit::NSKeyDown => {
let modifiers = event_mods(ns_event);
let keycode = NSEvent::keyCode(ns_event);
if modifiers.logo && keycode == 47 {
modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false)
.map(into_event)
} else {
None
}
},
appkit::NSFlagsChanged => {
let mut events = std::collections::VecDeque::new();
if let Some(window_event) = modifier_event(ns_event,
NSEventModifierFlags::NSShiftKeyMask,
events::VirtualKeyCode::LShift,
self.modifiers.shift_pressed)
{
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSShiftKeyMask,
self.modifiers.shift_pressed,
) {
self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(ns_event,
NSEventModifierFlags::NSControlKeyMask,
events::VirtualKeyCode::LControl,
self.modifiers.ctrl_pressed)
{
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSControlKeyMask,
self.modifiers.ctrl_pressed,
) {
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(ns_event,
NSEventModifierFlags::NSCommandKeyMask,
events::VirtualKeyCode::LWin,
self.modifiers.win_pressed)
{
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSCommandKeyMask,
self.modifiers.win_pressed,
) {
self.modifiers.win_pressed = !self.modifiers.win_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(ns_event,
NSEventModifierFlags::NSAlternateKeyMask,
events::VirtualKeyCode::LAlt,
self.modifiers.alt_pressed)
{
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSAlternateKeyMask,
self.modifiers.alt_pressed,
) {
self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
events.push_back(into_event(window_event));
}
let event = events.pop_front();
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
self.shared.pending_events
.lock()
.unwrap()
.extend(events.into_iter());
event
},
appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Left, modifiers: event_mods(ns_event) })) },
appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Left, modifiers: event_mods(ns_event) })) },
appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Right, modifiers: event_mods(ns_event) })) },
appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Right, modifiers: event_mods(ns_event) })) },
appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle, modifiers: event_mods(ns_event) })) },
appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Middle, modifiers: event_mods(ns_event) })) },
appkit::NSMouseEntered => {
let window = match maybe_window.or_else(maybe_key_window) {
Some(window) => window,
@@ -464,14 +398,16 @@ impl EventsLoop {
} else {
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
};
let view_rect = NSView::frame(*window.view);
let scale_factor = window.hidpi_factor();
let x = (scale_factor * view_point.x as f32) as f64;
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
let window_event = WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(ns_event),
};
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
self.shared.pending_events.lock().unwrap().push_back(event);
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
},
@@ -484,50 +420,30 @@ impl EventsLoop {
// If the mouse movement was on one of our windows, use it.
// Otherwise, if one of our windows is the key window (receiving input), use it.
// Otherwise, return `None`.
let window = match maybe_window.or_else(maybe_key_window) {
Some(window) => window,
match maybe_window.or_else(maybe_key_window) {
Some(_window) => (),
None => return None,
};
let window_point = ns_event.locationInWindow();
let view_point = if ns_window == cocoa::base::nil {
let ns_size = foundation::NSSize::new(0.0, 0.0);
let ns_rect = foundation::NSRect::new(window_point, ns_size);
let window_rect = window.window.convertRectFromScreen_(ns_rect);
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
} else {
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
};
let view_rect = NSView::frame(*window.view);
let scale_factor = window.hidpi_factor();
let mut events = std::collections::VecDeque::new();
{
let x = (scale_factor * view_point.x as f32) as f64;
let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) };
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
events.push_back(event);
}
let delta_x = (scale_factor * ns_event.deltaX() as f32) as f64;
let mut events = std::collections::VecDeque::with_capacity(3);
let delta_x = ns_event.deltaX() as f64;
if delta_x != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let delta_y = (scale_factor * ns_event.deltaY() as f32) as f64;
let delta_y = ns_event.deltaY() as f64;
if delta_y != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
if delta_x != 0.0 || delta_y != 0.0 {
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
@@ -538,19 +454,22 @@ impl EventsLoop {
appkit::NSScrollWheel => {
// If none of the windows received the scroll, return `None`.
let window = match maybe_window {
Some(window) => window,
None => return None,
};
if maybe_window.is_none() {
return None;
}
use events::MouseScrollDelta::{LineDelta, PixelDelta};
let scale_factor = window.hidpi_factor();
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
scale_factor * ns_event.scrollingDeltaY() as f32)
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
LineDelta(scale_factor * ns_event.scrollingDeltaX() as f32,
scale_factor * ns_event.scrollingDeltaY() as f32)
// TODO: This is probably wrong
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
};
let phase = match ns_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
@@ -561,11 +480,15 @@ impl EventsLoop {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel {
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta(ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32)
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
LineDelta(ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32)
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
},
}
});
@@ -621,8 +544,67 @@ impl Proxy {
}
}
pub fn char_to_keycode(c: char) -> Option<events::VirtualKeyCode> {
// We only translate keys that are affected by keyboard layout.
//
// Note that since keys are translated in a somewhat "dumb" way (reading character)
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
// letter to be received, and so we receive the wrong key.
//
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
Some(match c {
'a' | 'A' => events::VirtualKeyCode::A,
'b' | 'B' => events::VirtualKeyCode::B,
'c' | 'C' => events::VirtualKeyCode::C,
'd' | 'D' => events::VirtualKeyCode::D,
'e' | 'E' => events::VirtualKeyCode::E,
'f' | 'F' => events::VirtualKeyCode::F,
'g' | 'G' => events::VirtualKeyCode::G,
'h' | 'H' => events::VirtualKeyCode::H,
'i' | 'I' => events::VirtualKeyCode::I,
'j' | 'J' => events::VirtualKeyCode::J,
'k' | 'K' => events::VirtualKeyCode::K,
'l' | 'L' => events::VirtualKeyCode::L,
'm' | 'M' => events::VirtualKeyCode::M,
'n' | 'N' => events::VirtualKeyCode::N,
'o' | 'O' => events::VirtualKeyCode::O,
'p' | 'P' => events::VirtualKeyCode::P,
'q' | 'Q' => events::VirtualKeyCode::Q,
'r' | 'R' => events::VirtualKeyCode::R,
's' | 'S' => events::VirtualKeyCode::S,
't' | 'T' => events::VirtualKeyCode::T,
'u' | 'U' => events::VirtualKeyCode::U,
'v' | 'V' => events::VirtualKeyCode::V,
'w' | 'W' => events::VirtualKeyCode::W,
'x' | 'X' => events::VirtualKeyCode::X,
'y' | 'Y' => events::VirtualKeyCode::Y,
'z' | 'Z' => events::VirtualKeyCode::Z,
'1' | '!' => events::VirtualKeyCode::Key1,
'2' | '@' => events::VirtualKeyCode::Key2,
'3' | '#' => events::VirtualKeyCode::Key3,
'4' | '$' => events::VirtualKeyCode::Key4,
'5' | '%' => events::VirtualKeyCode::Key5,
'6' | '^' => events::VirtualKeyCode::Key6,
'7' | '&' => events::VirtualKeyCode::Key7,
'8' | '*' => events::VirtualKeyCode::Key8,
'9' | '(' => events::VirtualKeyCode::Key9,
'0' | ')' => events::VirtualKeyCode::Key0,
'=' | '+' => events::VirtualKeyCode::Equals,
'-' | '_' => events::VirtualKeyCode::Minus,
']' | '}' => events::VirtualKeyCode::RBracket,
'[' | '{' => events::VirtualKeyCode::LBracket,
'\''| '"' => events::VirtualKeyCode::Apostrophe,
';' | ':' => events::VirtualKeyCode::Semicolon,
'\\'| '|' => events::VirtualKeyCode::Backslash,
',' | '<' => events::VirtualKeyCode::Comma,
'/' | '?' => events::VirtualKeyCode::Slash,
'.' | '>' => events::VirtualKeyCode::Period,
'`' | '~' => events::VirtualKeyCode::Grave,
_ => return None,
})
}
fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
pub fn scancode_to_keycode(code: c_ushort) -> Option<events::VirtualKeyCode> {
Some(match code {
0x00 => events::VirtualKeyCode::A,
0x01 => events::VirtualKeyCode::S,
@@ -682,13 +664,13 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
0x37 => events::VirtualKeyCode::LWin,
0x38 => events::VirtualKeyCode::LShift,
//0x39 => Caps lock,
//0x3a => Left alt,
0x3a => events::VirtualKeyCode::LAlt,
0x3b => events::VirtualKeyCode::LControl,
0x3c => events::VirtualKeyCode::RShift,
//0x3d => Right alt,
0x3d => events::VirtualKeyCode::RAlt,
0x3e => events::VirtualKeyCode::RControl,
//0x3f => Fn key,
//0x40 => F17 Key,
0x40 => events::VirtualKeyCode::F17,
0x41 => events::VirtualKeyCode::Decimal,
//0x42 -> unkown,
0x43 => events::VirtualKeyCode::Multiply,
@@ -701,10 +683,11 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
0x4a => events::VirtualKeyCode::VolumeDown,
0x4b => events::VirtualKeyCode::Divide,
0x4c => events::VirtualKeyCode::NumpadEnter,
0x4e => events::VirtualKeyCode::Subtract,
//0x4d => unkown,
0x4e => events::VirtualKeyCode::Subtract,
//0x4f => F18 key,
//0x50 => F19 Key,
0x4f => events::VirtualKeyCode::F18,
0x50 => events::VirtualKeyCode::F19,
0x51 => events::VirtualKeyCode::NumpadEquals,
0x52 => events::VirtualKeyCode::Numpad0,
0x53 => events::VirtualKeyCode::Numpad1,
@@ -714,11 +697,11 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
0x57 => events::VirtualKeyCode::Numpad5,
0x58 => events::VirtualKeyCode::Numpad6,
0x59 => events::VirtualKeyCode::Numpad7,
//0x5a => F20 Key,
0x5a => events::VirtualKeyCode::F20,
0x5b => events::VirtualKeyCode::Numpad8,
0x5c => events::VirtualKeyCode::Numpad9,
//0x5d => unkown,
//0x5e => unkown,
0x5d => events::VirtualKeyCode::Yen,
//0x5e => JIS Ro,
//0x5f => unkown,
0x60 => events::VirtualKeyCode::F5,
0x61 => events::VirtualKeyCode::F6,
@@ -726,11 +709,11 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
0x63 => events::VirtualKeyCode::F3,
0x64 => events::VirtualKeyCode::F8,
0x65 => events::VirtualKeyCode::F9,
//0x66 => unkown,
//0x66 => JIS Eisuu (macOS),
0x67 => events::VirtualKeyCode::F11,
//0x68 => unkown,
//0x68 => JIS Kana (macOS),
0x69 => events::VirtualKeyCode::F13,
//0x6a => F16 Key,
0x6a => events::VirtualKeyCode::F16,
0x6b => events::VirtualKeyCode::F14,
//0x6c => unkown,
0x6d => events::VirtualKeyCode::F10,
@@ -758,7 +741,23 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
})
}
fn event_mods(event: cocoa::base::id) -> ModifiersState {
pub fn check_function_keys(
s: &String
) -> Option<events::VirtualKeyCode> {
if let Some(ch) = s.encode_utf16().next() {
return Some(match ch {
0xf718 => events::VirtualKeyCode::F21,
0xf719 => events::VirtualKeyCode::F22,
0xf71a => events::VirtualKeyCode::F23,
0xf71b => events::VirtualKeyCode::F24,
_ => return None,
})
}
None
}
pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
let flags = unsafe {
NSEvent::modifierFlags(event)
};
@@ -770,5 +769,44 @@ fn event_mods(event: cocoa::base::id) -> ModifiersState {
}
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
unsafe {
msg_send![event, keyCode]
}
}
unsafe fn modifier_event(
ns_event: cocoa::base::id,
keymask: NSEventModifierFlags,
was_key_pressed: bool,
) -> Option<WindowEvent> {
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = if was_key_pressed {
ElementState::Released
} else {
ElementState::Pressed
};
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode: scancode as u32,
virtual_keycode,
modifiers: event_mods(ns_event),
},
})
} else {
None
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

107
src/platform/macos/ffi.rs Normal file
View File

@@ -0,0 +1,107 @@
// TODO: Upstream these
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
use cocoa::base::id;
use cocoa::foundation::{NSInteger, NSUInteger};
use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value();
#[repr(C)]
pub struct NSRange {
pub location: NSUInteger,
pub length: NSUInteger,
}
impl NSRange {
#[inline]
pub fn new(location: NSUInteger, length: NSUInteger) -> NSRange {
NSRange { location, length }
}
}
unsafe impl objc::Encode for NSRange {
fn encode() -> objc::Encoding {
let encoding = format!(
// TODO: Verify that this is correct
"{{NSRange={}{}}}",
NSUInteger::encode().as_str(),
NSUInteger::encode().as_str(),
);
unsafe { objc::Encoding::from_str(&encoding) }
}
}
pub trait NSMutableAttributedString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSMutableAttributedString), alloc]
}
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
unsafe fn initWithString(self, string: id) -> id;
unsafe fn initWithAttributedString(self, string: id) -> id;
unsafe fn string(self) -> id; // *mut NSString
unsafe fn mutableString(self) -> id; // *mut NSMutableString
unsafe fn length(self) -> NSUInteger;
}
impl NSMutableAttributedString for id {
unsafe fn init(self) -> id {
msg_send![self, init]
}
unsafe fn initWithString(self, string: id) -> id {
msg_send![self, initWithString:string]
}
unsafe fn initWithAttributedString(self, string: id) -> id {
msg_send![self, initWithAttributedString:string]
}
unsafe fn string(self) -> id {
msg_send![self, string]
}
unsafe fn mutableString(self) -> id {
msg_send![self, mutableString]
}
unsafe fn length(self) -> NSUInteger {
msg_send![self, length]
}
}
pub const kCGBaseWindowLevelKey: NSInteger = 0;
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
pub const kCGNormalWindowLevelKey: NSInteger = 4;
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
pub const kCGDockWindowLevelKey: NSInteger = 7;
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
pub const kCGStatusWindowLevelKey: NSInteger = 9;
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
pub const kCGHelpWindowLevelKey: NSInteger = 16;
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
pub enum NSWindowLevel {
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
}

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 {
@@ -38,5 +44,8 @@ impl Window {
}
mod events_loop;
mod ffi;
mod monitor;
mod util;
mod view;
mod window;

View File

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

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

@@ -0,0 +1,63 @@
mod cursor;
mod into_option;
pub use self::{cursor::Cursor, into_option::IntoOption};
use cocoa::appkit::NSWindowStyleMask;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSRect, NSUInteger};
use core_graphics::display::CGDisplay;
use objc::runtime::{Class, Object};
use platform::platform::ffi;
use platform::platform::window::IdRef;
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
location: ffi::NSNotFound as NSUInteger,
length: 0,
};
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
use cocoa::appkit::NSWindow;
window.setStyleMask_(mask);
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
use cocoa::appkit::NSWindow;
let current_style_mask = window.styleMask();
if on {
window.setStyleMask_(current_style_mask | mask);
} else {
window.setStyleMask_(current_style_mask & (!mask));
}
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
let input_context: id = msg_send![input_context, initWithClient:view];
IdRef::new(input_context)
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let app: id = msg_send![class!(NSApplication), sharedApplication];
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
}

663
src/platform/macos/view.rs Normal file
View File

@@ -0,0 +1,663 @@
// This is a pretty close port of the implementation in GLFW:
// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m
use std::{slice, str};
use std::boxed::Box;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::{Arc, Mutex, Weak};
use cocoa::base::{id, nil};
use cocoa::appkit::{NSEvent, NSView, NSWindow};
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES};
use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, scancode_to_keycode, char_to_keycode, check_function_keys, get_scancode};
use platform::platform::util;
use platform::platform::ffi::*;
use platform::platform::window::{get_window_id, IdRef};
use events;
struct ViewState {
window: id,
shared: Weak<Shared>,
cursor: Arc<Mutex<util::Cursor>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
}
pub fn new_view(window: id, shared: Weak<Shared>) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
let state = ViewState {
window,
shared,
cursor,
ime_spot: None,
raw_characters: None,
is_key_down: false,
};
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let view: id = msg_send![VIEW_CLASS.0, alloc];
(IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access)
}
}
pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) {
unsafe {
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
state.ime_spot = Some((base_x + x, base_y - y));
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
}
struct ViewClass(*const Class);
unsafe impl Send for ViewClass {}
unsafe impl Sync for ViewClass {}
lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let superclass = class!(NSView);
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
decl.add_method(
sel!(initWithWinit:),
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
);
decl.add_method(
sel!(drawRect:),
draw_rect as extern fn(&Object, Sel, NSRect),
);
decl.add_method(
sel!(resetCursorRects),
reset_cursor_rects as extern fn(&Object, Sel),
);
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
decl.add_method(
sel!(markedRange),
marked_range as extern fn(&Object, Sel) -> NSRange,
);
decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange);
decl.add_method(
sel!(setMarkedText:selectedRange:replacementRange:),
set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange),
);
decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel));
decl.add_method(
sel!(validAttributesForMarkedText),
valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id,
);
decl.add_method(
sel!(attributedSubstringForProposedRange:actualRange:),
attributed_substring_for_proposed_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
);
decl.add_method(
sel!(insertText:replacementRange:),
insert_text as extern fn(&Object, Sel, id, NSRange),
);
decl.add_method(
sel!(characterIndexForPoint:),
character_index_for_point as extern fn(&Object, Sel, NSPoint) -> NSUInteger,
);
decl.add_method(
sel!(firstRectForCharacterRange:actualRange:),
first_rect_for_character_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
);
decl.add_method(
sel!(doCommandBySelector:),
do_command_by_selector as extern fn(&Object, Sel, Sel),
);
decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseDown:), mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseUp:), mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDown:), right_mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseUp:), right_mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDown:), other_mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseUp:), other_mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseMoved:), mouse_moved as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
decl.add_protocol(&protocol);
ViewClass(decl.register())
};
}
extern fn dealloc(this: &Object, _sel: Sel) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let marked_text: id = *this.get_ivar("markedText");
let _: () = msg_send![marked_text, release];
Box::from_raw(state as *mut ViewState);
}
}
extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
unsafe {
let this: id = msg_send![this, init];
if this != nil {
(*this).set_ivar("winitState", state);
let marked_text = <id as NSMutableAttributedString>::init(
NSMutableAttributedString::alloc(nil),
);
(*this).set_ivar("markedText", marked_text);
}
this
}
}
extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(shared) = state.shared.upgrade() {
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::Refresh,
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
let superclass = util::superclass(this);
let () = msg_send![super(this, superclass), drawRect:rect];
}
}
extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let bounds: NSRect = msg_send![this, bounds];
let cursor = state.cursor.lock().unwrap().load();
let _: () = msg_send![this,
addCursorRect:bounds
cursor:cursor
];
}
}
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
//println!("hasMarkedText");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
(marked_text.length() > 0) as i8
}
}
extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
//println!("markedRange");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let length = marked_text.length();
if length > 0 {
NSRange::new(0, length - 1)
} else {
util::EMPTY_RANGE
}
}
}
extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange {
//println!("selectedRange");
util::EMPTY_RANGE
}
extern fn set_marked_text(
this: &mut Object,
_sel: Sel,
string: id,
_selected_range: NSRange,
_replacement_range: NSRange,
) {
//println!("setMarkedText");
unsafe {
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
let _: () = msg_send![(*marked_text_ref), release];
let marked_text = NSMutableAttributedString::alloc(nil);
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
if has_attr {
marked_text.initWithAttributedString(string);
} else {
marked_text.initWithString(string);
};
*marked_text_ref = marked_text;
}
}
extern fn unmark_text(this: &Object, _sel: Sel) {
//println!("unmarkText");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let mutable_string = marked_text.mutableString();
let _: () = msg_send![mutable_string, setString:""];
let input_context: id = msg_send![this, inputContext];
let _: () = msg_send![input_context, discardMarkedText];
}
}
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
//println!("validAttributesForMarkedText");
unsafe { msg_send![class!(NSArray), array] }
}
extern fn attributed_substring_for_proposed_range(
_this: &Object,
_sel: Sel,
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> id {
//println!("attributedSubstringForProposedRange");
nil
}
extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger {
//println!("characterIndexForPoint");
0
}
extern fn first_rect_for_character_range(
this: &Object,
_sel: Sel,
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> NSRect {
//println!("firstRectForCharacterRange");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let (x, y) = state.ime_spot.unwrap_or_else(|| {
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let x = content_rect.origin.x;
let y = util::bottom_left_to_top_left(content_rect);
(x, y)
});
NSRect::new(
NSPoint::new(x as _, y as _),
NSSize::new(0.0, 0.0),
)
}
}
extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) {
//println!("insertText");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let has_attr = msg_send![string, isKindOfClass:class!(NSAttributedString)];
let characters = if has_attr {
// This is a *mut NSAttributedString
msg_send![string, string]
} else {
// This is already a *mut NSString
string
};
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
state.is_key_down = true;
// We don't need this now, but it's here if that changes.
//let event: id = msg_send![class!(NSApp), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter(character),
});
}
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
}
}
extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
//println!("doCommandBySelector");
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character
// happens, i.e. newlines, tabs, and Ctrl+C.
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let shared = if let Some(shared) = state.shared.upgrade() {
shared
} else {
return;
};
let mut events = VecDeque::with_capacity(1);
if command == sel!(insertNewline:) {
// The `else` condition would emit the same character, but I'm keeping this here both...
// 1) as a reminder for how `doCommandBySelector` works
// 2) to make our use of carriage return explicit
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter('\r'),
});
} else {
let raw_characters = state.raw_characters.take();
if let Some(raw_characters) = raw_characters {
for character in raw_characters.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter(character),
});
}
}
};
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
}
fn get_characters(event: id, ignore_modifiers: bool) -> String {
unsafe {
let characters: id = if ignore_modifiers {
msg_send![event, charactersIgnoringModifiers]
} else {
msg_send![event, characters]
};
assert_ne!(characters, nil);
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
string.to_owned()
}
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<events::VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<events::VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters.chars().next().map_or(None, |c| char_to_keycode(c))
}
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
// If we don't get a match, then we fall back to unmodified characters.
let code = get_code(event, false)
.or_else(|| {
get_code(event, true)
});
// We've checked all layout related keys, so fall through to scancode.
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
//
// We're additionally checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
code.or_else(|| {
let scancode = get_scancode(event);
scancode_to_keycode(scancode)
.or_else(|| {
check_function_keys(&get_characters(event, true))
})
})
}
extern fn key_down(this: &Object, _sel: Sel, event: id) {
//println!("keyDown");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_id = WindowId(get_window_id(state.window));
let characters = get_characters(event, false);
state.raw_characters = Some(characters.clone());
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let is_repeat = msg_send![event, isARepeat];
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Pressed,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down{
for character in characters.chars() {
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
} else {
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
// keys to generate twice as many characters.
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
}
}
}
}
extern fn key_up(this: &Object, _sel: Sel, event: id) {
//println!("keyUp");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
state.is_key_down = false;
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Released,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) {
unsafe {
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
let this_ptr = this as *const _ as *mut _;
if first_responder == this_ptr {
let (): _ = msg_send![window, selectNextKeyView:this];
}
}
}
extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) {
unsafe {
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
let this_ptr = this as *const _ as *mut _;
if first_responder == this_ptr {
let (): _ = msg_send![window, selectPreviousKeyView:this];
}
}
}
fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::MouseInput {
device_id: DEVICE_ID,
state: button_state,
button,
modifiers: event_mods(event),
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
}
extern fn mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Left, ElementState::Released);
}
extern fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
}
extern fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Right, ElementState::Released);
}
extern fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
}
extern fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
}
fn mouse_motion(this: &Object, event: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
// We have to do this to have access to the `NSView` trait...
let view: id = this as *const _ as *mut _;
let window_point = event.locationInWindow();
let view_point = view.convertPoint_fromView_(window_point, nil);
let view_rect = NSView::frame(view);
if view_point.x.is_sign_negative()
|| view_point.y.is_sign_negative()
|| view_point.x > view_rect.size.width
|| view_point.y > view_rect.size.height {
// Point is outside of the client area (view)
return;
}
let x = view_point.x as f64;
let y = view_rect.size.height as f64 - view_point.y as f64;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(event),
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn mouse_moved(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn right_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL {
YES
}

File diff suppressed because it is too large Load Diff

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),
@@ -205,31 +259,49 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
}
}
pub fn vkey_left_right(vkey: c_int, scancode: UINT, extended: bool) -> c_int {
match vkey {
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(
scancode,
winuser::MAPVK_VSC_TO_VK_EX,
) as _ },
winuser::VK_CONTROL => if extended {
winuser::VK_RCONTROL
} else {
winuser::VK_LCONTROL
},
winuser::VK_MENU => if extended {
winuser::VK_RMENU
} else {
winuser::VK_LMENU
},
_ => vkey,
}
pub fn handle_extended_keys(vkey: c_int, mut scancode: UINT, extended: bool) -> Option<(c_int, UINT)> {
// Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/
let vkey = match vkey {
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(
scancode,
winuser::MAPVK_VSC_TO_VK_EX,
) as _ },
winuser::VK_CONTROL => if extended {
winuser::VK_RCONTROL
} else {
winuser::VK_LCONTROL
},
winuser::VK_MENU => if extended {
winuser::VK_RMENU
} else {
winuser::VK_LMENU
},
_ => match scancode {
// This is only triggered when using raw input. Without this check, we get two events whenever VK_PAUSE is
// pressed, the first one having scancode 0x1D but vkey VK_PAUSE...
0x1D if vkey == winuser::VK_PAUSE => return None,
// ...and the second having scancode 0x45 but an unmatched vkey!
0x45 => winuser::VK_PAUSE,
// VK_PAUSE and VK_SCROLL have the same scancode when using modifiers, alongside incorrect vkey values.
0x46 => {
if extended {
scancode = 0x45;
winuser::VK_PAUSE
} else {
winuser::VK_SCROLL
}
},
_ => vkey,
},
};
Some((vkey, scancode))
}
pub fn vkeycode_to_element(wparam: WPARAM, lparam: LPARAM) -> (ScanCode, Option<VirtualKeyCode>) {
pub fn process_key_params(wparam: WPARAM, lparam: LPARAM) -> Option<(ScanCode, Option<VirtualKeyCode>)> {
let scancode = ((lparam >> 16) & 0xff) as UINT;
let extended = (lparam & 0x01000000) != 0;
let vkey = vkey_left_right(wparam as _, scancode, extended);
(scancode, vkey_to_winit_vkey(vkey))
handle_extended_keys(wparam as _, scancode, extended)
.map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey)))
}
// This is needed as windows doesn't properly distinguish

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,17 +11,27 @@ 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 {}
// TODO: document what this means
pub type Cursor = *const winapi::ctypes::wchar_t;
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
#[derive(Debug, Clone, Copy)]
pub struct Cursor(pub *const winapi::ctypes::wchar_t);
unsafe impl Send for Cursor {}
unsafe impl Sync for Cursor {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u32);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
impl DeviceId {
pub fn get_persistent_identifier(&self) -> Option<String> {
if self.0 != 0 {
@@ -44,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;
@@ -51,3 +71,4 @@ mod monitor;
mod raw_input;
mod util;
mod window;
mod window_state;

View File

@@ -1,172 +1,173 @@
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::{DWORD, LPARAM, BOOL, TRUE};
use winapi::shared::windef::{HMONITOR, HDC, LPRECT, HWND};
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
use winapi::um::winnt::LONG;
use winapi::um::winuser;
use std::collections::VecDeque;
use std::{mem, ptr};
use std::collections::VecDeque;
use super::{EventsLoop, util};
use dpi::{PhysicalPosition, PhysicalSize};
use platform::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
use platform::platform::window::Window;
/// Win32 implementation of the main `MonitorId` object.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct MonitorId {
/// The system name of the adapter.
adapter_name: [wchar_t; 32],
/// Monitor handle.
hmonitor: HMonitor,
/// The system name of the monitor.
monitor_name: String,
/// True if this is the primary monitor.
primary: bool,
/// The position of the monitor in pixels on the desktop.
///
/// A window that is positioned at these coordinates will overlap the monitor.
position: (i32, i32),
/// The current resolution in pixels on the monitor.
dimensions: (u32, u32),
/// DPI scaling factor.
hidpi_factor: f32,
/// DPI scale factor.
hidpi_factor: f64,
}
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
// For more info see:
// https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396
#[derive(Clone)]
#[derive(Debug, Clone)]
struct HMonitor(HMONITOR);
unsafe impl Send for HMonitor {}
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
}
/// This is a Win32-only function for `MonitorId` that returns the system name of the adapter
/// device.
#[inline]
pub fn get_adapter_name(&self) -> &[wchar_t] {
&self.adapter_name
}
/// A window that is positioned at these coordinates will overlap the monitor.
#[inline]
pub fn get_position(&self) -> (i32, i32) {
self.position
pub fn get_dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}

View File

@@ -1,8 +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::minwindef::{BOOL, DWORD};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winbase::{
FormatMessageW,
@@ -18,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:
@@ -27,9 +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()
}
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)]
@@ -70,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,39 @@ impl WindowBuilder {
}
/// Requests the window to be of specific dimensions.
///
/// Width and height are in pixels.
#[inline]
pub fn with_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
self.window.dimensions = Some((width, height));
pub fn with_dimensions(mut self, size: LogicalSize) -> WindowBuilder {
self.window.dimensions = Some(size);
self
}
/// Sets a minimum dimension size for the window
///
/// Width and height are in pixels.
#[inline]
pub fn with_min_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
self.window.min_dimensions = Some((width, height));
pub fn with_min_dimensions(mut self, min_size: LogicalSize) -> WindowBuilder {
self.window.min_dimensions = Some(min_size);
self
}
/// Sets a maximum dimension size for the window
///
/// Width and height are in pixels.
#[inline]
pub fn with_max_dimensions(mut self, width: u32, height: u32) -> WindowBuilder {
self.window.max_dimensions = Some((width, height));
pub fn with_max_dimensions(mut self, max_size: LogicalSize) -> WindowBuilder {
self.window.max_dimensions = Some(max_size);
self
}
/// Sets whether the window is resizable or not
///
/// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be
/// triggered by DPI scaling, entering fullscreen mode, etc.
///
/// ## Platform-specific
///
/// This only has an effect on desktop platforms.
///
/// Due to a bug in XFCE, this has no effect on Xfwm.
#[inline]
pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder {
self.window.resizable = resizable;
self
}
@@ -92,6 +105,13 @@ impl WindowBuilder {
self
}
/// Sets whether or not the window will always be on top of other windows.
#[inline]
pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder {
self.window.always_on_top = always_on_top;
self
}
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
///
@@ -121,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()
}
}));
@@ -196,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()
}
@@ -205,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()
}
@@ -215,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()
}
@@ -279,70 +269,92 @@ impl Window {
///
/// This is a no-op if the window has already been closed.
#[inline]
pub fn set_inner_size(&self, x: u32, y: u32) {
self.window.set_inner_size(x, y)
pub fn set_inner_size(&self, size: LogicalSize) {
self.window.set_inner_size(size)
}
/// Sets a minimum dimension size for the window.
///
/// Width and height are in pixels.
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_min_dimensions(dimensions)
}
/// Sets a maximum dimension size for the window.
///
/// Width and height are in pixels.
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_max_dimensions(dimensions)
}
/// DEPRECATED. Gets the native platform specific display for this window.
/// This is typically only required when integrating with
/// other libraries that need this information.
#[deprecated]
/// Sets whether the window is resizable or not.
///
/// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be
/// triggered by DPI scaling, entering fullscreen mode, etc.
///
/// ## Platform-specific
///
/// This only has an effect on desktop platforms.
///
/// Due to a bug in XFCE, this has no effect on Xfwm.
#[inline]
pub unsafe fn platform_display(&self) -> *mut libc::c_void {
self.window.platform_display()
pub fn set_resizable(&self, resizable: bool) {
self.window.set_resizable(resizable)
}
/// DEPRECATED. Gets the native platform specific window handle. This is
/// typically only required when integrating with other libraries
/// that need this information.
#[deprecated]
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
/// the most robust way to track the DPI you need to use to draw.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
#[inline]
pub unsafe fn platform_window(&self) -> *mut libc::c_void {
self.window.platform_window()
pub fn get_hidpi_factor(&self) -> f64 {
self.window.get_hidpi_factor()
}
/// Modifies the mouse cursor of the window.
/// Has no effect on Android.
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
self.window.set_cursor(cursor);
}
/// Returns the ratio between the backing framebuffer resolution and the
/// window size in screen pixels. This is typically one for a normal display
/// and two for a retina display.
#[inline]
pub fn hidpi_factor(&self) -> f32 {
self.window.hidpi_factor()
}
/// Changes the position of the cursor in window coordinates.
#[inline]
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
self.window.set_cursor_position(x, y)
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
self.window.set_cursor_position(position)
}
/// Sets how winit handles the cursor. See the documentation of `CursorState` for details.
/// Grabs the cursor, preventing it from leaving the window.
///
/// Has no effect on Android.
/// ## Platform-specific
///
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
self.window.set_cursor_state(state)
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
self.window.grab_cursor(grab)
}
/// Hides the cursor, making it invisible but still usable.
///
/// ## Platform-specific
///
/// On Windows and X11, the cursor is only hidden within the confines of the window.
///
/// On macOS, the cursor is hidden as long as the window has input focus, even if the cursor is outside of the
/// window.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn hide_cursor(&self, hide: bool) {
self.window.hide_cursor(hide)
}
/// Sets the window to maximized or back
@@ -357,12 +369,24 @@ impl Window {
self.window.set_fullscreen(monitor)
}
/// Gets the window's current fullscreen state.
#[inline]
pub fn get_fullscreen(&self) -> Option<MonitorId> {
self.window.get_fullscreen()
}
/// Turn window decorations on or off.
#[inline]
pub fn set_decorations(&self, decorations: bool) {
self.window.set_decorations(decorations)
}
/// Change whether or not the window will always be on top of other windows.
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
self.window.set_always_on_top(always_on_top)
}
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
///
@@ -376,20 +400,51 @@ impl Window {
self.window.set_window_icon(window_icon)
}
/// Sets location of IME candidate box in client area coordinates relative to the top left.
#[inline]
pub fn set_ime_spot(&self, position: LogicalPosition) {
self.window.set_ime_spot(position)
}
/// Returns the monitor on which the window currently resides
#[inline]
pub fn get_current_monitor(&self) -> MonitorId {
self.window.get_current_monitor()
}
/// Returns the list of all the monitors available on the system.
///
/// This is the same as `EventsLoop::get_available_monitors`, and is provided for convenience.
#[inline]
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
let data = self.window.get_available_monitors();
AvailableMonitorsIter { data: data.into_iter() }
}
/// Returns the primary monitor of the system.
///
/// This is the same as `EventsLoop::get_primary_monitor`, and is provided for convenience.
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId { inner: self.window.get_primary_monitor() }
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.window.id())
}
}
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
self.window.raw_window_handle()
}
}
/// An iterator for the list of available monitors.
// Implementation note: we retrieve the list once, then serve each element by one by one.
// This may change in the future.
#[derive(Debug)]
pub struct AvailableMonitorsIter {
pub(crate) data: VecDequeIter<platform::MonitorId>,
}
@@ -409,7 +464,7 @@ impl Iterator for AvailableMonitorsIter {
}
/// Identifier for a monitor.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct MonitorId {
pub(crate) inner: platform::MonitorId
}
@@ -423,22 +478,29 @@ impl MonitorId {
self.inner.get_name()
}
/// Returns the number of pixels currently displayed on the monitor.
/// Returns the monitor's resolution.
#[inline]
pub fn get_dimensions(&self) -> (u32, u32) {
pub fn get_dimensions(&self) -> PhysicalSize {
self.inner.get_dimensions()
}
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
#[inline]
pub fn get_position(&self) -> (i32, i32) {
pub fn get_position(&self) -> PhysicalPosition {
self.inner.get_position()
}
/// Returns the ratio between the monitor's physical pixels and logical pixels.
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// ## Platform-specific
///
/// - **X11:** This respects Xft.dpi XResource, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
#[inline]
pub fn get_hidpi_factor(&self) -> f32 {
pub fn get_hidpi_factor(&self) -> f64 {
self.inner.get_hidpi_factor()
}
}

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