Compare commits

..

149 Commits

Author SHA1 Message Date
Artúr Kovács
316d8306ab Merge branch 'master' of https://github.com/rust-windowing/winit 2021-05-15 19:17:41 +02:00
Artúr Kovács
91591c4e94 Release 0.25.0 (#1939) 2021-05-15 19:17:08 +02:00
Artúr Kovács
3f155167ba Release 0.25.0 2021-05-15 18:52:47 +02:00
Luis Wirth
078b9719cc implement mint conversions (#1930)
Implement conversions for [mint](https://docs.rs/mint) (math interoperability standard types).

- `impl From<mint::Point2> for {Physical, Logical}Position`
- `impl From<{Physical, Logical}Position> for mint::Point2`

- `impl From<mint::Vector2> for {Physical, Logical}Size`
- `impl From<{Physical, Logical}Size> for mint::Vector2`
2021-05-09 00:56:52 +02:00
Markus Røyset
41d9826ee9 Fix incorrect changelog entry for #1524 (#1916)
* Fix incorrect changelog entry for #1524

* Fix incorrect changelog entry for #1524
2021-05-04 11:00:11 -07:00
Artúr Kovács
0152508a39 Allow preventing the creation of the default menu (#1923)
* Allow preventing the creation of the default menu

* Use more grammar friendly naming

* Update the changelog
2021-04-30 13:34:50 +02:00
Artúr Kovács
cdeb1c3828 Require setting the activation policy on the event loop (#1922)
* Require setting the activation policy on the event loop

* Run cargo fmt

* Update changelog

* Fixes and tweaks from review

* Correct comment in app_state.rs

Co-authored-by: Mads Marquart <mads@marquart.dk>
2021-04-30 11:31:28 +02:00
z4122
0986fae066 Add accept_first_mouse for macOS (#1882)
* feat: add accept_first_mouse for macOS

* Update the changelog

Co-authored-by: Artur Kovacs <kovacs.artur.barnabas@gmail.com>
2021-04-30 11:30:09 +02:00
Mads Marquart
277515636d MacOS: Only activate after the application has finished launching (#1903)
* MacOS: Only activate after the application has finished launching

This fixes the main menu not responding until you refocus, at least from what I can tell - though we might have to do something similar to https://github.com/linebender/druid/pull/994 to fix it fully?

* MacOS: Remove activation hack

* Stop unnecessarily calling `makeKeyWindow` on initially hidden windows

You can't make hidden windows the key window

* Add new, simpler activation hack

For activating multiple windows created before the application finished launching
2021-04-29 19:49:17 +02:00
Mads Marquart
45aacd8407 Use initialFirstResponder instead of makeFirstResponder (#1920)
As recommended by the documentation: https://developer.apple.com/documentation/appkit/nswindow/1419366-makefirstresponder?language=objc
2021-04-29 12:52:41 +02:00
Casper Rogild Storm
e8cdf8b092 Add MacOS menu (#1583)
* feat: added MacOS menu

* fix: ran fmt

* extracted function into variable

* idiomatic formatting

* Set the default menu only during app startup

* Don't set the activation policy in the menu init

Co-authored-by: Artur Kovacs <kovacs.artur.barnabas@gmail.com>
2021-04-24 16:56:46 +02:00
Artúr Kovács
1c4d6e7613 Correct the false documentation about macOS dpi (#1905) 2021-04-13 21:31:41 +02:00
LoganDark
04b4e48265 Derive Default, Hash, and Eq for some dpi types (#1833)
* Derive more things

* Changelog entry
2021-04-12 23:12:39 +02:00
Rodrigodd
dabcb1834d On Windows, allow the creation of popup window (#1895)
Add with_owner_window to WindowBuilderExtWindows.
Add set_enable to WindowExtWindows.
2021-04-10 15:47:19 +02:00
Mads Marquart
629cd86c7c Stop calling NSApplication.finishLaunching on window creation (#1902)
This is called internally by NSApplication.run, and is not something we should call - I couldn't find the reasoning behind this being there in the first place, git blame reveals c38110cac from 2014, so probably a piece of legacy code.

Removing this fixes creating new windows when you have assigned a main menu to the application.
2021-04-07 22:24:49 +02:00
Xiaopeng Li
ba704c4eb4 Mac: Redraw immediately to prevent shaking on window resize (#1901)
* Mac: Redraw immediately to prevent shaking on window resize

* Update CHANGELOG.md

* Update CHANGELOG.md

Co-authored-by: 李小鹏 <lixiaopeng.jetspark@bytedance.com>
Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-04-06 09:22:38 +02:00
Aleksandr Ovchinnikov
0487876826 On macOS, wake up the event loop immediately when a redraw is requested. (#1812)
We allow to have RunLoop running only on the main thread. Which means if
we call Window::request_redraw() from other the thread then we have to
wait until some other event arrives on the main thread. That situation
is even worse when we have ControlFlow set to the `Wait` mode then user
will not ever render anything.
2021-04-06 09:19:25 +02:00
Markus Røyset
ca9c05368e Fix CI warnings (#1898)
* Fix CI warnings

* Use the panic! macro rather than format! + panic_any
2021-03-30 21:27:32 +02:00
Michal Srb
0d634a0061 Add WindowBuilder::with_outer_position (#1866) 2021-03-25 19:18:51 +01:00
Norbert Nemec
86748fbc68 Fix communication of fractional RI_MOUSE_WHEEL events (Windows) (#1877) 2021-03-11 22:08:29 +01:00
Artúr Kovács
599477d754 Only try publishing when a version tag is pushed (#1876) 2021-03-10 14:10:35 -08:00
daxpedda
889258f538 Upgrade mio to 0.7 (#1875)
* Upgrade `mio` to 0.7
Replaced `mio-extras` with `mio-misc`.

* Possible improvement

* Remove leftover

* Wrong rebase

* Fix typo
2021-03-09 09:50:15 -07:00
Artúr Kovács
ffe2143d14 Fix for closure-captured values not being dropped on panic (#1853)
* Fix for #1850

* Update changelog

* Fix for compilation warnings

* Apply suggestions from code review

Co-authored-by: Markus Røyset <maroider@protonmail.com>

* Improve code quality

* Change Arc<Mutex> to Rc<RefCell>

* Panicking in the user callback is now well defined

* Address feedback

* Fix nightly warning

* The panic info is now not a global.

* Apply suggestions from code review

Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>

* Address feedback

Co-authored-by: Markus Røyset <maroider@protonmail.com>
Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>
2021-03-08 19:56:39 +01:00
daxpedda
98470393d1 Add dragging window with cursor feature (#1840)
* X11 implementation.

* Introduce example.

* Wayland implementation.

* Windows implementation.

* Improve Wayland seat passing.

* MacOS implementation.

* Correct windows implementation per specification.

* Update dependency smithay-client-toolkit from branch to master.

* Fixed blocking thread in windows implementation.

* Add multi-window example.

* Move Wayland to a different PR.

* Fix CHANGELOG.

* Improve example.

Co-authored-by: Markus Røyset <maroider@protonmail.com>

* Rename `set_drag_window` to `begin_drag`.

* Improve example.

* Fix CHANGELOG.

* Fix CHANGELOG.

Co-authored-by: Markus Røyset <maroider@protonmail.com>

* Rename to `drag_window`.

* Fix typo.

* Re-introduce Wayland implementation.

* Fixing Wayland build.

* Fixing Wayland build.

* Move SCTK to 0.12.3.

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-03-07 10:43:23 +01:00
Artúr Kovács
4192d04a53 Fix seg-fault when using without a window (#1874)
* Fix seg-fault when using without a window #1869

* Update changelog
2021-03-06 11:17:23 +01:00
leafjolt
3571dcd68c Update window.rs (#1871) 2021-02-27 21:25:26 +01:00
mmacedo
952edcb804 Android: Add KeyEvent handling (#1839) 2021-02-23 22:35:38 +01:00
Axel Cocat
10a94c0794 Fix Windows' try_theme returning Theme::Dark when new theme is light (#1861) 2021-02-20 00:06:12 +01:00
Björn Steinbrink
dd32ace9ab Restore the ability to have fully transparent windows on Windows (#1815)
* Restore the ability to have fully transparent windows on Windows

Besides its original purpose, commit 6343059b "Fix Windows transparency
behavior to support fully-opaque regions (#1621)" also included some
changes considered cleanups, one of them was:

* Remove the `CreateRectRgn` call, since we want the entire window's region to
  have blur behind it, and `DwnEnableBlurBehindWindow` does that by default.

But the original code actually disabled the blur effect for the whole
window by creating an empty region for it, because that allows for the
window to be truely fully transparent. With the blur effect in place,
the areas meant to be transparent either blur the things behind it
(until Windows 8) or are darkened (since Windows 8). This also means
that on Windows 8 and newer, the resulting colors are darker than
intended in translucent areas when the blur effect is enabled.

This restores the behaviour from winit <0.24 and fixes #1814.

Arguably, one might want to expose the ability to control the blur
region, but that is outside the scope of this commit.

* Remove useless WS_EX_LAYERED from transparent windows on Windows

`WS_EX_LAYERED` is not supposed to be used in combination with
`CS_OWNDC`. In winit, as it is currently used, `WS_EX_LAYERED` actually
has no effect at all. The only relevant call is to
`SetLayeredWindowAttributes`, which is required to make the window
visible at all with `WS_EX_LAYERED` set, but is called with full
opacity, i.e. there's no transparency involved at all.

The actual transparency is already achieved by using
`DwmEnableBlurBehindWindow`, so `WS_EX_LAYERED` and the call to
`SetLayeredWindowAttributes` can both be removed.
2021-02-17 13:50:24 +01:00
Will Crichton
7e0c6ee097 Add DeviceEvent::MouseMove on web platform to support pointer lock (#1827)
* Add DeviceEvent::MouseMove on web platform to support pointer lock

* Update changelog

* Add support for stdweb too

* Add mouse_delta to stdweb

* Remove reference to pointer lock
2021-02-16 17:50:46 -05:00
Ssaely
b1be34c6a0 fix cursor blinking when clicking decorations bar on Windows (#1852) 2021-02-06 21:10:36 +01:00
Imberflur
b9307a9967 Change linking of CGDisplayCreateUUIDFromDisplayID on macos (#1626)
* Link CGDisplayCreateUUIDFromDisplayID through ColorSync instead of CoreGraphics

* Conditionally link through ColorSync only if WINIT_LINK_COLORSYNC is set
to true

* Document new macos env var in README
2021-02-05 08:58:55 +01:00
Mads Marquart
b1d353180b Add ability to assign a menu when creating a window on Windows (#1842) 2021-02-04 22:26:33 +01:00
Marijn Suijten
bd99eb1347 Android: Bump ndk/ndk-glue to 0.3 and use constants for event ident (#1847)
Following the changes in [1] this bumps ndk and ndk-glue to 0.3 and uses
the new constants. The minor version has been bumped to prevent
applications from running an older winit (without #1826) with a newer
ndk/ndk-glue that does not pass this `ident` through the `data` pointer
anymore.

[1]: https://github.com/rust-windowing/android-ndk-rs/pull/112
2021-01-30 19:43:26 +01:00
Mads Marquart
f79c01b0cf Fix HINSTANCE returned by raw_window_handle on 64 bit Windows (#1841) 2021-01-28 18:51:49 +01:00
Simas Toleikis
3f1e09ec0e Add Window::is_maximized method (#1804) 2021-01-27 19:01:17 +01:00
Markus Røyset
05125029c6 On Windows, fix deadlock caused by mouse capture (#1830)
The issue was caused by calling SetCapture on a window which already
had the capture. The WM_CAPTURECHANGED handler assumed that it would
only run if the capture was lost, but that wasn't the case. This made
the handler to try to lock the window state mutex while it was already
locked.

The issue was introduced in #1797 (932cbe4).
2021-01-19 17:41:02 +01:00
Marijn Suijten
05fe983757 android: Use event identifier instead of userdata pointer (#1826)
ndk-glue currently sets both the `ident` field and user-data pointer to
`0` or `1` for the event pipe and input queue respectively, to tell
these sources apart. While it works to reinterpret this `data` pointer
as integer identifier it shouldn't be abused for that, in particular
when one may wish to provide extra information with an event in the
future; then the `data` field is used as pointer (or abused as abstract
value) for that.
2021-01-13 23:02:55 +01:00
alula
d1a7749df5 Android: Do not mark unhandled events as handled. (#1820) 2021-01-12 08:25:56 +01:00
Markus Røyset
9d63fc7ca0 On Windows, set the cursor icon when the cursor first enters a window (#1807) 2021-01-05 17:39:13 +01:00
Markus Røyset
38fccebe1f On Windows, change the default window size (#1805) 2020-12-20 17:59:46 +01:00
Markus Røyset
c05952b813 On Windows, improve handling of window destruction (#1798) 2020-12-20 12:54:42 +01:00
Samuel
932cbe40bf On Windows, fix bug causing mouse capture to not be released. (#1797) 2020-12-15 07:31:13 +01:00
relrelb
39573d65d0 Windows: Preserve minimized/maximized state in fullscreen (#1784) 2020-12-13 19:06:53 +01:00
Kirill Chibisov
6db308f1e9 Release 0.24.0 2020-12-10 19:12:46 +03:00
Viktor Zoutman
6f70fd90b9 Windows: Changed thread_event_target_callback's WM_DESTROY to WM_NCDESTROY (#1780) 2020-12-10 12:09:08 +01:00
moko256
db038d943c On Windows, implement 'Window::set_ime_position' with IMM API 2020-12-09 23:16:59 +03:00
Kirill Chibisov
c5620efc9c On Wayland, don't drop extra mouse buttons
This commit forwards "unknown" Wayland mouse buttons downstream via
'MouseButton::Other'. Possible values for those could be found in
<linux/input-event-codes.h>.

Also, since Wayland just forwards buttons from the kernel, which are
'u16', we must adjust 'MouseButton::Other' to take 'u16' instead of
'u8'.
2020-12-09 23:11:25 +03:00
Marnix Kuijs
8fb7aa5cef Android: Improved multi-touch (#1783)
* Improved multi-touch

* Update feature matrix

* Generate cancelled events for all pointers

* Changed back features matrix layout

* Reduced code duplication

* Updated changelog

* Revert changelog update
2020-12-02 12:13:42 -08:00
Viktor Zoutman
6ddee9a8ac Ability to force a theme on Windows (#1666) 2020-11-30 19:04:26 +01:00
Max de Danschutter
5700359a61 Android: support multi-touch (#1776) 2020-11-28 17:41:11 +01:00
Max de Danschutter
0861a353d6 Add 'request_user_attention' to Window
This commit introduces a cross platform way to request a user attention
to the window via a 'request_user_attention' method on a Window struct.
This method is inspired by macOS's 'request_user_attention' method and
thus reuses its signature and semantics to some extent.
2020-11-27 05:03:08 +03:00
Philippe Renon
f79efec7ef Fix deprecation warning in the window icon example 2020-11-26 00:20:35 +00:00
Viktor Zoutman
77d5d20391 Windows: Delayed Message Boxes Fix. (#1769) 2020-11-24 23:05:29 +01:00
Kirill Chibisov
165e51d850 On Wayland, increase default font size in CSD
This commit increased default font size from 11 to 17 making it
identical to the one SCTK is using under the hood, since it's more
readable.
2020-11-22 01:53:56 +03:00
Max de Danschutter
1c38f113b3 Remove println call from Android's eventloop 2020-11-19 17:56:24 +00:00
msiglreith
66859607a3 Rename desktop eventloop extensions to run_return extension (#1738) 2020-11-12 20:49:44 +01:00
Wladimir J. van der Laan
edf396b1a4 On Wayland, add missing mappings for numpad arrows
The mappings for 'keysyms::XKB_KEY_KP_{Up,Down,Left,Rigt}' were missing making the arrow keys on the numpad not sending proper 'VirtualKeyCode's.
2020-11-11 00:55:29 +03:00
Murarth
cbeb51b436 X11: Fix multiple RedrawRequested events per event loop iteration (#1758)
* X11: Fix multiple RedrawRequested per event loop iteration

* Prevent infinite loop
2020-11-07 11:46:37 -07:00
Murarth
45e4fd6ec1 X11: Fix request_redraw not waking the event loop (#1756) 2020-11-05 16:42:03 -07:00
Mikko Lehtonen
3a077ff211 macos: Fix compile on aarch64 2020-11-02 21:06:00 +00:00
Brad
be850e483a Document Android raw_window_handle requirements (#1749)
* Add docs describing raw_winow_handle on android

* Run cargo fmt

* Change raw_window_handle panic to give more info
2020-10-29 14:23:46 -07:00
Simon Hausmann
33fb62bb25 Fix WindowEvent::ReceivedCharacter on web (#1747)
* Fix WindowEvent::ReceivedCharacter on web

The event was never sent to the application because of the unconditional
preventDefault() call on keydown.

Fixes #1741

* Don't scroll when pressing space on a focused canvas

After reaching keypress, we should prevent further propagation.

Relates to #1741
2020-10-29 17:13:21 -04:00
qthree
66c117e599 [Windows] Fix use after free during window destruction (#1746) 2020-10-23 19:04:18 +02:00
Waridley
8aa1be8336 On Unix, fix cross-compiling to wasm32
Aborting compilation by using 'compile_error!' macro in build.rs was resulting in failing cross
compilation, thus this commit removes build.rs. The compilation will now be aborted on existing 'compile_error!' macros in corresponding platform sources.
2020-10-22 07:14:33 +03:00
Kirill Chibisov
037d4121a1 On Wayland, fix 'with_min_inner_size' disabling resize
Building window with 'set_min_inner_size' was setting 'max_inner_size'
under the hood, thus completely disabling window resize, since
the window isn't resizeable on Wayland when its minimum size
is equal to its maximum size.
2020-10-20 03:30:19 +03:00
Vickles
fbd3918d3a Add prefix byte for extended scancodes on Windows (#1679) 2020-10-19 16:35:01 +02:00
Alex Butler
7c543a43a9 Windows: Fix alt tab bordless fullscreen (#1740) 2020-10-19 16:15:23 +02:00
Kirill Chibisov
ee3996cac6 Feature gate more dependencies
Wayland backend is self contained and only requires
smithay-client-toolkit to work, thus feature gate the
rest of the deps that are only for X11.
2020-10-18 02:05:08 +03:00
Murarth
96809ac659 Fix warnings (#1742) 2020-10-15 11:33:06 -07:00
Jim Porter
6343059bc0 Fix Windows transparency behavior to support fully-opaque regions (#1621)
This patch removes an unneeded workaround for transparent windows on the
Windows platform. In addition, it simplifies a couple of related API calls:

* Remove the `CreateRectRgn` call, since we want the entire window's region to
  have blur behind it, and `DwnEnableBlurBehindWindow` does that by default.
* Remove the `color_key` for `SetLayeredWindowAttributes`, since it's not used
  (we're not passing `winuser::LWA_COLORKEY` to the flags).
2020-10-14 12:23:34 +02:00
Simon Hausmann
5a78fe33e8 Fix failing assertion on start-up with Safari (#1736)
The initial media query that's used to watch for device pixel ratio
changes should match. Unfortunately it doesn't with Safari and the
corresponding assertion fails. This is because resolution is not a
supported support property for queries. As a workaround, this patch
extends the query to optionally match for the webkit specific DPR
property directly.

This fixes #1734
2020-10-10 00:31:51 -04:00
Max de Danschutter
676fb947f2 Added WindowHasFocus and WindowLostFocus events to Android (#1733)
* Added WindowHasFocus and WindowLostFocus events to Android

* Update changelog
2020-10-08 19:44:41 +02:00
Kirill Chibisov
d18afb4a50 Release 0.23.0 2020-10-02 18:05:07 +03:00
Nathan Lilienthal
fc336a76bf Fix incorrect modifiers state on startup
Fixes #1563.
2020-10-02 05:07:09 +03:00
Kirill Chibisov
b9f3d333e4 Update SCTK to 0.12
SCTK was a bit behind on Wayland protocol version, and so this release
brings it up to date. It also cleans up 'Environment'.
2020-10-01 01:19:15 +03:00
Kirill Chibisov
3d85af04be Update SCTK to 0.11.0
* Update SCTK to 0.11.0

Updates smithay-client-toolkit to 0.11.0. The major highlight
of that updated, is update of wayland-rs to 0.27.0. Switching
to wayland-cursor, instead of using libwayland-cursor. It
also fixes the following bugs:

  - Disabled repeat rate not being handled.
  - Decoration buttons not working after tty switch.
  - Scaling not being applied on output reenable.
  - Crash when `XCURSOR_SIZE` is `0`.
  - Pointer getting created in some cases without pointer capability.
  - On kwin, fix space between window and decorations on startup.
  - Incorrect size event when entering fullscreen when using
    client side decorations.
  - Client side decorations not being hided properly in fullscreen.
  - Size tracking between fullscreen/tiled state changes.
  - Repeat rate triggering multiple times from slow callback handler.
  - Resizable attribute not being applied properly on startup.
  - Not working IME

Besides those fixes it also adds a bunch of missing virtual key codes,
implements proper cursor grabbing, adds right click on decorations
to open application menu, disabled maximize button for non-resizeable
window, and fall back for cursor icon to similar ones, if the requested
is missing.

It also adds new methods to a `Theme` trait, such as:
  - `title_font(&self) -> Option<(String, f32)>` - The font for a title.
  - `title_color(&self, window_active: bool) -> [u8; 4]` - The color of
  the text in the title.

Fixes #1680.
Fixes #1678.
Fixes #1676.
Fixes #1646.
Fixes #1614.
Fixes #1601.
Fixes #1533.
Fixes #1509.
Fixes #952.
Fixes #947.
2020-09-29 00:11:43 +03:00
Logan Magee
471b1e003a Bump console_log from 0.1 to 0.2 2020-09-27 17:47:47 +03:00
alvinhochun
be2e17d605 Update readme info regarding WebAssembly and web target (#1726) 2020-09-24 10:52:11 -07:00
alvinhochun
9d6b9797c0 Clarify ControlFlow::Poll doc for web (#1725)
Make it clear that ControlFlow::Poll causing events to be sent on
`requestAnimationFrame` is an implementation detail which should not be
relied on.
2020-09-24 10:30:26 -04:00
Wang Kai
3cd6a18048 Fix WindowEvent::Moved ignoring DPI on macOS 2020-09-23 10:54:53 +00:00
Michael Hills
c9558c5f0e Fix view frame in portrait when starting iOS app in landscape (#1703)
Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>
2020-09-22 11:21:07 -07:00
Kirill Chibisov
71e3d25422 Rework 'Fullscreen::Borderless' enum variant
This changes 'Fullscreen::Borderless' enum variant from
'Fullscreen::Borderless(MonitorHandle)' to
'Fullscreen::Borderless(Option<MonitorHandle>)'. Providing
'None' to it will result in picking the current monitor.
2020-09-22 04:54:47 +03:00
alvinhochun
644dc13e00 web: Emit WindowEvent::Resized on Window::set_inner_size (#1717)
* web: Allow event to be queued from inside the EventLoop handler

The Runner is behind a RefCell, which is mutably borrowed when the event
handler is being called. To queue events, `send_events` needs to check
`is_closed()` and the `is_busy` flag, but it cannot be done since the
RefCell is already locked. This commit changes the conditions to work
without needing a successful borrow.

* web: Emit WindowEvent::Resized on Window::set_inner_size

* Update changelog
2020-09-21 18:19:00 -04:00
alvinhochun
47e7aa4209 Add cleanup code to web backend, mostly web-sys (#1715)
* web-sys: Impl. event listeners removal for canvas

* web-sys: Impl. media query listeners cleanup

* web: Emit WindowEvent::Destroyed after Window is dropped

* web-sys: Fix unload event closure being dropped early

* web: Impl. cleanup on ControlFlow::Exit

- Drops the Runner, which causes the event handler closure to be
  dropped.
- (web-sys only:) Remove event listeners from DOM.

* web: Do not remove canvas from DOM when dropping Window

The canvas was inserted by the user, so it should be up to the user
whether the canvas should be removed.

* Update changelog
2020-09-20 18:42:07 -04:00
Ryan G
1c97a310b1 Deprecate the stdweb backend (#1712)
* Deprecate the stdweb backend

* Add a changelog entry

* Fmt

* Move the deprecation notice
2020-09-20 18:41:44 -04:00
Kirill Chibisov
d612a1b5a1 Prefix numpad virtual key codes with Numpad
This commit is a follow up to a2db4c0a32
to make it clear which virtual key codes are located on numeric pad.

It also adds Asterisk and Plus virtual key codes.
2020-09-20 12:58:24 +03:00
msiglreith
386ead15a3 Android: bump ndk versions (#1708)
* Bump ndk versions

* Update README for new ndk proc attribute

* android: add CHANGELOG entry to ndk vesion bump
2020-09-18 11:14:56 -07:00
alvinhochun
83c95e774d Explicitly require simple_logger 1.9 for examples 2020-09-17 16:58:53 +03:00
Logan Magee
e4754999b7 Replace deprecated simple_logger initialization 2020-09-10 01:58:30 +00:00
Logan Magee
c66489dbb1 Bump parking_lot to 0.11
Fixes #1657.
2020-09-09 23:56:48 +03:00
Josh Groves
21f9aefc7e Update macOS dependencies
Fixes #1658.
2020-09-07 23:43:51 +03:00
Kirill Chibisov
d103dc2631 Make 'primary_monitor' return 'Option<MonitorHandle>'
Certain platforms like Wayland don't have a concept of
primary Monitor in particular. To indicate that
'primary_monitor' will return 'None' as well as in cases
where the primary monitor can't be detected.

Fixes #1683.
2020-09-07 20:20:47 +03:00
Kirill Chibisov
cac627ed05 Make 'current_monitor' return 'Option<MonitorHandle>'
On certain platforms window couldn't be on any monitor
resulting in failures of 'current_monitor' function.

Such issue was happening on Wayland, since the window
isn't on any monitor, unless the user has drawn something into it.

Returning 'Option<MonitorHandle>' will give an ability to
handle such situations gracefully by properly indicating that
there's no current monitor.

Fixes #793.
2020-09-07 20:09:24 +03:00
Michael Kirk
e2cf2a5754 Fix inverted horizontal scroll on macOS
In winit the swipe from left to right on touchpad should
generate positive horizontal delta change, however on
macOS it was the other way around without
natural scrolling.

This commit inverses the horizontal scrolling delta
in 'MouseScrollDelta' events to match other platforms.

Fixes #1695.
2020-09-06 17:41:19 +03:00
alvinhochun
658a9a4ea8 Handle scale factor change on web-sys backend (#1690)
* Change web backend `event_handler` to without  'static lifetime

* Refactor web runner and fix ControlFlow::Exit not being sticky

* Impl. scaling change event for web-sys backend

* Improve `dpi` docs regarding the web backend

* Add changes to changelog

* Update features.md
2020-08-30 09:15:44 -04:00
Christian Duerr
a2db4c0a32 Unify Minus/Subtract virtual keycodes
On all platforms other than Linux/X11, the Subtract key was uniformly
used only for the Numpad. To make this cross-platform compatible, the
`-` key will now map to `Minus` on X11 instead of `Subtract`.

Since people have been confused about the difference between `Minus` and
`Subtract` in the past, the `Subtract` key has also been renamed to
`NumpadSubtract`. This is a breaking change that might be annoying to
downstream since there's no direct improvement, but it should help new
users in the future. Alternatively this could just be documented, rather
than explicitly mentioning the Numpad in the name.
2020-08-29 16:38:41 +03:00
alvinhochun
02a34a167a Impl. mouse capturing on web target (#1672)
* Impl. mouse capturing for web-sys with PointerEvent

* Impl. mouse capturing for web-sys with MouseEvent by manual tracking

* Reorganize web-sys backend mouse and pointer handling code

* Impl. mouse capturing for stdweb with PointerEvent

* Add mouse capturing for web target to changelog
2020-08-29 09:34:33 -04:00
alvinhochun
bea60930b6 Use send_events instead of send_event in web backend (#1681) 2020-08-26 12:11:27 -04:00
alvinhochun
0f7c82d38f Send CursorMove before mouse press event and note that touch is unimplemented on web target (#1668)
* Change to send CursorMove before mouse press event on web target

* Fix feature matrix to indicate touch being unimplemented on web
2020-08-21 20:23:08 -04:00
alvinhochun
6ba583d198 Fix vertical scroll being inverted on web targets (#1665) 2020-08-20 21:09:04 -04:00
Christian Duerr
89d4c06dec Fix crash on NetBSD
The `_lwp_self` function cannot be used to reliably determine the main
thread, see
https://github.com/alacritty/alacritty/issues/2631#issuecomment-676723289.

It might always be equal to the PID, but it's certainly not always 1
when the thread is the main thread.

However, Rust's built in `Thread::id` and `Thread::name` function will
always return `ThreadId(1)` and `Some("main")`. Since converting the
thread's ID to a number is not supported on stable Rust, checking that
the thread is labeled `Some("main")` seems like the most reliable
option. It should also be a good fallback in general.
2020-08-20 21:12:01 +03:00
Michael Kirk
9c72cc2a98 Fix HiDPI vs. set_cursor_icon for web (#1652)
PhysicalSize is recorded as canvas.size, whereas LogicalSize is stored
as canvas.style.size.

The previous cursor behavior on stdweb clobbered all style - thus losing
the LogicalSize.
2020-08-17 19:48:29 -04:00
simlay
412bd94ea4 Renamed NSString to NSStringRust to support Debug View Heirarchy in Xcode (#1631)
* Renamed NSString to NSStringRust to support Debug View Heirarchy

* Updated from comments

* Update CHANGELOG.md
2020-08-14 12:26:16 -07:00
TakWolf
514ab043f2 [macos] add NSWindow.hasShadow support (#1637)
* [macos] add NSWindow.hasShadow

* change log

* cargo fmt

* Update CHANGELOG.md

* Update src/platform_impl/macos/window.rs

* Update src/platform/macos.rs

* set_has_shadow() with cuter format

* adjust code

* cargo fmt

* changelog
2020-08-13 11:10:34 -07:00
msiglreith
68100102be android: fix event loop polling (#1638)
* android: poll looper for ControlFlow::Poll and don't exit when no new event received

* Add Android ControlFlow:Poll fix to CHANGELOG
2020-08-12 11:56:28 -07:00
josh65536
05fdcb5b27 Web: Use mouse events instead of pointer events if the latter isn't supported (#1630)
* Fixed Safari not getting mouse events

* Edited changelog

* Addressed compiler warnings

Co-authored-by: Ryan G <ryanisaacg@users.noreply.github.com>
2020-08-04 21:39:09 -04:00
Christian Duerr
7a49c88200 Fix with_fullscreen signature 2020-08-02 02:10:33 +03:00
Christian Duerr
40232d48ba Use PhysicalPosition in PixelDelta event
This removes the `LogicalPosition` from the `PixelDelta`, since all
other APIs have been switched to use `PhysicalPosition` instead.

Fixes #1406.
2020-07-27 01:16:21 +03:00
Christian Duerr
55dff53a98 Fix Window platform support documentation
This resolves various problems with the documentation about platform
support on the Window struct.

It also completely removes pointless runtime errors in favor of
consistent no-ops on all platforms that do not support a certain
features.
2020-07-27 00:13:17 +03:00
Matt Kraai
6919c2fb2d Fix misspellings in comments (#1618) 2020-07-09 08:08:26 -07:00
Xavier L'Heureux
3d5d05eac7 Move available_monitors and primary_monitor to EventLoopWindowTarget (#1616) 2020-07-04 15:46:41 -04:00
Osspial
dd866a74a6 On Windows, fix bug where we'd try to emit MainEventsCleared events during nested win32 event loops (#1615) 2020-07-02 16:53:47 -04:00
Jurgis
b1e22aa559 Make drag and drop optional (fixes OleInitialize failure #1255) (#1524)
Co-authored-by: Osspial <osspial@gmail.com>
2020-06-28 18:17:27 -04:00
Andrey Lesnikov
2191e9ecd5 macOS: Support click-dragging out of a window (#1607)
* macos: Support click-dragging out of a window

* macos: Use NSEvent::pressedMouseButtons for click-dragging

* macos: Click-dragging: Move pressedMouseButtons inside
2020-06-19 17:42:19 -07:00
Viktor Zoutman
bf62103417 Android run return (#1604)
* Initial Draft

* Minor clean up

* cargo fmt

* Removed accidental change

* Update CHANGELOG.md

Co-authored-by: VZout <=>
2020-06-17 15:55:52 +02:00
Murarth
4b1b314ce2 Test x11 and wayland features on CI 2020-06-15 22:49:09 +03:00
Olivier Goffart
c1ea0dde92 On Unix, add option to pick backends
Add features 'x11'  and 'wayland' to pick backends on Linux/BSD, with
both enabled by default.

Fixes #774.
2020-06-15 10:15:27 +03:00
Viktor Zoutman
5a6cfc314e Macos fullscreen & dialog support with run_return (#1581)
* Fix for fullscreen with run_return on mac

* Cleanup

* Removed a comment

* fmt

* This doesn't break exiting run_return anymore

* Now you can also transition from code

* Fmt & cleanup

* Now using a atomic instead of a static bool

* reinserted a line

* Fmt

* Added support for dialogs and child windows

* Cargo fmt

* Dialogs are now being shutdown properly

* Cargo fmt

* Update CHANGELOG.md
2020-06-09 14:46:33 -07:00
Boqin Qin
a4121a2c2e platform_impl/linux/x11: fix deadlock in fn set_fullscreen_inner (#1579)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2020-05-27 09:24:08 -07:00
Andrew Slater
03335cef85 macOS: add function to hide other applications 2020-05-24 19:26:29 +03:00
Kirill Chibisov
ff66bdda7c On Wayland, fix deadlock when calling set_inner_size from event loop
Fixes #1571.
2020-05-22 13:33:04 +03:00
Ryan G
6cfddfea21 Prevent the default browser behavior of events (#1576)
This stops things like page scrolling on spacebar / arrow keys
2020-05-21 13:13:33 -04:00
Osspial
49bcec1d27 Release 0.22.2 (#1570) 2020-05-16 12:27:16 -04:00
Michal Hornický
878c179761 Implement Clone for 'static events (#1478) 2020-05-15 14:58:12 -04:00
j4qfrost
bc19c04339 Fixed changelog line for core-* dependencies (#1561) 2020-05-15 14:32:04 -04:00
curldivergence
c7a33f926b Fixed a couple of typos in repo description (#1568) 2020-05-15 14:31:32 -04:00
j4qfrost
3c38afdb47 Update macOS dependencies (#1554)
* update macos libs

* modify dependency

* changelog

* update core-video-sys version
2020-05-07 22:32:09 -04:00
Jasper De Sutter
b8828105cf add android NDK event loop (#1556)
* add android NDK event loop

* add Android build documentation & cargo-apk to CI

Co-authored-by: David Craven <david@craven.ch>
2020-05-06 15:27:49 +02:00
Francesca Lovebloom
007b195a5e iOS: convert touch positions to physical (#1551) 2020-05-04 15:55:58 -07:00
Osspial
b4c6cdf9a3 Fix several crashes on Windows by heavily simplifying the event loop code (#1496) 2020-05-04 15:14:13 -04:00
Christian Duerr
26775fa0b6 Report mouse motion before click (#1490)
* Report mouse motion before click

This fixes an issue on macOS where a mouse click would be generated,
without ever getting a mouse motion to the position before the click.
This leads to the application thinking the mouse click occurred at a
position other than the actual mouse location.

This happens due to mouse motion above the window not automatically
giving focus to the window, unless it is actually clicked, making it
possible to move the window without motion events.

Fixes #942.

* Add additional mouse motion events

Co-authored-by: Ryan Goldstein <ryan@ryanisaacg.com>
2020-04-26 16:42:45 -04:00
Matthias Fauconneau
114fe9d502 wayland: rework scale factor handling (#1538)
- Always send Resized events in case of scale factor change
- Properly take into account the resize the user can do using the resize event.
2020-04-22 18:00:41 +02:00
Héctor Ramón
54bc41f68b Implement Drop for Proxy on macOS platform (#1526) 2020-04-20 17:48:42 -04:00
Osspial
47ff8d61d1 Document that platforms will display garbage data in the window by default (#1541) 2020-04-20 00:04:30 -04:00
Benjamin Saunders
849b8f5dce Clarify when RedrawRequested is useful (#1529)
Co-Authored-By: Osspial <osspial@gmail.com>
2020-04-19 17:09:08 -04:00
Yanchi Toth
aabe42d252 Preserve with_maximized on windows (#1515) 2020-04-19 15:52:48 -04:00
simlay
78a62ec547 Added more docs.rs targets (#1521) 2020-04-19 15:37:13 -04:00
Benjamin Saunders
6dae994bb4 Mention raw-window-handle in library docs (#1528) 2020-04-19 14:58:58 -04:00
Philippe Renon
4c4d0916fd control_flow example: fix wait_cancelled logic again (#1511) 2020-04-19 13:55:10 -04:00
Ryan G
d5609729cc Bump version to 0.22.1 (#1537)
There are a few relatively important bugfixes with no API impact in the
master branch. We might as well release this as a non-breaking change.
2020-04-17 13:36:42 -04:00
Jurgis
1f24a09570 Implement requestAnimationFrame for web (#1519)
* Use requestAnimationFrame for polling wasm

* Implement `requestAnimationFrame` for stdweb

Co-authored-by: Ryan G <ryanisaacg@users.noreply.github.com>
2020-04-11 15:49:07 -04:00
Ryan G
a8e777a5df Fix a possible double-borrow during event handling (#1512) 2020-04-11 15:20:38 -04:00
Murarth
0bc58f695b Fix warnings (#1530) 2020-04-10 11:29:33 -07:00
Matthew Russo
28023d9f5b upgrades x11-dl to 2.18.5 to fix #376 (#1517)
x11-dl was using std::mem::uninitialized incorrectly and when
rustlang added MaybeUninit and intrinsic panics on UB caused
by improper use of uninitialized (see rust-lang/rust/pull/69922)
it caused issues with X11 initialization. x11-dl pr
erlepereira/x11-rs/pull/101 updated x11-dl to use MaybeUninit
correctly
2020-03-25 22:38:25 -07:00
Murarth
c2aed1979d X11: Fix ResumeTimeReached being fired early (#1505)
* X11: Fix `ResumeTimeReached` being fired early

* Update CHANGELOG.md

Co-authored-by: Osspial <osspial@gmail.com>
2020-03-11 21:54:23 -07:00
Osspial
7e04273719 Replace Travis and Appveyor CI badges with GitHub Actions CI badge 2020-03-09 18:23:03 -04:00
Osspial
0683bdcd42 Add Unreleased category back to changelog 2020-03-09 17:01:34 -04:00
Osspial
29ab0bb629 Correct 0.22.0 date 2020-03-09 16:59:39 -04:00
126 changed files with 9561 additions and 5323 deletions

View File

@@ -37,20 +37,24 @@ jobs:
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: wayland }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
WEB: ${{ matrix.platform.web }}
CMD: ${{ matrix.platform.cmd }}
runs-on: ${{ matrix.platform.os }}
steps:
@@ -70,6 +74,9 @@ jobs:
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Install cargo-web
continue-on-error: true
if: contains(matrix.platform.target, 'wasm32')
@@ -78,29 +85,35 @@ jobs:
- name: Check documentation
shell: bash
if: matrix.platform.target != 'wasm32-unknown-unknown'
run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build
shell: bash
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build tests
shell: bash
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
shell: bash
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build with serde enabled
shell: bash
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Build tests with serde enabled
shell: bash
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES

View File

@@ -2,8 +2,8 @@ name: Publish
on:
push:
branches: [master]
paths: "Cargo.toml"
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
Publish:

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ rls/
*.ts
*.js
#*#
.DS_Store

View File

@@ -1,4 +1,147 @@
# 0.22.0 (2020-03-07)
# 0.25.0 (2021-05-15)
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
- On macOS, wait with activating the application until the application has initialized.
- On macOS, fix creating new windows when the application has a main menu.
- On Windows, fix fractional deltas for mouse wheel device events.
- On macOS, fix segmentation fault after dropping the main window.
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
- Added `is_maximized` method to `Window`.
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
- On macOS, wake up the event loop immediately when a redraw is requested.
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
- On Windows, fix bug causing mouse capture to not be released.
- On Windows, fix fullscreen not preserving minimized/maximized state.
- On Android, unimplemented events are marked as unhandled on the native event loop.
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
- On X11, bump `mio` to 0.7.
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
# 0.24.0 (2020-12-09)
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
- On Windows, implement `Window::set_ime_position`.
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
- **Breaking:** On Windows, include prefix byte in scancodes.
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
- On Windows, fix use-after-free crash during window destruction.
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
- On macOS, fix compilation when targeting aarch64.
- On X11, fix `Window::request_redraw` not waking the event loop.
- On Wayland, the keypad arrow keys are now recognized.
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
- Added `request_user_attention` method to `Window`.
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
- On Wayland, default font size in CSD increased from 11 to 17.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, support multi-touch.
- On Wayland, extra mouse buttons are not dropped anymore.
- **Breaking**: `MouseButton::Other` now uses `u16`.
# 0.23.0 (2020-10-02)
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
- On Unix, X11 and Wayland are now optional features (enabled by default)
- On X11, fix deadlock when calling `set_fullscreen_inner`.
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
- On android added support for `run_return`.
- On MacOS, Fixed fullscreen and dialog support for `run_return`.
- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops.
- On Web, use mouse events if pointer events aren't supported. This affects Safari.
- On Windows, `set_ime_position` is now a no-op instead of a runtime crash.
- On Android, `set_fullscreen` is now a no-op instead of a runtime crash.
- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash.
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
- On macOS, add `NSWindow.hasShadow` support.
- On Web, fix vertical mouse wheel scrolling being inverted.
- On Web, implement mouse capturing for click-dragging out of the canvas.
- On Web, fix `ControlFlow::Exit` not properly handled.
- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed.
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
- On NetBSD, fixed crash due to incorrect detection of the main thread.
- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`.
- On macOS, fix inverted horizontal scroll.
- **Breaking:** `current_monitor` now returns `Option<MonitorHandle>`.
- **Breaking:** `primary_monitor` now returns `Option<MonitorHandle>`.
- On macOS, updated core-* dependencies and cocoa.
- Bump `parking_lot` to 0.11
- On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute.
- On iOS, fixed starting the app in landscape where the view still had portrait dimensions.
- Deprecate the stdweb backend, to be removed in a future release
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
- Added `Asterisk` and `Plus` virtual key codes.
- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page.
- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped.
- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed.
- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed.
- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped.
- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called.
- **Breaking:** `Fullscreen` enum now uses `Borderless(Option<MonitorHandle>)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor.
- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor.
- On Wayland, add missing virtual keycodes.
- On Wayland, implement proper `set_cursor_grab`.
- On Wayland, the cursor will use similar icons if the requested one isn't available.
- On Wayland, right clicking on client side decorations will request application menu.
- On Wayland, fix tracking of window size after state changes.
- On Wayland, fix client side decorations not being hidden properly in fullscreen.
- On Wayland, fix incorrect size event when entering fullscreen with client side decorations.
- On Wayland, fix `resizable` attribute not being applied properly on startup.
- On Wayland, fix disabled repeat rate not being handled.
- On Wayland, fix decoration buttons not working after tty switch.
- On Wayland, fix scaling not being applied on output re-enable.
- On Wayland, fix crash when `XCURSOR_SIZE` is `0`.
- On Wayland, fix pointer getting created in some cases without pointer capability.
- On Wayland, on kwin, fix space between window and decorations on startup.
- **Breaking:** On Wayland, `Theme` trait was reworked.
- On Wayland, disable maximize button for non-resizable window.
- On Wayland, added support for `set_ime_position`.
- On Wayland, fix crash on startup since GNOME 3.37.90.
- On X11, fix incorrect modifiers state on startup.
# 0.22.2 (2020-05-16)
- Added Clone implementation for 'static events.
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
- On Android, minimal platform support.
- On iOS, touch positions are now properly converted to physical pixels.
- On macOS, updated core-* dependencies and cocoa
# 0.22.1 (2020-04-16)
- On X11, fix `ResumeTimeReached` being fired too early.
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
- On Web, fix a possible panic during event handling
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
# 0.22.0 (2020-03-09)
- On Windows, fix minor timing issue in wait_until_time_or_msg
- On Windows, rework handling of request_redraw() to address panics.
@@ -18,6 +161,7 @@
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
- On macOS, a mouse motion event is now generated before every mouse click.
# 0.21.0 (2020-02-04)

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.22.0"
version = "0.25.0"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -12,11 +12,16 @@ documentation = "https://docs.rs/winit"
categories = ["gui"]
[package.metadata.docs.rs]
features = ["serde"]
features = ["serde", "web-sys"]
default-target = "x86_64-unknown-linux-gnu"
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
[features]
default = ["x11", "wayland"]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
stdweb = ["std_web", "instant/stdweb"]
x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "sctk"]
[dependencies]
instant = "0.1"
@@ -26,29 +31,35 @@ log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.3"
bitflags = "1"
mint = { version = "0.5.6", optional = true }
[dev-dependencies]
image = "0.23"
simple_logger = "1"
image = "0.23.12"
simple_logger = "1.9"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.3"
ndk-sys = "0.2.0"
ndk-glue = "0.3"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.3"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc = "0.2.7"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.19.1"
core-foundation = "0.6"
core-graphics = "0.17.3"
cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
objc = "0.2.6"
scopeguard = "1.1"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
version = "0.1.3"
version = "0.1.4"
default_features = false
features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies]
parking_lot = "0.11"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
features = [
@@ -56,6 +67,7 @@ features = [
"commctrl",
"dwmapi",
"errhandlingapi",
"imm",
"hidusage",
"libloaderapi",
"objbase",
@@ -74,15 +86,13 @@ features = [
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
mio = "0.6"
mio-extras = "2.0"
smithay-client-toolkit = "^0.6.6"
x11-dl = "2.18.3"
percent-encoding = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.10"
wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true }
mio = { version = "0.7", features = ["os-ext"], optional = true }
mio-misc = { version = "1.0", optional = true }
x11-dl = { version = "2.18.5", optional = true }
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.11.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
@@ -90,6 +100,7 @@ version = "0.3.22"
optional = true
features = [
'console',
"AddEventListenerOptions",
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
@@ -121,4 +132,4 @@ optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.1"
console_log = "0.2"

View File

@@ -109,15 +109,16 @@ If your PR makes notable changes to Winit's features, please update this section
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.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
### macOS
* Window activation policy
@@ -182,9 +183,11 @@ Legend:
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ \*1|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
\*1: `WindowEvent::ScaleFactorChanged` is not sent on `stdweb` backend.
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
@@ -198,14 +201,15 @@ Legend:
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |
|Multitouch |✔️ |❌ |✔️ |✔️ | |✔️ |✔️ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ | |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ | |
|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 |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.

View File

@@ -2,12 +2,11 @@
[![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/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)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml
[dependencies]
winit = "0.22.0"
winit = "0.25.0"
```
## [Documentation](https://docs.rs/winit)
@@ -65,15 +64,60 @@ Winit is only officially supported on the latest stable version of the Rust comp
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
### Platform-specific usage
#### WebAssembly
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
Winit supports compiling to the `wasm32-unknown-unknown` target with either a
`stdweb` or a `web-sys` backend for use on web browsers. However, please note
that **the `stdweb` backend is being deprecated and may be removed in a future
release of Winit**. The `web-sys` backend is also more feature complete.
On the web platform, a Winit window is backed by a `<canvas>` element. You can
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For example code using Winit with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
[web example]: ./examples/web.rs
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
#### Android
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[[example]]
name = "request_redraw_threaded"
crate-type = ["cdylib"]
```
And add this to the example file to add the native activity glue:
```rust
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() {
...
}
```
And run the application with `cargo apk run --example request_redraw_threaded`
#### MacOS
To ensure compatibility with older MacOS systems, winit links to
CGDisplayCreateUUIDFromDisplayID through the CoreGraphics framework.
However, under certain setups this function is only available to be linked
through the newer ColorSync framework. So, winit provides the
`WINIT_LINK_COLORSYNC` environment variable which can be set to `1` or `true`
while compiling to enable linking via ColorSync.
- Put a `<canvas id="my_id"></canvas>` element somewhere. A canvas corresponds to a winit "window".
- Write a Javascript code that creates a global variable named `Module`. Set `Module.canvas` to
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.

10
build.rs Normal file
View File

@@ -0,0 +1,10 @@
fn main() {
// If building for macos and WINIT_LINK_COLORSYNC is set to true
// use CGDisplayCreateUUIDFromDisplayID from ColorSync instead of CoreGraphics
if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |os| os == "macos")
&& std::env::var("WINIT_LINK_COLORSYNC")
.map_or(false, |v| v == "1" || v.eq_ignore_ascii_case("true"))
{
println!("cargo:rustc-cfg=use_colorsync_cgdisplaycreateuuidfromdisplayid");
}
}

View File

@@ -1,5 +1,6 @@
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -17,7 +18,7 @@ const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode.");
println!("Press '2' to switch to WaitUntil mode.");
@@ -41,15 +42,9 @@ fn main() {
println!("{:?}", event);
match event {
Event::NewEvents(start_cause) => {
wait_cancelled = mode == Mode::WaitUntil;
match start_cause {
StartCause::ResumeTimeReached {
start: _,
requested_resume: _,
} => {
wait_cancelled = false;
}
_ => (),
wait_cancelled = match start_cause {
StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil,
_ => false,
}
}
Event::WindowEvent { event, .. } => match event {

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -5,7 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -5,7 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,5 +1,6 @@
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -11,7 +12,7 @@ fn main() {
Timer,
}
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()

73
examples/drag_window.rs Normal file
View File

@@ -0,0 +1,73 @@
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder, WindowId},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.drag_window().unwrap()
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::X),
..
},
..
} => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
_ => (),
},
_ => (),
});
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
let (drag_target, other) =
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
(&window_2, &window_1)
} else {
(&window_1, &window_2)
};
drag_target.set_title("drag target");
other.set_title("winit window");
}

View File

@@ -1,11 +1,13 @@
use std::io::{stdin, stdout, Write};
use simple_logger::SimpleLogger;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::monitor::{MonitorHandle, VideoMode};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
@@ -17,11 +19,10 @@ fn main() {
let fullscreen = Some(match num {
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))),
_ => panic!("Please enter a valid number"),
});
let mut is_maximized = false;
let mut decorations = true;
let window = WindowBuilder::new()
@@ -57,8 +58,8 @@ fn main() {
println!("window.fullscreen {:?}", window.fullscreen());
}
(VirtualKeyCode::M, ElementState::Pressed) => {
is_maximized = !is_maximized;
window.set_maximized(is_maximized);
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
(VirtualKeyCode::D, ElementState::Pressed) => {
decorations = !decorations;

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -5,7 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
@@ -6,7 +7,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -1,11 +1,12 @@
extern crate winit;
use simple_logger::SimpleLogger;
use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,7 +1,8 @@
use simple_logger::SimpleLogger;
use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

48
examples/mouse_wheel.rs Normal file
View File

@@ -0,0 +1,48 @@
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Mouse Wheel events")
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseWheel { delta } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({},{})", x, y);
let pixels_per_line = 120.0;
let mut pos = window.outer_position().unwrap();
pos.x -= (x * pixels_per_line) as i32;
pos.y -= (y * pixels_per_line) as i32;
window.set_outer_position(pos)
}
winit::event::MouseScrollDelta::PixelDelta(p) => {
println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y);
let mut pos = window.outer_position().unwrap();
pos.x -= p.x as i32;
pos.y -= p.y as i32;
window.set_outer_position(pos)
}
},
_ => (),
},
_ => (),
}
});
}

View File

@@ -2,6 +2,7 @@
fn main() {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
@@ -12,7 +13,7 @@ fn main() {
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
@@ -21,7 +22,7 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel();
@@ -34,7 +35,7 @@ fn main() {
// was moved to an another monitor, so that the window
// appears on this monitor instead when we go fullscreen
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
video_modes = window.current_monitor().video_modes().collect();
video_modes = window.current_monitor().unwrap().video_modes().collect();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.iter().nth(video_mode_id);
@@ -82,9 +83,7 @@ fn main() {
);
}
F => window.set_fullscreen(match (state, modifiers.alt()) {
(true, false) => {
Some(Fullscreen::Borderless(window.current_monitor()))
}
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => Some(Fullscreen::Exclusive(
video_modes.iter().nth(video_mode_id).unwrap().clone(),
)),

View File

@@ -1,4 +1,6 @@
use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -6,7 +8,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -5,7 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,5 +1,6 @@
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -7,7 +8,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
@@ -6,7 +7,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let mut resizable = false;

View File

@@ -0,0 +1,54 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_title("A fantastic window!");
println!("Ime position will system default");
println!("Click to set ime position to cursor's");
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
cursor_position = position;
}
Event::WindowEvent {
event:
WindowEvent::MouseInput {
state: ElementState::Released,
..
},
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
window.set_ime_position(cursor_position);
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
return;
}
_ => (),
}
});
}

View File

@@ -1,5 +1,7 @@
use instant::Instant;
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -7,7 +9,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -5,7 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,9 +1,16 @@
use simple_logger::SimpleLogger;
use winit::event_loop::EventLoop;
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let monitor = event_loop.primary_monitor();
let monitor = match event_loop.primary_monitor() {
Some(monitor) => monitor,
None => {
println!("No primary monitor detected.");
return;
}
};
println!("Listing available video modes:");

View File

@@ -1,3 +1,4 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -5,7 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,5 +1,6 @@
// This example is used by developers to test various window functions.
use simple_logger::SimpleLogger;
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
@@ -8,7 +9,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -20,13 +21,13 @@ fn main() {
eprintln!("debugging keys:");
eprintln!(" (E) Enter exclusive fullscreen");
eprintln!(" (F) Toggle borderless fullscreen");
eprintln!(" (P) Toggle borderless fullscreen on system's preffered monitor");
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
let mut minimized = false;
let mut maximized = false;
let mut visible = true;
event_loop.run(move |event, _, control_flow| {
@@ -70,7 +71,7 @@ fn main() {
size.width * size.height
}
let monitor = window.current_monitor();
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
@@ -88,6 +89,13 @@ fn main() {
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
VirtualKeyCode::P => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
@@ -100,8 +108,8 @@ fn main() {
window.set_visible(visible);
}
VirtualKeyCode::X => {
maximized = !maximized;
window.set_maximized(maximized);
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
_ => (),
},

View File

@@ -1,5 +1,7 @@
extern crate image;
use std::path::Path;
use simple_logger::SimpleLogger;
use winit::{
event::Event,
event_loop::{ControlFlow, EventLoop},
@@ -7,7 +9,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
// 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,
@@ -47,7 +49,7 @@ fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba();
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)

View File

@@ -10,15 +10,17 @@
))]
fn main() {
use std::{thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
let mut event_loop = EventLoop::new();
simple_logger::init().unwrap();
SimpleLogger::new().init().unwrap();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)

View File

@@ -69,9 +69,10 @@
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0.
//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale
//! factor, given the use of the command line.
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
@@ -89,6 +90,8 @@
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
//! both the screen scaling and the browser zoom level and can go below `1.0`.
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
@@ -96,6 +99,7 @@
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self;
@@ -160,7 +164,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// 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)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
@@ -224,8 +228,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(mint: mint::Point2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(winit: LogicalPosition<P>) -> Self {
mint::Point2 {
x: winit.x,
y: winit.y,
}
}
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
@@ -289,8 +310,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(mint: mint::Point2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(winit: PhysicalPosition<P>) -> Self {
mint::Point2 {
x: winit.x,
y: winit.y,
}
}
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
@@ -354,8 +392,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(mint: mint::Vector2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(winit: LogicalSize<P>) -> Self {
mint::Vector2 {
x: winit.width,
y: winit.height,
}
}
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
@@ -416,6 +471,23 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(mint: mint::Vector2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(winit: PhysicalSize<P>) -> Self {
mint::Vector2 {
x: winit.width,
y: winit.height,
}
}
}
/// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

View File

@@ -37,7 +37,7 @@ use instant::Instant;
use std::path::PathBuf;
use crate::{
dpi::{LogicalPosition, PhysicalPosition, PhysicalSize},
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
window::{Theme, WindowId},
};
@@ -81,10 +81,11 @@ pub enum Event<'a, T: 'static> {
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program draws
/// graphics, it's usually better to do it in response to
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
/// graphics when something changes, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event.
/// immediately after this event. Programs that draw graphics continuously, like most games,
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Emitted after `MainEventsCleared` when a window should be redrawn.
@@ -97,6 +98,9 @@ pub enum Event<'a, T: 'static> {
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
///
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
/// something changes, like most non-game GUIs.
RedrawRequested(WindowId),
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
@@ -114,6 +118,30 @@ pub enum Event<'a, T: 'static> {
LoopDestroyed,
}
impl<T: Clone> Clone for Event<'static, T> {
fn clone(&self) -> Self {
use self::Event::*;
match self {
WindowEvent { window_id, event } => WindowEvent {
window_id: *window_id,
event: event.clone(),
},
UserEvent(event) => UserEvent(event.clone()),
DeviceEvent { device_id, event } => DeviceEvent {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(cause.clone()),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
LoopDestroyed => LoopDestroyed,
Suspended => Suspended,
Resumed => Resumed,
}
}
}
impl<'a, T> Event<'a, T> {
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
use self::Event::*;
@@ -326,6 +354,97 @@ pub enum WindowEvent<'a> {
ThemeChanged(Theme),
}
impl Clone for WindowEvent<'static> {
fn clone(&self) -> Self {
use self::WindowEvent::*;
return match self {
Resized(size) => Resized(size.clone()),
Moved(pos) => Moved(pos.clone()),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
DroppedFile(file) => DroppedFile(file.clone()),
HoveredFile(file) => HoveredFile(file.clone()),
HoveredFileCancelled => HoveredFileCancelled,
ReceivedCharacter(c) => ReceivedCharacter(*c),
Focused(f) => Focused(*f),
KeyboardInput {
device_id,
input,
is_synthetic,
} => KeyboardInput {
device_id: *device_id,
input: *input,
is_synthetic: *is_synthetic,
},
ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()),
#[allow(deprecated)]
CursorMoved {
device_id,
position,
modifiers,
} => CursorMoved {
device_id: *device_id,
position: *position,
modifiers: *modifiers,
},
CursorEntered { device_id } => CursorEntered {
device_id: *device_id,
},
CursorLeft { device_id } => CursorLeft {
device_id: *device_id,
},
#[allow(deprecated)]
MouseWheel {
device_id,
delta,
phase,
modifiers,
} => MouseWheel {
device_id: *device_id,
delta: *delta,
phase: *phase,
modifiers: *modifiers,
},
#[allow(deprecated)]
MouseInput {
device_id,
state,
button,
modifiers,
} => MouseInput {
device_id: *device_id,
state: *state,
button: *button,
modifiers: *modifiers,
},
TouchpadPressure {
device_id,
pressure,
stage,
} => TouchpadPressure {
device_id: *device_id,
pressure: *pressure,
stage: *stage,
},
AxisMotion {
device_id,
axis,
value,
} => AxisMotion {
device_id: *device_id,
axis: *axis,
value: *value,
},
Touch(touch) => Touch(*touch),
ThemeChanged(theme) => ThemeChanged(theme.clone()),
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
};
}
}
impl<'a> WindowEvent<'a> {
pub fn to_static(self) -> Option<WindowEvent<'static>> {
use self::WindowEvent::*;
@@ -626,7 +745,7 @@ pub enum MouseButton {
Left,
Right,
Middle,
Other(u8),
Other(u16),
}
/// Describes a difference in the mouse scroll wheel state.
@@ -645,7 +764,7 @@ pub enum MouseScrollDelta {
/// Scroll events are expressed as a PixelDelta if
/// supported by the device (eg. a touchpad) and
/// platform.
PixelDelta(LogicalPosition<f64>),
PixelDelta(PhysicalPosition<f64>),
}
/// Symbolic name for a keyboard key.
@@ -773,12 +892,20 @@ pub enum VirtualKeyCode {
Numpad7,
Numpad8,
Numpad9,
NumpadAdd,
NumpadDivide,
NumpadDecimal,
NumpadComma,
NumpadEnter,
NumpadEquals,
NumpadMultiply,
NumpadSubtract,
AbntC1,
AbntC2,
Add,
Apostrophe,
Apps,
Asterisk,
At,
Ax,
Backslash,
@@ -787,8 +914,6 @@ pub enum VirtualKeyCode {
Colon,
Comma,
Convert,
Decimal,
Divide,
Equals,
Grave,
Kana,
@@ -802,19 +927,18 @@ pub enum VirtualKeyCode {
MediaSelect,
MediaStop,
Minus,
Multiply,
Mute,
MyComputer,
NavigateForward, // also called "Prior"
NavigateBackward, // also called "Next"
// also called "Next"
NavigateForward,
// also called "Prior"
NavigateBackward,
NextTrack,
NoConvert,
NumpadComma,
NumpadEnter,
NumpadEquals,
OEM102,
Period,
PlayPause,
Plus,
Power,
PrevTrack,
RAlt,
@@ -826,7 +950,6 @@ pub enum VirtualKeyCode {
Slash,
Sleep,
Stop,
Subtract,
Sysrq,
Tab,
Underline,

View File

@@ -73,6 +73,12 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
///
/// ## Platform-specific
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
/// example when the scaling of the page has changed. This should be treated as an implementation
/// detail which should not be relied on.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait,
@@ -154,23 +160,6 @@ impl<T> EventLoop<T> {
event_loop_proxy: self.event_loop.create_proxy(),
}
}
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.event_loop
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle {
inner: self.event_loop.primary_monitor(),
}
}
}
impl<T> Deref for EventLoop<T> {
@@ -180,6 +169,29 @@ impl<T> Deref for EventLoop<T> {
}
}
impl<T> EventLoopWindowTarget<T> {
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.p
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
///
/// Returns `None` if it can't identify any monitor as a primary one.
///
/// ## Platform-specific
///
/// **Wayland:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p.primary_monitor()
}
}
/// Used to send custom events to `EventLoop`.
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,

View File

@@ -28,17 +28,17 @@
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
//!
//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
//! entire program terminates.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on web and mobile platforms and works poorly on
//! most desktop platforms. However, this model can be re-implemented to an extent on desktops with
//! [`EventLoopExtDesktop::run_return`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond mobile/web compatibility reasons.
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond compatibility reasons.
//!
//!
//! ```no_run
@@ -73,14 +73,18 @@
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! //
//! // You only need to call this if you've determined that you need to redraw, in
//! // applications which do not always need to. Applications that redraw continuously
//! // can just render here instead.
//! window.request_redraw();
//! },
//! Event::RedrawRequested(_) => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in MainEventsCleared, since
//! // rendering in here allows the program to gracefully handle redraws requested
//! // by the OS.
//! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in MainEventsCleared, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS.
//! },
//! _ => ()
//! }
@@ -94,11 +98,18 @@
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! retrieve the raw handle of the window (see the [`platform`] module and/or the
//! [`raw_window_handle`] method), which in turn allows you to create an
//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
//! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the
//! window visible only once you're ready to render into it.
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
@@ -116,9 +127,10 @@
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
#![deny(rust_2018_idioms)]
#![deny(intra_doc_link_resolution_failure)]
#![deny(broken_intra_doc_links)]
#[allow(unused_imports)]
#[macro_use]

View File

@@ -1,13 +1,13 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
//! type. This is retreived from one of the following methods, which return an iterator of
//! type. This is retrieved from one of the following methods, which return an iterator of
//! [`MonitorHandle`][monitor_handle]:
//! - [`EventLoop::available_monitors`][loop_get]
//! - [`EventLoopWindowTarget::available_monitors`][loop_get]
//! - [`Window::available_monitors`][window_get].
//!
//! [monitor_handle]: crate::monitor::MonitorHandle
//! [loop_get]: crate::event_loop::EventLoop::available_monitors
//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors
//! [window_get]: crate::window::Window::available_monitors
use crate::{
dpi::{PhysicalPosition, PhysicalSize},

View File

@@ -1,32 +1,39 @@
#![cfg(any(target_os = "android"))]
use crate::{EventLoop, Window, WindowBuilder};
use std::os::raw::c_void;
use crate::{
event_loop::{EventLoop, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use ndk::configuration::Configuration;
use ndk_glue::Rect;
/// Additional methods on `EventLoop` that are specific to Android.
pub trait EventLoopExtAndroid {
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>);
}
pub trait EventLoopExtAndroid {}
impl EventLoopExtAndroid for EventLoop {
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
self.event_loop.set_suspend_callback(cb);
}
}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on `EventLoopWindowTarget` that are specific to Android.
pub trait EventLoopWindowTargetExtAndroid {}
/// Additional methods on `Window` that are specific to Android.
pub trait WindowExtAndroid {
fn native_window(&self) -> *const c_void;
fn content_rect(&self) -> Rect;
fn config(&self) -> Configuration;
}
impl WindowExtAndroid for Window {
#[inline]
fn native_window(&self) -> *const c_void {
self.window.native_window()
fn content_rect(&self) -> Rect {
self.window.content_rect()
}
fn config(&self) -> Configuration {
self.window.config()
}
}
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
/// Additional methods on `WindowBuilder` that are specific to Android.
pub trait WindowBuilderExtAndroid {}

View File

@@ -4,31 +4,12 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::EventLoopWindowTarget,
event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle,
platform_impl::get_aux_state_mut,
window::{Window, WindowBuilder},
};
/// Corresponds to `NSRequestUserAttentionType`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RequestUserAttentionType {
/// Corresponds to `NSCriticalRequest`.
///
/// Dock icon will bounce until the application is focused.
Critical,
/// Corresponds to `NSInformationalRequest`.
///
/// Dock icon will bounce once.
Informational,
}
impl Default for RequestUserAttentionType {
fn default() -> Self {
RequestUserAttentionType::Critical
}
}
/// Additional methods on `Window` that are specific to MacOS.
pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
@@ -41,10 +22,6 @@ pub trait WindowExtMacOS {
/// The pointer will become invalid when the `Window` is destroyed.
fn ns_view(&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.
fn request_user_attention(&self, request_type: RequestUserAttentionType);
/// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool;
@@ -56,6 +33,12 @@ pub trait WindowExtMacOS {
/// 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;
/// Returns whether or not the window has shadow.
fn has_shadow(&self) -> bool;
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
}
impl WindowExtMacOS for Window {
@@ -69,11 +52,6 @@ impl WindowExtMacOS for Window {
self.window.ns_view()
}
#[inline]
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
self.window.request_user_attention(request_type)
}
#[inline]
fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen()
@@ -83,6 +61,16 @@ impl WindowExtMacOS for Window {
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
}
#[inline]
fn has_shadow(&self) -> bool {
self.window.has_shadow()
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow)
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -113,8 +101,6 @@ impl Default for ActivationPolicy {
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS {
/// 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;
@@ -131,15 +117,10 @@ pub trait WindowBuilderExtMacOS {
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
}
impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
fn with_movable_by_window_background(
mut self,
@@ -190,6 +171,45 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
self.platform_specific.has_shadow = has_shadow;
self
}
}
pub trait EventLoopExtMacOS {
/// Sets the activation policy for the application. It is set to
/// `NSApplicationActivationPolicyRegular` by default.
///
/// This function only takes effect if it's called before calling [`run`](crate::event_loop::EventLoop::run) or
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);
/// Used to prevent a default menubar menu from getting created
///
/// The default menu creation is enabled by default.
///
/// This function only takes effect if it's called before calling
/// [`run`](crate::event_loop::EventLoop::run) or
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
fn enable_default_menu_creation(&mut self, enable: bool);
}
impl<T> EventLoopExtMacOS for EventLoop<T> {
#[inline]
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
unsafe {
get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy;
}
}
#[inline]
fn enable_default_menu_creation(&mut self, enable: bool) {
unsafe {
get_aux_state_mut(&**self.event_loop.delegate).create_default_menu = enable;
}
}
}
/// Additional methods on `MonitorHandle` that are specific to MacOS.
@@ -215,6 +235,8 @@ impl MonitorHandleExtMacOS for MonitorHandle {
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
fn hide_other_applications(&self);
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
@@ -223,4 +245,10 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
}
fn hide_other_applications(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hideOtherApplications: 0] }
}
}

View File

@@ -11,7 +11,7 @@
//!
//! And the following platform-specific module:
//!
//! - `desktop` (available on `windows`, `unix`, and `macos`)
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
//!
//! However only the module corresponding to the platform you're compiling to will be available.
@@ -21,5 +21,5 @@ pub mod macos;
pub mod unix;
pub mod windows;
pub mod desktop;
pub mod run_return;
pub mod web;

View File

@@ -1,7 +1,12 @@
#![cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use crate::{
@@ -9,8 +14,8 @@ use crate::{
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// Additional methods on `EventLoop` that are specific to desktop platforms.
pub trait EventLoopExtDesktop {
/// Additional methods on `EventLoop` to return control flow to the caller.
pub trait EventLoopExtRunReturn {
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent;
@@ -20,12 +25,12 @@ pub trait EventLoopExtDesktop {
/// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`.
///
/// # Caveats
/// Despite its apperance at first glance, this is *not* a perfect replacement for
/// Despite its appearance at first glance, this is *not* a perfect replacement for
/// `poll_events`. For example, this function will not return on Windows or macOS while a
/// window is getting resized, resulting in all application logic outside of the
/// `event_handler` closure not running until the resize operation ends. Other OS operations
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
/// underyling OS APIs, which cannot be hidden by Winit without severe stability reprecussions.
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
///
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
fn run_return<F>(&mut self, event_handler: F)
@@ -37,7 +42,7 @@ pub trait EventLoopExtDesktop {
);
}
impl<T> EventLoopExtDesktop for EventLoop<T> {
impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F)

View File

@@ -1,37 +1,49 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use std::{os::raw, ptr, sync::Arc};
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
use std::os::raw;
#[cfg(feature = "x11")]
use std::{ptr, sync::Arc};
use crate::{
dpi::Size,
event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
#[cfg(feature = "x11")]
use crate::dpi::Size;
#[cfg(feature = "x11")]
use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection};
use crate::platform_impl::{
x11::{ffi::XVisualInfo, XConnection},
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
// TODO: stupid hack so that glutin can do its work
#[doc(hidden)]
#[cfg(feature = "x11")]
pub use crate::platform_impl::x11;
#[cfg(feature = "x11")]
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland.
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool;
///
/// True if the `EventLoopWindowTarget` uses X11.
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
@@ -40,35 +52,42 @@ pub trait EventLoopWindowTargetExtUnix {
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
#[inline]
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
#[inline]
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p {
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
}
#[cfg(feature = "x11")]
_ => None,
}
}
@@ -82,6 +101,7 @@ pub trait EventLoopExtUnix {
///
/// If called outside the main thread. To initialize an X11 event loop outside
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
#[cfg(feature = "x11")]
fn new_x11() -> Result<Self, XNotSupported>
where
Self: Sized;
@@ -92,6 +112,7 @@ pub trait EventLoopExtUnix {
///
/// If called outside the main thread. To initialize a Wayland event loop outside
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
#[cfg(feature = "wayland")]
fn new_wayland() -> Self
where
Self: Sized;
@@ -108,6 +129,7 @@ pub trait EventLoopExtUnix {
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
#[cfg(feature = "x11")]
fn new_x11_any_thread() -> Result<Self, XNotSupported>
where
Self: Sized;
@@ -116,6 +138,7 @@ pub trait EventLoopExtUnix {
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
#[cfg(feature = "wayland")]
fn new_wayland_any_thread() -> Self
where
Self: Sized;
@@ -135,11 +158,13 @@ impl<T> EventLoopExtUnix for EventLoop<T> {
}
#[inline]
#[cfg(feature = "x11")]
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
}
#[inline]
#[cfg(feature = "wayland")]
fn new_wayland_any_thread() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland_any_thread()
@@ -149,11 +174,13 @@ impl<T> EventLoopExtUnix for EventLoop<T> {
}
#[inline]
#[cfg(feature = "x11")]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11().map(wrap_ev)
}
#[inline]
#[cfg(feature = "wayland")]
fn new_wayland() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland()
@@ -168,6 +195,7 @@ pub trait WindowExtUnix {
/// Returns the ID of the `Window` xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
@@ -175,21 +203,22 @@ pub trait WindowExtUnix {
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void>;
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// 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`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
@@ -197,6 +226,7 @@ pub trait WindowExtUnix {
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
@@ -204,9 +234,11 @@ pub trait WindowExtUnix {
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
#[cfg(feature = "wayland")]
fn set_wayland_theme<T: Theme>(&self, theme: T);
/// Check if the window is ready for drawing
@@ -221,73 +253,82 @@ pub trait WindowExtUnix {
impl WindowExtUnix for Window {
#[inline]
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
}
}
#[inline]
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn set_wayland_theme<T: Theme>(&self, theme: T) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
LinuxWindow::Wayland(ref w) => w.set_theme(theme),
#[cfg(feature = "x11")]
_ => {}
}
}
@@ -300,20 +341,28 @@ impl WindowExtUnix for Window {
/// Additional methods on `WindowBuilder` that are specific to Unix.
pub trait WindowBuilderExtUnix {
#[cfg(feature = "x11")]
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
#[cfg(feature = "x11")]
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_class(self, class: String, instance: String) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
/// Build window with base size hint. Only implemented on X11.
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
@@ -321,60 +370,72 @@ pub trait WindowBuilderExtUnix {
///
/// 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)
#[cfg(feature = "wayland")]
fn with_app_id(self, app_id: String) -> Self;
}
impl WindowBuilderExtUnix for WindowBuilder {
#[inline]
#[cfg(feature = "x11")]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_class(mut self, instance: String, class: String) -> Self {
self.platform_specific.class = Some((instance, class));
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_app_id(mut self, app_id: String) -> Self {
self.platform_specific.app_id = Some(app_id);
self
@@ -394,81 +455,50 @@ impl MonitorHandleExtUnix for MonitorHandle {
}
}
/// Wrapper for implementing SCTK's theme trait.
struct WaylandTheme<T: Theme>(T);
/// A theme for a Wayland's client side decorations.
#[cfg(feature = "wayland")]
pub trait Theme: Send + 'static {
/// Primary color of the scheme.
fn primary_color(&self, window_active: bool) -> [u8; 4];
/// Title bar color.
fn element_color(&self, element: Element, window_active: bool) -> ARGBColor;
/// Secondary color of the scheme.
fn secondary_color(&self, window_active: bool) -> [u8; 4];
/// Color for a given button part.
fn button_color(
&self,
button: Button,
state: ButtonState,
foreground: bool,
window_active: bool,
) -> ARGBColor;
/// Color for the close button.
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the close button, defaults to the secondary color.
#[allow(unused_variables)]
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the maximize button.
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the maximize button, defaults to the secondary color.
#[allow(unused_variables)]
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the minimize button.
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the minimize button, defaults to the secondary color.
#[allow(unused_variables)]
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
/// Font name and the size for the title bar.
///
/// By default the font is `sans-serif` at the size of 17.
///
/// Returning `None` means that title won't be drawn.
fn font(&self) -> Option<(String, f32)> {
// Not having any title isn't something desirable for the users, so setting it to
// something generic.
Some((String::from("sans-serif"), 17.))
}
}
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
self.0.primary_color(active)
}
/// A button on Wayland's client side decorations.
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Button {
/// Button that maximizes the window.
Maximize,
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
self.0.secondary_color(active)
}
/// Button that minimizes the window.
Minimize,
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.close_button_color(ButtonState::from_sctk(status))
}
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.close_button_icon_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.maximize_button_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.maximize_button_icon_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.minimize_button_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.minimize_button_icon_color(ButtonState::from_sctk(status))
}
/// Button that closes the window.
Close,
}
/// A button state of the button on Wayland's client side decorations.
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ButtonState {
/// Button is being hovered over by pointer.
Hovered,
@@ -478,12 +508,24 @@ pub enum ButtonState {
Disabled,
}
impl ButtonState {
fn from_sctk(button_state: SCTKButtonState) -> Self {
match button_state {
SCTKButtonState::Hovered => Self::Hovered,
SCTKButtonState::Idle => Self::Idle,
SCTKButtonState::Disabled => Self::Disabled,
}
}
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Element {
/// Bar itself.
Bar,
/// Separator between window and title bar.
Separator,
/// Title bar text.
Text,
}
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ARGBColor {
pub a: u8,
pub r: u8,
pub g: u8,
pub b: u8,
}

View File

@@ -5,15 +5,15 @@ use std::path::Path;
use libc;
use winapi::shared::minwindef::WORD;
use winapi::shared::windef::HWND;
use winapi::shared::windef::{HMENU, HWND};
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
window::{BadIcon, Icon, Window, WindowBuilder},
platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
};
/// Additional methods on `EventLoop` that are specific to Windows.
@@ -78,11 +78,26 @@ pub trait WindowExtWindows {
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> *mut libc::c_void;
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
/// If a child window is disabled, it is ignored when the system tries to determine which
/// window should receive mouse messages.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
fn set_enable(&self, enabled: bool);
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Whether the system theme is currently Windows 10's "Dark Mode".
fn is_dark_mode(&self) -> bool;
/// Returns the current window theme.
fn theme(&self) -> Theme;
}
impl WindowExtWindows for Window {
@@ -96,33 +111,89 @@ impl WindowExtWindows for Window {
self.window.hwnd() as *mut _
}
#[inline]
fn set_enable(&self, enabled: bool) {
unsafe {
winapi::um::winuser::EnableWindow(self.hwnd() as _, enabled as _);
}
}
#[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn is_dark_mode(&self) -> bool {
self.window.is_dark_mode()
fn theme(&self) -> Theme {
self.window.theme()
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
pub trait WindowBuilderExtWindows {
/// Sets a parent to the window to be created.
///
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
///
/// From MSDN:
/// - An owned window is always above its owner in the z-order.
/// - The system automatically destroys an owned window when its owner is destroyed.
/// - An owned window is hidden when its owner is minimized.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
/// Sets a menu on the window to be created.
///
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
///
/// The menu must have been manually created beforehand with [`winapi::um::winuser::CreateMenu`] or similar.
///
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
fn with_menu(self, menu: HMENU) -> WindowBuilder;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
/// Forces a theme or uses the system settings if `None` was provided.
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Some(parent);
self.platform_specific.parent = Parent::ChildOf(parent);
self
}
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::OwnedBy(parent);
self
}
#[inline]
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
self.platform_specific.menu = Some(menu);
self
}
@@ -137,6 +208,18 @@ impl WindowBuilderExtWindows for WindowBuilder {
self.platform_specific.no_redirection_bitmap = flag;
self
}
#[inline]
fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
self.platform_specific.drag_and_drop = flag;
self
}
#[inline]
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Windows.

View File

@@ -1,122 +0,0 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
use libc;
use std::os::raw;
#[link(name = "android")]
#[link(name = "EGL")]
#[link(name = "GLESv2")]
extern "C" {}
/**
** asset_manager.h
**/
pub type AAssetManager = raw::c_void;
/**
** native_window.h
**/
pub type ANativeWindow = raw::c_void;
extern "C" {
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
}
/**
** native_activity.h
**/
pub type JavaVM = ();
pub type JNIEnv = ();
pub type jobject = *const libc::c_void;
pub type AInputQueue = (); // FIXME: wrong
pub type ARect = (); // FIXME: wrong
#[repr(C)]
pub struct ANativeActivity {
pub callbacks: *mut ANativeActivityCallbacks,
pub vm: *mut JavaVM,
pub env: *mut JNIEnv,
pub clazz: jobject,
pub internalDataPath: *const libc::c_char,
pub externalDataPath: *const libc::c_char,
pub sdkVersion: libc::int32_t,
pub instance: *mut libc::c_void,
pub assetManager: *mut AAssetManager,
pub obbPath: *const libc::c_char,
}
#[repr(C)]
pub struct ANativeActivityCallbacks {
pub onStart: extern "C" fn(*mut ANativeActivity),
pub onResume: extern "C" fn(*mut ANativeActivity),
pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t),
pub onPause: extern "C" fn(*mut ANativeActivity),
pub onStop: extern "C" fn(*mut ANativeActivity),
pub onDestroy: extern "C" fn(*mut ANativeActivity),
pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int),
pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect),
pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity),
pub onLowMemory: extern "C" fn(*mut ANativeActivity),
}
/**
** looper.h
**/
pub type ALooper = ();
#[link(name = "android")]
extern "C" {
pub fn ALooper_forThread() -> *const ALooper;
pub fn ALooper_acquire(looper: *const ALooper);
pub fn ALooper_release(looper: *const ALooper);
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
pub fn ALooper_pollOnce(
timeoutMillis: libc::c_int,
outFd: *mut libc::c_int,
outEvents: *mut libc::c_int,
outData: *mut *mut libc::c_void,
) -> libc::c_int;
pub fn ALooper_pollAll(
timeoutMillis: libc::c_int,
outFd: *mut libc::c_int,
outEvents: *mut libc::c_int,
outData: *mut *mut libc::c_void,
) -> libc::c_int;
pub fn ALooper_wake(looper: *const ALooper);
pub fn ALooper_addFd(
looper: *const ALooper,
fd: libc::c_int,
ident: libc::c_int,
events: libc::c_int,
callback: ALooper_callbackFunc,
data: *mut libc::c_void,
) -> libc::c_int;
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
}
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
pub type ALooper_callbackFunc =
extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ use crate::{
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
},
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::Idiom,
};
@@ -24,7 +25,8 @@ use crate::platform_impl::platform::{
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain,
UIUserInterfaceIdiom,
},
monitor, view, MonitorHandle,
};
@@ -49,6 +51,20 @@ pub struct EventLoopWindowTarget<T: 'static> {
sender_to_clone: Sender<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
// guaranteed to be on main thread
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
}
}
pub struct EventLoop<T: 'static> {
window_target: RootEventLoopWindowTarget<T>,
}
@@ -105,7 +121,7 @@ impl<T: 'static> EventLoop<T> {
0,
ptr::null(),
nil,
NSString::alloc(nil).init_str("AppDelegate"),
NSStringRust::alloc(nil).init_str("AppDelegate"),
);
unreachable!()
}
@@ -115,16 +131,6 @@ impl<T: 'static> EventLoop<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> MonitorHandle {
// guaranteed to be on main thread
unsafe { monitor::main_uiscreen() }
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}

View File

@@ -29,14 +29,14 @@ pub struct NSOperatingSystemVersion {
}
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
@@ -52,7 +52,7 @@ impl CGSize {
}
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
@@ -359,7 +359,10 @@ pub struct CFRunLoopSourceContext {
pub perform: Option<extern "C" fn(*mut c_void)>,
}
pub trait NSString: Sized {
// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of
// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy
// so please test if you change the name back to NSString.
pub trait NSStringRust: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc]
}
@@ -370,7 +373,7 @@ pub trait NSString: Sized {
unsafe fn UTF8String(self) -> *const c_char;
}
impl NSString for id {
impl NSStringRust for id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String: c_string as id]
}

View File

@@ -6,6 +6,7 @@ use objc::{
};
use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS,
platform_impl::platform::{
@@ -122,17 +123,26 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
let window: id = msg_send![object, window];
assert!(!window.is_null());
let bounds: CGRect = msg_send![window, bounds];
let window_bounds: CGRect = msg_send![window, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
msg_send![object, convertRect:window_bounds toCoordinateSpace:screen_space];
let scale_factor: CGFloat = msg_send![screen, scale];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(scale_factor.into());
// If the app is started in landscape, the view frame and window bounds can be mismatched.
// The view frame will be in portrait and the window bounds in landscape. So apply the
// window bounds to the view frame to make it consistent.
let view_frame: CGRect = msg_send![object, frame];
if view_frame != window_bounds {
let () = msg_send![object, setFrame: window_bounds];
}
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
@@ -209,7 +219,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force {
let trait_collection: id = msg_send![object, traitCollection];
@@ -248,12 +258,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
let physical_location = {
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
location: physical_location,
force,
phase,
}),
@@ -511,7 +528,15 @@ pub unsafe fn create_window(
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => {
msg_send![window, setScreen:monitor.ui_screen()]
let uiscreen: id = match &monitor {
Some(monitor) => monitor.ui_screen() as id,
None => {
let uiscreen: id = msg_send![window, screen];
uiscreen
}
};
msg_send![window, setScreen: uiscreen]
}
None => (),
}

View File

@@ -22,7 +22,9 @@ use crate::{
},
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
},
};
pub struct Inner {
@@ -142,7 +144,7 @@ impl Inner {
}
pub fn set_inner_size(&self, _size: Size) {
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
warn!("not clear what `Window::set_inner_size` means on iOS");
}
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
@@ -180,6 +182,10 @@ impl Inner {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS")
}
@@ -188,6 +194,11 @@ impl Inner {
warn!("`Window::set_maximized` is ignored on iOS")
}
pub fn is_maximized(&self) -> bool {
warn!("`Window::is_maximized` is ignored on iOS");
false
}
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe {
let uiscreen = match monitor {
@@ -196,7 +207,9 @@ impl Inner {
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
Some(Fullscreen::Borderless(monitor)) => monitor
.unwrap_or_else(|| self.current_monitor_inner())
.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
@@ -224,7 +237,7 @@ impl Inner {
pub fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor();
let monitor = self.current_monitor_inner();
let uiscreen = monitor.inner.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
@@ -235,7 +248,7 @@ impl Inner {
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(Fullscreen::Borderless(monitor))
Some(Fullscreen::Borderless(Some(monitor)))
} else {
None
}
@@ -258,7 +271,12 @@ impl Inner {
warn!("`Window::set_ime_position` is ignored on iOS")
}
pub fn current_monitor(&self) -> RootMonitorHandle {
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
warn!("`Window::request_user_attention` is ignored on iOS")
}
// Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> RootMonitorHandle {
unsafe {
let uiscreen: id = msg_send![self.window, screen];
RootMonitorHandle {
@@ -267,12 +285,17 @@ impl Inner {
}
}
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
Some(self.current_monitor_inner())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> MonitorHandle {
unsafe { monitor::main_uiscreen() }
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
}
pub fn id(&self) -> WindowId {
@@ -347,8 +370,10 @@ impl Window {
Some(Fullscreen::Exclusive(ref video_mode)) => {
video_mode.video_mode.monitor.ui_screen() as id
}
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
None => monitor::main_uiscreen().ui_screen(),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => {
monitor::main_uiscreen().ui_screen() as id
}
};
let screen_bounds: CGRect = msg_send![screen, bounds];

View File

@@ -1,12 +1,27 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(feature = "wayland")]
use std::error::Error;
use std::{collections::VecDeque, env, fmt};
#[cfg(feature = "x11")]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
#[cfg(feature = "x11")]
use parking_lot::Mutex;
use raw_window_handle::RawWindowHandle;
use smithay_client_toolkit::reexports::client::ConnectError;
#[cfg(feature = "x11")]
pub use self::x11::XNotSupported;
#[cfg(feature = "x11")]
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
@@ -15,12 +30,14 @@ use crate::{
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, Fullscreen, WindowAttributes},
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
#[cfg(feature = "wayland")]
pub mod wayland;
#[cfg(feature = "x11")]
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
@@ -34,175 +51,216 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
#[cfg(feature = "x11")]
pub visual_infos: Option<XVisualInfo>,
#[cfg(feature = "x11")]
pub screen_id: Option<i32>,
#[cfg(feature = "x11")]
pub resize_increments: Option<Size>,
#[cfg(feature = "x11")]
pub base_size: Option<Size>,
#[cfg(feature = "x11")]
pub class: Option<(String, String)>,
#[cfg(feature = "x11")]
pub override_redirect: bool,
#[cfg(feature = "x11")]
pub x11_window_types: Vec<XWindowType>,
#[cfg(feature = "x11")]
pub gtk_theme_variant: Option<String>,
#[cfg(feature = "wayland")]
pub app_id: Option<String>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
#[cfg(feature = "x11")]
visual_infos: None,
#[cfg(feature = "x11")]
screen_id: None,
#[cfg(feature = "x11")]
resize_increments: None,
#[cfg(feature = "x11")]
base_size: None,
#[cfg(feature = "x11")]
class: None,
#[cfg(feature = "x11")]
override_redirect: false,
#[cfg(feature = "x11")]
x11_window_types: vec![XWindowType::Normal],
#[cfg(feature = "x11")]
gtk_theme_variant: None,
#[cfg(feature = "wayland")]
app_id: None,
}
}
}
#[cfg(feature = "x11")]
lazy_static! {
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new));
}
#[derive(Debug, Clone)]
pub enum OsError {
#[cfg(feature = "x11")]
XError(XError),
#[cfg(feature = "x11")]
XMisc(&'static str),
#[cfg(feature = "wayland")]
WaylandMisc(&'static str),
}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
OsError::XError(e) => f.pad(&e.description),
OsError::XMisc(e) => f.pad(e),
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
#[cfg(feature = "x11")]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(feature = "x11")]
OsError::XMisc(ref e) => _f.pad(e),
#[cfg(feature = "wayland")]
OsError::WaylandMisc(ref e) => _f.pad(e),
}
}
}
pub enum Window {
#[cfg(feature = "x11")]
X(x11::Window),
#[cfg(feature = "wayland")]
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WindowId {
#[cfg(feature = "x11")]
X(x11::WindowId),
#[cfg(feature = "wayland")]
Wayland(wayland::WindowId),
}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId::Wayland(wayland::WindowId::dummy())
#[cfg(feature = "wayland")]
return WindowId::Wayland(wayland::WindowId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
return WindowId::X(x11::WindowId::dummy());
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
#[cfg(feature = "x11")]
X(x11::DeviceId),
#[cfg(feature = "wayland")]
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId::Wayland(wayland::DeviceId::dummy())
#[cfg(feature = "wayland")]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
return DeviceId::X(x11::DeviceId::dummy());
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle {
#[cfg(feature = "x11")]
X(x11::MonitorHandle),
#[cfg(feature = "wayland")]
Wayland(wayland::MonitorHandle),
}
/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
/// expands to the equivalent of
/// ```ignore
/// match self {
/// Enum::X(foo) => foo.something(),
/// Enum::Wayland(foo) => foo.something(),
/// }
/// ```
/// The result can be converted to another enum by adding `; as AnotherEnum`
macro_rules! x11_or_wayland {
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
match $what {
#[cfg(feature = "x11")]
$enum::X($($c1)*) => $enum2::X($x),
#[cfg(feature = "wayland")]
$enum::Wayland($($c1)*) => $enum2::Wayland($x),
}
};
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
match $what {
#[cfg(feature = "x11")]
$enum::X($($c1)*) => $x,
#[cfg(feature = "wayland")]
$enum::Wayland($($c1)*) => $x,
}
};
}
impl MonitorHandle {
#[inline]
pub fn name(&self) -> Option<String> {
match self {
&MonitorHandle::X(ref m) => m.name(),
&MonitorHandle::Wayland(ref m) => m.name(),
}
x11_or_wayland!(match self; MonitorHandle(m) => m.name())
}
#[inline]
pub fn native_identifier(&self) -> u32 {
match self {
&MonitorHandle::X(ref m) => m.native_identifier(),
&MonitorHandle::Wayland(ref m) => m.native_identifier(),
}
x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier())
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
match self {
&MonitorHandle::X(ref m) => m.size(),
&MonitorHandle::Wayland(ref m) => m.size(),
}
x11_or_wayland!(match self; MonitorHandle(m) => m.size())
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
match self {
&MonitorHandle::X(ref m) => m.position(),
&MonitorHandle::Wayland(ref m) => m.position(),
}
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
match self {
&MonitorHandle::X(ref m) => m.scale_factor(),
&MonitorHandle::Wayland(ref m) => m.scale_factor() as f64,
}
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
match self {
MonitorHandle::X(m) => Box::new(m.video_modes()),
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
}
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoMode {
#[cfg(feature = "x11")]
X(x11::VideoMode),
#[cfg(feature = "wayland")]
Wayland(wayland::VideoMode),
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
match self {
&VideoMode::X(ref m) => m.size(),
&VideoMode::Wayland(ref m) => m.size(),
}
x11_or_wayland!(match self; VideoMode(m) => m.size())
}
#[inline]
pub fn bit_depth(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.bit_depth(),
&VideoMode::Wayland(ref m) => m.bit_depth(),
}
x11_or_wayland!(match self; VideoMode(m) => m.bit_depth())
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.refresh_rate(),
&VideoMode::Wayland(ref m) => m.refresh_rate(),
}
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate())
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
match self {
&VideoMode::X(ref m) => m.monitor(),
&VideoMode::Wayland(ref m) => m.monitor(),
}
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
}
}
@@ -214,9 +272,11 @@ impl Window {
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
match *window_target {
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
}
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref window_target) => {
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
}
@@ -225,232 +285,195 @@ impl Window {
#[inline]
pub fn id(&self) -> WindowId {
match self {
&Window::X(ref w) => WindowId::X(w.id()),
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
}
x11_or_wayland!(match self; Window(w) => w.id(); as WindowId)
}
#[inline]
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),
}
x11_or_wayland!(match self; Window(w) => w.set_title(title));
}
#[inline]
pub fn set_visible(&self, visible: bool) {
match self {
&Window::X(ref w) => w.set_visible(visible),
&Window::Wayland(ref w) => w.set_visible(visible),
}
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
match self {
&Window::X(ref w) => w.outer_position(),
&Window::Wayland(ref w) => w.outer_position(),
}
x11_or_wayland!(match self; Window(w) => w.outer_position())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
match self {
&Window::X(ref m) => m.inner_position(),
&Window::Wayland(ref m) => m.inner_position(),
}
x11_or_wayland!(match self; Window(w) => w.inner_position())
}
#[inline]
pub fn set_outer_position(&self, position: Position) {
match self {
&Window::X(ref w) => w.set_outer_position(position),
&Window::Wayland(ref w) => w.set_outer_position(position),
}
x11_or_wayland!(match self; Window(w) => w.set_outer_position(position))
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
match self {
&Window::X(ref w) => w.inner_size(),
&Window::Wayland(ref w) => w.inner_size(),
}
x11_or_wayland!(match self; Window(w) => w.inner_size())
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
match self {
&Window::X(ref w) => w.outer_size(),
&Window::Wayland(ref w) => w.outer_size(),
}
x11_or_wayland!(match self; Window(w) => w.outer_size())
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
match self {
&Window::X(ref w) => w.set_inner_size(size),
&Window::Wayland(ref w) => w.set_inner_size(size),
}
x11_or_wayland!(match self; Window(w) => w.set_inner_size(size))
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
match self {
&Window::X(ref w) => w.set_min_inner_size(dimensions),
&Window::Wayland(ref w) => w.set_min_inner_size(dimensions),
}
x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions))
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
match self {
&Window::X(ref w) => w.set_max_inner_size(dimensions),
&Window::Wayland(ref w) => w.set_max_inner_size(dimensions),
}
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
match self {
&Window::X(ref w) => w.set_resizable(resizable),
&Window::Wayland(ref w) => w.set_resizable(resizable),
}
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
match self {
&Window::X(ref w) => w.set_cursor_icon(cursor),
&Window::Wayland(ref w) => w.set_cursor_icon(cursor),
}
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
match self {
&Window::X(ref window) => window.set_cursor_grab(grab),
&Window::Wayland(ref window) => window.set_cursor_grab(grab),
}
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab))
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
match self {
&Window::X(ref window) => window.set_cursor_visible(visible),
&Window::Wayland(ref window) => window.set_cursor_visible(visible),
}
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
match self {
&Window::X(ref w) => w.scale_factor(),
&Window::Wayland(ref w) => w.scale_factor() as f64,
}
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
match self {
&Window::X(ref w) => w.set_cursor_position(position),
&Window::Wayland(ref w) => w.set_cursor_position(position),
}
x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position))
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
match self {
&Window::X(ref w) => w.set_maximized(maximized),
&Window::Wayland(ref w) => w.set_maximized(maximized),
}
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
}
#[inline]
pub fn is_maximized(&self) -> bool {
// TODO: Not implemented
false
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
match self {
&Window::X(ref w) => w.set_minimized(minimized),
&Window::Wayland(ref w) => w.set_minimized(minimized),
}
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
match self {
&Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen(),
}
x11_or_wayland!(match self; Window(w) => w.fullscreen())
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
match self {
&Window::X(ref w) => w.set_fullscreen(monitor),
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
}
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
match self {
&Window::X(ref w) => w.set_decorations(decorations),
&Window::Wayland(ref w) => w.set_decorations(decorations),
#[cfg(feature = "x11")]
&Window::X(ref w) => w.set_always_on_top(_always_on_top),
#[cfg(feature = "wayland")]
_ => (),
}
}
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
&Window::X(ref w) => w.set_always_on_top(always_on_top),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
match self {
&Window::X(ref w) => w.set_window_icon(window_icon),
&Window::Wayland(_) => (),
#[cfg(feature = "x11")]
&Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(feature = "wayland")]
_ => (),
}
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
}
#[inline]
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
match self {
&Window::X(ref w) => w.set_ime_position(position),
&Window::Wayland(_) => (),
#[cfg(feature = "x11")]
&Window::X(ref w) => w.request_user_attention(_request_type),
#[cfg(feature = "wayland")]
_ => (),
}
}
#[inline]
pub fn request_redraw(&self) {
match self {
&Window::X(ref w) => w.request_redraw(),
&Window::Wayland(ref w) => w.request_redraw(),
}
x11_or_wayland!(match self; Window(w) => w.request_redraw())
}
#[inline]
pub fn current_monitor(&self) -> RootMonitorHandle {
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
match self {
&Window::X(ref window) => RootMonitorHandle {
inner: MonitorHandle::X(window.current_monitor()),
},
&Window::Wayland(ref window) => RootMonitorHandle {
inner: MonitorHandle::Wayland(window.current_monitor()),
},
#[cfg(feature = "x11")]
&Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(RootMonitorHandle {
inner: current_monitor,
})
}
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(RootMonitorHandle {
inner: current_monitor,
})
}
}
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => window
.available_monitors()
.into_iter()
@@ -460,21 +483,31 @@ impl Window {
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
match self {
&Window::X(ref window) => MonitorHandle::X(window.primary_monitor()),
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
#[cfg(feature = "x11")]
&Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(RootMonitorHandle {
inner: primary_monitor,
})
}
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => window.primary_monitor(),
}
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
}
}
#[cfg(feature = "x11")]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
@@ -508,21 +541,22 @@ unsafe extern "C" fn x_error_callback(
}
pub enum EventLoop<T: 'static> {
#[cfg(feature = "wayland")]
Wayland(wayland::EventLoop<T>),
#[cfg(feature = "x11")]
X(x11::EventLoop<T>),
}
pub enum EventLoopProxy<T: 'static> {
#[cfg(feature = "x11")]
X(x11::EventLoopProxy<T>),
#[cfg(feature = "wayland")]
Wayland(wayland::EventLoopProxy<T>),
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
match self {
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
}
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy)
}
}
@@ -538,12 +572,18 @@ impl<T: 'static> EventLoop<T> {
match env_var.as_str() {
"x11" => {
// TODO: propagate
#[cfg(feature = "x11")]
return EventLoop::new_x11_any_thread()
.expect("Failed to initialize X11 backend");
#[cfg(not(feature = "x11"))]
panic!("x11 feature is not enabled")
}
"wayland" => {
#[cfg(feature = "wayland")]
return EventLoop::new_wayland_any_thread()
.expect("Failed to initialize Wayland backend");
#[cfg(not(feature = "wayland"))]
panic!("wayland feature is not enabled");
}
_ => panic!(
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
@@ -552,39 +592,49 @@ impl<T: 'static> EventLoop<T> {
}
}
#[cfg(feature = "wayland")]
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(feature = "x11")]
let x11_err = match EventLoop::new_x11_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
let err_string = format!(
#[cfg(not(feature = "wayland"))]
let wayland_err = "backend disabled";
#[cfg(not(feature = "x11"))]
let x11_err = "backend disabled";
panic!(
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err, x11_err,
);
panic!(err_string);
}
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
#[cfg(feature = "wayland")]
pub fn new_wayland() -> Result<EventLoop<T>, Box<dyn Error>> {
assert_is_main_thread("new_wayland_any_thread");
EventLoop::new_wayland_any_thread()
}
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
#[cfg(feature = "wayland")]
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(EventLoop::Wayland)
}
#[cfg(feature = "x11")]
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
assert_is_main_thread("new_x11_any_thread");
EventLoop::new_x11_any_thread()
}
#[cfg(feature = "x11")]
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().as_ref() {
Ok(xconn) => xconn.clone(),
@@ -594,15 +644,64 @@ impl<T: 'static> EventLoop<T> {
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback))
}
pub fn run<F>(self, callback: F) -> !
where
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
x11_or_wayland!(match self; EventLoop(evl) => evl.window_target())
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
}
}
pub enum EventLoopWindowTarget<T> {
#[cfg(feature = "wayland")]
Wayland(wayland::EventLoopWindowTarget<T>),
#[cfg(feature = "x11")]
X(x11::EventLoopWindowTarget<T>),
}
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(_) => true,
#[cfg(feature = "x11")]
_ => false,
}
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match *self {
EventLoop::Wayland(ref evlp) => evlp
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(ref evlp) => evlp
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
EventLoop::X(ref evlp) => evlp
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => evlp
.x_connection()
.available_monitors()
.into_iter()
@@ -612,68 +711,17 @@ impl<T: 'static> EventLoop<T> {
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
match *self {
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
match *self {
EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()),
EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
}
}
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
match *self {
EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback),
EventLoop::X(ref mut evlp) => evlp.run_return(callback),
}
}
pub fn run<F>(self, callback: F) -> !
where
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
match self {
EventLoop::Wayland(evlp) => evlp.run(callback),
EventLoop::X(evlp) => evlp.run(callback),
}
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
match *self {
EventLoop::Wayland(ref evl) => evl.window_target(),
EventLoop::X(ref evl) => evl.window_target(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
match *self {
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
}
}
}
pub enum EventLoopWindowTarget<T> {
Wayland(wayland::EventLoopWindowTarget<T>),
X(x11::EventLoopWindowTarget<T>),
}
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
EventLoopWindowTarget::Wayland(_) => true,
EventLoopWindowTarget::X(_) => false,
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => {
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
Some(RootMonitorHandle {
inner: primary_monitor,
})
}
}
}
}
@@ -725,7 +773,5 @@ fn is_main_thread() -> bool {
#[cfg(target_os = "netbsd")]
fn is_main_thread() -> bool {
use libc::_lwp_self;
unsafe { _lwp_self() == 1 }
std::thread::current().name() == Some("main")
}

View File

@@ -0,0 +1,149 @@
//! SCTK environment setup.
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
use sctk::reexports::client::protocol::wl_shell::WlShell;
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
use sctk::reexports::client::{Attached, DispatchData};
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::environment::{Environment, SimpleGlobal};
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
use sctk::shell::{Shell, ShellHandler, ShellHandling};
use sctk::shm::ShmHandler;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
cursor_grab: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let cursor_grab = env.get_global::<ZwpPointerConstraintsV1>().is_some();
Self { cursor_grab }
}
pub fn cursor_grab(&self) -> bool {
self.cursor_grab
}
}
sctk::environment!(WinitEnv,
singles = [
WlShm => shm,
WlCompositor => compositor,
WlSubcompositor => subcompositor,
WlShell => shell,
XdgWmBase => shell,
ZxdgShellV6 => shell,
ZxdgDecorationManagerV1 => decoration_manager,
ZwpRelativePointerManagerV1 => relative_pointer_manager,
ZwpPointerConstraintsV1 => pointer_constraints,
ZwpTextInputManagerV3 => text_input_manager,
],
multis = [
WlSeat => seats,
WlOutput => outputs,
]
);
/// The environment that we utilize.
pub struct WinitEnv {
seats: SeatHandler,
outputs: OutputHandler,
shm: ShmHandler,
compositor: SimpleGlobal<WlCompositor>,
subcompositor: SimpleGlobal<WlSubcompositor>,
shell: ShellHandler,
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
}
impl WinitEnv {
pub fn new() -> Self {
// Output tracking for available_monitors, etc.
let outputs = OutputHandler::new();
// Keyboard/Pointer/Touch input.
let seats = SeatHandler::new();
// Essential globals.
let shm = ShmHandler::new();
let compositor = SimpleGlobal::new();
let subcompositor = SimpleGlobal::new();
// Gracefully handle shell picking, since SCTK automatically supports multiple
// backends.
let shell = ShellHandler::new();
// Server side decorations.
let decoration_manager = SimpleGlobal::new();
// Device events for pointer.
let relative_pointer_manager = SimpleGlobal::new();
// Pointer grab functionality.
let pointer_constraints = SimpleGlobal::new();
// IME handling.
let text_input_manager = SimpleGlobal::new();
Self {
seats,
outputs,
shm,
compositor,
subcompositor,
shell,
decoration_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
}
}
}
impl ShellHandling for WinitEnv {
fn get_shell(&self) -> Option<Shell> {
self.shell.get_shell()
}
}
impl SeatHandling for WinitEnv {
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> SeatListener {
self.seats.listen(f)
}
}
impl OutputHandling for WinitEnv {
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> OutputStatusListener {
self.outputs.listen(f)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,535 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::process;
use std::rc::Rc;
use std::time::{Duration, Instant};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use sctk::environment::Environment;
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
use sctk::WaylandSource;
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
use super::seat::SeatManager;
use super::window::shim::{self, WindowUpdate};
use super::{DeviceId, WindowId};
mod proxy;
mod sink;
mod state;
pub use proxy::EventLoopProxy;
pub use state::WinitState;
use sink::EventSink;
pub struct EventLoopWindowTarget<T> {
/// Wayland display.
pub display: Display,
/// Environment to handle object creation, etc.
pub env: Environment<WinitEnv>,
/// Event loop handle.
pub event_loop_handle: calloop::LoopHandle<WinitState>,
/// Output manager.
pub output_manager: OutputManager,
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Wayland source.
pub wayland_source: Rc<calloop::Source<WaylandSource>>,
/// A proxy to wake up event loop.
pub event_loop_awakener: calloop::ping::Ping,
/// The available windowing features.
pub windowing_features: WindowingFeatures,
/// Theme manager to manage cursors.
///
/// It's being shared amoung all windows to avoid loading
/// multiple similar themes.
pub theme_manager: ThemeManager,
_marker: std::marker::PhantomData<T>,
}
pub struct EventLoop<T: 'static> {
/// Event loop.
event_loop: calloop::EventLoop<WinitState>,
/// Wayland display.
display: Display,
/// Pending user events.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
/// Wayland source of events.
wayland_source: Rc<calloop::Source<WaylandSource>>,
/// Window target.
window_target: RootEventLoopWindowTarget<T>,
/// Output manager.
_seat_manager: SeatManager,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
// Connect to wayland server and setup event queue.
let display = Display::connect_to_env()?;
let mut event_queue = display.create_event_queue();
let display_proxy = display.attach(event_queue.token());
// Setup environment.
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
// Create event loop.
let event_loop = calloop::EventLoop::<WinitState>::new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
// Create a theme manager.
let compositor = env.require_global::<WlCompositor>();
let shm = env.require_global::<WlShm>();
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
// Setup theme seat and output managers.
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
let output_manager = OutputManager::new(&env);
// A source of events that we plug into our event loop.
let wayland_source = WaylandSource::new(event_queue).quick_insert(event_loop.handle())?;
let wayland_source = Rc::new(wayland_source);
// A source of user events.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
// User events channel.
event_loop
.handle()
.insert_source(user_events_channel, move |event, _, _| {
if let calloop::channel::Event::Msg(msg) = event {
pending_user_events_clone.borrow_mut().push(msg);
}
})?;
// An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
// Handler of window requests.
event_loop.handle().insert_source(
event_loop_awakener_source,
move |_, _, winit_state| {
shim::handle_window_requests(winit_state);
},
)?;
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_updates = HashMap::new();
// Create event loop window target.
let event_loop_window_target = EventLoopWindowTarget {
display: display.clone(),
env,
state: RefCell::new(WinitState {
window_map,
event_sink,
window_updates,
}),
event_loop_handle,
output_manager,
event_loop_awakener,
wayland_source: wayland_source.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
};
// Create event loop itself.
let event_loop = Self {
event_loop,
display,
pending_user_events,
wayland_source,
_seat_manager: seat_manager,
user_events_sender,
window_target: RootEventLoopWindowTarget {
p: crate::platform_impl::EventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
},
};
Ok(event_loop)
}
pub fn run<F>(mut self, callback: F) -> !
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
{
self.run_return(callback);
process::exit(0)
}
pub fn run_return<F>(&mut self, mut callback: F)
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
// Send pending events to the server.
let _ = self.display.flush();
let mut control_flow = ControlFlow::default();
let pending_user_events = self.pending_user_events.clone();
callback(
Event::NewEvents(StartCause::Init),
&self.window_target,
&mut control_flow,
);
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
// NOTE We break on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
loop {
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in pending_user_events.borrow_mut().drain(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// Process 'new' pending updates.
self.with_state(|state| {
window_updates.clear();
window_updates.extend(
state
.window_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, window_update.take())),
);
});
for (window_id, window_update) in window_updates.iter_mut() {
if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) {
let mut physical_size = self.with_state(|state| {
let window_handle = state.window_map.get(&window_id).unwrap();
let mut size = window_handle.size.lock().unwrap();
// Update the new logical size if it was changed.
let window_size = window_update.size.unwrap_or(*size);
*size = window_size;
window_size.to_physical(scale_factor)
});
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
},
},
&self.window_target,
&mut control_flow,
&mut callback,
);
// We don't update size on a window handle since we'll do that later
// when handling size update.
let new_logical_size = physical_size.to_logical(scale_factor);
window_update.size = Some(new_logical_size);
}
if let Some(size) = window_update.size.take() {
let physical_size = self.with_state(|state| {
let window_handle = state.window_map.get_mut(&window_id).unwrap();
let mut window_size = window_handle.size.lock().unwrap();
// Always issue resize event on scale factor change.
let physical_size =
if window_update.scale_factor.is_none() && *window_size == size {
// The size hasn't changed, don't inform downstream about that.
None
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(&window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
// We still perform all of those resize related logic even if the size
// hasn't changed, since GNOME relies on `set_geometry` calls after
// configures.
window_handle.window.resize(size.width, size.height);
window_handle.window.refresh();
// Mark that refresh isn't required, since we've done it right now.
window_update.refresh_frame = false;
physical_size
});
if let Some(physical_size) = physical_size {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
if window_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// The purpose of the back buffer and that swap is to not hold borrow_mut when
// we're doing callback to the user, since we can double borrow if the user decides
// to create a window in one of those callbacks.
self.with_state(|state| {
std::mem::swap(
&mut event_sink_back_buffer,
&mut state.event_sink.window_events,
)
});
// Handle pending window events.
for event in event_sink_back_buffer.drain(..) {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Send events cleared.
sticky_exit_callback(
Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
// Handle RedrawRequested events.
for (window_id, window_update) in window_updates.iter() {
// Handle refresh of the frame.
if window_update.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(&window_id).unwrap();
window_handle.window.refresh();
if !window_update.redraw_requested {
window_handle.window.surface().commit();
}
});
}
// Handle redraw request.
if window_update.redraw_requested {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
)),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// Send RedrawEventCleared.
sticky_exit_callback(
Event::RedrawEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
// Send pending events to the server.
let _ = self.display.flush();
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the Wayland socket, to avoid delaying the
// dispatch of these events until we're woken up again.
let instant_wakeup = {
let handle = self.event_loop.handle();
let source = self.wayland_source.clone();
let dispatched = handle.with_source(&source, |wayland_source| {
let queue = wayland_source.queue();
self.with_state(|state| {
queue.dispatch_pending(state, |_, _, _| unimplemented!())
})
});
if let Ok(dispatched) = dispatched {
dispatched > 0
} else {
break;
}
};
match control_flow {
ControlFlow::Exit => break,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
if self.loop_dispatch(Some(timeout)).is_err() {
break;
}
callback(
Event::NewEvents(StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::from_millis(0))
} else {
None
};
if self.loop_dispatch(timeout).is_err() {
break;
}
callback(
Event::NewEvents(StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// Compute the amount of time we'll block for.
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
};
if self.loop_dispatch(Some(duration)).is_err() {
break;
}
let now = Instant::now();
if now < deadline {
callback(
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}),
&self.window_target,
&mut control_flow,
)
} else {
callback(
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}),
&self.window_target,
&mut control_flow,
)
}
}
}
}
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
}
#[inline]
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.user_events_sender.clone())
}
#[inline]
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
let state = match &mut self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
_ => unreachable!(),
};
f(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(
&mut self,
timeout: D,
) -> std::io::Result<()> {
let mut state = match &mut self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop.dispatch(timeout, &mut state)
}
}

View File

@@ -0,0 +1,32 @@
//! An event loop proxy.
use std::sync::mpsc::SendError;
use sctk::reexports::calloop::channel::Sender;
use crate::event_loop::EventLoopClosed;
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
pub struct EventLoopProxy<T: 'static> {
user_events_sender: Sender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn new(user_events_sender: Sender<T>) -> Self {
Self { user_events_sender }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|SendError(error)| EventLoopClosed(error))
}
}

View File

@@ -0,0 +1,36 @@
//! An event loop's sink to deliver events from the Wayland event callbacks.
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId};
use crate::window::WindowId as RootWindowId;
use super::{DeviceId, WindowId};
/// An event loop's sink to deliver events from the Wayland event callbacks
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub window_events: Vec<Event<'static, ()>>,
}
impl EventSink {
pub fn new() -> Self {
Default::default()
}
/// Add new device event to a queue.
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
self.window_events.push(Event::DeviceEvent {
event,
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
});
}
/// Add new window event to a queue.
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(PlatformWindowId::Wayland(window_id)),
});
}
}

View File

@@ -0,0 +1,25 @@
//! A state that we pass around in a dispatch.
use std::collections::HashMap;
use super::EventSink;
use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate};
use crate::platform_impl::wayland::WindowId;
/// Wrapper to carry winit's state.
pub struct WinitState {
/// A sink for window and device events that is being filled during dispatching
/// event loop and forwarded downstream afterwards.
pub event_sink: EventSink,
/// Window updates, which are coming from SCTK or the compositor, which require
/// calling back to the winit's downstream. They are handled right in the event loop,
/// unlike the ones coming from buffers on the `WindowHandle`'s.
pub window_updates: HashMap<WindowId, WindowUpdate>,
/// Window map containing all SCTK windows. Since those windows aren't allowed
/// to be sent to other threads, they live on the event loop's thread
/// and requests from winit's windows are being forwarded to them either via
/// `WindowUpdate` or buffer on the associated with it `WindowHandle`.
pub window_map: HashMap<WindowId, WindowHandle>,
}

View File

@@ -1,397 +0,0 @@
use std::sync::{Arc, Mutex};
use super::{event_loop::EventsSink, make_wid, DeviceId};
use smithay_client_toolkit::{
keyboard::{
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
},
reexports::client::protocol::{wl_keyboard, wl_seat},
};
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
sink: EventsSink,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> wl_keyboard::WlKeyboard {
// { variables to be captured by the closures
let target = Arc::new(Mutex::new(None));
let my_sink = sink.clone();
let repeat_sink = sink.clone();
let repeat_target = target.clone();
let my_modifiers = modifiers_tracker.clone();
// }
let ret = map_keyboard_auto_with_repeat(
seat,
KeyRepeatKind::System,
move |evt: KbEvent<'_>, _| {
match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(true), wid);
*target.lock().unwrap() = Some(wid);
let modifiers = *modifiers_tracker.lock().unwrap();
if !modifiers.is_empty() {
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
}
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
let modifiers = *modifiers_tracker.lock().unwrap();
if !modifiers.is_empty() {
my_sink.send_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty()),
wid,
);
}
my_sink.send_window_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,
_ => unreachable!(),
};
let vkcode = key_to_vkey(rawkey, keysym);
my_sink.send_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
is_synthetic: false,
},
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() {
my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
}
}
}
}
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers {
modifiers: event_modifiers,
} => {
let modifiers = ModifiersState::from_wayland(event_modifiers);
*modifiers_tracker.lock().unwrap() = modifiers;
if let Some(wid) = *target.lock().unwrap() {
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
}
}
}
},
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);
repeat_sink.send_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: KeyboardInput {
state,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
},
is_synthetic: false,
},
wid,
);
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
}
}
}
},
);
match ret {
Ok(keyboard) => keyboard,
Err(_) => {
// This is a fallback impl if libxkbcommon was not available
// This case should probably never happen, as most wayland
// compositors _need_ libxkbcommon anyway...
//
// In this case, we don't have the keymap information (it is
// supposed to be serialized by the compositor using libxkbcommon)
seat.get_keyboard(|keyboard| {
// { variables to be captured by the closure
let mut target = None;
let my_sink = sink;
// }
keyboard.implement_closure(
move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(true), wid);
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_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,
_ => unreachable!(),
};
my_sink.send_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
input: KeyboardInput {
state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
is_synthetic: false,
},
wid,
);
}
}
_ => (),
},
(),
)
})
.unwrap()
}
}
}
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
match rawkey {
1 => Some(VirtualKeyCode::Escape),
2 => Some(VirtualKeyCode::Key1),
3 => Some(VirtualKeyCode::Key2),
4 => Some(VirtualKeyCode::Key3),
5 => Some(VirtualKeyCode::Key4),
6 => Some(VirtualKeyCode::Key5),
7 => Some(VirtualKeyCode::Key6),
8 => Some(VirtualKeyCode::Key7),
9 => Some(VirtualKeyCode::Key8),
10 => Some(VirtualKeyCode::Key9),
11 => Some(VirtualKeyCode::Key0),
_ => keysym_to_vkey(keysym),
}
}
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
use smithay_client_toolkit::keyboard::keysyms;
match keysym {
// letters
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
// F--
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
// flow control
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
// arrows
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
//
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
// keypad
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
// misc
// => Some(VirtualKeyCode::AbntC1),
// => Some(VirtualKeyCode::AbntC2),
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
// => Some(VirtualKeyCode::Apps),
// => Some(VirtualKeyCode::At),
// => Some(VirtualKeyCode::Ax),
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
// => Some(VirtualKeyCode::Calculator),
// => Some(VirtualKeyCode::Capital),
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
// => Some(VirtualKeyCode::Convert),
// => Some(VirtualKeyCode::Decimal),
// => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
// => Some(VirtualKeyCode::Grave),
// => Some(VirtualKeyCode::Kana),
// => Some(VirtualKeyCode::Kanji),
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
// => Some(VirtualKeyCode::LBracket),
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
// => Some(VirtualKeyCode::LWin),
// => Some(VirtualKeyCode::Mail),
// => Some(VirtualKeyCode::MediaSelect),
// => Some(VirtualKeyCode::MediaStop),
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Multiply),
// => Some(VirtualKeyCode::Mute),
// => Some(VirtualKeyCode::MyComputer),
// => Some(VirtualKeyCode::NextTrack),
// => Some(VirtualKeyCode::NoConvert),
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),
// => Some(VirtualKeyCode::Power),
// => Some(VirtualKeyCode::Prevtrack),
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
// => Some(VirtualKeyCode::RBracket),
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
// => Some(VirtualKeyCode::RWin),
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
// => Some(VirtualKeyCode::Sleep),
// => Some(VirtualKeyCode::Stop),
// => Some(VirtualKeyCode::Subtract),
// => Some(VirtualKeyCode::Sysrq),
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
// => Some(VirtualKeyCode::Underline),
// => Some(VirtualKeyCode::Unlabeled),
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
// => Some(VirtualKeyCode::Wake),
// => Some(VirtualKeyCode::Webback),
// => Some(VirtualKeyCode::WebFavorites),
// => Some(VirtualKeyCode::WebForward),
// => Some(VirtualKeyCode::WebHome),
// => Some(VirtualKeyCode::WebRefresh),
// => Some(VirtualKeyCode::WebSearch),
// => Some(VirtualKeyCode::WebStop),
// => Some(VirtualKeyCode::Yen),
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
// fallback
_ => None,
}
}
impl ModifiersState {
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, mods.shift);
m.set(ModifiersState::CTRL, mods.ctrl);
m.set(ModifiersState::ALT, mods.alt);
m.set(ModifiersState::LOGO, mods.logo);
m
}
}

View File

@@ -1,17 +1,21 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
target_os = "netbsd", target_os = "openbsd"))]
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode},
window::Window,
};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use smithay_client_toolkit::reexports::client::protocol::wl_surface;
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
mod env;
mod event_loop;
mod keyboard;
mod pointer;
mod touch;
mod output;
mod seat;
mod window;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -33,6 +37,6 @@ impl WindowId {
}
#[inline]
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
WindowId(s.as_ref().c_ptr() as usize)
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.as_ref().c_ptr() as usize)
}

View File

@@ -0,0 +1,240 @@
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Display;
use sctk::environment::Environment;
use sctk::output::OutputStatusListener;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode};
use crate::platform_impl::platform::{
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
};
use super::env::WinitEnv;
use super::event_loop::EventLoopWindowTarget;
/// Output manager.
pub struct OutputManager {
/// A handle that actually performs all operations on outputs.
handle: OutputManagerHandle,
_output_listener: OutputStatusListener,
}
impl OutputManager {
pub fn new(env: &Environment<WinitEnv>) -> Self {
let handle = OutputManagerHandle::new();
// Handle existing outputs.
for output in env.get_all_outputs() {
match sctk::output::with_output_info(&output, |info| info.obsolete) {
Some(false) => (),
// The output is obsolete or we've failed to access its data, skipping.
_ => continue,
}
// The output is present and unusable, add it to the output manager manager.
handle.add_output(output);
}
let handle_for_listener = handle.clone();
let output_listener = env.listen_for_outputs(move |output, info, _| {
if info.obsolete {
handle_for_listener.remove_output(output)
} else {
handle_for_listener.add_output(output)
}
});
Self {
handle,
_output_listener: output_listener,
}
}
pub fn handle(&self) -> OutputManagerHandle {
self.handle.clone()
}
}
/// A handle to output manager.
#[derive(Debug, Clone)]
pub struct OutputManagerHandle {
outputs: Arc<Mutex<VecDeque<MonitorHandle>>>,
}
impl OutputManagerHandle {
fn new() -> Self {
let outputs = Arc::new(Mutex::new(VecDeque::new()));
Self { outputs }
}
/// Handle addition of the output.
fn add_output(&self, output: WlOutput) {
let mut outputs = self.outputs.lock().unwrap();
let position = outputs.iter().position(|handle| handle.proxy == output);
if position.is_none() {
outputs.push_back(MonitorHandle::new(output));
}
}
/// Handle removal of the output.
fn remove_output(&self, output: WlOutput) {
let mut outputs = self.outputs.lock().unwrap();
let position = outputs.iter().position(|handle| handle.proxy == output);
if let Some(position) = position {
outputs.remove(position);
}
}
/// Get all observed outputs.
pub fn available_outputs(&self) -> VecDeque<MonitorHandle> {
self.outputs.lock().unwrap().clone()
}
}
#[derive(Clone, Debug)]
pub struct MonitorHandle {
pub(crate) proxy: WlOutput,
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.native_identifier() == other.native_identifier()
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.native_identifier().cmp(&other.native_identifier())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.native_identifier().hash(state);
}
}
impl MonitorHandle {
#[inline]
pub(crate) fn new(proxy: WlOutput) -> Self {
Self { proxy }
}
#[inline]
pub fn name(&self) -> Option<String> {
sctk::output::with_output_info(&self.proxy, |info| {
format!("{} ({})", info.model, info.make)
})
}
#[inline]
pub fn native_identifier(&self) -> u32 {
sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0)
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
match sctk::output::with_output_info(&self.proxy, |info| {
info.modes
.iter()
.find(|mode| mode.is_current)
.map(|mode| mode.dimensions)
}) {
Some(Some((w, h))) => (w as u32, h as u32),
_ => (0, 0),
}
.into()
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
sctk::output::with_output_info(&self.proxy, |info| info.location)
.unwrap_or((0, 0))
.into()
}
#[inline]
pub fn scale_factor(&self) -> i32 {
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
.unwrap_or_else(Vec::new);
let monitor = self.clone();
modes.into_iter().map(move |mode| RootVideoMode {
video_mode: PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: 32,
monitor: monitor.clone(),
}),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
}
}
}
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn display(&self) -> &Display {
&self.display
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
self.output_manager.handle.available_outputs()
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
// There's no primary monitor on Wayland.
None
}
}

View File

@@ -1,289 +0,0 @@
use std::sync::{Arc, Mutex};
use crate::dpi::LogicalPosition;
use crate::event::{
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
};
use super::{
event_loop::{CursorManager, EventsSink},
make_wid,
window::WindowStore,
DeviceId,
};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_pointer::{self, Event as PtrEvent, WlPointer},
wl_seat,
};
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event,
zwp_relative_pointer_v1::ZwpRelativePointerV1,
};
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime,
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
};
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
pub fn implement_pointer(
seat: &wl_seat::WlSeat,
sink: EventsSink,
store: Arc<Mutex<WindowStore>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
cursor_manager: Arc<Mutex<CursorManager>>,
) -> WlPointer {
seat.get_pointer(|pointer| {
// Currently focused winit surface
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
let mut axis_state = TouchPhase::Ended;
pointer.implement_closure(
move |evt, pointer| {
let store = store.lock().unwrap();
let mut cursor_manager = cursor_manager.lock().unwrap();
match evt {
PtrEvent::Enter {
surface,
surface_x,
surface_y,
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
let scale_factor = surface::get_dpi_factor(&surface) as f64;
mouse_focus = Some(surface);
// Reload cursor style only when we enter winit's surface. Calling
// this function every time on `PtrEvent::Enter` could interfere with
// SCTK CSD handling, since it changes cursor icons when you hover
// cursor over the window borders.
cursor_manager.reload_cursor_style();
sink.send_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
},
wid,
);
let position = LogicalPosition::new(surface_x, surface_y)
.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
sink.send_window_event(
WindowEvent::CursorLeft {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
},
wid,
);
}
}
PtrEvent::Motion {
surface_x,
surface_y,
..
} => {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let position = LogicalPosition::new(surface_x, surface_y)
.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(surface) = mouse_focus.as_ref() {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
_ => unreachable!(),
};
let button = match button {
0x110 => MouseButton::Left,
0x111 => MouseButton::Right,
0x112 => MouseButton::Middle,
// TODO figure out the translation ?
_ => return,
};
sink.send_window_event(
WindowEvent::MouseInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
state,
button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
make_wid(surface),
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if pointer.as_ref().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,
_ => unreachable!(),
}
sink.send_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(
crate::platform_impl::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,
_ => unreachable!(),
}
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(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if let Some((x, y)) = axis_discrete_buffer {
sink.send_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(
crate::platform_impl::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_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(
crate::platform_impl::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,
_ => unreachable!(),
}
axis_discrete_buffer = Some((x, y));
axis_state = match axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
_ => unreachable!(),
}
},
(),
)
})
.unwrap()
}
pub fn implement_relative_pointer(
sink: EventsSink,
pointer: &WlPointer,
manager: &ZwpRelativePointerManagerV1,
) -> Result<ZwpRelativePointerV1, ()> {
manager.get_relative_pointer(pointer, |rel_pointer| {
rel_pointer.implement_closure(
move |evt, _rel_pointer| match evt {
Event::RelativeMotion { dx, dy, .. } => {
sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
}
_ => unreachable!(),
},
(),
)
})
}
pub fn implement_locked_pointer(
surface: &WlSurface,
pointer: &WlPointer,
constraints: &ZwpPointerConstraintsV1,
) -> Result<ZwpLockedPointerV1, ()> {
constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| {
c.implement_closure(|_, _| (), ())
})
}

View File

@@ -0,0 +1,151 @@
//! Handling of various keyboard events.
use sctk::reexports::client::protocol::wl_keyboard::KeyState;
use sctk::seat::keyboard::Event as KeyboardEvent;
use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
use super::keymap;
use super::KeyboardInner;
#[inline]
pub(super) fn handle_keyboard(
event: KeyboardEvent<'_>,
inner: &mut KeyboardInner,
winit_state: &mut WinitState,
) {
let event_sink = &mut winit_state.event_sink;
match event {
KeyboardEvent::Enter { surface, .. } => {
let window_id = wayland::make_wid(&surface);
// Window gained focus.
event_sink.push_window_event(WindowEvent::Focused(true), window_id);
// Dispatch modifers changes that we've received before getting `Enter` event.
if let Some(modifiers) = inner.pending_modifers_state.take() {
*inner.modifiers_state.borrow_mut() = modifiers;
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
}
inner.target_window_id = Some(window_id);
}
KeyboardEvent::Leave { surface, .. } => {
let window_id = wayland::make_wid(&surface);
// Notify that no modifiers are being pressed.
if !inner.modifiers_state.borrow().is_empty() {
event_sink.push_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty()),
window_id,
);
}
// Window lost focus.
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
// Reset the id.
inner.target_window_id = None;
}
KeyboardEvent::Key {
rawkey,
keysym,
state,
utf8,
..
} => {
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
None => return,
};
let state = match state {
KeyState::Pressed => ElementState::Pressed,
KeyState::Released => ElementState::Released,
_ => unreachable!(),
};
let virtual_keycode = keymap::keysym_to_vkey(keysym);
event_sink.push_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode,
modifiers: *inner.modifiers_state.borrow(),
},
is_synthetic: false,
},
window_id,
);
// Send ReceivedCharacter event only on ElementState::Pressed.
if ElementState::Released == state {
return;
}
if let Some(txt) = utf8 {
for ch in txt.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
}
}
}
KeyboardEvent::Repeat {
rawkey,
keysym,
utf8,
..
} => {
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
None => return,
};
let virtual_keycode = keymap::keysym_to_vkey(keysym);
event_sink.push_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: KeyboardInput {
state: ElementState::Pressed,
scancode: rawkey,
virtual_keycode,
modifiers: *inner.modifiers_state.borrow(),
},
is_synthetic: false,
},
window_id,
);
if let Some(txt) = utf8 {
for ch in txt.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
}
}
}
KeyboardEvent::Modifiers { modifiers } => {
let modifiers = ModifiersState::from(modifiers);
if let Some(window_id) = inner.target_window_id {
*inner.modifiers_state.borrow_mut() = modifiers;
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
} else {
// Compositor must send modifiers after wl_keyboard::enter, however certain
// compositors are still sending it before, so stash such events and send
// them on wl_keyboard::enter.
inner.pending_modifers_state = Some(modifiers);
}
}
}
}

View File

@@ -0,0 +1,192 @@
//! Convert Wayland keys to winit keys.
use crate::event::VirtualKeyCode;
pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
use sctk::seat::keyboard::keysyms;
match keysym {
// Numbers.
keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1),
keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2),
keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3),
keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4),
keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5),
keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6),
keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7),
keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8),
keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9),
keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0),
// Letters.
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
// Escape.
keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape),
// Function keys.
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
// Flow control.
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
// Arrows.
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose),
keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret),
// Keypad.
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
// Misc.
// => Some(VirtualKeyCode::AbntC1),
// => Some(VirtualKeyCode::AbntC2),
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus),
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
// => Some(VirtualKeyCode::Apps),
keysyms::XKB_KEY_at => Some(VirtualKeyCode::At),
// => Some(VirtualKeyCode::Ax),
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator),
// => Some(VirtualKeyCode::Capital),
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
// => Some(VirtualKeyCode::Convert),
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave),
// => Some(VirtualKeyCode::Kana),
keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji),
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket),
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin),
keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail),
// => Some(VirtualKeyCode::MediaSelect),
// => Some(VirtualKeyCode::MediaStop),
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk),
keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute),
// => Some(VirtualKeyCode::MyComputer),
keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack),
// => Some(VirtualKeyCode::NoConvert),
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::NumpadAdd),
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract),
keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply),
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide),
keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal),
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),
keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left),
keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up),
keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right),
keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down),
// => Some(VirtualKeyCode::OEM102),
keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period),
// => Some(VirtualKeyCode::Playpause),
keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power),
keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack),
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket),
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin),
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep),
// => Some(VirtualKeyCode::Stop),
// => Some(VirtualKeyCode::Sysrq),
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline),
// => Some(VirtualKeyCode::Unlabeled),
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
// => Some(VirtualKeyCode::Wake),
// => Some(VirtualKeyCode::Webback),
// => Some(VirtualKeyCode::WebFavorites),
// => Some(VirtualKeyCode::WebForward),
// => Some(VirtualKeyCode::WebHome),
// => Some(VirtualKeyCode::WebRefresh),
// => Some(VirtualKeyCode::WebSearch),
// => Some(VirtualKeyCode::WebStop),
keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen),
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
// Fallback.
_ => None,
}
}

View File

@@ -0,0 +1,105 @@
//! Wayland keyboard handling.
use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::calloop::{LoopHandle, Source};
use sctk::seat::keyboard::{self, RepeatSource};
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::WindowId;
mod handlers;
mod keymap;
pub(crate) struct Keyboard {
pub keyboard: WlKeyboard,
/// The source for repeat keys.
pub repeat_source: Option<Source<RepeatSource>>,
/// LoopHandle to drop `RepeatSource`, when dropping the keyboard.
pub loop_handle: LoopHandle<WinitState>,
}
impl Keyboard {
pub fn new(
seat: &Attached<WlSeat>,
loop_handle: LoopHandle<WinitState>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Option<Self> {
let mut inner = KeyboardInner::new(modifiers_state);
let keyboard_data = keyboard::map_keyboard_repeat(
loop_handle.clone(),
&seat,
None,
keyboard::RepeatKind::System,
move |event, _, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_keyboard(event, &mut inner, winit_state);
},
);
let (keyboard, repeat_source) = keyboard_data.ok()?;
Some(Self {
keyboard,
loop_handle,
repeat_source: Some(repeat_source),
})
}
}
impl Drop for Keyboard {
fn drop(&mut self) {
if self.keyboard.as_ref().version() >= 3 {
self.keyboard.release();
}
if let Some(repeat_source) = self.repeat_source.take() {
self.loop_handle.remove(repeat_source);
}
}
}
struct KeyboardInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
/// A pending state of modifiers.
///
/// This state is getting set if we've got a modifiers update
/// before `Enter` event, which shouldn't happen in general, however
/// some compositors are still doing so.
pending_modifers_state: Option<ModifiersState>,
/// Current state of modifiers keys.
modifiers_state: Rc<RefCell<ModifiersState>>,
}
impl KeyboardInner {
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
Self {
target_window_id: None,
pending_modifers_state: None,
modifiers_state,
}
}
}
impl From<keyboard::ModifiersState> for ModifiersState {
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
let mut wl_mods = ModifiersState::empty();
wl_mods.set(ModifiersState::SHIFT, mods.shift);
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
wl_mods.set(ModifiersState::ALT, mods.alt);
wl_mods.set(ModifiersState::LOGO, mods.logo);
wl_mods
}
}

View File

@@ -0,0 +1,208 @@
//! Seat handling and managing.
use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::environment::Environment;
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::pointer::ThemeManager;
use sctk::seat::{SeatData, SeatListener};
use super::env::WinitEnv;
use super::event_loop::WinitState;
use crate::event::ModifiersState;
mod keyboard;
pub mod pointer;
pub mod text_input;
mod touch;
use keyboard::Keyboard;
use pointer::Pointers;
use text_input::TextInput;
use touch::Touch;
pub struct SeatManager {
/// Listener for seats.
_seat_listener: SeatListener,
}
impl SeatManager {
pub fn new(
env: &Environment<WinitEnv>,
loop_handle: LoopHandle<WinitState>,
theme_manager: ThemeManager,
) -> Self {
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>();
let text_input_manager = env.get_global::<ZwpTextInputManagerV3>();
let mut inner = SeatManagerInner::new(
theme_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
loop_handle,
);
// Handle existing seats.
for seat in env.get_all_seats() {
let seat_data = match sctk::seat::clone_seat_data(&seat) {
Some(seat_data) => seat_data,
None => continue,
};
inner.process_seat_update(&seat, &seat_data);
}
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
inner.process_seat_update(&seat, &seat_data);
});
Self {
_seat_listener: seat_listener,
}
}
}
/// Inner state of the seat manager.
struct SeatManagerInner {
/// Currently observed seats.
seats: Vec<SeatInfo>,
/// Loop handle.
loop_handle: LoopHandle<WinitState>,
/// Relative pointer manager.
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
/// Pointer constraints.
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
/// Text input manager.
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
/// A theme manager.
theme_manager: ThemeManager,
}
impl SeatManagerInner {
fn new(
theme_manager: ThemeManager,
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
loop_handle: LoopHandle<WinitState>,
) -> Self {
Self {
seats: Vec::new(),
loop_handle,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
theme_manager,
}
}
/// Handle seats update from the `SeatListener`.
pub fn process_seat_update(&mut self, seat: &Attached<WlSeat>, seat_data: &SeatData) {
let detached_seat = seat.detach();
let position = self.seats.iter().position(|si| si.seat == detached_seat);
let index = position.unwrap_or_else(|| {
self.seats.push(SeatInfo::new(detached_seat));
self.seats.len() - 1
});
let seat_info = &mut self.seats[index];
// Pointer handling.
if seat_data.has_pointer && !seat_data.defunct {
if seat_info.pointer.is_none() {
seat_info.pointer = Some(Pointers::new(
&seat,
&self.theme_manager,
&self.relative_pointer_manager,
&self.pointer_constraints,
seat_info.modifiers_state.clone(),
));
}
} else {
seat_info.pointer = None;
}
// Handle keyboard.
if seat_data.has_keyboard && !seat_data.defunct {
if seat_info.keyboard.is_none() {
seat_info.keyboard = Keyboard::new(
&seat,
self.loop_handle.clone(),
seat_info.modifiers_state.clone(),
);
}
} else {
seat_info.keyboard = None;
}
// Handle touch.
if seat_data.has_touch && !seat_data.defunct {
if seat_info.touch.is_none() {
seat_info.touch = Some(Touch::new(&seat));
}
} else {
seat_info.touch = None;
}
// Handle text input.
if let Some(text_input_manager) = self.text_input_manager.as_ref() {
if seat_data.defunct {
seat_info.text_input = None;
} else if seat_info.text_input.is_none() {
seat_info.text_input = Some(TextInput::new(&seat, &text_input_manager));
}
}
}
}
/// Resources associtated with a given seat.
struct SeatInfo {
/// Seat to which this `SeatInfo` belongs.
seat: WlSeat,
/// A keyboard handle with its repeat rate handling.
keyboard: Option<Keyboard>,
/// All pointers we're using on a seat.
pointer: Option<Pointers>,
/// Touch handling.
touch: Option<Touch>,
/// Text input handling aka IME.
text_input: Option<TextInput>,
/// The current state of modifiers observed in keyboard handler.
///
/// We keep modifiers state on a seat, since it's being used by pointer events as well.
modifiers_state: Rc<RefCell<ModifiersState>>,
}
impl SeatInfo {
pub fn new(seat: WlSeat) -> Self {
Self {
seat,
keyboard: None,
pointer: None,
touch: None,
text_input: None,
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
}
}
}

View File

@@ -0,0 +1,74 @@
//! Data which is used in pointer callbacks.
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use crate::event::{ModifiersState, TouchPhase};
/// A data being used by pointer handlers.
pub(super) struct PointerData {
/// Winit's surface the pointer is currently over.
pub surface: Option<WlSurface>,
/// Current modifiers state.
///
/// This refers a state of modifiers from `WlKeyboard` on
/// the given seat.
pub modifiers_state: Rc<RefCell<ModifiersState>>,
/// Pointer constraints.
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
/// A latest event serial.
pub latest_serial: Rc<Cell<u32>>,
/// The currently accumulated axis data on a pointer.
pub axis_data: AxisData,
}
impl PointerData {
pub fn new(
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
Self {
surface: None,
latest_serial: Rc::new(Cell::new(0)),
confined_pointer,
modifiers_state,
pointer_constraints,
axis_data: AxisData::new(),
}
}
}
/// Axis data.
#[derive(Clone, Copy)]
pub(super) struct AxisData {
/// Current state of the axis.
pub axis_state: TouchPhase,
/// A buffer for `PixelDelta` event.
pub axis_buffer: Option<(f32, f32)>,
/// A buffer for `LineDelta` event.
pub axis_discrete_buffer: Option<(f32, f32)>,
}
impl AxisData {
pub fn new() -> Self {
Self {
axis_state: TouchPhase::Ended,
axis_buffer: None,
axis_discrete_buffer: None,
}
}
}

View File

@@ -0,0 +1,305 @@
//! Handlers for the pointers we're using.
use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
use sctk::seat::pointer::ThemedPointer;
use crate::dpi::LogicalPosition;
use crate::event::{
DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
use super::{PointerData, WinitPointer};
// These values are comming from <linux/input-event-codes.h>.
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
#[inline]
pub(super) fn handle_pointer(
pointer: ThemedPointer,
event: PointerEvent,
pointer_data: &Rc<RefCell<PointerData>>,
winit_state: &mut WinitState,
seat: WlSeat,
) {
let event_sink = &mut winit_state.event_sink;
let mut pointer_data = pointer_data.borrow_mut();
match event {
PointerEvent::Enter {
surface,
surface_x,
surface_y,
serial,
..
} => {
pointer_data.latest_serial.replace(serial);
let window_id = wayland::make_wid(&surface);
if !winit_state.window_map.contains_key(&window_id) {
return;
}
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
pointer_data.surface = Some(surface);
// Notify window that pointer entered the surface.
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
seat,
};
window_handle.pointer_entered(winit_pointer);
event_sink.push_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
},
window_id,
);
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
event_sink.push_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
position,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
}
PointerEvent::Leave { surface, serial } => {
pointer_data.surface = None;
pointer_data.latest_serial.replace(serial);
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
// Notify a window that pointer is no longer observing it.
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
seat,
};
window_handle.pointer_left(winit_pointer);
event_sink.push_window_event(
WindowEvent::CursorLeft {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
},
window_id,
);
}
PointerEvent::Motion {
surface_x,
surface_y,
..
} => {
let surface = match pointer_data.surface.as_ref() {
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(surface);
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
event_sink.push_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
position,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
}
PointerEvent::Button {
button,
state,
serial,
..
} => {
pointer_data.latest_serial.replace(serial);
let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) {
Some(window_id) => window_id,
None => return,
};
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
_ => unreachable!(),
};
let button = match button {
BTN_LEFT => MouseButton::Left,
BTN_RIGHT => MouseButton::Right,
BTN_MIDDLE => MouseButton::Middle,
button => MouseButton::Other(button as u16),
};
event_sink.push_window_event(
WindowEvent::MouseInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
state,
button,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
}
PointerEvent::Axis { axis, value, .. } => {
let surface = match pointer_data.surface.as_ref() {
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(&surface);
if pointer.as_ref().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,
_ => unreachable!(),
}
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
event_sink.push_window_event(
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
delta: MouseScrollDelta::PixelDelta(delta),
phase: TouchPhase::Moved,
modifiers: *pointer_data.modifiers_state.borrow(),
},
window_id,
);
} else {
let (mut x, mut y) = pointer_data.axis_data.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,
_ => unreachable!(),
}
pointer_data.axis_data.axis_buffer = Some((x, y));
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
}
PointerEvent::AxisDiscrete { axis, discrete } => {
let (mut x, mut y) = pointer_data
.axis_data
.axis_discrete_buffer
.unwrap_or((0., 0.));
match axis {
// Wayland vertical sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
wl_pointer::Axis::HorizontalScroll => x += discrete as f32,
_ => unreachable!(),
}
pointer_data.axis_data.axis_discrete_buffer = Some((x, y));
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
}
PointerEvent::AxisSource { .. } => (),
PointerEvent::AxisStop { .. } => {
pointer_data.axis_data.axis_state = TouchPhase::Ended;
}
PointerEvent::Frame => {
let axis_buffer = pointer_data.axis_data.axis_buffer.take();
let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take();
let surface = match pointer_data.surface.as_ref() {
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(&surface);
let window_event = if let Some((x, y)) = axis_discrete_buffer {
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
delta: MouseScrollDelta::LineDelta(x, y),
phase: pointer_data.axis_data.axis_state,
modifiers: *pointer_data.modifiers_state.borrow(),
}
} else if let Some((x, y)) = axis_buffer {
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
WindowEvent::MouseWheel {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
delta: MouseScrollDelta::PixelDelta(delta),
phase: pointer_data.axis_data.axis_state,
modifiers: *pointer_data.modifiers_state.borrow(),
}
} else {
return;
};
event_sink.push_window_event(window_event, window_id);
}
_ => (),
}
}
#[inline]
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
if let RelativePointerEvent::RelativeMotion { dx, dy, .. } = event {
winit_state
.event_sink
.push_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
}
}

View File

@@ -0,0 +1,257 @@
//! All pointer related handling.
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::{ConceptFrame, Window};
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::window::CursorIcon;
mod data;
mod handlers;
use data::PointerData;
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
pub struct WinitPointer {
pointer: ThemedPointer,
/// Create confined pointers.
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
/// Cursor to handle confine requests.
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Latest observed serial in pointer events.
latest_serial: Rc<Cell<u32>>,
/// Seat.
seat: WlSeat,
}
impl PartialEq for WinitPointer {
fn eq(&self, other: &Self) -> bool {
*self.pointer == *other.pointer
}
}
impl Eq for WinitPointer {}
impl WinitPointer {
/// Set the cursor icon.
///
/// Providing `None` will hide the cursor.
pub fn set_cursor(&self, cursor_icon: Option<CursorIcon>) {
let cursor_icon = match cursor_icon {
Some(cursor_icon) => cursor_icon,
None => {
// Hide the cursor.
(*self.pointer).set_cursor(self.latest_serial.get(), None, 0, 0);
return;
}
};
let cursors: &[&str] = match cursor_icon {
CursorIcon::Alias => &["link"],
CursorIcon::Arrow => &["arrow"],
CursorIcon::Cell => &["plus"],
CursorIcon::Copy => &["copy"],
CursorIcon::Crosshair => &["crosshair"],
CursorIcon::Default => &["left_ptr"],
CursorIcon::Hand => &["hand"],
CursorIcon::Help => &["question_arrow"],
CursorIcon::Move => &["move"],
CursorIcon::Grab => &["openhand", "grab"],
CursorIcon::Grabbing => &["closedhand", "grabbing"],
CursorIcon::Progress => &["progress"],
CursorIcon::AllScroll => &["all-scroll"],
CursorIcon::ContextMenu => &["context-menu"],
CursorIcon::NoDrop => &["no-drop", "circle"],
CursorIcon::NotAllowed => &["crossed_circle"],
// Resize cursors
CursorIcon::EResize => &["right_side"],
CursorIcon::NResize => &["top_side"],
CursorIcon::NeResize => &["top_right_corner"],
CursorIcon::NwResize => &["top_left_corner"],
CursorIcon::SResize => &["bottom_side"],
CursorIcon::SeResize => &["bottom_right_corner"],
CursorIcon::SwResize => &["bottom_left_corner"],
CursorIcon::WResize => &["left_side"],
CursorIcon::EwResize => &["h_double_arrow"],
CursorIcon::NsResize => &["v_double_arrow"],
CursorIcon::NwseResize => &["bd_double_arrow", "size_bdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_fdiag"],
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
CursorIcon::Text => &["text", "xterm"],
CursorIcon::VerticalText => &["vertical-text"],
CursorIcon::Wait => &["watch"],
CursorIcon::ZoomIn => &["zoom-in"],
CursorIcon::ZoomOut => &["zoom-out"],
};
let serial = Some(self.latest_serial.get());
for cursor in cursors {
if self.pointer.set_cursor(cursor, serial).is_ok() {
break;
}
}
}
/// Confine the pointer to a surface.
pub fn confine(&self, surface: &WlSurface) {
let pointer_constraints = match &self.pointer_constraints {
Some(pointer_constraints) => pointer_constraints,
None => return,
};
let confined_pointer = match self.confined_pointer.upgrade() {
Some(confined_pointer) => confined_pointer,
// A pointer is gone.
None => return,
};
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
&pointer_constraints,
&surface,
&*self.pointer,
));
}
/// Tries to unconfine the pointer if the current pointer is confined.
pub fn unconfine(&self) {
let confined_pointer = match self.confined_pointer.upgrade() {
Some(confined_pointer) => confined_pointer,
// A pointer is gone.
None => return,
};
let mut confined_pointer = confined_pointer.borrow_mut();
if let Some(confined_pointer) = confined_pointer.take() {
confined_pointer.destroy();
}
}
pub fn drag_window(&self, window: &Window<ConceptFrame>) {
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
}
/// A pointer wrapper for easy releasing and managing pointers.
pub(super) struct Pointers {
/// A pointer itself.
pointer: ThemedPointer,
/// A relative pointer handler.
relative_pointer: Option<ZwpRelativePointerV1>,
/// Confined pointer.
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
}
impl Pointers {
pub(super) fn new(
seat: &Attached<WlSeat>,
theme_manager: &ThemeManager,
relative_pointer_manager: &Option<Attached<ZwpRelativePointerManagerV1>>,
pointer_constraints: &Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
let confined_pointer = Rc::new(RefCell::new(None));
let pointer_data = Rc::new(RefCell::new(PointerData::new(
confined_pointer.clone(),
pointer_constraints.clone(),
modifiers_state,
)));
let pointer_seat = seat.detach();
let pointer = theme_manager.theme_pointer_with_impl(
seat,
move |event, pointer, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_pointer(
pointer,
event,
&pointer_data,
winit_state,
pointer_seat.clone(),
);
},
);
// Setup relative_pointer if it's available.
let relative_pointer = match relative_pointer_manager.as_ref() {
Some(relative_pointer_manager) => {
Some(init_relative_pointer(&relative_pointer_manager, &*pointer))
}
None => None,
};
Self {
pointer,
relative_pointer,
confined_pointer,
}
}
}
impl Drop for Pointers {
fn drop(&mut self) {
// Drop relative pointer.
if let Some(relative_pointer) = self.relative_pointer.take() {
relative_pointer.destroy();
}
// Drop confined pointer.
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
confined_pointer.destroy();
}
// Drop the pointer itself in case it's possible.
if self.pointer.as_ref().version() >= 3 {
self.pointer.release();
}
}
}
pub(super) fn init_relative_pointer(
relative_pointer_manager: &ZwpRelativePointerManagerV1,
pointer: &WlPointer,
) -> ZwpRelativePointerV1 {
let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer);
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_relative_pointer(event, winit_state);
});
relative_pointer.detach()
}
pub(super) fn init_confined_pointer(
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
surface: &WlSurface,
pointer: &WlPointer,
) -> ZwpConfinedPointerV1 {
let confined_pointer =
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent.to_raw());
confined_pointer.quick_assign(move |_, _, _| {});
confined_pointer.detach()
}

View File

@@ -0,0 +1,78 @@
//! Handling of IME events.
use sctk::reexports::client::Main;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::WindowEvent;
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::event_loop::WinitState;
use super::{TextInputHandler, TextInputInner};
#[inline]
pub(super) fn handle_text_input(
text_input: Main<ZwpTextInputV3>,
inner: &mut TextInputInner,
event: TextInputEvent,
winit_state: &mut WinitState,
) {
let event_sink = &mut winit_state.event_sink;
match event {
TextInputEvent::Enter { surface } => {
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
inner.target_window_id = Some(window_id);
// Enable text input on that surface.
text_input.enable();
text_input.commit();
// Notify a window we're currently over about text input handler.
let text_input_handler = TextInputHandler {
text_input: text_input.detach(),
};
window_handle.text_input_entered(text_input_handler);
}
TextInputEvent::Leave { surface } => {
// Always issue a disable.
text_input.disable();
text_input.commit();
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
inner.target_window_id = None;
// Remove text input handler from the window we're leaving.
let text_input_handler = TextInputHandler {
text_input: text_input.detach(),
};
window_handle.text_input_left(text_input_handler);
}
TextInputEvent::CommitString { text } => {
// Update currenly commited string.
inner.commit_string = text;
}
TextInputEvent::Done { .. } => {
let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) {
(Some(window_id), Some(text)) => (window_id, text),
_ => return,
};
for ch in text.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
}
}
_ => (),
}
}

View File

@@ -0,0 +1,66 @@
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::WindowId;
mod handlers;
/// A handler for text input that we're advertising for `WindowHandle`.
#[derive(Eq, PartialEq)]
pub struct TextInputHandler {
text_input: ZwpTextInputV3,
}
impl TextInputHandler {
#[inline]
pub fn set_ime_position(&self, x: i32, y: i32) {
self.text_input.set_cursor_rectangle(x, y, 0, 0);
self.text_input.commit();
}
}
/// A wrapper around text input to automatically destroy the object on `Drop`.
pub struct TextInput {
text_input: Attached<ZwpTextInputV3>,
}
impl TextInput {
pub fn new(seat: &Attached<WlSeat>, text_input_manager: &ZwpTextInputManagerV3) -> Self {
let text_input = text_input_manager.get_text_input(seat);
let mut text_input_inner = TextInputInner::new();
text_input.quick_assign(move |text_input, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state);
});
let text_input: Attached<ZwpTextInputV3> = text_input.into();
Self { text_input }
}
}
impl Drop for TextInput {
fn drop(&mut self) {
self.text_input.destroy();
}
}
struct TextInputInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
/// Pending string to commit.
commit_string: Option<String>,
}
impl TextInputInner {
fn new() -> Self {
Self {
target_window_id: None,
commit_string: None,
}
}
}

View File

@@ -0,0 +1,122 @@
//! Various handlers for touch events.
use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent;
use crate::dpi::LogicalPosition;
use crate::event::{TouchPhase, WindowEvent};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
use super::{TouchInner, TouchPoint};
/// Handle WlTouch events.
#[inline]
pub(super) fn handle_touch(
event: TouchEvent,
inner: &mut TouchInner,
winit_state: &mut WinitState,
) {
let event_sink = &mut winit_state.event_sink;
match event {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let window_id = wayland::make_wid(&surface);
if !winit_state.window_map.contains_key(&window_id) {
return;
}
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let position = LogicalPosition::new(x, y);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Started,
location: position.to_physical(scale_factor),
force: None, // TODO
id: id as u64,
}),
window_id,
);
inner
.touch_points
.push(TouchPoint::new(surface, position, id));
}
TouchEvent::Up { id, .. } => {
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
Some(touch_point) => touch_point,
None => return,
};
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
let location = touch_point.position.to_physical(scale_factor);
let window_id = wayland::make_wid(&touch_point.surface);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Ended,
location,
force: None, // TODO
id: id as u64,
}),
window_id,
);
}
TouchEvent::Motion { id, x, y, .. } => {
let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) {
Some(touch_point) => touch_point,
None => return,
};
touch_point.position = LogicalPosition::new(x, y);
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
let location = touch_point.position.to_physical(scale_factor);
let window_id = wayland::make_wid(&touch_point.surface);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Moved,
location,
force: None, // TODO
id: id as u64,
}),
window_id,
);
}
TouchEvent::Frame => (),
TouchEvent::Cancel => {
for touch_point in inner.touch_points.drain(..) {
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
let location = touch_point.position.to_physical(scale_factor);
let window_id = wayland::make_wid(&touch_point.surface);
event_sink.push_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
location,
force: None, // TODO
id: touch_point.id as u64,
}),
window_id,
);
}
}
_ => (),
}
}

View File

@@ -0,0 +1,78 @@
//! Touch handling.
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::Attached;
use crate::dpi::LogicalPosition;
use crate::platform_impl::wayland::event_loop::WinitState;
mod handlers;
/// Wrapper around touch to handle release.
pub struct Touch {
/// Proxy to touch.
touch: WlTouch,
}
impl Touch {
pub fn new(seat: &Attached<WlSeat>) -> Self {
let touch = seat.get_touch();
let mut inner = TouchInner::new();
touch.quick_assign(move |_, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_touch(event, &mut inner, winit_state);
});
Self {
touch: touch.detach(),
}
}
}
impl Drop for Touch {
fn drop(&mut self) {
if self.touch.as_ref().version() >= 3 {
self.touch.release();
}
}
}
/// The data used by touch handlers.
pub(super) struct TouchInner {
/// Current touch points.
touch_points: Vec<TouchPoint>,
}
impl TouchInner {
fn new() -> Self {
Self {
touch_points: Vec::new(),
}
}
}
/// Location of touch press.
pub(super) struct TouchPoint {
/// A surface where the touch point is located.
surface: WlSurface,
/// Location of the touch point.
position: LogicalPosition<f64>,
/// Id.
id: i32,
}
impl TouchPoint {
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
Self {
surface,
position,
id,
}
}
}

View File

@@ -1,132 +0,0 @@
use std::sync::{Arc, Mutex};
use crate::dpi::LogicalPosition;
use crate::event::{TouchPhase, WindowEvent};
use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_seat,
wl_surface::WlSurface,
wl_touch::{Event as TouchEvent, WlTouch},
};
// location is in logical coordinates.
struct TouchPoint {
surface: WlSurface,
position: LogicalPosition<f64>,
id: i32,
}
pub(crate) fn implement_touch(
seat: &wl_seat::WlSeat,
sink: EventsSink,
store: Arc<Mutex<WindowStore>>,
) -> WlTouch {
let mut pending_ids = Vec::new();
seat.get_touch(|touch| {
touch.implement_closure(
move |evt, _| {
let store = store.lock().unwrap();
match evt {
TouchEvent::Down {
surface, id, x, y, ..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let position = LogicalPosition::new(x, y);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Started,
location: position.to_physical(scale_factor),
force: None, // TODO
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
surface,
position,
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);
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Ended,
location,
force: None, // TODO
id: id as u64,
}),
make_wid(&pt.surface),
);
}
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.position = LogicalPosition::new(x, y);
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Moved,
location,
force: None, // TODO
id: id as u64,
}),
make_wid(&pt.surface),
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => {
for pt in pending_ids.drain(..) {
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Cancelled,
location,
force: None, // TODO
id: pt.id as u64,
}),
make_wid(&pt.surface),
);
}
}
_ => unreachable!(),
}
},
(),
)
})
.unwrap()
}

View File

@@ -1,555 +0,0 @@
use raw_window_handle::unix::WaylandHandle;
use std::{
collections::VecDeque,
mem::replace,
sync::{Arc, Mutex, Weak},
};
use crate::{
dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::{
platform::wayland::event_loop::{available_monitors, primary_monitor},
MonitorHandle as PlatformMonitorHandle,
PlatformSpecificWindowBuilderAttributes as PlAttributes,
},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
use smithay_client_toolkit::{
output::OutputMgr,
reexports::client::{
protocol::{wl_seat, wl_surface},
Display,
},
surface::{get_dpi_factor, get_outputs},
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
};
use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
pub struct Window {
surface: wl_surface::WlSurface,
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
cursor_manager: Arc<Mutex<CursorManager>>,
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>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
decorated: Arc<Mutex<bool>>,
}
#[derive(Clone, Copy, Debug)]
pub enum DecorationsAction {
Hide,
Show,
}
impl Window {
pub fn new<T>(
evlp: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
pl_attribs: PlAttributes,
) -> Result<Window, RootOsError> {
// Create the surface first to get initial DPI
let window_store = evlp.store.clone();
let cursor_manager = evlp.cursor_manager.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
window_store.lock().unwrap().dpi_change(&surface, dpi);
surface.set_buffer_scale(dpi);
});
let dpi = get_dpi_factor(&surface) as f64;
let (width, height) = attributes
.inner_size
.map(|size| size.to_logical::<f64>(dpi).into())
.unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let decorated = Arc::new(Mutex::new(attributes.decorations));
let pending_decorations_action = Arc::new(Mutex::new(None));
let my_surface = surface.clone();
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
surface.clone(),
(width, height),
move |event| match event {
WEvent::Configure { new_size, states } => {
let mut store = window_store.lock().unwrap();
let is_fullscreen = states.contains(&WState::Fullscreen);
for window in &mut store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.newsize = new_size;
*(window.need_refresh.lock().unwrap()) = true;
{
// Get whether we're in fullscreen
let mut fullscreen = window.fullscreen.lock().unwrap();
// Fullscreen state was changed, so update decorations
if *fullscreen != is_fullscreen {
let decorated = { *window.decorated.lock().unwrap() };
if decorated {
*window.pending_decorations_action.lock().unwrap() =
if is_fullscreen {
Some(DecorationsAction::Hide)
} else {
Some(DecorationsAction::Show)
};
}
}
*fullscreen = is_fullscreen;
}
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
}
}
WEvent::Refresh => {
let store = window_store.lock().unwrap();
for window in &store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
}
}
WEvent::Close => {
let mut store = window_store.lock().unwrap();
for window in &mut store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.closed = true;
return;
}
}
}
},
)
.unwrap();
if let Some(app_id) = pl_attribs.app_id {
frame.set_app_id(app_id);
}
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
frame.new_seat(seat);
}
// Check for fullscreen requirements
match attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => {
panic!("Wayland doesn't support exclusive fullscreen")
}
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => {
if attributes.maximized {
frame.set_maximized();
}
}
}
frame.set_resizable(attributes.resizable);
// set decorations
frame.set_decorate(attributes.decorations);
// set title
frame.set_title(attributes.title);
// min-max dimensions
frame.set_min_size(
attributes
.min_inner_size
.map(|size| size.to_logical::<f64>(dpi).into()),
);
frame.set_max_size(
attributes
.max_inner_size
.map(|size| size.to_logical::<f64>(dpi).into()),
);
let kill_switch = Arc::new(Mutex::new(false));
let need_frame_refresh = Arc::new(Mutex::new(true));
let frame = Arc::new(Mutex::new(frame));
let need_refresh = Arc::new(Mutex::new(true));
let cursor_grab_changed = Arc::new(Mutex::new(None));
evlp.store.lock().unwrap().windows.push(InternalWindow {
closed: false,
newsize: None,
size: size.clone(),
need_refresh: need_refresh.clone(),
fullscreen: fullscreen.clone(),
cursor_grab_changed: cursor_grab_changed.clone(),
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,
decorated: decorated.clone(),
pending_decorations_action: pending_decorations_action.clone(),
});
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
Ok(Window {
display: evlp.display.clone(),
surface,
frame,
outputs: evlp.env.outputs.clone(),
size,
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
need_frame_refresh,
need_refresh,
cursor_manager,
fullscreen,
cursor_grab_changed,
decorated,
})
}
#[inline]
pub fn id(&self) -> WindowId {
make_wid(&self.surface)
}
pub fn set_title(&self, title: &str) {
self.frame.lock().unwrap().set_title(title.into());
}
pub fn set_visible(&self, _visible: bool) {
// TODO
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _pos: Position) {
// Not possible with wayland
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
let dpi = self.scale_factor() as f64;
let size = LogicalSize::<f64>::from(*self.size.lock().unwrap());
size.to_physical(dpi)
}
pub fn request_redraw(&self) {
*self.need_refresh.lock().unwrap() = true;
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
let dpi = self.scale_factor() as f64;
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
let size = LogicalSize::<f64>::from((w, h));
size.to_physical(dpi)
}
#[inline]
// NOTE: This will only resize the borders, the contents must be updated by the user
pub fn set_inner_size(&self, size: Size) {
let dpi = self.scale_factor() as f64;
let (w, h) = size.to_logical::<u32>(dpi).into();
self.frame.lock().unwrap().resize(w, h);
*(self.size.lock().unwrap()) = (w, h);
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
let dpi = self.scale_factor() as f64;
self.frame
.lock()
.unwrap()
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let dpi = self.scale_factor() as f64;
self.frame
.lock()
.unwrap()
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.frame.lock().unwrap().set_resizable(resizable);
}
#[inline]
pub fn scale_factor(&self) -> i32 {
get_dpi_factor(&self.surface)
}
pub fn set_decorations(&self, decorate: bool) {
*(self.decorated.lock().unwrap()) = decorate;
self.frame.lock().unwrap().set_decorate(decorate);
*(self.need_frame_refresh.lock().unwrap()) = true;
}
pub fn set_minimized(&self, minimized: bool) {
// An app cannot un-minimize itself on Wayland
if minimized {
self.frame.lock().unwrap().set_minimized();
}
}
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.frame.lock().unwrap().set_maximized();
} else {
self.frame.lock().unwrap().unset_maximized();
}
}
pub fn fullscreen(&self) -> Option<Fullscreen> {
if *(self.fullscreen.lock().unwrap()) {
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
}))
} else {
None
}
}
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
match fullscreen {
Some(Fullscreen::Exclusive(_)) => {
panic!("Wayland doesn't support exclusive fullscreen")
}
Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
})) => {
self.frame
.lock()
.unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
}
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => self.frame.lock().unwrap().unset_fullscreen(),
}
}
pub fn set_theme<T: Theme>(&self, theme: T) {
self.frame.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let mut cursor_manager = self.cursor_manager.lock().unwrap();
cursor_manager.set_cursor_icon(cursor);
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let mut cursor_manager = self.cursor_manager.lock().unwrap();
cursor_manager.set_cursor_visible(visible);
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
*self.cursor_grab_changed.lock().unwrap() = Some(grab);
Ok(())
}
#[inline]
pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn display(&self) -> &Display {
&*self.display
}
pub fn surface(&self) -> &wl_surface::WlSurface {
&self.surface
}
pub fn current_monitor(&self) -> MonitorHandle {
let output = get_outputs(&self.surface).last().unwrap().clone();
MonitorHandle {
proxy: output,
mgr: self.outputs.clone(),
}
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
available_monitors(&self.outputs)
}
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor(&self.outputs)
}
pub fn raw_window_handle(&self) -> WaylandHandle {
WaylandHandle {
surface: self.surface().as_ref().c_ptr() as *mut _,
display: self.display().as_ref().c_ptr() as *mut _,
..WaylandHandle::empty()
}
}
}
impl Drop for Window {
fn drop(&mut self) {
*(self.kill_switch.0.lock().unwrap()) = true;
*(self.kill_switch.1.lock().unwrap()) = true;
}
}
/*
* Internal store for windows
*/
struct InternalWindow {
surface: wl_surface::WlSurface,
// TODO: CONVERT TO LogicalSize<u32>s
newsize: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
need_frame_refresh: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>,
decorated: Arc<Mutex<bool>>,
pending_decorations_action: Arc<Mutex<Option<DecorationsAction>>>,
}
pub struct WindowStore {
windows: Vec<InternalWindow>,
}
pub struct WindowStoreForEach<'a> {
pub newsize: Option<(u32, u32)>,
pub size: &'a mut (u32, u32),
pub prev_dpi: i32,
pub new_dpi: Option<i32>,
pub closed: bool,
pub grab_cursor: Option<bool>,
pub surface: &'a wl_surface::WlSurface,
pub wid: WindowId,
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
pub decorations_action: Option<DecorationsAction>,
}
impl WindowStore {
pub fn new() -> WindowStore {
WindowStore {
windows: Vec::new(),
}
}
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
for window in &self.windows {
if surface.as_ref().equals(&window.surface.as_ref()) {
return Some(make_wid(surface));
}
}
None
}
pub fn cleanup(&mut self) -> Vec<WindowId> {
let mut pruned = Vec::new();
self.windows.retain(|w| {
if *w.kill_switch.lock().unwrap() {
// window is dead, cleanup
pruned.push(make_wid(&w.surface));
w.surface.destroy();
false
} else {
true
}
});
pruned
}
pub fn new_seat(&self, seat: &wl_seat::WlSeat) {
for window in &self.windows {
if let Some(w) = window.frame.upgrade() {
w.lock().unwrap().new_seat(seat);
}
}
}
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
for window in &mut self.windows {
if surface.as_ref().equals(&window.surface.as_ref()) {
window.new_dpi = Some(new);
}
}
}
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(WindowStoreForEach<'_>),
{
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());
let mut size = { *window.size.lock().unwrap() };
let decorations_action = { window.pending_decorations_action.lock().unwrap().take() };
f(WindowStoreForEach {
newsize: window.newsize.take(),
size: &mut size,
prev_dpi: window.current_dpi,
new_dpi: window.new_dpi,
closed: window.closed,
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
surface: &window.surface,
wid: make_wid(&window.surface),
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
decorations_action,
});
*window.size.lock().unwrap() = size;
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
// avoid re-spamming the event
window.closed = false;
}
}
pub fn for_each_redraw_trigger<F>(&mut self, mut f: F)
where
F: FnMut(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(
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
}
}
}

View File

@@ -0,0 +1,668 @@
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use sctk::window::{
ARGBColor, ButtonColorSpec, ColorSpec, ConceptConfig, ConceptFrame, Decorations,
};
use raw_window_handle::unix::WaylandHandle;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::unix::{ARGBColor as LocalARGBColor, Button, ButtonState, Element, Theme};
use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{CursorIcon, Fullscreen, WindowAttributes};
use super::env::WindowingFeatures;
use super::event_loop::WinitState;
use super::output::{MonitorHandle, OutputManagerHandle};
use super::{EventLoopWindowTarget, WindowId};
pub mod shim;
use shim::{WindowHandle, WindowRequest, WindowUpdate};
pub struct Window {
/// Window id.
window_id: WindowId,
/// The Wayland display.
display: Display,
/// The underlying wl_surface.
surface: WlSurface,
/// The current window size.
size: Arc<Mutex<LogicalSize<u32>>>,
/// A handle to output manager.
output_manager_handle: OutputManagerHandle,
/// Event loop proxy to wake it up.
event_loop_awakener: calloop::ping::Ping,
/// Fullscreen state.
fullscreen: Arc<AtomicBool>,
/// Available windowing features.
windowing_features: WindowingFeatures,
/// Requests that SCTK window should perform.
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
}
impl Window {
pub fn new<T>(
event_loop_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
platform_attributes: PlatformAttributes,
) -> Result<Self, RootOsError> {
let surface = event_loop_window_target
.env
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
// Get the window that receiced the event.
let window_id = super::make_wid(&surface);
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
// Set pending scale factor.
window_update.scale_factor = Some(scale);
window_update.redraw_requested = true;
surface.set_buffer_scale(scale);
})
.detach();
let scale_factor = sctk::get_surface_scale_factor(&surface);
let window_id = super::make_wid(&surface);
let fullscreen = Arc::new(AtomicBool::new(false));
let fullscreen_clone = fullscreen.clone();
let (width, height) = attributes
.inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
.unwrap_or((800, 600));
let theme_manager = event_loop_window_target.theme_manager.clone();
let mut window = event_loop_window_target
.env
.create_window::<ConceptFrame, _>(
surface.clone(),
Some(theme_manager),
(width, height),
move |event, mut dispatch_data| {
use sctk::window::{Event, State};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
match event {
Event::Refresh => {
window_update.refresh_frame = true;
}
Event::Configure { new_size, states } => {
let is_fullscreen = states.contains(&State::Fullscreen);
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
window_update.refresh_frame = true;
window_update.redraw_requested = true;
if let Some((w, h)) = new_size {
window_update.size = Some(LogicalSize::new(w, h));
}
}
Event::Close => {
window_update.close_window = true;
}
}
},
)
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
// Set decorations.
if attributes.decorations {
window.set_decorate(Decorations::FollowServer);
} else {
window.set_decorate(Decorations::None);
}
// Min dimensions.
let min_size = attributes
.min_inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
window.set_min_size(min_size);
// Max dimensions.
let max_size = attributes
.max_inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
window.set_max_size(max_size);
// Set Wayland specific window attributes.
if let Some(app_id) = platform_attributes.app_id {
window.set_app_id(app_id);
}
// Set common window attributes.
//
// We set resizable after other attributes, since it touches min and max size under
// the hood.
window.set_resizable(attributes.resizable);
window.set_title(attributes.title);
// Set fullscreen/maximized if so was requested.
match attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland")
}
Some(Fullscreen::Borderless(monitor)) => {
let monitor =
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(feature = "x11")]
PlatformMonitorHandle::X(_) => None,
});
window.set_fullscreen(monitor.as_ref());
}
None => {
if attributes.maximized {
window.set_maximized();
}
}
}
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
// We should trigger redraw and commit the surface for the newly created window.
let mut window_update = WindowUpdate::new();
window_update.refresh_frame = true;
window_update.redraw_requested = true;
let window_id = super::make_wid(&surface);
let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64)));
// Create a handle that performs all the requests on underlying sctk a window.
let window_handle = WindowHandle::new(window, size.clone(), window_requests.clone());
let mut winit_state = event_loop_window_target.state.borrow_mut();
winit_state.window_map.insert(window_id, window_handle);
winit_state
.window_updates
.insert(window_id, WindowUpdate::new());
let windowing_features = event_loop_window_target.windowing_features;
// Send all updates to the server.
let wayland_source = &event_loop_window_target.wayland_source;
let event_loop_handle = &event_loop_window_target.event_loop_handle;
// To make our window usable for drawing right away we must `ack` a `configure`
// from the server, the acking part here is done by SCTK window frame, so we just
// need to sync with server so it'll be done automatically for us.
event_loop_handle.with_source(&wayland_source, |event_queue| {
let event_queue = event_queue.queue();
let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!());
});
// We all praise GNOME for these 3 lines of pure magic. If we don't do that,
// GNOME will shrink our window a bit for the size of the decorations. I guess it
// happens because we haven't committed them with buffers to the server.
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
window_handle.window.refresh();
let output_manager_handle = event_loop_window_target.output_manager.handle();
let window = Self {
window_id,
surface,
display: event_loop_window_target.display.clone(),
output_manager_handle,
size,
window_requests,
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
fullscreen,
windowing_features,
};
Ok(window)
}
}
impl Window {
#[inline]
pub fn id(&self) -> WindowId {
self.window_id
}
#[inline]
pub fn set_title(&self, title: &str) {
let title_request = WindowRequest::Title(title.to_owned());
self.window_requests.lock().unwrap().push(title_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_visible(&self, _visible: bool) {
// Not possible on Wayland.
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _: Position) {
// Not possible on Wayland.
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.size
.lock()
.unwrap()
.to_physical(self.scale_factor() as f64)
}
#[inline]
pub fn request_redraw(&self) {
let redraw_request = WindowRequest::Redraw;
self.window_requests.lock().unwrap().push(redraw_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
self.size
.lock()
.unwrap()
.to_physical(self.scale_factor() as f64)
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
let scale_factor = self.scale_factor() as f64;
let size = size.to_logical::<u32>(scale_factor);
*self.size.lock().unwrap() = size;
let frame_size_request = WindowRequest::FrameSize(size);
self.window_requests
.lock()
.unwrap()
.push(frame_size_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
let scale_factor = self.scale_factor() as f64;
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
let min_size_request = WindowRequest::MinSize(size);
self.window_requests.lock().unwrap().push(min_size_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let scale_factor = self.scale_factor() as f64;
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
let max_size_request = WindowRequest::MaxSize(size);
self.window_requests.lock().unwrap().push(max_size_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
let resizeable_request = WindowRequest::Resizeable(resizable);
self.window_requests
.lock()
.unwrap()
.push(resizeable_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn scale_factor(&self) -> u32 {
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
// u32 conversion is safe.
sctk::get_surface_scale_factor(&self.surface) as u32
}
#[inline]
pub fn set_decorations(&self, decorate: bool) {
let decorate_request = WindowRequest::Decorate(decorate);
self.window_requests.lock().unwrap().push(decorate_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
// You can't unminimize the window on Wayland.
if !minimized {
return;
}
let minimize_request = WindowRequest::Minimize;
self.window_requests.lock().unwrap().push(minimize_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let maximize_request = WindowRequest::Maximize(maximized);
self.window_requests.lock().unwrap().push(maximize_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
if self.fullscreen.load(Ordering::Relaxed) {
let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(monitor),
});
Some(Fullscreen::Borderless(current_monitor))
} else {
None
}
}
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
let fullscreen_request = match fullscreen {
Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
return;
}
Some(Fullscreen::Borderless(monitor)) => {
let monitor =
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(feature = "x11")]
PlatformMonitorHandle::X(_) => None,
});
WindowRequest::Fullscreen(monitor)
}
None => WindowRequest::UnsetFullscreen,
};
self.window_requests
.lock()
.unwrap()
.push(fullscreen_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_theme<T: Theme>(&self, theme: T) {
// First buttons is minimize, then maximize, and then close.
let buttons: Vec<(ButtonColorSpec, ButtonColorSpec)> =
[Button::Minimize, Button::Maximize, Button::Close]
.iter()
.map(|button| {
let button = *button;
let idle_active_bg = theme
.button_color(button, ButtonState::Idle, false, true)
.into();
let idle_inactive_bg = theme
.button_color(button, ButtonState::Idle, false, false)
.into();
let idle_active_icon = theme
.button_color(button, ButtonState::Idle, true, true)
.into();
let idle_inactive_icon = theme
.button_color(button, ButtonState::Idle, true, false)
.into();
let idle_bg = ColorSpec {
active: idle_active_bg,
inactive: idle_inactive_bg,
};
let idle_icon = ColorSpec {
active: idle_active_icon,
inactive: idle_inactive_icon,
};
let hovered_active_bg = theme
.button_color(button, ButtonState::Hovered, false, true)
.into();
let hovered_inactive_bg = theme
.button_color(button, ButtonState::Hovered, false, false)
.into();
let hovered_active_icon = theme
.button_color(button, ButtonState::Hovered, true, true)
.into();
let hovered_inactive_icon = theme
.button_color(button, ButtonState::Hovered, true, false)
.into();
let hovered_bg = ColorSpec {
active: hovered_active_bg,
inactive: hovered_inactive_bg,
};
let hovered_icon = ColorSpec {
active: hovered_active_icon,
inactive: hovered_inactive_icon,
};
let disabled_active_bg = theme
.button_color(button, ButtonState::Disabled, false, true)
.into();
let disabled_inactive_bg = theme
.button_color(button, ButtonState::Disabled, false, false)
.into();
let disabled_active_icon = theme
.button_color(button, ButtonState::Disabled, true, true)
.into();
let disabled_inactive_icon = theme
.button_color(button, ButtonState::Disabled, true, false)
.into();
let disabled_bg = ColorSpec {
active: disabled_active_bg,
inactive: disabled_inactive_bg,
};
let disabled_icon = ColorSpec {
active: disabled_active_icon,
inactive: disabled_inactive_icon,
};
let button_bg = ButtonColorSpec {
idle: idle_bg,
hovered: hovered_bg,
disabled: disabled_bg,
};
let button_icon = ButtonColorSpec {
idle: idle_icon,
hovered: hovered_icon,
disabled: disabled_icon,
};
(button_icon, button_bg)
})
.collect();
let minimize_button = Some(buttons[0]);
let maximize_button = Some(buttons[1]);
let close_button = Some(buttons[2]);
// The first color is bar, then separator, and then text color.
let titlebar_colors: Vec<ColorSpec> = [Element::Bar, Element::Separator, Element::Text]
.iter()
.map(|element| {
let element = *element;
let active = theme.element_color(element, true).into();
let inactive = theme.element_color(element, false).into();
ColorSpec { active, inactive }
})
.collect();
let primary_color = titlebar_colors[0];
let secondary_color = titlebar_colors[1];
let title_color = titlebar_colors[2];
let title_font = theme.font();
let concept_config = ConceptConfig {
primary_color,
secondary_color,
title_color,
title_font,
minimize_button,
maximize_button,
close_button,
};
let theme_request = WindowRequest::Theme(concept_config);
self.window_requests.lock().unwrap().push(theme_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let cursor_icon_request = WindowRequest::NewCursorIcon(cursor);
self.window_requests
.lock()
.unwrap()
.push(cursor_icon_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let cursor_visible_request = WindowRequest::ShowCursor(visible);
self.window_requests
.lock()
.unwrap()
.push(cursor_visible_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
if !self.windowing_features.cursor_grab() {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
let cursor_grab_request = WindowRequest::GrabCursor(grab);
self.window_requests
.lock()
.unwrap()
.push(cursor_grab_request);
self.event_loop_awakener.ping();
Ok(())
}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> {
// XXX This is possible if the locked pointer is being used. We don't have any
// API for that right now, but it could be added in
// https://github.com/rust-windowing/winit/issues/1677.
//
// This function is essential for the locked pointer API.
//
// See pointer-constraints-unstable-v1.xml.
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
let drag_window_request = WindowRequest::DragWindow;
self.window_requests
.lock()
.unwrap()
.push(drag_window_request);
self.event_loop_awakener.ping();
Ok(())
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
let ime_position_request = WindowRequest::IMEPosition(position);
self.window_requests
.lock()
.unwrap()
.push(ime_position_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn display(&self) -> &Display {
&self.display
}
#[inline]
pub fn surface(&self) -> &WlSurface {
&self.surface
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
let output = sctk::get_surface_outputs(&self.surface).last()?.clone();
Some(MonitorHandle::new(output))
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
self.output_manager_handle.available_outputs()
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None
}
#[inline]
pub fn raw_window_handle(&self) -> WaylandHandle {
let display = self.display.get_display_ptr() as *mut _;
let surface = self.surface.as_ref().c_ptr() as *mut _;
WaylandHandle {
display,
surface,
..WaylandHandle::empty()
}
}
}
impl From<LocalARGBColor> for ARGBColor {
fn from(color: LocalARGBColor) -> Self {
let a = color.a;
let r = color.r;
let g = color.g;
let b = color.b;
Self { a, r, g, b }
}
}
impl Drop for Window {
fn drop(&mut self) {
let close_request = WindowRequest::Close;
self.window_requests.lock().unwrap().push(close_request);
self.event_loop_awakener.ping();
}
}

View File

@@ -0,0 +1,400 @@
use std::cell::Cell;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::window::{ConceptConfig, ConceptFrame, Decorations, Window};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::WindowEvent;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId;
use crate::window::CursorIcon;
/// A request to SCTK window from Winit window.
#[derive(Debug, Clone)]
pub enum WindowRequest {
/// Set fullscreen.
///
/// Passing `None` will set it on the current monitor.
Fullscreen(Option<WlOutput>),
/// Unset fullscreen.
UnsetFullscreen,
/// Show cursor for the certain window or not.
ShowCursor(bool),
/// Change the cursor icon.
NewCursorIcon(CursorIcon),
/// Grab cursor.
GrabCursor(bool),
/// Drag window.
DragWindow,
/// Maximize the window.
Maximize(bool),
/// Minimize the window.
Minimize,
/// Request decorations change.
Decorate(bool),
/// Make the window resizeable.
Resizeable(bool),
/// Set the title for window.
Title(String),
/// Min size.
MinSize(Option<LogicalSize<u32>>),
/// Max size.
MaxSize(Option<LogicalSize<u32>>),
/// New frame size.
FrameSize(LogicalSize<u32>),
/// Set IME window position.
IMEPosition(LogicalPosition<u32>),
/// Redraw was requested.
Redraw,
/// A new theme for a concept frame was requested.
Theme(ConceptConfig),
/// Window should be closed.
Close,
}
/// Pending update to a window from SCTK window.
#[derive(Debug, Clone, Copy)]
pub struct WindowUpdate {
/// New window size.
pub size: Option<LogicalSize<u32>>,
/// New scale factor.
pub scale_factor: Option<i32>,
/// Whether `redraw` was requested.
pub redraw_requested: bool,
/// Wether the frame should be refreshed.
pub refresh_frame: bool,
/// Close the window.
pub close_window: bool,
}
impl WindowUpdate {
pub fn new() -> Self {
Self {
size: None,
scale_factor: None,
redraw_requested: false,
refresh_frame: false,
close_window: false,
}
}
pub fn take(&mut self) -> Self {
let size = self.size.take();
let scale_factor = self.scale_factor.take();
let redraw_requested = self.redraw_requested;
self.redraw_requested = false;
let refresh_frame = self.refresh_frame;
self.refresh_frame = false;
let close_window = self.close_window;
self.close_window = false;
Self {
size,
scale_factor,
redraw_requested,
refresh_frame,
close_window,
}
}
}
/// A handle to perform operations on SCTK window
/// and react to events.
pub struct WindowHandle {
/// An actual window.
pub window: Window<ConceptFrame>,
/// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>,
/// A pending requests to SCTK window.
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
/// Current cursor icon.
pub cursor_icon: Cell<CursorIcon>,
/// Visible cursor or not.
cursor_visible: Cell<bool>,
/// Cursor confined to the surface.
confined: Cell<bool>,
/// Pointers over the current surface.
pointers: Vec<WinitPointer>,
/// Text inputs on the current surface.
text_inputs: Vec<TextInputHandler>,
}
impl WindowHandle {
pub fn new(
window: Window<ConceptFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
Self {
window,
size,
pending_window_requests,
cursor_icon: Cell::new(CursorIcon::Default),
confined: Cell::new(false),
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
}
}
pub fn set_cursor_grab(&self, grab: bool) {
// The new requested state matches the current confine status, return.
if self.confined.get() == grab {
return;
}
self.confined.replace(grab);
for pointer in self.pointers.iter() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(&surface);
} else {
pointer.unconfine();
}
}
}
/// Pointer appeared over the window.
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
if position.is_none() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(&surface);
}
self.pointers.push(pointer);
}
// Apply the current cursor style.
self.set_cursor_visible(self.cursor_visible.get());
}
/// Pointer left the window.
pub fn pointer_left(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
if let Some(position) = position {
let pointer = self.pointers.remove(position);
// Drop the confined pointer.
if self.confined.get() {
pointer.unconfine();
}
}
}
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
if self
.text_inputs
.iter()
.find(|t| *t == &text_input)
.is_none()
{
self.text_inputs.push(text_input);
}
}
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
self.text_inputs.remove(position);
}
}
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
// XXX This won't fly unless user will have a way to request IME window per seat, since
// the ime windows will be overlapping, but winit doesn't expose API to specify for
// which seat we're setting IME position.
let (x, y) = (position.x as i32, position.y as i32);
for text_input in self.text_inputs.iter() {
text_input.set_ime_position(x, y);
}
}
pub fn set_cursor_visible(&self, visible: bool) {
self.cursor_visible.replace(visible);
let cursor_icon = match visible {
true => Some(self.cursor_icon.get()),
false => None,
};
for pointer in self.pointers.iter() {
pointer.set_cursor(cursor_icon)
}
}
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
self.cursor_icon.replace(cursor_icon);
if !self.cursor_visible.get() {
return;
}
for pointer in self.pointers.iter() {
pointer.set_cursor(Some(cursor_icon));
}
}
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
}
#[inline]
pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_map = &mut winit_state.window_map;
let window_updates = &mut winit_state.window_updates;
let mut windows_to_close: Vec<WindowId> = Vec::new();
// Process the rest of the events.
for (window_id, window_handle) in window_map.iter_mut() {
let mut requests = window_handle.pending_window_requests.lock().unwrap();
for request in requests.drain(..) {
match request {
WindowRequest::Fullscreen(fullscreen) => {
window_handle.window.set_fullscreen(fullscreen.as_ref());
}
WindowRequest::UnsetFullscreen => {
window_handle.window.unset_fullscreen();
}
WindowRequest::ShowCursor(show_cursor) => {
window_handle.set_cursor_visible(show_cursor);
}
WindowRequest::NewCursorIcon(cursor_icon) => {
window_handle.set_cursor_icon(cursor_icon);
}
WindowRequest::IMEPosition(position) => {
window_handle.set_ime_position(position);
}
WindowRequest::GrabCursor(grab) => {
window_handle.set_cursor_grab(grab);
}
WindowRequest::DragWindow => {
window_handle.drag_window();
}
WindowRequest::Maximize(maximize) => {
if maximize {
window_handle.window.set_maximized();
} else {
window_handle.window.unset_maximized();
}
}
WindowRequest::Minimize => {
window_handle.window.set_minimized();
}
WindowRequest::Decorate(decorate) => {
let decorations = match decorate {
true => Decorations::FollowServer,
false => Decorations::None,
};
window_handle.window.set_decorate(decorations);
// We should refresh the frame to apply decorations change.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable);
// We should refresh the frame to update button state.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Title(title) => {
window_handle.window.set_title(title);
// We should refresh the frame to draw new title.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::MinSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_min_size(size);
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::MaxSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_max_size(size);
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::FrameSize(size) => {
// Set new size.
window_handle.window.resize(size.width, size.height);
// We should refresh the frame after resize.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Redraw => {
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::Theme(concept_config) => {
window_handle.window.set_frame_config(concept_config);
// We should refresh the frame to apply new theme.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Close => {
// The window was requested to be closed.
windows_to_close.push(*window_id);
// Send event that the window was destroyed.
let event_sink = &mut winit_state.event_sink;
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
}
};
}
}
// Close the windows.
for window in windows_to_close {
let _ = window_map.remove(&window);
let _ = window_updates.remove(&window);
}
}

View File

@@ -20,6 +20,9 @@ use crate::{
event_loop::EventLoopWindowTarget as RootELW,
};
/// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]".
const KEYCODE_OFFSET: u8 = 8;
pub(super) struct EventProcessor<T: 'static> {
pub(super) dnd: Dnd,
pub(super) ime_receiver: ImeReceiver,
@@ -565,7 +568,7 @@ impl<T: 'static> EventProcessor<T> {
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
// a keycode of 0.
if keycode != 0 {
let scancode = keycode - 8;
let scancode = keycode - KEYCODE_OFFSET as u32;
let keysym = wt.xconn.lookup_keysym(xkev);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
@@ -706,7 +709,7 @@ impl<T: 'static> EventProcessor<T> {
event: MouseInput {
device_id,
state,
button: Other(x as u8),
button: Other(x as u16),
modifiers,
},
}),
@@ -921,9 +924,12 @@ impl<T: 'static> EventProcessor<T> {
});
// Issue key press events for all pressed keys
self.handle_pressed_keys(
Self::handle_pressed_keys(
&wt,
window_id,
ElementState::Pressed,
&self.mod_keymap,
&mut self.device_mod_state,
&mut callback,
);
}
@@ -942,9 +948,12 @@ impl<T: 'static> EventProcessor<T> {
let window_id = mkwid(xev.event);
// Issue key release events for all pressed keys
self.handle_pressed_keys(
Self::handle_pressed_keys(
&wt,
window_id,
ElementState::Released,
&self.mod_keymap,
&mut self.device_mod_state,
&mut callback,
);
@@ -1081,10 +1090,10 @@ impl<T: 'static> EventProcessor<T> {
let device_id = mkdid(xev.sourceid);
let keycode = xev.detail;
if keycode < 8 {
let scancode = keycode - KEYCODE_OFFSET as i32;
if scancode < 0 {
return;
}
let scancode = (keycode - 8) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.device_mod_state.modifiers();
@@ -1093,7 +1102,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::Key(KeyboardInput {
scancode,
scancode: scancode as u32,
virtual_keycode,
state,
modifiers,
@@ -1221,30 +1230,37 @@ impl<T: 'static> EventProcessor<T> {
}
fn handle_pressed_keys<F>(
&self,
wt: &super::EventLoopWindowTarget<T>,
window_id: crate::window::WindowId,
state: ElementState,
mod_keymap: &ModifierKeymap,
device_mod_state: &mut ModifierKeyState,
callback: &mut F,
) where
F: FnMut(Event<'_, T>),
{
let wt = get_xtarget(&self.target);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let modifiers = self.device_mod_state.modifiers();
let modifiers = device_mod_state.modifiers();
// Get the set of keys currently pressed and apply Key events to each
let keys = wt.xconn.query_keymap();
for keycode in &keys {
if keycode < 8 {
continue;
}
let scancode = (keycode - 8) as u32;
// Update modifiers state and emit key events based on which keys are currently pressed.
for keycode in wt
.xconn
.query_keymap()
.into_iter()
.filter(|k| *k >= KEYCODE_OFFSET)
{
let scancode = (keycode - KEYCODE_OFFSET) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
if let Some(modifier) = mod_keymap.get_modifier(keycode as ffi::KeyCode) {
device_mod_state.key_event(
ElementState::Pressed,
keycode as ffi::KeyCode,
modifier,
);
}
#[allow(deprecated)]
callback(Event::WindowEvent {
window_id,

View File

@@ -81,12 +81,12 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
ffi::XK_KP_Insert => VirtualKeyCode::Insert,
ffi::XK_KP_Delete => VirtualKeyCode::Delete,
ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals,
//ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
ffi::XK_KP_Add => VirtualKeyCode::Add,
ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd,
//ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator,
ffi::XK_KP_Subtract => VirtualKeyCode::Subtract,
//ffi::XK_KP_Decimal => VirtualKeyCode::Kp_decimal,
ffi::XK_KP_Divide => VirtualKeyCode::Divide,
ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract,
ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal,
ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide,
ffi::XK_KP_0 => VirtualKeyCode::Numpad0,
ffi::XK_KP_1 => VirtualKeyCode::Numpad1,
ffi::XK_KP_2 => VirtualKeyCode::Numpad2,
@@ -183,10 +183,10 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_quoteright => VirtualKeyCode::Quoteright,
//ffi::XK_parenleft => VirtualKeyCode::Parenleft,
//ffi::XK_parenright => VirtualKeyCode::Parenright,
//ffi::XK_asterisk => VirtualKeyCode::Asterisk,
ffi::XK_plus => VirtualKeyCode::Add,
ffi::XK_asterisk => VirtualKeyCode::Asterisk,
ffi::XK_plus => VirtualKeyCode::Plus,
ffi::XK_comma => VirtualKeyCode::Comma,
ffi::XK_minus => VirtualKeyCode::Subtract,
ffi::XK_minus => VirtualKeyCode::Minus,
ffi::XK_period => VirtualKeyCode::Period,
ffi::XK_slash => VirtualKeyCode::Slash,
ffi::XK_0 => VirtualKeyCode::Key0,

View File

@@ -32,15 +32,20 @@ use std::{
ptr,
rc::Rc,
slice,
sync::{mpsc, Arc, Mutex, Weak},
sync::mpsc::Receiver,
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
use mio_extras::channel::{channel, Receiver, SendError, Sender};
use mio_misc::{
channel::{channel, SendError, Sender},
queue::NotificationQueue,
NotificationId,
};
use self::{
dnd::{Dnd, DndState},
@@ -57,7 +62,7 @@ use crate::{
};
const X_TOKEN: Token = Token(0);
const USER_TOKEN: Token = Token(1);
const USER_REDRAW_TOKEN: Token = Token(1);
pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
@@ -67,13 +72,14 @@ pub struct EventLoopWindowTarget<T> {
root: ffi::Window,
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
pending_redraws: Arc<Mutex<HashSet<WindowId>>>,
redraw_sender: Sender<WindowId>,
_marker: ::std::marker::PhantomData<T>,
}
pub struct EventLoop<T: 'static> {
poll: Poll,
event_processor: EventProcessor<T>,
redraw_channel: Receiver<WindowId>,
user_channel: Receiver<T>,
user_sender: Sender<T>,
target: Rc<RootELW<T>>,
@@ -129,7 +135,7 @@ impl<T: 'static> EventLoop<T> {
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn));
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
panic!(format!("Failed to open input method: {:#?}", state));
panic!("Failed to open input method: {:#?}", state);
}
result.expect("Failed to set input method destruction callback")
});
@@ -174,11 +180,21 @@ impl<T: 'static> EventLoop<T> {
xconn.update_cached_wm_info(root);
let pending_redraws: Arc<Mutex<HashSet<WindowId>>> = Default::default();
let mut mod_keymap = ModifierKeymap::new();
mod_keymap.reset_from_x_connection(&xconn);
let poll = Poll::new().unwrap();
let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap());
let queue = Arc::new(NotificationQueue::new(waker));
poll.registry()
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
.unwrap();
let (user_sender, user_channel) = channel(queue.clone(), NotificationId::gen_next());
let (redraw_sender, redraw_channel) = channel(queue, NotificationId::gen_next());
let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime,
@@ -189,31 +205,11 @@ impl<T: 'static> EventLoop<T> {
xconn,
wm_delete_window,
net_wm_ping,
pending_redraws: pending_redraws.clone(),
redraw_sender,
}),
_marker: ::std::marker::PhantomData,
});
let poll = Poll::new().unwrap();
let (user_sender, user_channel) = channel();
poll.register(
&EventedFd(&get_xtarget(&target).xconn.x11_fd),
X_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
poll.register(
&user_channel,
USER_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let event_processor = EventProcessor {
target: target.clone(),
dnd,
@@ -239,6 +235,7 @@ impl<T: 'static> EventLoop<T> {
let result = EventLoop {
poll,
redraw_channel,
user_channel,
user_sender,
event_processor,
@@ -258,29 +255,25 @@ impl<T: 'static> EventLoop<T> {
&self.target
}
pub(crate) fn x_connection(&self) -> &Arc<XConnection> {
get_xtarget(&self.target).x_connection()
}
pub fn run_return<F>(&mut self, mut callback: F)
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::default();
let mut events = Events::with_capacity(8);
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Init),
&self.target,
&mut control_flow,
);
let mut cause = StartCause::Init;
loop {
sticky_exit_callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
&mut callback,
);
// Process all pending events
self.drain_events(&mut callback, &mut control_flow);
let wt = get_xtarget(&self.target);
// Empty the user event buffer
{
while let Ok(event) = self.user_channel.try_recv() {
@@ -303,12 +296,16 @@ impl<T: 'static> EventLoop<T> {
}
// Empty the redraw requests
{
// Release the lock to prevent deadlock
let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
let mut windows = HashSet::new();
for wid in windows {
while let Ok(window_id) = self.redraw_channel.try_recv() {
windows.insert(window_id);
}
for window_id in windows {
let window_id = crate::window::WindowId(super::WindowId::X(window_id));
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))),
Event::RedrawRequested(window_id),
&self.target,
&mut control_flow,
&mut callback,
@@ -326,7 +323,7 @@ impl<T: 'static> EventLoop<T> {
}
let start = Instant::now();
let (mut cause, deadline, timeout);
let (deadline, timeout);
match control_flow {
ControlFlow::Exit => break,
@@ -357,38 +354,20 @@ impl<T: 'static> EventLoop<T> {
}
}
if self.event_processor.poll() {
// If the XConnection already contains buffered events, we don't
// need to wait for data on the socket.
// However, we still need to check for user events.
self.poll
.poll(&mut events, Some(Duration::from_millis(0)))
.unwrap();
events.clear();
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
} else {
// If the XConnection already contains buffered events, we don't
// need to wait for data on the socket.
if !self.event_processor.poll() {
self.poll.poll(&mut events, timeout).unwrap();
events.clear();
}
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
if wait_cancelled {
cause = StartCause::WaitCancelled {
start,
requested_resume: deadline,
};
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
if wait_cancelled {
cause = StartCause::WaitCancelled {
start,
requested_resume: deadline,
};
}
}
@@ -428,7 +407,7 @@ impl<T: 'static> EventLoop<T> {
super::WindowId::X(wid),
)) = event
{
wt.pending_redraws.lock().unwrap().insert(wid);
wt.redraw_sender.send(wid).unwrap();
} else {
callback(event, window_target, control_flow);
}
@@ -442,6 +421,7 @@ impl<T: 'static> EventLoop<T> {
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
match target.p {
super::EventLoopWindowTarget::X(ref target) => target,
#[cfg(feature = "wayland")]
_ => unreachable!(),
}
}
@@ -509,9 +489,21 @@ impl<'a> Deref for DeviceInfo<'a> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
pub struct Window(Arc<UnownedWindow>);
impl Deref for Window {

View File

@@ -27,12 +27,11 @@ impl XConnection {
(self.xlib.XInternAtom)(self.display, name.as_ptr() as *const c_char, ffi::False)
};
if atom == 0 {
let msg = format!(
panic!(
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
name,
self.check_errors(),
);
panic!(msg);
}
/*println!(
"XInternAtom name:{:?} atom:{:?}",

View File

@@ -190,6 +190,24 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_position(&self) -> Option<(i32, i32)> {
if has_flag(self.size_hints.flags, ffi::PPosition) {
Some((self.size_hints.x as i32, self.size_hints.y as i32))
} else {
None
}
}
pub fn set_position(&mut self, position: Option<(i32, i32)>) {
if let Some((x, y)) = position {
self.size_hints.flags |= ffi::PPosition;
self.size_hints.x = x as c_int;
self.size_hints.y = y as c_int;
} else {
self.size_hints.flags &= !ffi::PPosition;
}
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {

View File

@@ -23,6 +23,7 @@ pub use self::{
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
ptr,
};
@@ -39,6 +40,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
pub struct Flusher<'a> {
xconn: &'a XConnection,

View File

@@ -1,8 +1,6 @@
use raw_window_handle::unix::XlibHandle;
use std::{
cmp,
collections::HashSet,
env,
cmp, env,
ffi::CString,
mem::{self, replace, MaybeUninit},
os::raw::*,
@@ -12,6 +10,7 @@ use std::{
};
use libc;
use mio_misc::channel::Sender;
use parking_lot::Mutex;
use crate::{
@@ -23,7 +22,7 @@ use crate::{
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
},
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
};
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
@@ -104,7 +103,7 @@ pub struct UnownedWindow {
cursor_visible: Mutex<bool>,
ime_sender: Mutex<ImeSender>,
pub shared_state: Mutex<SharedState>,
pending_redraws: Arc<::std::sync::Mutex<HashSet<WindowId>>>,
redraw_sender: Sender<WindowId>,
}
impl UnownedWindow {
@@ -147,6 +146,10 @@ impl UnownedWindow {
.min_inner_size
.map(|size| size.to_physical::<u32>(scale_factor).into());
let position = window_attrs
.position
.map(|position| position.to_physical::<i32>(scale_factor).into());
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
@@ -212,8 +215,8 @@ impl UnownedWindow {
(xconn.xlib.XCreateWindow)(
xconn.display,
root,
0,
0,
position.map_or(0, |p: PhysicalPosition<i32>| p.x as c_int),
position.map_or(0, |p: PhysicalPosition<i32>| p.y as c_int),
dimensions.0 as c_uint,
dimensions.1 as c_uint,
0,
@@ -227,7 +230,7 @@ impl UnownedWindow {
// is > 0, like we do in glutin.
//
// It is non obvious which masks, if any, we should pass to
// `XGetVisualInfo`. winit doesn't recieve any info about what
// `XGetVisualInfo`. winit doesn't receive any info about what
// properties the user wants. Users should consider choosing the
// visual themselves as glutin does.
match pl_attribs.visual_infos {
@@ -249,7 +252,7 @@ impl UnownedWindow {
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(guessed_monitor, window_attrs.visible),
pending_redraws: event_loop.pending_redraws.clone(),
redraw_sender: event_loop.redraw_sender.clone(),
};
// Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
@@ -345,6 +348,7 @@ impl UnownedWindow {
}
let mut normal_hints = util::NormalHints::new(xconn);
normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y)));
normal_hints.set_size(Some(dimensions));
normal_hints.set_min_size(min_inner_size.map(Into::into));
normal_hints.set_max_size(max_inner_size.map(Into::into));
@@ -440,6 +444,12 @@ impl UnownedWindow {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.map(|flusher| flusher.queue());
if let Some(PhysicalPosition { x, y }) = position {
let shared_state = window.shared_state.get_mut();
shared_state.restore_position = Some((x, y));
}
}
if window_attrs.always_on_top {
window
@@ -524,23 +534,6 @@ impl UnownedWindow {
)
}
#[inline]
pub fn set_urgent(&self, is_urgent: bool) {
let mut wm_hints = self
.xconn
.get_wm_hints(self.xwindow)
.expect("`XGetWMHints` failed");
if is_urgent {
(*wm_hints).flags |= ffi::XUrgencyHint;
} else {
(*wm_hints).flags &= !ffi::XUrgencyHint;
}
self.xconn
.set_wm_hints(self.xwindow, wm_hints)
.flush()
.expect("Failed to set urgency hint");
}
fn set_netwm(
&self,
operation: util::StateOperation,
@@ -640,6 +633,7 @@ impl UnownedWindow {
let flusher = self.set_fullscreen_hint(false);
let mut shared_state_lock = self.shared_state.lock();
if let Some(position) = shared_state_lock.restore_position.take() {
drop(shared_state_lock);
self.set_position_inner(position.0, position.1).queue();
}
Some(flusher)
@@ -648,10 +642,12 @@ impl UnownedWindow {
let (video_mode, monitor) = match fullscreen {
Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
}) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()),
Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::X(ref monitor),
}) => (None, monitor),
}) => (Some(video_mode), video_mode.monitor.clone().unwrap()),
Fullscreen::Borderless(Some(RootMonitorHandle {
inner: PlatformMonitorHandle::X(monitor),
})) => (None, monitor),
Fullscreen::Borderless(None) => (None, self.current_monitor()),
#[cfg(feature = "wayland")]
_ => unreachable!(),
};
@@ -1291,6 +1287,46 @@ impl UnownedWindow {
self.set_cursor_position_physical(x, y)
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
let pointer = self
.xconn
.query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
let window = self.inner_position().map_err(ExternalError::NotSupported)?;
let message = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_MOVERESIZE\0") };
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
// if the cursor isn't currently grabbed
let mut grabbed_lock = self.cursor_grabbed.lock();
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
self.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
*grabbed_lock = false;
// we keep the lock until we are done
self.xconn
.send_client_msg(
self.xwindow,
self.root,
message,
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
[
(window.x as c_long + pointer.win_x as c_long),
(window.y as c_long + pointer.win_y as c_long),
8, // _NET_WM_MOVERESIZE_MOVE
ffi::Button1 as c_long,
1,
],
)
.flush()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
}
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
let _ = self
.ime_sender
@@ -1304,6 +1340,23 @@ impl UnownedWindow {
self.set_ime_position_physical(x, y);
}
#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let mut wm_hints = self
.xconn
.get_wm_hints(self.xwindow)
.expect("`XGetWMHints` failed");
if request_type.is_some() {
(*wm_hints).flags |= ffi::XUrgencyHint;
} else {
(*wm_hints).flags &= !ffi::XUrgencyHint;
}
self.xconn
.set_wm_hints(self.xwindow, wm_hints)
.flush()
.expect("Failed to set urgency hint");
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.xwindow)
@@ -1311,10 +1364,7 @@ impl UnownedWindow {
#[inline]
pub fn request_redraw(&self) {
self.pending_redraws
.lock()
.unwrap()
.insert(WindowId(self.xwindow));
self.redraw_sender.send(WindowId(self.xwindow)).unwrap();
}
#[inline]

View File

@@ -1,208 +0,0 @@
// Normally when you run or distribute a macOS app, it's bundled: it's in one
// of those fun little folders that you have to right click "Show Package
// Contents" on, and usually contains myriad delights including, but not
// limited to, plists, icons, and of course, your beloved executable. However,
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
// executable.
//
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
// barely be supported. If you move the mouse while opening a winit window from
// an unbundled app, the window will fail to activate and be in a grayed-out
// uninteractable state. Switching to another app and back is the only way to
// get the winit window into a normal state. None of this happens if the app is
// bundled, i.e. when running via Xcode.
//
// To workaround this, we just switch focus to the Dock and then switch back to
// our app. We only do this for unbundled apps, and only when they fail to
// become active on their own.
//
// This solution was derived from this Godot PR:
// https://github.com/godotengine/godot/pull/17187
// (which appears to be based on https://stackoverflow.com/a/7602677)
// The curious specialness of mouse motions is touched upon here:
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
//
// We omit the 2nd step of the solution used in Godot, since it appears to have
// no effect - I speculate that it's just technical debt picked up from the SO
// answer; the API used is fairly exotic, and was historically used for very
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
// in previous versions of SDL:
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
//
// The `performSelector` delays in the Godot solution are used for sequencing,
// since refocusing the app will fail if the call is made before it finishes
// unfocusing. The delays used there are much smaller than the ones in the
// original SO answer, presumably because they found the fastest delay that
// works reliably through trial and error. Instead of using delays, we just
// handle `applicationDidResignActive`; despite the app not activating reliably,
// that still triggers when we switch focus to the Dock.
//
// The Godot solution doesn't appear to skip the hack when an unbundled app
// activates normally. Checking for this is difficult, since if you call
// `isActive` too early, it will always be `NO`. Even though we receive
// `applicationDidResignActive` when switching focus to the Dock, we never
// receive a preceding `applicationDidBecomeActive` if the app fails to
// activate normally. I wasn't able to find a proper point in time to perform
// the `isActive` check, so we instead check for the cause of the quirk: if
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
// we assume the app failed to become active.
//
// Fun fact: this issue is still present in GLFW
// (https://github.com/glfw/glfw/issues/1515)
//
// A similar issue was found in SDL, but the resolution doesn't seem to work
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
use super::util;
use cocoa::{
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
base::id,
foundation::NSUInteger,
};
use objc::runtime::{Object, Sel, BOOL, NO, YES};
use std::{
os::raw::c_void,
sync::atomic::{AtomicBool, Ordering},
};
#[derive(Debug, Default)]
pub struct State {
// Indicates that the hack has either completed or been skipped.
activated: AtomicBool,
// Indicates that the mouse has moved at some point in time.
mouse_moved: AtomicBool,
// Indicates that the hack is in progress, and that we should refocus when
// the app resigns active.
needs_refocus: AtomicBool,
}
impl State {
pub fn name() -> &'static str {
"activationHackState"
}
pub fn new() -> *mut c_void {
let this = Box::new(Self::default());
Box::into_raw(this) as *mut c_void
}
pub unsafe fn free(this: *mut Self) {
Box::from_raw(this);
}
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
let this: *mut c_void = *(*obj).get_ivar(Self::name());
assert!(!this.is_null(), "`activationHackState` pointer was null");
this as *mut Self
}
pub unsafe fn set_activated(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).activated.store(value, Ordering::Release);
}
unsafe fn get_activated(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).activated.load(Ordering::Acquire)
}
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).mouse_moved.store(value, Ordering::Release);
}
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).mouse_moved.load(Ordering::Acquire)
}
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).needs_refocus.store(value, Ordering::Release);
}
unsafe fn get_needs_refocus(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).needs_refocus.load(Ordering::Acquire)
}
}
// This is the entry point for the hack - if the app is unbundled and a mouse
// movement occurs before the app activates, it will trigger the hack. Because
// mouse movements prior to activation are the cause of this quirk, they should
// be a reliable way to determine if the hack needs to be performed.
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
trace!("Triggered `activationHackMouseMoved`");
unsafe {
if !State::get_activated(this) {
// We check if `CFBundleName` is undefined to determine if the
// app is unbundled.
if let None = util::app_name() {
info!("App detected as unbundled");
unfocus(this);
} else {
info!("App detected as bundled");
}
}
}
trace!("Completed `activationHackMouseMoved`");
}
// Switch focus to the dock.
unsafe fn unfocus(this: &Object) {
// We only perform the hack if the app failed to activate, since otherwise,
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
// However, we only enter this function if we detect mouse movement prior
// to activation, so this should always be `NO`.
//
// Note that this check isn't necessarily reliable in detecting a violation
// of the invariant above, since it's not guaranteed that activation will
// resolve before this point. In other words, it can spuriously return `NO`.
// This is also why the mouse motion approach was chosen, since it's not
// obvious how to sequence this check - if someone knows how to, then that
// would almost surely be a cleaner approach.
let active: BOOL = msg_send![NSApp(), isActive];
if active == YES {
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
} else {
info!("Performing unbundled app activation hack");
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
let dock_array: id = msg_send![
class!(NSRunningApplication),
runningApplicationsWithBundleIdentifier: *dock_bundle_id
];
let dock_array_len: NSUInteger = msg_send![dock_array, count];
if dock_array_len == 0 {
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
} else {
State::set_needs_refocus(this, true);
let dock: id = msg_send![dock_array, objectAtIndex: 0];
// This will trigger `applicationDidResignActive`, which will in
// turn call `refocus`.
let status: BOOL = msg_send![
dock,
activateWithOptions: NSApplicationActivateIgnoringOtherApps
];
if status == NO {
error!("Failed to switch focus to Dock");
}
}
}
}
// Switch focus back to our app, causing the user to rejoice!
pub unsafe fn refocus(this: &Object) {
if State::get_needs_refocus(this) {
State::set_needs_refocus(this, false);
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
// nuanced difference isn't clear to me, but hey, I tried.
let success: BOOL = msg_send![
app,
activateWithOptions: NSApplicationActivateIgnoringOtherApps
];
if success == NO {
error!("Failed to refocus app");
}
}
}

View File

@@ -2,14 +2,14 @@ use std::collections::VecDeque;
use cocoa::{
appkit::{self, NSEvent},
base::{id, nil},
base::id,
};
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
};
use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID};
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};
pub struct AppClass(pub *const Class);
@@ -49,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
let key_window: id = msg_send![this, keyWindow];
let _: () = msg_send![key_window, sendEvent: event];
} else {
maybe_dispatch_device_event(this, event);
maybe_dispatch_device_event(event);
let superclass = util::superclass(this);
let _: () = msg_send![super(this, superclass), sendEvent: event];
}
}
}
unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
unsafe fn maybe_dispatch_device_event(event: id) {
let event_type = event.eventType();
match event_type {
appkit::NSMouseMoved
@@ -98,21 +98,6 @@ unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
}
AppState::queue_events(events);
// Notify the delegate when the first mouse move occurs. This is
// used for the unbundled app activation hack, which needs to know
// if any mouse motions occurred prior to the app activating.
let delegate: id = msg_send![this, delegate];
assert_ne!(delegate, nil);
if !activation_hack::State::get_mouse_moved(&*delegate) {
activation_hack::State::set_mouse_moved(&*delegate, true);
let () = msg_send![
delegate,
performSelector: sel!(activationHackMouseMoved:)
withObject: nil
afterDelay: 0.0
];
}
}
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
let mut events = VecDeque::with_capacity(1);

View File

@@ -1,10 +1,25 @@
use super::{activation_hack, app_state::AppState};
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
use cocoa::base::id;
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
};
use std::os::raw::c_void;
use std::{
cell::{RefCell, RefMut},
os::raw::c_void,
};
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
pub struct AuxDelegateState {
/// We store this value in order to be able to defer setting the activation policy until
/// after the app has finished launching. If the activation policy is set earlier, the
/// menubar is initially unresponsive on macOS 10.15 for example.
pub activation_policy: ActivationPolicy,
pub create_default_menu: bool,
}
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
@@ -17,36 +32,34 @@ lazy_static! {
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidBecomeActive:),
did_become_active as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidResignActive:),
did_resign_active as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
decl.add_method(
sel!(activationHackMouseMoved:),
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
AppDelegateClass(decl.register())
};
}
/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
let ptr: *mut c_void = *this.get_ivar(AUX_DELEGATE_STATE_NAME);
// Watch out that this needs to be the correct type
(*(ptr as *mut RefCell<AuxDelegateState>)).borrow_mut()
}
extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe {
let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init];
(*this).set_ivar(
activation_hack::State::name(),
activation_hack::State::new(),
AUX_DELEGATE_STATE_NAME,
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
activation_policy: ActivationPolicy::Regular,
create_default_menu: true,
}))) as *mut c_void,
);
this
}
@@ -54,28 +67,15 @@ extern "C" fn new(class: &Class, _: Sel) -> id {
extern "C" fn dealloc(this: &Object, _: Sel) {
unsafe {
activation_hack::State::free(activation_hack::State::get_ptr(this));
let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME));
// As soon as the box is constructed it is immediately dropped, releasing the underlying
// memory
Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>);
}
}
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidFinishLaunching`");
AppState::launched();
AppState::launched(this);
trace!("Completed `applicationDidFinishLaunching`");
}
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidBecomeActive`");
unsafe {
activation_hack::State::set_activated(this, true);
}
trace!("Completed `applicationDidBecomeActive`");
}
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidResignActive`");
unsafe {
activation_hack::refocus(this);
}
trace!("Completed `applicationDidResignActive`");
}

View File

@@ -1,9 +1,10 @@
use std::{
cell::{RefCell, RefMut},
collections::VecDeque,
fmt::{self, Debug},
hint::unreachable_unchecked,
mem,
rc::Rc,
rc::{Rc, Weak},
sync::{
atomic::{AtomicBool, Ordering},
Mutex, MutexGuard,
@@ -12,22 +13,29 @@ use std::{
};
use cocoa::{
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
appkit::{NSApp, NSApplication, NSWindow},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize},
foundation::{NSAutoreleasePool, NSSize},
};
use objc::runtime::YES;
use objc::runtime::Object;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{
event::{EventProxy, EventWrapper},
observer::EventLoopWaker,
util::{IdRef, Never},
window::get_window_id,
platform::macos::ActivationPolicy,
platform_impl::{
get_aux_state_mut,
platform::{
event::{EventProxy, EventWrapper},
event_loop::{post_dummy_event, PanicInfo},
menu,
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
util::{IdRef, Never},
window::get_window_id,
},
},
window::WindowId,
};
@@ -52,11 +60,31 @@ pub trait EventHandler: Debug {
}
struct EventLoopHandler<T: 'static> {
callback: Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
will_exit: bool,
window_target: Rc<RootWindowTarget<T>>,
}
impl<T> EventLoopHandler<T> {
fn with_callback<F>(&mut self, f: F)
where
F: FnOnce(
&mut EventLoopHandler<T>,
RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
),
{
if let Some(callback) = self.callback.upgrade() {
let callback = callback.borrow_mut();
(f)(self, callback);
} else {
panic!(
"Tried to dispatch an event, but the event loop that \
owned the event handler callback seems to be destroyed"
);
}
}
}
impl<T> Debug for EventLoopHandler<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
@@ -68,23 +96,27 @@ impl<T> Debug for EventLoopHandler<T> {
impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
(self.callback)(event.userify(), &self.window_target, control_flow);
self.will_exit |= *control_flow == ControlFlow::Exit;
if self.will_exit {
*control_flow = ControlFlow::Exit;
}
self.with_callback(|this, mut callback| {
(callback)(event.userify(), &this.window_target, control_flow);
this.will_exit |= *control_flow == ControlFlow::Exit;
if this.will_exit {
*control_flow = ControlFlow::Exit;
}
});
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
let mut will_exit = self.will_exit;
for event in self.window_target.p.receiver.try_iter() {
(self.callback)(Event::UserEvent(event), &self.window_target, control_flow);
will_exit |= *control_flow == ControlFlow::Exit;
if will_exit {
*control_flow = ControlFlow::Exit;
self.with_callback(|this, mut callback| {
let mut will_exit = this.will_exit;
for event in this.window_target.p.receiver.try_iter() {
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
will_exit |= *control_flow == ControlFlow::Exit;
if will_exit {
*control_flow = ControlFlow::Exit;
}
}
}
self.will_exit = will_exit;
this.will_exit = will_exit;
});
}
}
@@ -92,6 +124,7 @@ impl<T> EventHandler for EventLoopHandler<T> {
struct Handler {
ready: AtomicBool,
in_callback: AtomicBool,
dialog_is_closing: AtomicBool,
control_flow: Mutex<ControlFlow>,
control_flow_prev: Mutex<ControlFlow>,
start_time: Mutex<Option<Instant>>,
@@ -223,23 +256,17 @@ impl Handler {
}
}
pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false);
pub enum AppState {}
impl AppState {
// This function extends lifetime of `callback` to 'static as its side effect
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
pub fn set_callback<T>(
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
window_target: Rc<RootWindowTarget<T>>,
) {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
// This transmute is always safe, in case it was reached through `run`, since our
// lifetime will be already 'static. In other cases caller should ensure that all data
// they passed to callback will actually outlive it, some apps just can't move
// everything to event loop, so this is something that they should care about.
callback: mem::transmute::<
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
>(Box::new(callback)),
callback,
will_exit: false,
window_target,
}));
@@ -252,9 +279,22 @@ impl AppState {
HANDLER.callback.lock().unwrap().take();
}
pub fn launched() {
pub fn launched(app_delegate: &Object) {
apply_activation_policy(app_delegate);
unsafe {
let ns_app = NSApp();
window_activation_hack(ns_app);
// TODO: Consider allowing the user to specify they don't want their application activated
ns_app.activateIgnoringOtherApps_(YES);
};
HANDLER.set_ready();
HANDLER.waker().start();
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).create_default_menu };
if create_default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize();
}
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
@@ -262,8 +302,11 @@ impl AppState {
HANDLER.set_in_callback(false);
}
pub fn wakeup() {
if !HANDLER.is_ready() {
pub fn wakeup(panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !HANDLER.is_ready() {
return;
}
let start = HANDLER.get_start_time().unwrap();
@@ -299,6 +342,14 @@ impl AppState {
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
unsafe {
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
pub fn handle_redraw(window_id: WindowId) {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
}
pub fn queue_event(wrapper: EventWrapper) {
@@ -315,8 +366,11 @@ impl AppState {
HANDLER.events().append(&mut wrappers);
}
pub fn cleared() {
if !HANDLER.is_ready() {
pub fn cleared(panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !HANDLER.is_ready() {
return;
}
if !HANDLER.get_in_callback() {
@@ -336,29 +390,40 @@ impl AppState {
}
if HANDLER.should_exit() {
unsafe {
let _: () = msg_send![NSApp(), stop: nil];
let app: id = NSApp();
let windows: id = msg_send![app, windows];
let window_count: usize = msg_send![windows, count];
let dialog_open = if window_count > 1 {
let dialog: id = msg_send![windows, lastObject];
let is_main_window: bool = msg_send![dialog, isMainWindow];
msg_send![dialog, isVisible] && !is_main_window
} else {
false
};
let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst);
let pool = NSAutoreleasePool::new(nil);
let windows: id = msg_send![NSApp(), windows];
let window: id = msg_send![windows, objectAtIndex:0];
assert_ne!(window, nil);
let dummy_event: id = msg_send![class!(NSEvent),
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0
];
// To stop event loop immediately, we need to post some event here.
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst)
&& !dialog_open
&& !dialog_is_closing
{
let () = msg_send![app, stop: nil];
// To stop event loop immediately, we need to post some event here.
post_dummy_event(app);
}
pool.drain();
if window_count > 0 {
let window: id = msg_send![windows, objectAtIndex:0];
let window_has_focus = msg_send![window, isKeyWindow];
if !dialog_open && window_has_focus && dialog_is_closing {
HANDLER.dialog_is_closing.store(false, Ordering::SeqCst);
}
if dialog_open {
HANDLER.dialog_is_closing.store(true, Ordering::SeqCst);
}
}
};
}
HANDLER.update_start_time();
@@ -371,3 +436,49 @@ impl AppState {
}
}
}
/// A hack to make activation of multiple windows work when creating them before
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
///
/// Alternative to this would be the user calling `window.set_visible(true)` in
/// `StartCause::Init`.
///
/// If this becomes too bothersome to maintain, it can probably be removed
/// without too much damage.
unsafe fn window_activation_hack(ns_app: id) {
// Get the application's windows
// TODO: Proper ordering of the windows
let ns_windows: id = msg_send![ns_app, windows];
let ns_enumerator: id = msg_send![ns_windows, objectEnumerator];
loop {
// Enumerate over the windows
let ns_window: id = msg_send![ns_enumerator, nextObject];
if ns_window == nil {
break;
}
// And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new`
// This way we preserve the user's desired initial visiblity status
// TODO: Also filter on the type/"level" of the window, and maybe other things?
if ns_window.isVisible() == YES {
trace!("Activating visible window");
ns_window.makeKeyAndOrderFront_(nil);
} else {
trace!("Skipping activating invisible window");
}
}
}
fn apply_activation_policy(app_delegate: &Object) {
unsafe {
use cocoa::appkit::NSApplicationActivationPolicy::*;
let ns_app = NSApp();
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar won't be interactable.
let act_pol = get_aux_state_mut(app_delegate).activation_policy;
ns_app.setActivationPolicy_(match act_pol {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
});
}
}

View File

@@ -156,20 +156,20 @@ pub fn scancode_to_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
0x3e => VirtualKeyCode::RControl,
//0x3f => Fn key,
0x40 => VirtualKeyCode::F17,
0x41 => VirtualKeyCode::Decimal,
0x41 => VirtualKeyCode::NumpadDecimal,
//0x42 -> unkown,
0x43 => VirtualKeyCode::Multiply,
0x43 => VirtualKeyCode::NumpadMultiply,
//0x44 => unkown,
0x45 => VirtualKeyCode::Add,
0x45 => VirtualKeyCode::NumpadAdd,
//0x46 => unkown,
0x47 => VirtualKeyCode::Numlock,
//0x48 => KeypadClear,
0x49 => VirtualKeyCode::VolumeUp,
0x4a => VirtualKeyCode::VolumeDown,
0x4b => VirtualKeyCode::Divide,
0x4b => VirtualKeyCode::NumpadDivide,
0x4c => VirtualKeyCode::NumpadEnter,
//0x4d => unkown,
0x4e => VirtualKeyCode::Subtract,
0x4e => VirtualKeyCode::NumpadSubtract,
0x4f => VirtualKeyCode::F18,
0x50 => VirtualKeyCode::F19,
0x51 => VirtualKeyCode::NumpadEquals,

View File

@@ -1,17 +1,28 @@
use std::{
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc,
any::Any,
cell::{Cell, RefCell},
collections::VecDeque,
marker::PhantomData,
mem,
os::raw::c_void,
panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe},
process, ptr,
rc::{Rc, Weak},
sync::mpsc,
};
use cocoa::{
appkit::NSApp,
base::{id, nil},
foundation::NSAutoreleasePool,
appkit::{NSApp, NSEventType::NSApplicationDefined},
base::{id, nil, YES},
foundation::{NSAutoreleasePool, NSPoint},
};
use scopeguard::defer;
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{
app::APP_CLASS,
app_delegate::APP_DELEGATE_CLASS,
@@ -22,6 +33,34 @@ use crate::{
},
};
#[derive(Default)]
pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
}
// WARNING:
// As long as this struct is used through its `impl`, it is UnwindSafe.
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
impl UnwindSafe for PanicInfo {}
impl RefUnwindSafe for PanicInfo {}
impl PanicInfo {
pub fn is_panicking(&self) -> bool {
let inner = self.inner.take();
let result = inner.is_some();
self.inner.set(inner);
result
}
/// Overwrites the curret state if the current state is not panicking
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
if !self.is_panicking() {
self.inner.set(Some(p));
}
}
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
self.inner.take()
}
}
pub struct EventLoopWindowTarget<T: 'static> {
pub sender: mpsc::Sender<T>, // this is only here to be cloned elsewhere
pub receiver: mpsc::Receiver<T>,
@@ -34,9 +73,32 @@ impl<T> Default for EventLoopWindowTarget<T> {
}
}
impl<T: 'static> EventLoopWindowTarget<T> {
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::available_monitors()
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
let monitor = monitor::primary_monitor();
Some(RootMonitorHandle { inner: monitor })
}
}
pub struct EventLoop<T: 'static> {
pub(crate) delegate: IdRef,
window_target: Rc<RootWindowTarget<T>>,
_delegate: IdRef,
panic_info: Rc<PanicInfo>,
/// We make sure that the callback closure is dropped during a panic
/// by making the event loop own it.
///
/// Every other reference should be a Weak reference which is only upgraded
/// into a strong reference in order to call the callback but then the
/// strong reference should be dropped as soon as possible.
_callback: Option<Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>>,
}
impl<T> EventLoop<T> {
@@ -58,26 +120,19 @@ impl<T> EventLoop<T> {
let _: () = msg_send![pool, drain];
delegate
};
setup_control_flow_observers();
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info));
EventLoop {
delegate,
window_target: Rc::new(RootWindowTarget {
p: Default::default(),
_marker: PhantomData,
}),
_delegate: delegate,
panic_info,
_callback: None,
}
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::available_monitors()
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
monitor::primary_monitor()
}
pub fn window_target(&self) -> &RootWindowTarget<T> {
&self.window_target
}
@@ -94,14 +149,37 @@ impl<T> EventLoop<T> {
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
// This transmute is always safe, in case it was reached through `run`, since our
// lifetime will be already 'static. In other cases caller should ensure that all data
// they passed to callback will actually outlive it, some apps just can't move
// everything to event loop, so this is something that they should care about.
let callback = unsafe {
mem::transmute::<
Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
>(Rc::new(RefCell::new(callback)))
};
self._callback = Some(Rc::clone(&callback));
unsafe {
let pool = NSAutoreleasePool::new(nil);
defer!(pool.drain());
let app = NSApp();
assert_ne!(app, nil);
AppState::set_callback(callback, Rc::clone(&self.window_target));
let _: () = msg_send![app, run];
// A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback.
let weak_cb: Weak<_> = Rc::downgrade(&callback);
mem::drop(callback);
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
let () = msg_send![app, run];
if let Some(panic) = self.panic_info.take() {
resume_unwind(panic);
}
AppState::exit();
pool.drain();
}
}
@@ -110,6 +188,56 @@ impl<T> EventLoop<T> {
}
}
#[inline]
pub unsafe fn post_dummy_event(target: id) {
let event_class = class!(NSEvent);
let dummy_event: id = msg_send![
event_class,
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0
];
let () = msg_send![target, postEvent: dummy_event atStart: YES];
}
/// Catches panics that happen inside `f` and when a panic
/// happens, stops the `sharedApplication`
#[inline]
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
panic_info: Weak<PanicInfo>,
f: F,
) -> Option<R> {
match catch_unwind(f) {
Ok(r) => Some(r),
Err(e) => {
// It's important that we set the panic before requesting a `stop`
// because some callback are still called during the `stop` message
// and we need to know in those callbacks if the application is currently
// panicking
{
let panic_info = panic_info.upgrade().unwrap();
panic_info.set_panic(e);
}
unsafe {
let app_class = class!(NSApplication);
let app: id = msg_send![app_class, sharedApplication];
let () = msg_send![app, stop: nil];
// Posting a dummy event to get `stop` to take effect immediately.
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
post_dummy_event(app);
}
None
}
}
}
pub struct Proxy<T> {
sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef,
@@ -117,6 +245,14 @@ pub struct Proxy<T> {
unsafe impl<T: Send> Send for Proxy<T> {}
impl<T> Drop for Proxy<T> {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
}
}
}
impl<T> Clone for Proxy<T> {
fn clone(&self) -> Self {
Proxy::new(self.sender.clone())

View File

@@ -161,6 +161,18 @@ pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut libc::c_void;
#[cfg_attr(
not(use_colorsync_cgdisplaycreateuuidfromdisplayid),
link(name = "CoreGraphics", kind = "framework")
)]
#[cfg_attr(
use_colorsync_cgdisplaycreateuuidfromdisplayid,
link(name = "ColorSync", kind = "framework")
)]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
@@ -189,7 +201,6 @@ extern "C" {
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,

View File

@@ -0,0 +1,114 @@
use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use cocoa::base::{nil, selector};
use cocoa::foundation::{NSAutoreleasePool, NSProcessInfo, NSString};
use objc::{
rc::autoreleasepool,
runtime::{Object, Sel},
};
struct KeyEquivalent<'a> {
key: &'a str,
masks: Option<NSEventModifierFlags>,
}
pub fn initialize() {
autoreleasepool(|| unsafe {
let menubar = NSMenu::new(nil).autorelease();
let app_menu_item = NSMenuItem::new(nil).autorelease();
menubar.addItem_(app_menu_item);
let app = NSApp();
app.setMainMenu_(menubar);
let app_menu = NSMenu::new(nil);
let process_name = NSProcessInfo::processInfo(nil).processName();
// About menu item
let about_item_prefix = NSString::alloc(nil).init_str("About ");
let about_item_title = about_item_prefix.stringByAppendingString_(process_name);
let about_item = menu_item(
about_item_title,
selector("orderFrontStandardAboutPanel:"),
None,
);
// Seperator menu item
let sep_first = NSMenuItem::separatorItem(nil);
// Hide application menu item
let hide_item_prefix = NSString::alloc(nil).init_str("Hide ");
let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name);
let hide_item = menu_item(
hide_item_title,
selector("hide:"),
Some(KeyEquivalent {
key: "h",
masks: None,
}),
);
// Hide other applications menu item
let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others");
let hide_others_item = menu_item(
hide_others_item_title,
selector("hideOtherApplications:"),
Some(KeyEquivalent {
key: "h",
masks: Some(
NSEventModifierFlags::NSAlternateKeyMask
| NSEventModifierFlags::NSCommandKeyMask,
),
}),
);
// Show applications menu item
let show_all_item_title = NSString::alloc(nil).init_str("Show All");
let show_all_item = menu_item(
show_all_item_title,
selector("unhideAllApplications:"),
None,
);
// Seperator menu item
let sep = NSMenuItem::separatorItem(nil);
// Quit application menu item
let quit_item_prefix = NSString::alloc(nil).init_str("Quit ");
let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name);
let quit_item = menu_item(
quit_item_title,
selector("terminate:"),
Some(KeyEquivalent {
key: "q",
masks: None,
}),
);
app_menu.addItem_(about_item);
app_menu.addItem_(sep_first);
app_menu.addItem_(hide_item);
app_menu.addItem_(hide_others_item);
app_menu.addItem_(show_all_item);
app_menu.addItem_(sep);
app_menu.addItem_(quit_item);
app_menu_item.setSubmenu_(app_menu);
});
}
fn menu_item(
title: *mut Object,
selector: Sel,
key_equivalent: Option<KeyEquivalent<'_>>,
) -> *mut Object {
unsafe {
let (key, masks) = match key_equivalent {
Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks),
None => (NSString::alloc(nil).init_str(""), None),
};
let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key);
if let Some(masks) = masks {
item.setKeyEquivalentModifierMask_(masks)
}
item
}
}

View File

@@ -1,12 +1,12 @@
#![cfg(target_os = "macos")]
mod activation_hack;
mod app;
mod app_delegate;
mod app_state;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod observer;
mod util;
@@ -17,6 +17,7 @@ mod window_delegate;
use std::{fmt, ops::Deref, sync::Arc};
pub use self::{
app_delegate::{get_aux_state_mut, AuxDelegateState},
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
monitor::{MonitorHandle, VideoMode},
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},

View File

@@ -1,6 +1,17 @@
use std::{self, os::raw::*, ptr, time::Instant};
use std::{
self,
os::raw::*,
panic::{AssertUnwindSafe, UnwindSafe},
ptr,
rc::Weak,
time::Instant,
};
use crate::platform_impl::platform::{app_state::AppState, ffi};
use crate::platform_impl::platform::{
app_state::AppState,
event_loop::{stop_app_on_panic, PanicInfo},
ffi,
};
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
@@ -85,9 +96,20 @@ pub type CFRunLoopObserverCallBack =
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
pub enum CFRunLoopObserverContext {}
pub enum CFRunLoopTimerContext {}
/// This mirrors the struct with the same name from Core Foundation.
/// https://developer.apple.com/documentation/corefoundation/cfrunloopobservercontext?language=objc
#[allow(non_snake_case)]
#[repr(C)]
pub struct CFRunLoopObserverContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
pub release: Option<extern "C" fn(info: *const c_void)>,
pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
}
#[allow(non_snake_case)]
#[repr(C)]
pub struct CFRunLoopSourceContext {
@@ -103,21 +125,42 @@ pub struct CFRunLoopSourceContext {
pub perform: Option<extern "C" fn(*mut c_void)>,
}
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
{
let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo);
// Asserting unwind safety on this type should be fine because `PanicInfo` is
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
// `from_raw` takes ownership of the data behind the pointer.
// But if this scope takes ownership of the weak pointer, then
// the weak pointer will get free'd at the end of the scope.
// However we want to keep that weak reference around after the function.
std::mem::forget(info_from_raw);
stop_app_on_panic(Weak::clone(&panic_info), move || f(panic_info.0));
}
// begin is queued with the highest priority to ensure it is processed before other observers
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
panic_info: *mut c_void,
) {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => {
//trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::wakeup();
//trace!("Completed `CFRunLoopAfterWaiting`");
}
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => {
//trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::wakeup(panic_info);
//trace!("Completed `CFRunLoopAfterWaiting`");
}
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
});
}
}
@@ -126,17 +169,21 @@ extern "C" fn control_flow_begin_handler(
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
panic_info: *mut c_void,
) {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => {
//trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::cleared();
//trace!("Completed `CFRunLoopBeforeWaiting`");
}
kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen
_ => unreachable!(),
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => {
//trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::cleared(panic_info);
//trace!("Completed `CFRunLoopBeforeWaiting`");
}
kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
});
}
}
@@ -152,6 +199,7 @@ impl RunLoop {
flags: CFOptionFlags,
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) {
let observer = CFRunLoopObserverCreate(
ptr::null_mut(),
@@ -159,24 +207,33 @@ impl RunLoop {
ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
ptr::null_mut(),
context,
);
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
}
}
pub fn setup_control_flow_observers() {
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
version: 0,
retain: None,
release: None,
copyDescription: None,
};
let run_loop = RunLoop::get();
run_loop.add_observer(
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
CFIndex::min_value(),
control_flow_begin_handler,
&mut context as *mut _,
);
run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::max_value(),
control_flow_end_handler,
&mut context as *mut _,
);
}
}

View File

@@ -10,6 +10,7 @@ use cocoa::{
};
use dispatch::Queue;
use objc::rc::autoreleasepool;
use objc::runtime::NO;
use crate::{
dpi::LogicalSize,
@@ -167,7 +168,7 @@ pub unsafe fn set_maximized_async(
} else {
shared_state_lock.saved_standard_frame()
};
ns_window.setFrame_display_(new_rect, 0);
ns_window.setFrame_display_(new_rect, NO);
}
trace!("Unlocked shared state in `set_maximized`");
@@ -206,7 +207,10 @@ pub unsafe fn set_title_async(ns_window: id, title: String) {
// `close:` is thread-safe, but we want the event to be triggered from the main
// thread. Though, it's a good idea to look into that more...
pub unsafe fn close_async(ns_window: id) {
//
// ArturKovacs: It's important that this operation keeps the underlying window alive
// through the `IdRef` because otherwise it would dereference free'd memory
pub unsafe fn close_async(ns_window: IdRef) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
autoreleasepool(move || {

View File

@@ -8,11 +8,12 @@ use std::ops::{BitAnd, Deref};
use cocoa::{
appkit::{NSApp, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSString, NSUInteger},
};
use core_graphics::display::CGDisplay;
use objc::runtime::{Class, Object, Sel, BOOL, YES};
use crate::dpi::LogicalPosition;
use crate::platform_impl::platform::ffi;
// Replace with `!` once stable
@@ -91,10 +92,21 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
/// Converts from winit screen-coordinates to macOS screen-coordinates.
/// Winit: top-left is (0, 0) and y increasing downwards
/// macOS: bottom-left is (0, 0) and y increasing upwards
pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
NSPoint::new(
position.x,
CGDisplay::main().pixels_high() as f64 - position.y,
)
}
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
IdRef::new(NSString::alloc(nil).init_str(s))
}
#[allow(dead_code)] // In case we want to use this function in the future
pub unsafe fn app_name() -> Option<id> {
let bundle: id = msg_send![class!(NSBundle), mainBundle];
let dict: id = msg_send![bundle, infoDictionary];

View File

@@ -255,6 +255,10 @@ lazy_static! {
sel!(frameDidChange:),
frame_did_change as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(acceptsFirstMouse:),
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
@@ -346,7 +350,7 @@ extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
AppState::queue_redraw(WindowId(get_window_id(state.ns_window)));
AppState::handle_redraw(WindowId(get_window_id(state.ns_window)));
let superclass = util::superclass(this);
let () = msg_send![super(this, superclass), drawRect: rect];
@@ -388,7 +392,7 @@ extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
trace!("Triggered `hasMarkedText`");
let marked_text: id = *this.get_ivar("markedText");
trace!("Completed `hasMarkedText`");
(marked_text.length() > 0) as i8
(marked_text.length() > 0) as BOOL
}
}
@@ -869,26 +873,32 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
}
extern "C" fn mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
}
extern "C" fn mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Left, ElementState::Released);
}
extern "C" fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
}
extern "C" fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Right, ElementState::Released);
}
extern "C" fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
}
extern "C" fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
}
@@ -909,8 +919,11 @@ fn mouse_motion(this: &Object, event: id) {
|| view_point.x > view_rect.size.width
|| view_point.y > view_rect.size.height
{
// Point is outside of the client area (view)
return;
let mouse_buttons_down: NSInteger = msg_send![class!(NSEvent), pressedMouseButtons];
if mouse_buttons_down == 0 {
// Point is outside of the client area (view) and no buttons are pressed
return;
}
}
let x = view_point.x as f64;
@@ -986,11 +999,19 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) {
extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `scrollWheel`");
mouse_motion(this, event);
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let delta = {
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
// macOS horizontal sign convention is the inverse of winit.
let (x, y) = (event.scrollingDeltaX() * -1.0, event.scrollingDeltaY());
if event.hasPreciseScrollingDeltas() == YES {
MouseScrollDelta::PixelDelta((x as f64, y as f64).into())
let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor());
MouseScrollDelta::PixelDelta(delta)
} else {
MouseScrollDelta::LineDelta(x as f32, y as f32)
}
@@ -1031,6 +1052,9 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `pressureChangeWithEvent`");
mouse_motion(this, event);
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
@@ -1058,3 +1082,7 @@ extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
extern "C" fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL {
YES
}
extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL {
YES
}

View File

@@ -16,9 +16,10 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform::macos::WindowExtMacOS,
platform_impl::platform::{
app_state::AppState,
app_state::INTERRUPT_EVENT_LOOP_EXIT,
ffi,
monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef},
@@ -27,13 +28,14 @@ use crate::{
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
},
};
use cocoa::{
appkit::{
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
NSApplicationPresentationOptions, NSColor, NSRequestUserAttentionType, NSScreen, NSView,
NSWindow, NSWindowButton, NSWindowStyleMask,
self, CGFloat, NSApp, NSApplication, NSApplicationPresentationOptions, NSColor,
NSRequestUserAttentionType, NSScreen, NSView, NSWindow, NSWindowButton, NSWindowStyleMask,
},
base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
@@ -59,9 +61,8 @@ pub fn get_window_id(window_cocoa_id: id) -> Id {
Id(window_cocoa_id as *const Object as _)
}
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub activation_policy: ActivationPolicy,
pub movable_by_window_background: bool,
pub titlebar_transparent: bool,
pub title_hidden: bool,
@@ -70,22 +71,22 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub fullsize_content_view: bool,
pub resize_increments: Option<LogicalSize<f64>>,
pub disallow_hidpi: bool,
pub has_shadow: bool,
}
fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
unsafe {
let ns_app = NSApp();
if ns_app == nil {
None
} else {
use self::NSApplicationActivationPolicy::*;
ns_app.setActivationPolicy_(match activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
});
ns_app.finishLaunching();
Some(ns_app)
impl Default for PlatformSpecificWindowBuilderAttributes {
#[inline]
fn default() -> Self {
Self {
movable_by_window_background: false,
titlebar_transparent: false,
title_hidden: false,
titlebar_hidden: false,
titlebar_buttons_hidden: false,
fullsize_content_view: false,
resize_increments: None,
disallow_hidpi: false,
has_shadow: true,
}
}
}
@@ -109,8 +110,6 @@ unsafe fn create_view(
ns_view.setWantsLayer(YES);
}
ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*ns_view);
(ns_view, cursor_state)
})
}
@@ -122,13 +121,14 @@ fn create_window(
unsafe {
let pool = NSAutoreleasePool::new(nil);
let screen = match attrs.fullscreen {
Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor })))
| Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: VideoMode { ref monitor, .. },
})) => {
let monitor_screen = monitor.ns_screen();
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
}
Some(Fullscreen::Borderless(None)) => Some(appkit::NSScreen::mainScreen(nil)),
None => None,
};
let frame = match screen {
@@ -143,7 +143,17 @@ fn create_window(
}
None => (800.0, 600.0),
};
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height))
let (left, bottom) = match attrs.position {
Some(position) => {
let logical = util::window_position(position.to_logical(scale_factor));
// macOS wants the position of the bottom left corner,
// but caller is setting the position of top left corner
(logical.x, logical.y - height)
}
// This value is ignored by calling win.center() below
None => (0.0, 0.0),
};
NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height))
}
};
@@ -223,7 +233,12 @@ fn create_window(
}
}
ns_window.center();
if !pl_attrs.has_shadow {
ns_window.setHasShadow_(NO);
}
if attrs.position.is_none() {
ns_window.center();
}
ns_window
});
pool.drain();
@@ -319,14 +334,9 @@ impl UnownedWindow {
panic!("Windows can only be created on the main thread on macOS");
}
}
trace!("Creating new window");
let pool = unsafe { NSAutoreleasePool::new(nil) };
let ns_app = create_app(pl_attribs.activation_policy).ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSApplication`"))
})?;
let ns_window = create_window(&win_attribs, &pl_attribs).ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
@@ -338,6 +348,12 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSView`"))
})?;
// Configure the new view as the "key view" for the window
unsafe {
ns_window.setContentView_(*ns_view);
ns_window.setInitialFirstResponder_(*ns_view);
}
let input_context = unsafe { util::create_input_context(*ns_view) };
let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
@@ -348,7 +364,6 @@ impl UnownedWindow {
ns_window.setBackgroundColor_(NSColor::clearColor(nil));
}
ns_app.activateIgnoringOtherApps_(YES);
win_attribs.min_inner_size.map(|dim| {
let logical_dim = dim.to_logical(scale_factor);
set_min_inner_size(*ns_window, logical_dim)
@@ -398,12 +413,9 @@ impl UnownedWindow {
// Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size
// before it transitions.
unsafe {
if visible {
window.ns_window.makeKeyAndOrderFront_(nil);
} else {
window.ns_window.makeKeyWindow();
}
if visible {
// Tightly linked with `app_state::window_activation_hack`
unsafe { window.ns_window.makeKeyAndOrderFront_(nil) };
}
if maximized {
@@ -469,17 +481,8 @@ impl UnownedWindow {
pub fn set_outer_position(&self, position: Position) {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
let dummy = NSRect::new(
NSPoint::new(
position.x,
// While it's true that we're setting the top-left position,
// it still needs to be in a bottom-left coordinate system.
CGDisplay::main().pixels_high() as f64 - position.y,
),
NSSize::new(0f64, 0f64),
);
unsafe {
util::set_frame_top_left_point_async(*self.ns_window, dummy.origin);
util::set_frame_top_left_point_async(*self.ns_window, util::window_position(position));
}
}
@@ -609,6 +612,16 @@ impl UnownedWindow {
Ok(())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
unsafe {
let event: id = msg_send![NSApp(), currentEvent];
let _: () = msg_send![*self.ns_window, performWindowDragWithEvent: event];
}
Ok(())
}
pub(crate) fn is_zoomed(&self) -> bool {
// because `isZoomed` doesn't work if the window's borderless,
// we make it resizable temporalily.
@@ -628,7 +641,7 @@ impl UnownedWindow {
self.set_style_mask_async(curr_mask);
}
is_zoomed != 0
is_zoomed != NO
}
fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask {
@@ -703,6 +716,11 @@ impl UnownedWindow {
shared_state_lock.fullscreen.clone()
}
#[inline]
pub fn is_maximized(&self) -> bool {
self.is_zoomed()
}
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
trace!("Locked shared state in `set_fullscreen`");
@@ -731,10 +749,15 @@ impl UnownedWindow {
// does not take a screen parameter, but uses the current screen)
if let Some(ref fullscreen) = fullscreen {
let new_screen = match fullscreen {
Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor,
Fullscreen::Borderless(borderless) => {
let RootMonitorHandle { inner: monitor } = borderless
.clone()
.unwrap_or_else(|| self.current_monitor_inner());
monitor
}
Fullscreen::Exclusive(RootVideoMode {
video_mode: VideoMode { ref monitor, .. },
}) => monitor,
}) => monitor.clone(),
}
.ns_screen()
.unwrap();
@@ -820,6 +843,8 @@ impl UnownedWindow {
shared_state_lock.fullscreen = fullscreen.clone();
trace!("Unlocked shared state in `set_fullscreen`");
INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst);
match (&old_fullscreen, &fullscreen) {
(&None, &Some(_)) => unsafe {
util::toggle_full_screen_async(
@@ -865,7 +890,7 @@ impl UnownedWindow {
) => unsafe {
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
},
_ => (),
_ => INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst),
}
}
@@ -946,7 +971,21 @@ impl UnownedWindow {
}
#[inline]
pub fn current_monitor(&self) -> RootMonitorHandle {
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let ns_request_type = request_type.map(|ty| match ty {
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
});
unsafe {
if let Some(ty) = ns_request_type {
NSApp().requestUserAttention_(ty);
}
}
}
#[inline]
// Allow directly accessing the current monitor internally without unwrapping.
pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle {
unsafe {
let screen: id = msg_send![*self.ns_window, screen];
let desc = NSScreen::deviceDescription(screen);
@@ -959,14 +998,20 @@ impl UnownedWindow {
}
}
#[inline]
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
Some(self.current_monitor_inner())
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::available_monitors()
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
monitor::primary_monitor()
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
let monitor = monitor::primary_monitor();
Some(RootMonitorHandle { inner: monitor })
}
#[inline]
@@ -991,18 +1036,6 @@ impl WindowExtMacOS for UnownedWindow {
*self.ns_view as *mut _
}
#[inline]
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
unsafe {
NSApp().requestUserAttention_(match request_type {
RequestUserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
RequestUserAttentionType::Informational => {
NSRequestUserAttentionType::NSInformationalRequest
}
});
}
}
#[inline]
fn simple_fullscreen(&self) -> bool {
let shared_state_lock = self.shared_state.lock().unwrap();
@@ -1091,6 +1124,19 @@ impl WindowExtMacOS for UnownedWindow {
}
}
}
#[inline]
fn has_shadow(&self) -> bool {
unsafe { self.ns_window.hasShadow() == YES }
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
unsafe {
self.ns_window
.setHasShadow_(if has_shadow { YES } else { NO })
}
}
}
impl Drop for UnownedWindow {
@@ -1098,7 +1144,7 @@ impl Drop for UnownedWindow {
trace!("Dropping `UnownedWindow` ({:?})", self as *mut _);
// Close the window if it has not yet been closed.
if *self.ns_window != nil {
unsafe { util::close_async(*self.ns_window) };
unsafe { util::close_async(self.ns_window.clone()) };
}
}
}
@@ -1116,14 +1162,14 @@ unsafe fn set_min_inner_size<V: NSWindow + Copy>(window: V, mut min_size: Logica
// If necessary, resize the window to match constraint
if current_rect.size.width < min_size.width {
current_rect.size.width = min_size.width;
window.setFrame_display_(current_rect, 0)
window.setFrame_display_(current_rect, NO)
}
if current_rect.size.height < min_size.height {
// The origin point of a rectangle is at its bottom left in Cocoa.
// To ensure the window's top-left point remains the same:
current_rect.origin.y += current_rect.size.height - min_size.height;
current_rect.size.height = min_size.height;
window.setFrame_display_(current_rect, 0)
window.setFrame_display_(current_rect, NO)
}
}
@@ -1140,13 +1186,13 @@ unsafe fn set_max_inner_size<V: NSWindow + Copy>(window: V, mut max_size: Logica
// If necessary, resize the window to match constraint
if current_rect.size.width > max_size.width {
current_rect.size.width = max_size.width;
window.setFrame_display_(current_rect, 0)
window.setFrame_display_(current_rect, NO)
}
if current_rect.size.height > max_size.height {
// The origin point of a rectangle is at its bottom left in Cocoa.
// To ensure the window's top-left point remains the same:
current_rect.origin.y += current_rect.size.height - max_size.height;
current_rect.size.height = max_size.height;
window.setFrame_display_(current_rect, 0)
window.setFrame_display_(current_rect, NO)
}
}

View File

@@ -1,7 +1,7 @@
use std::{
f64,
os::raw::c_void,
sync::{Arc, Weak},
sync::{atomic::Ordering, Arc, Weak},
};
use cocoa::{
@@ -15,10 +15,11 @@ use objc::{
};
use crate::{
dpi::LogicalSize,
dpi::{LogicalPosition, LogicalSize},
event::{Event, ModifiersState, WindowEvent},
platform_impl::platform::{
app_state::AppState,
app_state::INTERRUPT_EVENT_LOOP_EXIT,
event::{EventProxy, EventWrapper},
util::{self, IdRef},
view::ViewState,
@@ -111,7 +112,9 @@ impl WindowDelegateState {
let moved = self.previous_position != Some((x, y));
if moved {
self.previous_position = Some((x, y));
self.emit_event(WindowEvent::Moved((x, y).into()));
let scale_factor = self.get_scale_factor();
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
self.emit_event(WindowEvent::Moved(physical_pos));
}
}
@@ -429,6 +432,9 @@ extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
/// Invoked when before enter fullscreen
extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillEnterFullscreen:`");
INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst);
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_will_enter_fullscreen`");
@@ -446,7 +452,8 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => {
shared_state.fullscreen = Some(Fullscreen::Borderless(window.current_monitor()))
let current_monitor = Some(window.current_monitor_inner());
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
}
}
shared_state.in_fullscreen_transition = true;
@@ -459,6 +466,9 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
/// Invoked when before exit fullscreen
extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillExitFullScreen:`");
INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst);
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_will_exit_fullscreen`");
@@ -492,6 +502,8 @@ extern "C" fn window_will_use_fullscreen_presentation_options(
/// Invoked when entered fullscreen
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst);
trace!("Triggered `windowDidEnterFullscreen:`");
with_state(this, |state| {
state.initial_fullscreen = false;
@@ -512,6 +524,8 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
/// Invoked when exited fullscreen
extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst);
trace!("Triggered `windowDidExitFullscreen:`");
with_state(this, |state| {
state.with_window(|window| {

View File

@@ -6,11 +6,10 @@ mod window_target;
pub use self::proxy::Proxy;
pub use self::window_target::WindowTarget;
use super::{backend, device, monitor, window};
use super::{backend, device, window};
use crate::event::Event;
use crate::event_loop as root;
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
use std::marker::PhantomData;
pub struct EventLoop<T: 'static> {
@@ -27,18 +26,9 @@ impl<T> EventLoop<T> {
}
}
pub fn available_monitors(&self) -> VecDequeIter<monitor::Handle> {
VecDeque::new().into_iter()
}
pub fn primary_monitor(&self) -> monitor::Handle {
monitor::Handle
}
pub fn run<F>(self, mut event_handler: F) -> !
where
F: 'static
+ FnMut(Event<'static, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
F: 'static + FnMut(Event<'_, T>, &root::EventLoopWindowTarget<T>, &mut root::ControlFlow),
{
let target = root::EventLoopWindowTarget {
p: self.elw.p.clone(),

View File

@@ -1,4 +1,4 @@
use super::{backend, state::State};
use super::{super::ScaleChangeArgs, backend, state::State};
use crate::event::{Event, StartCause};
use crate::event_loop as root;
use crate::window::WindowId;
@@ -9,7 +9,8 @@ use std::{
clone::Clone,
collections::{HashSet, VecDeque},
iter,
rc::Rc,
ops::Deref,
rc::{Rc, Weak},
};
pub struct Shared<T: 'static>(Rc<Execution<T>>);
@@ -21,50 +22,129 @@ impl<T> Clone for Shared<T> {
}
pub struct Execution<T: 'static> {
runner: RefCell<Option<Runner<T>>>,
runner: RefCell<RunnerEnum<T>>,
events: RefCell<VecDeque<Event<'static, T>>>,
id: RefCell<u32>,
all_canvases: RefCell<Vec<(WindowId, Weak<RefCell<backend::Canvas>>)>>,
redraw_pending: RefCell<HashSet<WindowId>>,
destroy_pending: RefCell<VecDeque<WindowId>>,
scale_change_detector: RefCell<Option<backend::ScaleChangeDetector>>,
unload_event_handle: RefCell<Option<backend::UnloadEventHandle>>,
}
enum RunnerEnum<T: 'static> {
/// The `EventLoop` is created but not being run.
Pending,
/// The `EventLoop` is being run.
Running(Runner<T>),
/// The `EventLoop` is exited after being started with `EventLoop::run`. Since
/// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain
/// that this event loop will never be run again.
Destroyed,
}
impl<T: 'static> RunnerEnum<T> {
fn maybe_runner(&self) -> Option<&Runner<T>> {
match self {
RunnerEnum::Running(runner) => Some(runner),
_ => None,
}
}
}
struct Runner<T: 'static> {
state: State,
is_busy: bool,
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>,
}
impl<T: 'static> Runner<T> {
pub fn new(event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>) -> Self {
pub fn new(event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>) -> Self {
Runner {
state: State::Init,
is_busy: false,
event_handler,
}
}
/// Returns the cooresponding `StartCause` for the current `state`, or `None`
/// when in `Exit` state.
fn maybe_start_cause(&self) -> Option<StartCause> {
Some(match self.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => return None,
})
}
fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) {
let is_closed = *control == root::ControlFlow::Exit;
(self.event_handler)(event, control);
// Maintain closed state, even if the callback changes it
if is_closed {
*control = root::ControlFlow::Exit;
}
}
}
impl<T: 'static> Shared<T> {
pub fn new() -> Self {
Shared(Rc::new(Execution {
runner: RefCell::new(None),
runner: RefCell::new(RunnerEnum::Pending),
events: RefCell::new(VecDeque::new()),
id: RefCell::new(0),
all_canvases: RefCell::new(Vec::new()),
redraw_pending: RefCell::new(HashSet::new()),
destroy_pending: RefCell::new(VecDeque::new()),
scale_change_detector: RefCell::new(None),
unload_event_handle: RefCell::new(None),
}))
}
pub fn add_canvas(&self, id: WindowId, canvas: &Rc<RefCell<backend::Canvas>>) {
self.0
.all_canvases
.borrow_mut()
.push((id, Rc::downgrade(canvas)));
}
pub fn notify_destroy_window(&self, id: WindowId) {
self.0.destroy_pending.borrow_mut().push_back(id);
}
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootEventLoopWindowTarget reference
pub fn set_listener(
&self,
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
event_handler: Box<dyn FnMut(Event<'_, T>, &mut root::ControlFlow)>,
) {
self.0.runner.replace(Some(Runner::new(event_handler)));
{
let mut runner = self.0.runner.borrow_mut();
assert!(matches!(*runner, RunnerEnum::Pending));
*runner = RunnerEnum::Running(Runner::new(event_handler));
}
self.init();
let close_instance = self.clone();
backend::on_unload(move || close_instance.handle_unload());
*self.0.unload_event_handle.borrow_mut() =
Some(backend::on_unload(move || close_instance.handle_unload()));
}
pub(crate) fn set_on_scale_change<F>(&self, handler: F)
where
F: 'static + FnMut(ScaleChangeArgs),
{
*self.0.scale_change_detector.borrow_mut() =
Some(backend::ScaleChangeDetector::new(handler));
}
// Generate a strictly increasing ID
@@ -118,18 +198,25 @@ impl<T: 'static> Shared<T> {
}
// If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true;
if let Some(ref runner) = &*self.0.runner.borrow() {
// If we're currently polling, queue this and wait for the poll() method to be called
if let State::Poll { .. } = runner.state {
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
Ok(RunnerEnum::Running(ref runner)) => {
// If we're currently polling, queue this and wait for the poll() method to be called
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
}
Ok(RunnerEnum::Pending) => {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
}
// If the runner is busy, queue this and wait for it to process it later
if runner.is_busy {
// Some other code is mutating the runner, which most likely means
// the event loop is running and busy. So we queue this event for
// it to be processed later.
Err(_) => {
process_immediately = false;
}
} else {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
// This is unreachable since `self.is_closed() == true`.
Ok(RunnerEnum::Destroyed) => unreachable!(),
}
if !process_immediately {
// Queue these events to look at later
@@ -138,25 +225,15 @@ impl<T: 'static> Shared<T> {
}
// At this point, we know this is a fresh set of events
// Now we determine why new events are incoming, and handle the events
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
match runner.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => {
// If we're in the exit state, don't do event processing
return;
}
}
} else {
unreachable!("The runner cannot process events when it is not attached");
let start_cause = match (self.0.runner.borrow().maybe_runner())
.unwrap_or_else(|| {
unreachable!("The runner cannot process events when it is not attached")
})
.maybe_start_cause()
{
Some(c) => c,
// If we're in the exit state, don't do event processing
None => return,
};
// Take the start event, then the events provided to this function, and run an iteration of
// the event loop
@@ -165,6 +242,26 @@ impl<T: 'static> Shared<T> {
self.run_until_cleared(events);
}
// Process the destroy-pending windows. This should only be called from
// `run_until_cleared` and `handle_scale_changed`, somewhere between emitting
// `NewEvents` and `MainEventsCleared`.
fn process_destroy_pending_windows(&self, control: &mut root::ControlFlow) {
while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() {
self.0
.all_canvases
.borrow_mut()
.retain(|&(item_id, _)| item_id != id);
self.handle_event(
Event::WindowEvent {
window_id: id,
event: crate::event::WindowEvent::Destroyed,
},
control,
);
self.0.redraw_pending.borrow_mut().remove(&id);
}
}
// Given the set of new events, run the event loop until the main events and redraw events are
// cleared
//
@@ -174,6 +271,7 @@ impl<T: 'static> Shared<T> {
for event in events {
self.handle_event(event, &mut control);
}
self.process_destroy_pending_windows(&mut control);
self.handle_event(Event::MainEventsCleared, &mut control);
// Collect all of the redraw events to avoid double-locking the RefCell
@@ -187,47 +285,139 @@ impl<T: 'static> Shared<T> {
// If the event loop is closed, it has been closed this iteration and now the closing
// event should be emitted
if self.is_closed() {
self.handle_event(Event::LoopDestroyed, &mut control);
self.handle_loop_destroyed(&mut control);
}
}
pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) {
// If there aren't any windows, then there is nothing to do here.
if self.0.all_canvases.borrow().is_empty() {
return;
}
let start_cause = match (self.0.runner.borrow().maybe_runner())
.unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner"))
.maybe_start_cause()
{
Some(c) => c,
// If we're in the exit state, don't do event processing
None => return,
};
let mut control = self.current_control_flow();
// Handle the start event and all other events in the queue.
self.handle_event(Event::NewEvents(start_cause), &mut control);
// It is possible for windows to be dropped before this point. We don't
// want to send `ScaleFactorChanged` for destroyed windows, so we process
// the destroy-pending windows here.
self.process_destroy_pending_windows(&mut control);
// Now handle the `ScaleFactorChanged` events.
for &(id, ref canvas) in &*self.0.all_canvases.borrow() {
let canvas = match canvas.upgrade() {
Some(rc) => rc.borrow().raw().clone(),
// This shouldn't happen, but just in case...
None => continue,
};
// First, we send the `ScaleFactorChanged` event:
let current_size = crate::dpi::PhysicalSize {
width: canvas.width() as u32,
height: canvas.height() as u32,
};
let logical_size = current_size.to_logical::<f64>(old_scale);
let mut new_size = logical_size.to_physical(new_scale);
self.handle_single_event_sync(
Event::WindowEvent {
window_id: id,
event: crate::event::WindowEvent::ScaleFactorChanged {
scale_factor: new_scale,
new_inner_size: &mut new_size,
},
},
&mut control,
);
// Then we resize the canvas to the new size and send a `Resized` event:
backend::set_canvas_size(&canvas, crate::dpi::Size::Physical(new_size));
self.handle_single_event_sync(
Event::WindowEvent {
window_id: id,
event: crate::event::WindowEvent::Resized(new_size),
},
&mut control,
);
}
// Process the destroy-pending windows again.
self.process_destroy_pending_windows(&mut control);
self.handle_event(Event::MainEventsCleared, &mut control);
// Discard all the pending redraw as we shall just redraw all windows.
self.0.redraw_pending.borrow_mut().clear();
for &(window_id, _) in &*self.0.all_canvases.borrow() {
self.handle_event(Event::RedrawRequested(window_id), &mut control);
}
self.handle_event(Event::RedrawEventsCleared, &mut control);
self.apply_control_flow(control);
// If the event loop is closed, it has been closed this iteration and now the closing
// event should be emitted
if self.is_closed() {
self.handle_loop_destroyed(&mut control);
}
}
fn handle_unload(&self) {
self.apply_control_flow(root::ControlFlow::Exit);
let mut control = self.current_control_flow();
// We don't call `handle_loop_destroyed` here because we don't need to
// perform cleanup when the web browser is going to destroy the page.
self.handle_event(Event::LoopDestroyed, &mut control);
}
// handle_single_event_sync takes in an event and handles it synchronously.
//
// It should only ever be called from `scale_changed`.
fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut root::ControlFlow) {
if self.is_closed() {
*control = root::ControlFlow::Exit;
}
match *self.0.runner.borrow_mut() {
RunnerEnum::Running(ref mut runner) => {
runner.handle_single_event(event, control);
}
_ => panic!("Cannot handle event synchronously without a runner"),
}
}
// handle_event takes in events and either queues them or applies a callback
//
// It should only ever be called from send_event
// It should only ever be called from `run_until_cleared` and `scale_changed`.
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
let is_closed = self.is_closed();
if self.is_closed() {
*control = root::ControlFlow::Exit;
}
match *self.0.runner.borrow_mut() {
Some(ref mut runner) => {
// An event is being processed, so the runner should be marked busy
runner.is_busy = true;
(runner.event_handler)(event, control);
// Maintain closed state, even if the callback changes it
if is_closed {
*control = root::ControlFlow::Exit;
}
// An event is no longer being processed
runner.is_busy = false;
RunnerEnum::Running(ref mut runner) => {
runner.handle_single_event(event, control);
}
// If an event is being handled without a runner somehow, add it to the event queue so
// it will eventually be processed
_ => self.0.events.borrow_mut().push_back(event),
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event),
// If the Runner has been destroyed, there is nothing to do.
RunnerEnum::Destroyed => return,
}
let is_closed = *control == root::ControlFlow::Exit;
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
// If the runner doesn't exist and this method recurses, it will recurse infinitely
if !is_closed && self.0.runner.borrow().is_some() {
if !is_closed && self.0.runner.borrow().maybe_runner().is_some() {
// Take an event out of the queue and handle it
if let Some(event) = self.0.events.borrow_mut().pop_front() {
// Make sure not to let the borrow_mut live during the next handle_event
let event = { self.0.events.borrow_mut().pop_front() };
if let Some(event) = event {
self.handle_event(event, control);
}
}
@@ -240,7 +430,7 @@ impl<T: 'static> Shared<T> {
root::ControlFlow::Poll => {
let cloned = self.clone();
State::Poll {
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)),
request: backend::AnimationFrameRequest::new(move || cloned.poll()),
}
}
root::ControlFlow::Wait => State::Wait {
@@ -270,26 +460,61 @@ impl<T: 'static> Shared<T> {
};
match *self.0.runner.borrow_mut() {
Some(ref mut runner) => {
RunnerEnum::Running(ref mut runner) => {
runner.state = new_state;
}
None => (),
_ => (),
}
}
fn handle_loop_destroyed(&self, control: &mut root::ControlFlow) {
self.handle_event(Event::LoopDestroyed, control);
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
*self.0.scale_change_detector.borrow_mut() = None;
*self.0.unload_event_handle.borrow_mut() = None;
// Dropping the `Runner` drops the event handler closure, which will in
// turn drop all `Window`s moved into the closure.
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
for (_, canvas) in all_canvases {
// In case any remaining `Window`s are still not dropped, we will need
// to explicitly remove the event handlers associated with their canvases.
if let Some(canvas) = canvas.upgrade() {
let mut canvas = canvas.borrow_mut();
canvas.remove_listeners();
}
}
// At this point, the `self.0` `Rc` should only be strongly referenced
// by the following:
// * `self`, i.e. the item which triggered this event loop wakeup, which
// is usually a `wasm-bindgen` `Closure`, which will be dropped after
// returning to the JS glue code.
// * The `EventLoopWindowTarget` leaked inside `EventLoop::run` due to the
// JS exception thrown at the end.
// * For each undropped `Window`:
// * The `register_redraw_request` closure.
// * The `destroy_fn` closure.
}
// Check if the event loop is currently closed
fn is_closed(&self) -> bool {
match *self.0.runner.borrow() {
Some(ref runner) => runner.state.is_exit(),
None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
Ok(RunnerEnum::Running(runner)) => runner.state.is_exit(),
// The event loop is not closed since it is not initialized.
Ok(RunnerEnum::Pending) => false,
// The event loop is closed since it has been destroyed.
Ok(RunnerEnum::Destroyed) => true,
// Some other code is mutating the runner, which most likely means
// the event loop is running and busy.
Err(_) => false,
}
}
// Get the current control flow state
fn current_control_flow(&self) -> root::ControlFlow {
match *self.0.runner.borrow() {
Some(ref runner) => runner.state.control_flow(),
None => root::ControlFlow::Poll,
RunnerEnum::Running(ref runner) => runner.state.control_flow(),
RunnerEnum::Pending => root::ControlFlow::Poll,
RunnerEnum::Destroyed => root::ControlFlow::Exit,
}
}
}

View File

@@ -15,7 +15,7 @@ pub enum State {
start: Instant,
},
Poll {
timeout: backend::Timeout,
request: backend::AnimationFrameRequest,
},
Exit,
}

Some files were not shown because too many files have changed in this diff Show More