Compare commits

...

127 Commits

Author SHA1 Message Date
Artúr Kovács
d3e6949007 Release 0.26.1 (#2125) 2022-01-05 15:38:59 +01:00
Mads Marquart
a033b25ecb Make CI run on changes to non-code files as well (#2130)
The repository is configured such that specific checks are required to pass before merging; since CI doesn't run on PRs that don't change code, they can't be merged without administrator intervention.

Also, while this definition is currently correct, we might in the future change something so that changes to other files (like markdown files) really should be run through CI.

And example of that could be:
```rust
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
extern "C" {}
```
2022-01-05 15:01:17 +01:00
Mads Marquart
39dd30c239 Fix CGDisplayCreateUUIDFromDisplayID linking (again) (#2078)
See also https://github.com/rust-windowing/winit/pull/1626.

The `cocoa` crate links to AppKit, which made the symbol `CGDisplayCreateUUIDFromDisplayID` from ApplicationServices/ColorSync (which AppKit uses internally) available to us on macOS 10.8 to 10.13.

However, this does not work on macOS 10.7 (where AppKit does not link to ColorSync internally). Instead of relying on this, we should just link to ApplicationServices directly.
2022-01-05 14:38:24 +01:00
TotalKrill
c5c99d2357 Add cursor grab for web target (#2025)
* Add cursor grab

* Update feature matrix, changelog and platform information

* Add proper error propagation

* Remove "expect" from fallible code

    code would crash if handling pointer capture outside of winit on
    every mouse click

    we swallow the error since we could not think of a case where this
    would fail in a way that would want to handle that it fails

* Remove unnecessary implementation comment

Co-authored-by: Will Crichton <wcrichto@cs.stanford.edu>
2022-01-05 11:13:46 +01:00
Shuoliu Yang
25ff30ee8c Fix transparent window crash on Windows 11 (#2121)
Maybe the transparent setting in WM_NCCREATE on Windows 11 will cause a block when calling DwmEnableBlureBehindWindow and will crash. Puts them into WM_CREATE and it works.
2022-01-03 13:54:31 +01:00
Artúr Kovács
6b250a74f8 Revert "Add composition event on macOS (#1979)" (#2119)
This reverts commit 8afeb910bd.

Reverting because this change made Pinyin input unusable
(only latin characters showed even after selecting the
desired Chinese character)
2022-01-02 22:01:51 +01:00
Lucas Kent
5331397c6c Provide examples for all window position/size setters (#2107) 2022-01-02 04:56:13 +01:00
Lucas Kent
0b39024133 Fix clippy warnings (#2108)
* Fix clippy warnings

* review feedback.
2022-01-01 03:00:11 +01:00
Mika
438d286fd5 Add new mappings for numlock, numpadenter and numpadcomma on X11 (#1937)
* Add X11 mappings

* Update CHANGELOG.md

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

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-12-11 03:14:31 +01:00
Philippe Renon
18a61f1058 Fix warnings (#2076)
* examples: Fix unused `Result` that must be used when initializing console_log

* examples: Fix unused imports

* Fix unread name field warning in linux x11 ime InputMethod struct

* Fix unread name field warning in linux x11 Device struct

* Ignore unread field warning in macos/ios MonitorHandle struct

* ci: Add `--deny warnings` to `RUSTFLAGS`
2021-12-11 03:02:48 +01:00
Markus Røyset
20d012ae3f Update contact links (#2079) 2021-12-07 11:27:30 -08:00
Mads Marquart
efc54ab8ba Add note about cargo update to the raw-window-handle changelog update (#2086) 2021-12-06 12:53:56 -08:00
Kirill Chibisov
ea1c031b54 Release 0.26.0 version 2021-12-01 14:43:38 +03:00
Mads Marquart
11a44081df macOS move impl details of platform into platform_impl 2021-12-01 14:20:56 +03:00
Mads Marquart
5eb9c9504b Update raw-window-handle to 0.4.1 (#1957)
* Update raw-window-handle to `0.4.2`

See:
- https://github.com/rust-windowing/raw-window-handle/issues/72
- https://github.com/rust-windowing/raw-window-handle/pull/73
- https://github.com/rust-windowing/raw-window-handle/pull/74

* Clean up raw_window_handle functions a bit
2021-11-30 17:50:23 +01:00
Adrien Bennadji
29a078f65c bump ndk dependencies to 0.5 (#2071) 2021-11-24 16:56:57 +01:00
Alphyr
be61ca13fe On X11, update 'mio' to 0.8 2021-11-20 03:42:23 +03:00
mahkoh
e9d5b2007a On X11, don't panic when getting EINTR
Fixes #1972.
2021-11-20 03:24:45 +03:00
Ian Hobson
f2de8475fc Show the menu bar in borderless fullscreen on macOS (#2053)
* In MacOS, only disable menu bar in exclusive fullscreen

* Save and restore fullscreen options in set_fullscreen

* Don't always cache presentation options when entering exclusive fullscreen

This commit caches presentation options when entering exclusive fullscreen
only if we're coming from borderless fullscreen.

Then, when transitioning from exclusive -> borderless, if no cached presentation
options are present, then the default borderless options are applied.

This fixes the menu bar being unavailable when taking the following path:
[not fullscreen] -> [exclusive fullscreen] -> [borderless fullscreen].

Without this commit, the presentation options from [not fullscreen] were being
cached and then applied to [borderless fullscreen].

* Restore the window level when switching to exclusive fullscreen

The hack of using `CGShieldingWindowLevel() + 1` in borderless fullscreen needs
to be undone when switching from [borderless] -> [exclusive] fullscreen,
otherwise there are menu bar glitches when following a path through
[borderless] -> [exclusive] -> [borderless] modes.

Now, this might appear to conflict with the 'always on top' feature which uses
the 'floating window' level, but this feature appears to be broken anyway when
entering and exiting fullscreen with an always-on-top window. So, rather than
introducing logic to attempt to restore to the 'floating' level here, I think
it's better to do the simple thing for now and then introduce logic for
always-on-top windows when fixing the overall fullscreen behaviour.

* Update the changelog

Co-authored-by: Ehden Sinai <ehdens@gmail.com>
Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>
2021-11-19 13:05:36 -08:00
Markus Siglreithmaier
3ecbea3c39 Windows: Split window initialization across NCCREATE and CREATE (#2062)
* Refactor window initialization by splitting NCCREATE and CREATE related tasks.

Fixes issue with invisible owner windows.

* address review comments

* Update src/platform_impl/windows/event_loop.rs

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

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-11-17 18:33:44 +01:00
cmeissl
c4df7ad7a5 On Wayland, commit the window surface after setting the decoration mode
Fixes #2064.
2021-11-15 01:23:54 +03:00
Emil Ernerfeldt
387567a917 macOS: Fix native file dialogs freezing the event loop (#2027)
* macOS: Ignore all events while in the callback

Previously all native dialogs, such as [rfd](https://github.com/PolyMeilex/rfd), would cause the event loop (event_loop.run) to freeze.

* Update changelog

* spelling

* Fix merge mistake

* Add link to issue in the code

Co-authored-by: Poly <marynczak.bartlomiej@gmail.com>
2021-11-04 11:37:02 -07:00
Markus Siglreithmaier
cfbe8462cc Windows: Increase wait timer resolution (#2007)
Windows: Increase wait timer resolution for more accurate timing when using `WaitUntil`.
2021-11-02 21:51:39 +01:00
Markus Siglreithmaier
5f4df54895 Android: Bump ndk/ndk-glue version (#2047) 2021-11-02 18:54:59 +01:00
Kirill Chibisov
ed698f2462 On Wayland, add wayland-dlopen feature to use dlopen
While winit was always using dlopen for opening system libs, it
provides a way now to disable dlopen feature helping with linking
on some targets.

Fixes #2037.
2021-10-31 17:06:00 +03:00
Kirill Chibisov
b4774861db On X11, if RANDR based scale factor is higher than 20 reset it to 1
Some video drivers could set display metrics to odd values, which can result in
extra large scale factors (e.g. winit is sending 720 for 5k screen on nvidia
binary drivers), so let's just drop them to prevent clients from using them.
The value 20 was picked, because the DPR for 8k @ 5 inch is ~18.36.

Fixes #1983.
2021-10-30 23:47:31 +03:00
Alexis Hildebrandt
9768f73bb7 Update smithay-client-toolkit
Fixes libxcbcommon linking on OpenBSD by using pkg-config when
building without dlopen feature. For details see [1].

[1] - https://github.com/Smithay/client-toolkit/pull/198
2021-10-30 22:35:36 +03:00
Arthur Kaukal Valladares
805249e27e Android: Window::config now initially returns accurate data. (#2021)
Initialize Android's static 'CONFIG' from NativeActivity's Asset Manager
2021-10-26 23:28:28 +02:00
Markus Siglreithmaier
5f24c40d05 Fix wasm CI failure (#2024)
* Fix wasm ci

* remove cargo-web step
2021-10-26 11:05:01 -07:00
Philippe Renon
1b3b82a3c1 Clippy fixes (#2011)
* windows: bump winapi version

* windows: address dark_mode FIXMEs

use now available winapi structures

* clippy: fix clippy::upper_case_acronyms warnings

* clippy: fix needless_arbitrary_self_type warnings

* clippy: fix clone_on_copy warnings

* clippy: fix unnecessary_mut_passed warnings

* clippy: fix identity_op warnings

* clippy: fix misc warnings

* prefix rustdoc lints with rustdoc::

the prefix was introduced in Rust 1.52

* windows: silence file_drop_handler is never read warning

* clippy: fix from_over_into warnings

and a bit of naming simplification

* clippy: fix missing_safety_doc warnings

* make dummy() functions const
2021-08-30 19:40:02 +02:00
Davester47
9e72396709 Fix X11 memory leak and remove mio-misc (#1987)
* Fix X11 memory leak and remove mio-misc

I also fixed a couple of clippy lints.
Fixes #1984

* Send the redraw event before waking up the main event

* Use .map instead of a match, and remove comments saved by git

* Remove unnecessary pub keywords on `WakeSender` in x11/mod.rs
2021-08-24 12:38:56 +02:00
oxalica
125ee0b446 Emit ScaleFactorChanged event on monitor reconnect (#1963)
When disconnect the only monitor, scale factor is reset to 1.0. We need
to set it back when the monitor is reconnected.

We previously assume current window must be on an existing monitor, but
that's not true in case of reconnecting the only one monitor.
2021-08-24 12:36:13 +02:00
sandmor
3bfb580d7a Fix potential bug (#2009)
* polish and failed to find visual warning

* improvement
2021-08-24 12:35:11 +02:00
sandmor
b54d47796d Fix transparency on X11 (#2006)
* find transparent in x11

* remove debug hooks

* update changelog

* polish and failed to find visual warning
2021-08-22 20:45:24 +02:00
Kirill Chibisov
b5d0d6ff3e On Wayland, implement 'request_user_attention'
This commit implements 'request_user_attention' on Wayland with
new 'xdg_activation_v1' protocol.
2021-08-17 07:59:57 +03:00
Kirill Chibisov
c9520deef8 Update smithay-client-toolkit to 'v0.15.0'
This commit also drops 'Theme' trait with its support types
in favor of 'FallbackFrame' meaning that winit will use some
predefined frame for the time being, since porting 'ConceptFrame'
will require adding font rendering librarires right into winit,
which is not desired.

Fixes #1889.
2021-08-15 22:31:59 +03:00
Steven Bosnick
ceab0f8c40 On Wayland, log error for failure to set cursor
The inability to set the cursor using any of the named cursor files
likely indicates an error in the system on which we are running.
'WinitPointer::set_cursor' also does not have any way of returning an
error so just log the error to assist users in diagnosing the problem.

Fixes: #1988.
2021-08-15 22:05:14 +03:00
Markus Siglreithmaier
b87757c552 Fix Window visibility regression (#1994)
On Windows, set windows visible on init before position update

Ensure that the style change is correctly applied, triggering the necessary events.
2021-08-12 18:57:07 +02:00
Nuno Ribeiro
1972eb952d Adds Android winit<->ndk_glue version match table (#1993)
* Adds Android winit<->ndk_glue version match table

* Fixes justification

* Adds crosses

* Address review and instead of a m:n table it shows a 1:1 version compatibility

* Addresses review
2021-08-11 14:28:49 -07:00
Osspial
f92803d80e Prevent ghost window from showing up on taskbar (#1977)
Add WS_EX_TOOLWINDOW to event target window
2021-08-11 20:02:40 +02:00
Yusuke Kominami
8afeb910bd Add composition event on macOS (#1979)
* Enable to show text when IME is active

* Remove unnecessary variable

* Enable to use IME

* fmt

* Remove println! for debug

* Fix handling of utf-8 string

* clear_marked_text should be rust function, not member function

* Store state information in ViewState

* Remove unnecessary function

* format

* Remove mut

* format

* Remove duplicate marked text

* Remove unused `is_preediting` field

Co-authored-by: Artúr Kovács <kovacs.artur.barnabas@gmail.com>
2021-08-09 11:14:13 +02:00
Sven Niederberger
f16ed98af4 On X11 and Wayland, fx window resize cursor orientation
Fixes #1912.
2021-08-01 11:19:00 +03:00
Aidan Dang
2a9916103b On Wayland, load "hand2" and "hand1" cursor icons for CursorIcon::Hand 2021-08-01 11:16:02 +03:00
Markus Røyset
63ad47a7bf Remove window subclassing (#1933)
* Remove window subclassing

* Always call `DefWindowProcW` when we don't process a message

* Improve window initialization

Note that the error path in `init` is kind of cursed at the moment.

* Rename `ThreadMsgTargetCallbackData` to `ThreadMsgTargetData`

* Simplify window initialization

* Fix compilation on 32-bit targets

* Simplify the creation of the event target window

* Use `.clone()` rather than `Rc::clone()`

* Use concrete types for args to `SetWindowLongPtrW`

On 32-bit targets, `SetWindowLongPtrW` is an alias to `SetWindowLongW`,
which returns `LONG` (`i32`) rather than `LONG_PTR` (`isisze`).

* Minor comment adjustments
2021-07-16 12:40:48 +02:00
jim jammer
27e6548343 macOS: Remove is_key_down from ViewState (#1489)
Fixes #1488.

`is_key_down` was only set to true when `insertText` was called.
Therefore `is_key_down` was actually meant to store whether the most
recently pressed key generated an `insertText` event, at which, winit
produces a `ReceivedCharacter`. The issue is that `insertText` is *not*
called for "key repeat", but winit wants to send `ReceivedCharacter`
events for "key repeat" too. To solve this, the `is_key_down` variable
was then checked in the `key_down` function to determine whether it was
valid to produce repeated `ReceivedCharacter` events during "key
repeat". However this check is not needed since `ReceivedCharacter` must
always be called if the key event has a non-empty `characters` property.

Furthermore `is_key_down` didn't actually store whether the previously
pressed character had an `insertText` event, because it was incorrectly
set to false on every "key up". This meant that if two keys were pressed
consecutively and then the first was released, then `is_key_down` was
set to false even if the most recent keypress did actually produce an
`insertText`.

Update changelog.
2021-07-13 17:38:01 +02:00
Markus Røyset
8c91986dd3 Remove libc dependency on non-linux platforms (#1976) 2021-07-13 17:27:47 +02:00
Aden Haussmann
5a65347c4e Changed description of window scale factor in Events (#1834)
* changed description of window scale factor in Events

* Update src/dpi.rs

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

* Fix intra-doc link

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-06-16 09:24:49 +02:00
Francesca Lovebloom
635180c8be Update Hall of Champions (#1959)
* Lionize Aubrey

* Lionize Victor

* Lionize Freya

* Stylization consistency
2021-06-14 13:55:13 -07:00
Onirik79
019ce9862f Fix typo in events documentation (#1960) 2021-06-13 14:26:20 +02:00
Artúr Kovács
c7f46876a7 Drop the event callback before exiting on macOS (#1954)
* Drop the event callback before exiting

* Update the changelog

* Apply suggestion from review

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

* Apply review suggestions

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-06-13 14:25:06 +02:00
garasubo
c916eb6137 On X11 and Wayland, add is_maximized support
Fixes #1845.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2021-06-10 10:43:27 +03:00
Tilmann Meyer
67cca71524 Implement Window::request_redraw on Android (#1953) 2021-06-05 12:47:08 +02:00
i509VCB
1eff7ae004 Document how main_thread_id for Windows works (#1951) 2021-06-05 12:46:44 +02:00
Mads Marquart
982ad46c83 Use objc's autoreleasepool instead of manually with NSAutoreleasePool (#1936)
Ensures the pools are released even if we panic
2021-05-27 17:38:41 +02:00
Markus Røyset
657b4fd59e Remove support for stdweb (#1941)
* Remove support for `stdweb`

* Expunge `stdweb`; make `web-sys` the default

* Mark this change as a breaking change

* Re-insert accidental removal of space

* Use the correct cargo feature syntax

* Re-add some `cfg` attributes

* Remove `web-sys` feature from CI
2021-05-24 10:06:21 -07:00
Max de Danschutter
b371b406d5 Implemented focus_window (#1944) 2021-05-19 18:39:53 +02:00
Artúr Kovács
91591c4e94 Release 0.25.0 (#1939) 2021-05-15 19:17:08 +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
108 changed files with 3551 additions and 2893 deletions

View File

@@ -2,16 +2,8 @@ name: CI
on:
pull_request:
paths:
- '**.rs'
- '**.toml'
- '.github/workflows/ci.yml'
push:
branches: [master]
paths:
- '**.rs'
- '**.toml'
- '.github/workflows/ci.yml'
jobs:
Check_Formatting:
@@ -38,20 +30,19 @@ jobs:
- { 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: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { 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, cmd: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web }
- { target: wasm32-unknown-unknown, os: windows-latest, }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
CMD: ${{ matrix.platform.cmd }}
@@ -77,10 +68,6 @@ jobs:
- 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')
run: cargo install cargo-web
- name: Check documentation
shell: bash

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,100 @@
# Unreleased
# 0.26.1 (2022-01-05)
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
- On Web, implement cursor grabbing through the pointer lock API.
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
- On Windows, fix a crash with transparent windows on Windows 11.
# 0.26.0 (2021-12-01)
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
- On X11, bump `mio` to 0.8.
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
- On X11, select an appropriate visual for transparency if is requested
- On Wayland and X11, fix diagonal window resize cursor orientation.
- On macOS, drop the event callback before exiting.
- On Android, implement `Window::request_redraw`
- **Breaking:** On Web, remove the `stdweb` backend.
- Added `Window::focus_window`to bring the window to the front and set input focus.
- On Wayland and X11, implement `is_maximized` method on `Window`.
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5.
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
- On macOS, fix native file dialogs hanging the event loop.
- On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland`
- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode.
- On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup.
# 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)
@@ -8,7 +104,7 @@
- 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 and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`.
- 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`.
@@ -81,7 +177,6 @@
- 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.
- On Windows, drag and drop can now be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
# 0.22.0 (2020-03-09)

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.23.0"
version = "0.26.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -12,57 +12,59 @@ documentation = "https://docs.rs/winit"
categories = ["gui"]
[package.metadata.docs.rs]
features = ["serde", "web-sys"]
features = ["serde"]
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"]
wayland = ["wayland-client", "sctk"]
default = ["x11", "wayland", "wayland-dlopen"]
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "wayland-protocols", "sctk"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
[dependencies]
instant = "0.1"
instant = { version = "0.1", features = ["wasm-bindgen"] }
lazy_static = "1"
libc = "0.2.64"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.3"
raw-window-handle = "0.4.2"
bitflags = "1"
mint = { version = "0.5.6", optional = true }
[dev-dependencies]
image = "0.23"
image = "0.23.12"
simple_logger = "1.9"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2.0"
ndk = "0.5"
ndk-sys = "0.2.0"
ndk-glue = "0.2.0"
ndk-glue = "0.5"
[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.23"
cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
objc = "0.2.6"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
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"
version = "0.3.9"
features = [
"combaseapi",
"commctrl",
"dwmapi",
"errhandlingapi",
"imm",
"hidusage",
"libloaderapi",
"objbase",
@@ -78,23 +80,23 @@ features = [
"wingdi",
"winnt",
"winuser",
"mmsystem",
"timeapi"
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.12", optional = true }
mio = "0.6"
mio-extras = "2.0"
wayland-client = { version = "0.29", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29", features = [ "staging_protocols"], optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.15.1", default_features = false, features = ["calloop"], optional = true }
mio = { version = "0.8", features = ["os-ext"], optional = true }
x11-dl = { version = "2.18.5", optional = true }
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.11"
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.11.0", optional = true }
libc = "0.2.64"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.22"
optional = true
features = [
'console',
"AddEventListenerOptions",
@@ -120,13 +122,6 @@ features = [
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
package = "stdweb"
version = "=0.4.20"
optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"

View File

@@ -116,8 +116,9 @@ If your PR makes notable changes to Winit's features, please update this section
### 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,11 +183,9 @@ Legend:
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ \*1|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|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,16 +197,17 @@ Legend:
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**| |
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|✔️ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ |
|Multitouch |✔️ |❌ |✔️ |✔️ | |✔️ |❌ |
|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

@@ -1,14 +1,23 @@
# Hall of Champions
The Winit maintainers would like to recognize the following former Winit
contributors, without whom Winit would not exist in its current form. We thank
The winit maintainers would like to recognize the following former winit
contributors, without whom winit would not exist in its current form. We thank
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* [@tomaka]: For creating the Winit project and guiding it through its early
* [@tomaka]: For creating the winit project and guiding it through its early
years of existence.
* [@vberger]: For diligently creating the Wayland backend, and being its
extremely helpful and benevolent maintainer for years.
* [@francesca64]: For taking over the responsibility of maintaining almost every
Winit backend, and standardizing HiDPI support across all of them
winit backend, and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
vastly more sustainable era of winit.
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
stellar dedication to improving both winit and glutin.
[@tomaka]: https://github.com/tomaka
[@vberger]: https://github.com/vberger
[@francesca64]: https://github.com/francesca64
[@Osspial]: https://github.com/Osspial
[@goddessfreya]: https://github.com/goddessfreya

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.23.0"
winit = "0.26.1"
```
## [Documentation](https://docs.rs/winit)
@@ -19,9 +19,8 @@ For features _outside_ the scope of winit, see [Missing features provided by oth
Join us in any of these:
[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org)
[![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit)
## Usage
@@ -66,15 +65,13 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
* `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
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.
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
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
@@ -94,7 +91,18 @@ book].
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
The `ndk_glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk_glue`'s internal NativeActivity static is not the same due to version mismatch.
`ndk_glue` <-> `winit` version comparison compatibility:
| winit | ndk_glue |
| :---: | :------------------: |
| 0.24 | `ndk_glue = "0.2.0"` |
| 0.25 | `ndk_glue = "0.3.0"` |
| 0.26 | `ndk_glue = "0.5.0"` |
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[[example]]
name = "request_redraw_threaded"
@@ -110,3 +118,13 @@ 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.

View File

@@ -1,21 +1,10 @@
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(feature = "x11"),
not(feature = "wayland")
))]
compile_error!("at least one of the \"x11\"/\"wayland\" features must be enabled");
#[cfg(all(
target_arch = "wasm32",
not(feature = "web-sys"),
not(feature = "stdweb")
))]
compile_error!("at least one of the \"web-sys\"/\"stdweb\" features must be enabled");
fn main() {}
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

@@ -99,7 +99,7 @@ fn main() {
if wait_cancelled {
*control_flow
} else {
ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)
ControlFlow::WaitUntil(instant::Instant::now() + WAIT_TIME)
}
}
Mode::Poll => {

View File

@@ -43,7 +43,6 @@ fn main() {
..
} => {
*control_flow = ControlFlow::Exit;
return;
}
_ => (),
}

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

@@ -15,7 +15,7 @@ fn main() {
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let num = num.trim().parse().expect("Please enter a number");
let fullscreen = Some(match num {
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
@@ -23,7 +23,6 @@ fn main() {
_ => panic!("Please enter a valid number"),
});
let mut is_maximized = false;
let mut decorations = true;
let window = WindowBuilder::new()
@@ -59,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;
@@ -86,7 +85,7 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let num = num.trim().parse().expect("Please enter a number");
let monitor = event_loop
.available_monitors()
.nth(num)
@@ -107,7 +106,7 @@ fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let num = num.trim().parse().expect("Please enter a number");
let video_mode = monitor
.video_modes()
.nth(num)

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

@@ -34,10 +34,10 @@ fn main() {
// We need to update our chosen video mode if the window
// 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);
let previous_video_mode = video_modes.get(video_mode_id).cloned();
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);
let video_mode = video_modes.get(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
@@ -45,7 +45,7 @@ fn main() {
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
video_modes.get(video_mode_id).unwrap()
);
}
}
@@ -77,16 +77,13 @@ fn main() {
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!(
"Picking video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
println!("Picking video mode: {}", video_modes[video_mode_id]);
}
F => window.set_fullscreen(match (state, modifiers.alt()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => Some(Fullscreen::Exclusive(
video_modes.iter().nth(video_mode_id).unwrap().clone(),
)),
(true, true) => {
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
}
(false, _) => None,
}),
G => window.set_cursor_grab(state).unwrap(),
@@ -173,7 +170,7 @@ fn main() {
}
}
},
_ => (),
_ => {}
}
})
}

View File

@@ -41,7 +41,7 @@ fn main() {
},
..
} => {
let window = Window::new(&event_loop).unwrap();
let window = Window::new(event_loop).unwrap();
windows.insert(window.id(), window);
}
_ => (),

View File

@@ -1,13 +1,14 @@
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -38,3 +39,8 @@ fn main() {
}
});
}
#[cfg(target_arch = "wasm32")]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

View File

@@ -0,0 +1,53 @@
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;
}
_ => (),
}
});
}

View File

@@ -12,7 +12,7 @@ pub fn main() {
.build(&event_loop)
.unwrap();
#[cfg(feature = "web-sys")]
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
@@ -26,28 +26,12 @@ pub fn main() {
.expect("Append canvas to HTML body");
}
#[cfg(feature = "stdweb")]
{
use std_web::web::INode;
use winit::platform::web::WindowExtStdweb;
let canvas = window.canvas();
let document = std_web::web::document();
let body: std_web::web::Node = document.body().expect("Get HTML body").into();
body.append_child(&canvas);
}
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
#[cfg(feature = "web-sys")]
#[cfg(target_arch = "wasm32")]
log::debug!("{:?}", event);
#[cfg(feature = "stdweb")]
std_web::console!(log, "%s", format!("{:?}", event));
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
@@ -61,13 +45,13 @@ pub fn main() {
});
}
#[cfg(feature = "web-sys")]
#[cfg(target_arch = "wasm32")]
mod wasm {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug);
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
super::main();
}

View File

@@ -28,7 +28,6 @@ fn main() {
eprintln!(" (X) Toggle maximized");
let mut minimized = false;
let mut maximized = false;
let mut visible = true;
event_loop.run(move |event, _, control_flow| {
@@ -109,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

@@ -49,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

@@ -15,7 +15,7 @@ fn main() {
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
let mut event_loop = EventLoop::new();

View File

@@ -58,7 +58,7 @@
//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how
//! the platform changes the window's size to reflect the new scale factor. If a window hasn't
//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event,
//! then its scale factor is `1.0`.
//! then its scale factor can be found by calling [window.scale_factor()].
//!
//! ## How is the scale factor calculated?
//!
@@ -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.
@@ -94,6 +95,7 @@
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
//! [window.scale_factor()]: crate::window::Window::scale_factor
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
//! [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/
@@ -163,7 +165,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,
@@ -209,9 +211,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalPosition<P> {
fn into(self: Self) -> (X, X) {
(self.x.cast(), self.y.cast())
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
fn from(p: LogicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
@@ -221,14 +223,28 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
fn into(self: Self) -> [X; 2] {
[self.x.cast(), self.y.cast()]
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
fn from(p: LogicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(p: LogicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.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,
@@ -274,9 +290,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalPosition<P> {
fn into(self: Self) -> (X, X) {
(self.x.cast(), self.y.cast())
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
fn from(p: PhysicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
@@ -286,14 +302,28 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
fn into(self: Self) -> [X; 2] {
[self.x.cast(), self.y.cast()]
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
fn from(p: PhysicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(p: PhysicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.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,
@@ -339,9 +369,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalSize<P> {
fn into(self: LogicalSize<P>) -> (X, X) {
(self.width.cast(), self.height.cast())
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
fn from(s: LogicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
@@ -351,14 +381,31 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
fn into(self: Self) -> [X; 2] {
[self.width.cast(), self.height.cast()]
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
fn from(s: LogicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.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,
@@ -401,9 +448,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalSize<P> {
fn into(self: Self) -> (X, X) {
(self.width.cast(), self.height.cast())
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
fn from(s: PhysicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
@@ -413,9 +460,26 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
fn into(self: Self) -> [X; 2] {
[self.width.cast(), self.height.cast()]
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
fn from(s: PhysicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
}
}

View File

@@ -113,7 +113,7 @@ pub enum Event<'a, T: 'static> {
/// Emitted when the event loop is being shut down.
///
/// This is irreversable - if this event is emitted, it is guaranteed to be the last event that
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
/// gets emitted. You generally want to treat this as an "do on quit" event.
LoopDestroyed,
}
@@ -131,7 +131,7 @@ impl<T: Clone> Clone for Event<'static, T> {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(cause.clone()),
NewEvents(cause) => NewEvents(*cause),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
@@ -358,8 +358,8 @@ 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()),
Resized(size) => Resized(*size),
Moved(pos) => Moved(*pos),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
DroppedFile(file) => DroppedFile(file.clone()),
@@ -377,7 +377,7 @@ impl Clone for WindowEvent<'static> {
is_synthetic: *is_synthetic,
},
ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()),
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
#[allow(deprecated)]
CursorMoved {
device_id,
@@ -437,7 +437,7 @@ impl Clone for WindowEvent<'static> {
value: *value,
},
Touch(touch) => Touch(*touch),
ThemeChanged(theme) => ThemeChanged(theme.clone()),
ThemeChanged(theme) => ThemeChanged(*theme),
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
@@ -538,12 +538,16 @@ impl<'a> WindowEvent<'a> {
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
/// Returns a dummy `DeviceId`, useful for unit testing.
///
/// # Safety
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
@@ -745,7 +749,7 @@ pub enum MouseButton {
Left,
Right,
Middle,
Other(u8),
Other(u16),
}
/// Describes a difference in the mouse scroll wheel state.
@@ -998,9 +1002,9 @@ bitflags! {
// left and right modifiers are currently commented out, but we should be able to support
// them in a future release
/// The "shift" key.
const SHIFT = 0b100 << 0;
// const LSHIFT = 0b010 << 0;
// const RSHIFT = 0b001 << 0;
const SHIFT = 0b100;
// const LSHIFT = 0b010;
// const RSHIFT = 0b001;
/// The "control" key.
const CTRL = 0b100 << 3;
// const LCTRL = 0b010 << 3;

View File

@@ -35,10 +35,10 @@
//! 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
@@ -109,7 +109,7 @@
//! 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
@@ -130,7 +130,7 @@
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
#![deny(rust_2018_idioms)]
#![deny(intra_doc_link_resolution_failure)]
#![deny(rustdoc::broken_intra_doc_links)]
#[allow(unused_imports)]
#[macro_use]
@@ -146,8 +146,6 @@ extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
extern crate std_web as stdweb;
pub mod dpi;
#[macro_use]

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;
@@ -75,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()
@@ -129,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;
@@ -151,12 +121,6 @@ pub trait WindowBuilderExtMacOS {
}
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,
@@ -215,6 +179,39 @@ impl WindowBuilderExtMacOS for WindowBuilder {
}
}
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.
pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
@@ -244,14 +241,10 @@ pub trait EventLoopWindowTargetExtMacOS {
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
self.p.hide_application()
}
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] }
self.p.hide_other_applications()
}
}

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

@@ -14,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;
@@ -25,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)
@@ -42,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

@@ -213,10 +213,6 @@ pub trait WindowExtUnix {
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
#[cfg(feature = "x11")]
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).
@@ -241,10 +237,6 @@ pub trait WindowExtUnix {
#[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
///
/// It is a remnant of a previous implementation detail for the
@@ -297,16 +289,6 @@ impl WindowExtUnix for Window {
}
}
#[inline]
#[cfg(feature = "x11")]
fn set_urgent(&self, is_urgent: bool) {
match self.window {
LinuxWindow::X(ref w) => w.set_urgent(is_urgent),
#[cfg(feature = "wayland")]
_ => (),
}
}
#[inline]
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
@@ -337,16 +319,6 @@ impl WindowExtUnix for Window {
}
}
#[inline]
#[cfg(feature = "wayland")]
fn set_wayland_theme<T: Theme>(&self, theme: T) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(theme),
#[cfg(feature = "x11")]
_ => {}
}
}
#[inline]
fn is_ready(&self) -> bool {
true
@@ -373,9 +345,31 @@ pub trait WindowBuilderExtUnix {
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
@@ -468,78 +462,3 @@ impl MonitorHandleExtUnix for MonitorHandle {
self.inner.native_identifier()
}
}
/// A theme for a Wayland's client side decorations.
#[cfg(feature = "wayland")]
pub trait Theme: Send + 'static {
/// Title bar color.
fn element_color(&self, element: Element, window_active: bool) -> ARGBColor;
/// Color for a given button part.
fn button_color(
&self,
button: Button,
state: ButtonState,
foreground: bool,
window_active: bool,
) -> ARGBColor;
/// Font name and the size for the title bar.
///
/// By default the font is `sans-serif` at the size of 11.
///
/// 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"), 11.))
}
}
/// 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,
/// Button that minimizes the window.
Minimize,
/// 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,
/// Button is not being hovered over by pointer.
Idle,
/// Button is disabled.
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

@@ -1,28 +1,14 @@
#![cfg(target_arch = "wasm32")]
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
//! your own canvas.
//! allow end users to determine how the page should be laid out. Use the `WindowExtWebSys` trait
//! to retrieve the canvas from the Window. Alternatively, use the `WindowBuilderExtWebSys` trait
//! to provide your own canvas.
use crate::window::WindowBuilder;
#[cfg(feature = "stdweb")]
use stdweb::web::html_element::CanvasElement;
#[cfg(feature = "stdweb")]
pub trait WindowExtStdweb {
fn canvas(&self) -> CanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "web-sys")]
use web_sys::HtmlCanvasElement;
#[cfg(feature = "web-sys")]
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
@@ -30,26 +16,10 @@ pub trait WindowExtWebSys {
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "stdweb")]
pub trait WindowBuilderExtStdweb {
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
}
#[cfg(feature = "stdweb")]
impl WindowBuilderExtStdweb for WindowBuilder {
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
}
#[cfg(feature = "web-sys")]
pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
}
#[cfg(feature = "web-sys")]
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas;

View File

@@ -3,17 +3,16 @@
use std::os::raw::c_void;
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.
@@ -72,46 +71,92 @@ impl<T> EventLoopExtWindows for EventLoop<T> {
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
fn hinstance(&self) -> *mut libc::c_void;
fn hinstance(&self) -> *mut c_void;
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> *mut libc::c_void;
fn hwnd(&self) -> *mut 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 {
#[inline]
fn hinstance(&self) -> *mut libc::c_void {
fn hinstance(&self) -> *mut c_void {
self.window.hinstance() as *mut _
}
#[inline]
fn hwnd(&self) -> *mut libc::c_void {
fn hwnd(&self) -> *mut c_void {
self.window.hwnd() as *mut _
}
#[inline]
fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled)
}
#[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;
@@ -123,14 +168,29 @@ pub trait WindowBuilderExtWindows {
/// `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.
/// 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
}
@@ -151,6 +211,12 @@ impl WindowBuilderExtWindows for 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

@@ -8,10 +8,11 @@ use crate::{
};
use ndk::{
configuration::Configuration,
event::{InputEvent, MotionAction},
event::{InputEvent, KeyAction, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
};
use ndk_glue::{Event, Rect};
use raw_window_handle::{AndroidNdkHandle, RawWindowHandle};
use std::{
collections::VecDeque,
sync::{Arc, Mutex, RwLock},
@@ -19,24 +20,44 @@ use std::{
};
lazy_static! {
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::new());
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::from_asset_manager(
&ndk_glue::native_activity().asset_manager()
));
// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event
// contained in the `Option`. The event is moved outside of the `Option` replacing it with a
// `None`.
//
// This allows us to inject event into the event loop without going through `ndk-glue` and
// calling unsafe function that should only be called by Android.
static ref INTERNAL_EVENT: RwLock<Option<InternalEvent>> = RwLock::new(None);
}
enum InternalEvent {
RedrawRequested,
}
enum EventSource {
Callback,
InputQueue,
User,
Internal(InternalEvent),
}
fn poll(poll: Poll) -> Option<EventSource> {
match poll {
Poll::Event { data, .. } => match data as usize {
0 => Some(EventSource::Callback),
1 => Some(EventSource::InputQueue),
Poll::Event { ident, .. } => match ident {
ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback),
ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue),
_ => unreachable!(),
},
Poll::Timeout => None,
Poll::Wake => Some(EventSource::User),
Poll::Wake => Some(
INTERNAL_EVENT
.write()
.unwrap()
.take()
.map_or(EventSource::User, EventSource::Internal),
),
Poll::Callback => unreachable!(),
}
}
@@ -148,54 +169,126 @@ impl<T: 'static> EventLoop<T> {
);
}
}
Event::WindowHasFocus => {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(true),
}
);
}
Event::WindowLostFocus => {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(false),
}
);
}
_ => {}
},
Some(EventSource::InputQueue) => {
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
while let Some(event) = input_queue.get_event() {
println!("event {:?}", event);
if let Some(event) = input_queue.pre_dispatch(event) {
let mut handled = true;
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId);
match &event {
InputEvent::MotionEvent(motion_event) => {
let phase = match motion_event.action() {
MotionAction::Down => Some(event::TouchPhase::Started),
MotionAction::Up => Some(event::TouchPhase::Ended),
MotionAction::Down | MotionAction::PointerDown => {
Some(event::TouchPhase::Started)
}
MotionAction::Up | MotionAction::PointerUp => {
Some(event::TouchPhase::Ended)
}
MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => {
Some(event::TouchPhase::Cancelled)
}
_ => None, // TODO mouse events
_ => {
handled = false;
None // TODO mouse events
}
};
let pointer = motion_event.pointer_at_index(0);
let location = PhysicalPosition {
x: pointer.x() as _,
y: pointer.y() as _,
};
if let Some(phase) = phase {
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: 0,
force: None,
}),
let pointers: Box<
dyn Iterator<Item = ndk::event::Pointer<'_>>,
> = match phase {
event::TouchPhase::Started
| event::TouchPhase::Ended => Box::new(
std::iter::once(motion_event.pointer_at_index(
motion_event.pointer_index(),
)),
),
event::TouchPhase::Moved
| event::TouchPhase::Cancelled => {
Box::new(motion_event.pointers())
}
};
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event
);
for pointer in pointers {
let location = PhysicalPosition {
x: pointer.x() as _,
y: pointer.y() as _,
};
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(
event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: None,
},
),
};
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event
);
}
}
}
InputEvent::KeyEvent(_) => {} // TODO
InputEvent::KeyEvent(key) => {
let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed,
KeyAction::Up => event::ElementState::Released,
_ => event::ElementState::Released,
};
#[allow(deprecated)]
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::KeyboardInput {
device_id,
input: event::KeyboardInput {
scancode: key.scan_code() as u32,
state,
virtual_keycode: None,
modifiers: event::ModifiersState::default(),
},
is_synthetic: false,
},
};
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event
);
}
};
input_queue.finish_event(event, true);
input_queue.finish_event(event, handled);
}
}
}
@@ -211,6 +304,9 @@ impl<T: 'static> EventLoop<T> {
);
}
}
Some(EventSource::Internal(internal)) => match internal {
InternalEvent::RedrawRequested => redraw = true,
},
None => {}
}
@@ -350,7 +446,7 @@ impl<T: 'static> EventLoopWindowTarget<T> {
pub struct WindowId;
impl WindowId {
pub fn dummy() -> Self {
pub const fn dummy() -> Self {
WindowId
}
}
@@ -359,7 +455,7 @@ impl WindowId {
pub struct DeviceId;
impl DeviceId {
pub fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}
@@ -406,7 +502,8 @@ impl Window {
}
pub fn request_redraw(&self) {
// TODO
*INTERNAL_EVENT.write().unwrap() = Some(InternalEvent::RedrawRequested);
ForeignLooper::for_thread().unwrap().wake();
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
@@ -447,6 +544,10 @@ impl Window {
pub fn set_maximized(&self, _maximized: bool) {}
pub fn is_maximized(&self) -> bool {
false
}
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
warn!("Cannot set fullscreen on Android");
}
@@ -463,6 +564,10 @@ impl Window {
pub fn set_ime_position(&self, _position: Position) {}
pub fn focus_window(&self) {}
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
@@ -479,15 +584,20 @@ impl Window {
pub fn set_cursor_visible(&self, _: bool) {}
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = AndroidNdkHandle::empty();
if let Some(native_window) = ndk_glue::native_window().as_ref() {
handle.a_native_window = unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
} else {
panic!("native window null");
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
};
let mut handle = raw_window_handle::android::AndroidHandle::empty();
handle.a_native_window = a_native_window;
raw_window_handle::RawWindowHandle::Android(handle)
RawWindowHandle::AndroidNdk(handle)
}
pub fn config(&self) -> Configuration {

View File

@@ -91,7 +91,7 @@ pub struct DeviceId {
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId {
uiscreen: std::ptr::null_mut(),
}

View File

@@ -168,7 +168,9 @@ impl Drop for MonitorHandle {
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API
#[derive(Debug)]
#[allow(dead_code)]
struct MonitorHandle {
name: Option<String>,
size: PhysicalSize<u32>,

View File

@@ -1,4 +1,4 @@
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
use raw_window_handle::{RawWindowHandle, UiKitHandle};
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
@@ -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 {
@@ -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 {
@@ -260,6 +271,14 @@ impl Inner {
warn!("`Window::set_ime_position` is ignored on iOS")
}
pub fn focus_window(&self) {
warn!("`Window::set_focus` is ignored on iOS")
}
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 {
@@ -288,13 +307,11 @@ impl Inner {
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = IOSHandle {
ui_window: self.window as _,
ui_view: self.view as _,
ui_view_controller: self.view_controller as _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(handle)
let mut handle = UiKitHandle::empty();
handle.ui_window = self.window as _;
handle.ui_view = self.view as _;
handle.ui_view_controller = self.view_controller as _;
RawWindowHandle::UiKit(handle)
}
}
@@ -592,7 +609,7 @@ pub struct WindowId {
}
impl WindowId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
WindowId {
window: std::ptr::null_mut(),
}

View File

@@ -30,7 +30,7 @@ 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;
@@ -118,9 +118,9 @@ impl fmt::Display for OsError {
#[cfg(feature = "x11")]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(feature = "x11")]
OsError::XMisc(ref e) => _f.pad(e),
OsError::XMisc(e) => _f.pad(e),
#[cfg(feature = "wayland")]
OsError::WaylandMisc(ref e) => _f.pad(e),
OsError::WaylandMisc(e) => _f.pad(e),
}
}
}
@@ -141,7 +141,7 @@ pub enum WindowId {
}
impl WindowId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
#[cfg(feature = "wayland")]
return WindowId::Wayland(wayland::WindowId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
@@ -158,7 +158,7 @@ pub enum DeviceId {
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
#[cfg(feature = "wayland")]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
@@ -358,6 +358,11 @@ impl Window {
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 {
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
@@ -373,6 +378,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
}
#[inline]
pub fn is_maximized(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_maximized())
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
@@ -397,9 +407,9 @@ impl Window {
pub fn set_always_on_top(&self, _always_on_top: bool) {
match self {
#[cfg(feature = "x11")]
&Window::X(ref w) => w.set_always_on_top(_always_on_top),
Window::X(ref w) => w.set_always_on_top(_always_on_top),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
@@ -407,9 +417,9 @@ impl Window {
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
#[cfg(feature = "x11")]
&Window::X(ref w) => w.set_window_icon(_window_icon),
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
@@ -418,6 +428,24 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
}
#[inline]
pub fn focus_window(&self) {
match self {
#[cfg(feature = "x11")]
Window::X(ref w) => w.focus_window(),
#[cfg(feature = "wayland")]
Window::Wayland(_) => (),
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(feature = "x11")]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(feature = "wayland")]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
}
#[inline]
pub fn request_redraw(&self) {
x11_or_wayland!(match self; Window(w) => w.request_redraw())
@@ -427,14 +455,14 @@ impl Window {
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => {
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(RootMonitorHandle {
inner: current_monitor,
})
}
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => {
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(RootMonitorHandle {
inner: current_monitor,
@@ -447,13 +475,13 @@ impl Window {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => window
Window::X(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => window
Window::Wayland(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
@@ -465,23 +493,23 @@ impl Window {
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => {
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(),
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()),
Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
}
}
@@ -588,11 +616,10 @@ impl<T: 'static> EventLoop<T> {
#[cfg(not(feature = "x11"))]
let x11_err = "backend disabled";
let err_string = format!(
panic!(
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err, x11_err,
);
panic!(err_string);
}
#[cfg(feature = "wayland")]

View File

@@ -13,6 +13,7 @@ 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::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::{Environment, SimpleGlobal};
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
@@ -24,18 +25,27 @@ use sctk::shm::ShmHandler;
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
cursor_grab: bool,
xdg_activation: 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 }
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
cursor_grab,
xdg_activation,
}
}
pub fn cursor_grab(&self) -> bool {
self.cursor_grab
}
pub fn xdg_activation(&self) -> bool {
self.xdg_activation
}
}
sctk::environment!(WinitEnv,
@@ -50,6 +60,7 @@ sctk::environment!(WinitEnv,
ZwpRelativePointerManagerV1 => relative_pointer_manager,
ZwpPointerConstraintsV1 => pointer_constraints,
ZwpTextInputManagerV3 => text_input_manager,
XdgActivationV1 => xdg_activation,
],
multis = [
WlSeat => seats,
@@ -78,6 +89,8 @@ pub struct WinitEnv {
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
}
impl WinitEnv {
@@ -109,6 +122,9 @@ impl WinitEnv {
// IME handling.
let text_input_manager = SimpleGlobal::new();
// Surface activation.
let xdg_activation = SimpleGlobal::new();
Self {
seats,
outputs,
@@ -120,6 +136,7 @@ impl WinitEnv {
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
}
}
}

View File

@@ -1,6 +1,7 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::process;
use std::rc::Rc;
use std::time::{Duration, Instant};
@@ -18,6 +19,7 @@ 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 crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
@@ -34,6 +36,8 @@ pub use state::WinitState;
use sink::EventSink;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
pub struct EventLoopWindowTarget<T> {
/// Wayland display.
pub display: Display,
@@ -42,7 +46,7 @@ pub struct EventLoopWindowTarget<T> {
pub env: Environment<WinitEnv>,
/// Event loop handle.
pub event_loop_handle: calloop::LoopHandle<WinitState>,
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
/// Output manager.
pub output_manager: OutputManager,
@@ -50,8 +54,8 @@ pub struct EventLoopWindowTarget<T> {
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Wayland source.
pub wayland_source: Rc<calloop::Source<WaylandSource>>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// A proxy to wake up event loop.
pub event_loop_awakener: calloop::ping::Ping,
@@ -70,7 +74,7 @@ pub struct EventLoopWindowTarget<T> {
pub struct EventLoop<T: 'static> {
/// Event loop.
event_loop: calloop::EventLoop<WinitState>,
event_loop: calloop::EventLoop<'static, WinitState>,
/// Wayland display.
display: Display,
@@ -81,8 +85,8 @@ pub struct EventLoop<T: 'static> {
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
/// Wayland source of events.
wayland_source: Rc<calloop::Source<WaylandSource>>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// Window target.
window_target: RootEventLoopWindowTarget<T>,
@@ -102,7 +106,7 @@ impl<T: 'static> EventLoop<T> {
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
// Create event loop.
let event_loop = calloop::EventLoop::<WinitState>::new()?;
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
@@ -116,8 +120,22 @@ impl<T: 'static> EventLoop<T> {
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);
let wayland_source = WaylandSource::new(event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state, |event, object, _| {
panic!(
"[calloop] Encountered an orphan event: {}@{} : {}",
event.interface,
object.as_ref().id(),
event.name
);
})
});
let _wayland_source_dispatcher = event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
// A source of user events.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
@@ -161,7 +179,7 @@ impl<T: 'static> EventLoop<T> {
event_loop_handle,
output_manager,
event_loop_awakener,
wayland_source: wayland_source.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
@@ -172,11 +190,11 @@ impl<T: 'static> EventLoop<T> {
event_loop,
display,
pending_user_events,
wayland_source,
wayland_dispatcher,
_seat_manager: seat_manager,
user_events_sender,
window_target: RootEventLoopWindowTarget {
p: crate::platform_impl::EventLoopWindowTarget::Wayland(event_loop_window_target),
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
},
};
@@ -243,7 +261,7 @@ impl<T: 'static> EventLoop<T> {
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 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.
@@ -276,7 +294,7 @@ impl<T: 'static> EventLoop<T> {
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 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.
@@ -287,7 +305,7 @@ impl<T: 'static> EventLoop<T> {
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(&window_handle.window.surface());
sctk::get_surface_scale_factor(window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
@@ -363,7 +381,7 @@ impl<T: 'static> EventLoop<T> {
// 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();
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();
@@ -403,16 +421,17 @@ impl<T: 'static> EventLoop<T> {
// 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!())
})
});
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let queue = wayland_source.queue();
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
_ => unreachable!(),
};
if let Ok(dispatched) = dispatched {
if let Ok(dispatched) = queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
dispatched > 0
} else {
break;
@@ -508,9 +527,7 @@ impl<T: 'static> EventLoop<T> {
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()
}
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
@@ -518,18 +535,13 @@ impl<T: 'static> EventLoop<T> {
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()
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop.dispatch(timeout, &mut state)
self.event_loop.dispatch(timeout, state)
}
}

View File

@@ -22,7 +22,7 @@ mod window;
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}
@@ -31,7 +31,7 @@ impl DeviceId {
pub struct WindowId(usize);
impl WindowId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
WindowId(0)
}
}

View File

@@ -112,7 +112,7 @@ impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
Some(self.cmp(other))
}
}

View File

@@ -149,6 +149,10 @@ pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
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),

View File

@@ -7,9 +7,9 @@ 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::reexports::calloop::{LoopHandle, RegistrationToken};
use sctk::seat::keyboard::{self, RepeatSource};
use sctk::seat::keyboard;
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
@@ -22,22 +22,22 @@ pub(crate) struct Keyboard {
pub keyboard: WlKeyboard,
/// The source for repeat keys.
pub repeat_source: Option<Source<RepeatSource>>,
pub repeat_token: Option<RegistrationToken>,
/// LoopHandle to drop `RepeatSource`, when dropping the keyboard.
pub loop_handle: LoopHandle<WinitState>,
pub loop_handle: LoopHandle<'static, WinitState>,
}
impl Keyboard {
pub fn new(
seat: &Attached<WlSeat>,
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, 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,
seat,
None,
keyboard::RepeatKind::System,
move |event, _, mut dispatch_data| {
@@ -46,12 +46,12 @@ impl Keyboard {
},
);
let (keyboard, repeat_source) = keyboard_data.ok()?;
let (keyboard, repeat_token) = keyboard_data.ok()?;
Some(Self {
keyboard,
loop_handle,
repeat_source: Some(repeat_source),
repeat_token: Some(repeat_token),
})
}
}
@@ -62,8 +62,8 @@ impl Drop for Keyboard {
self.keyboard.release();
}
if let Some(repeat_source) = self.repeat_source.take() {
self.loop_handle.remove(repeat_source);
if let Some(repeat_token) = self.repeat_token.take() {
self.loop_handle.remove(repeat_token);
}
}
}

View File

@@ -37,7 +37,7 @@ pub struct SeatManager {
impl SeatManager {
pub fn new(
env: &Environment<WinitEnv>,
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
theme_manager: ThemeManager,
) -> Self {
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
@@ -63,7 +63,7 @@ impl SeatManager {
}
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
inner.process_seat_update(&seat, &seat_data);
inner.process_seat_update(&seat, seat_data);
});
Self {
@@ -78,7 +78,7 @@ struct SeatManagerInner {
seats: Vec<SeatInfo>,
/// Loop handle.
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
/// Relative pointer manager.
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
@@ -99,7 +99,7 @@ impl SeatManagerInner {
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Self {
Self {
seats: Vec::new(),
@@ -127,7 +127,7 @@ impl SeatManagerInner {
if seat_data.has_pointer && !seat_data.defunct {
if seat_info.pointer.is_none() {
seat_info.pointer = Some(Pointers::new(
&seat,
seat,
&self.theme_manager,
&self.relative_pointer_manager,
&self.pointer_constraints,
@@ -142,7 +142,7 @@ impl SeatManagerInner {
if seat_data.has_keyboard && !seat_data.defunct {
if seat_info.keyboard.is_none() {
seat_info.keyboard = Keyboard::new(
&seat,
seat,
self.loop_handle.clone(),
seat_info.modifiers_state.clone(),
);
@@ -154,7 +154,7 @@ impl SeatManagerInner {
// Handle touch.
if seat_data.has_touch && !seat_data.defunct {
if seat_info.touch.is_none() {
seat_info.touch = Some(Touch::new(&seat));
seat_info.touch = Some(Touch::new(seat));
}
} else {
seat_info.touch = None;
@@ -165,7 +165,7 @@ impl SeatManagerInner {
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));
seat_info.text_input = Some(TextInput::new(seat, text_input_manager));
}
}
}

View File

@@ -4,6 +4,7 @@ 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;
@@ -17,12 +18,18 @@ 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();
@@ -54,6 +61,7 @@ pub(super) fn handle_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);
@@ -96,6 +104,7 @@ pub(super) fn handle_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);
@@ -120,7 +129,7 @@ pub(super) fn handle_pointer(
let window_id = wayland::make_wid(surface);
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
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(
@@ -153,11 +162,10 @@ pub(super) fn handle_pointer(
};
let button = match button {
0x110 => MouseButton::Left,
0x111 => MouseButton::Right,
0x112 => MouseButton::Middle,
// TODO - figure out the translation.
_ => return,
BTN_LEFT => MouseButton::Left,
BTN_RIGHT => MouseButton::Right,
BTN_MIDDLE => MouseButton::Middle,
button => MouseButton::Other(button as u16),
};
event_sink.push_window_event(
@@ -178,7 +186,7 @@ pub(super) fn handle_pointer(
None => return,
};
let window_id = wayland::make_wid(&surface);
let window_id = wayland::make_wid(surface);
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
@@ -191,7 +199,7 @@ pub(super) fn handle_pointer(
_ => unreachable!(),
}
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
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(
@@ -254,7 +262,7 @@ pub(super) fn handle_pointer(
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(&surface);
let window_id = wayland::make_wid(surface);
let window_event = if let Some((x, y)) = axis_discrete_buffer {
WindowEvent::MouseWheel {
@@ -266,7 +274,7 @@ pub(super) fn handle_pointer(
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 scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
WindowEvent::MouseWheel {

View File

@@ -13,6 +13,7 @@ use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_p
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::{FallbackFrame, Window};
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
@@ -35,6 +36,9 @@ pub struct WinitPointer {
/// Latest observed serial in pointer events.
latest_serial: Rc<Cell<u32>>,
/// Seat.
seat: WlSeat,
}
impl PartialEq for WinitPointer {
@@ -66,7 +70,7 @@ impl WinitPointer {
CursorIcon::Copy => &["copy"],
CursorIcon::Crosshair => &["crosshair"],
CursorIcon::Default => &["left_ptr"],
CursorIcon::Hand => &["hand"],
CursorIcon::Hand => &["hand2", "hand1"],
CursorIcon::Help => &["question_arrow"],
CursorIcon::Move => &["move"],
CursorIcon::Grab => &["openhand", "grab"],
@@ -89,8 +93,8 @@ impl WinitPointer {
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::NwseResize => &["bd_double_arrow", "size_fdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
CursorIcon::Text => &["text", "xterm"],
@@ -105,9 +109,10 @@ impl WinitPointer {
let serial = Some(self.latest_serial.get());
for cursor in cursors {
if self.pointer.set_cursor(cursor, serial).is_ok() {
break;
return;
}
}
warn!("Failed to set cursor to {:?}", cursor_icon);
}
/// Confine the pointer to a surface.
@@ -124,8 +129,8 @@ impl WinitPointer {
};
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
&pointer_constraints,
&surface,
pointer_constraints,
surface,
&*self.pointer,
));
}
@@ -144,6 +149,10 @@ impl WinitPointer {
confined_pointer.destroy();
}
}
pub fn drag_window(&self, window: &Window<FallbackFrame>) {
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
}
/// A pointer wrapper for easy releasing and managing pointers.
@@ -172,21 +181,27 @@ impl Pointers {
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);
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,
};
let relative_pointer = relative_pointer_manager
.as_ref()
.map(|relative_pointer_manager| {
init_relative_pointer(relative_pointer_manager, &*pointer)
});
Self {
pointer,
@@ -234,7 +249,7 @@ pub(super) fn init_confined_pointer(
pointer: &WlPointer,
) -> ZwpConfinedPointerV1 {
let confined_pointer =
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent.to_raw());
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent);
confined_pointer.quick_assign(move |_, _, _| {});

View File

@@ -7,21 +7,17 @@ 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 raw_window_handle::WaylandHandle;
use sctk::window::{Decorations, FallbackFrame};
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 crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes};
use super::env::WindowingFeatures;
use super::event_loop::WinitState;
@@ -54,6 +50,9 @@ pub struct Window {
/// Fullscreen state.
fullscreen: Arc<AtomicBool>,
/// Maximized state.
maximized: Arc<AtomicBool>,
/// Available windowing features.
windowing_features: WindowingFeatures,
@@ -87,6 +86,8 @@ impl Window {
let scale_factor = sctk::get_surface_scale_factor(&surface);
let window_id = super::make_wid(&surface);
let maximized = Arc::new(AtomicBool::new(false));
let maximzied_clone = maximized.clone();
let fullscreen = Arc::new(AtomicBool::new(false));
let fullscreen_clone = fullscreen.clone();
@@ -98,7 +99,7 @@ impl Window {
let theme_manager = event_loop_window_target.theme_manager.clone();
let mut window = event_loop_window_target
.env
.create_window::<ConceptFrame, _>(
.create_window::<FallbackFrame, _>(
surface.clone(),
Some(theme_manager),
(width, height),
@@ -113,6 +114,8 @@ impl Window {
window_update.refresh_frame = true;
}
Event::Configure { new_size, states } => {
let is_maximized = states.contains(&State::Maximized);
maximzied_clone.store(is_maximized, Ordering::Relaxed);
let is_fullscreen = states.contains(&State::Fullscreen);
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
@@ -136,6 +139,12 @@ impl Window {
} else {
window.set_decorate(Decorations::None);
}
// Without this commit here at least on kwin 5.23.3 the initial configure
// will have a size (1,1), the second configure including the decoration
// mode will have the min_size as its size. With this commit the initial
// configure will have no size, the application will draw it's content
// with the initial size and everything works as expected afterwards.
window.surface().commit();
// Min dimensions.
let min_size = attributes
@@ -145,7 +154,7 @@ impl Window {
// Max dimensions.
let max_size = attributes
.min_inner_size
.max_inner_size
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
window.set_max_size(max_size);
@@ -194,7 +203,12 @@ impl Window {
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 window_handle = WindowHandle::new(
&event_loop_window_target.env,
window,
size.clone(),
window_requests.clone(),
);
let mut winit_state = event_loop_window_target.state.borrow_mut();
@@ -206,17 +220,14 @@ impl Window {
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 mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.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
@@ -235,6 +246,7 @@ impl Window {
window_requests,
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
fullscreen,
maximized,
windowing_features,
};
@@ -250,9 +262,7 @@ impl Window {
#[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();
self.send_request(WindowRequest::Title(title.to_owned()));
}
#[inline]
@@ -284,9 +294,7 @@ impl Window {
#[inline]
pub fn request_redraw(&self) {
let redraw_request = WindowRequest::Redraw;
self.window_requests.lock().unwrap().push(redraw_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::Redraw);
}
#[inline]
@@ -304,12 +312,7 @@ impl Window {
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();
self.send_request(WindowRequest::FrameSize(size));
}
#[inline]
@@ -317,9 +320,7 @@ impl Window {
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();
self.send_request(WindowRequest::MinSize(size));
}
#[inline]
@@ -327,19 +328,12 @@ impl Window {
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();
self.send_request(WindowRequest::MaxSize(size));
}
#[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();
self.send_request(WindowRequest::Resizeable(resizable));
}
#[inline]
@@ -351,9 +345,7 @@ impl Window {
#[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();
self.send_request(WindowRequest::Decorate(decorate));
}
#[inline]
@@ -363,16 +355,17 @@ impl Window {
return;
}
let minimize_request = WindowRequest::Minimize;
self.window_requests.lock().unwrap().push(minimize_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::Minimize);
}
#[inline]
pub fn is_maximized(&self) -> bool {
self.maximized.load(Ordering::Relaxed)
}
#[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();
self.send_request(WindowRequest::Maximize(maximized));
}
#[inline]
@@ -408,154 +401,17 @@ impl Window {
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();
self.send_request(fullscreen_request);
}
#[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();
self.send_request(WindowRequest::NewCursorIcon(cursor));
}
#[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();
self.send_request(WindowRequest::ShowCursor(visible));
}
#[inline]
@@ -564,16 +420,20 @@ impl Window {
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();
self.send_request(WindowRequest::GrabCursor(grab));
Ok(())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
if !self.windowing_features.xdg_activation() {
warn!("`request_user_attention` isn't supported");
return;
}
self.send_request(WindowRequest::Attention(request_type));
}
#[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
@@ -586,16 +446,18 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.send_request(WindowRequest::DragWindow);
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();
self.send_request(WindowRequest::IMEPosition(position));
}
#[inline]
@@ -626,31 +488,21 @@ impl Window {
#[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()
}
let mut handle = WaylandHandle::empty();
handle.display = self.display.get_display_ptr() as *mut _;
handle.surface = self.surface.as_ref().c_ptr() as *mut _;
handle
}
}
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 }
#[inline]
fn send_request(&self, request: WindowRequest) {
self.window_requests.lock().unwrap().push(request);
self.event_loop_awakener.ping();
}
}
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();
self.send_request(WindowRequest::Close);
}
}

View File

@@ -2,17 +2,23 @@ use std::cell::Cell;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::window::{ConceptConfig, ConceptFrame, Decorations, Window};
use sctk::environment::Environment;
use sctk::window::{Decorations, FallbackFrame, Window};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::WindowEvent;
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
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;
use crate::window::{CursorIcon, UserAttentionType};
/// A request to SCTK window from Winit window.
#[derive(Debug, Clone)]
@@ -34,6 +40,9 @@ pub enum WindowRequest {
/// Grab cursor.
GrabCursor(bool),
/// Drag window.
DragWindow,
/// Maximize the window.
Maximize(bool),
@@ -61,12 +70,14 @@ pub enum WindowRequest {
/// Set IME window position.
IMEPosition(LogicalPosition<u32>),
/// Request Attention.
///
/// `None` unsets the attention request.
Attention(Option<UserAttentionType>),
/// Redraw was requested.
Redraw,
/// A new theme for a concept frame was requested.
Theme(ConceptConfig),
/// Window should be closed.
Close,
}
@@ -128,7 +139,7 @@ impl WindowUpdate {
/// and react to events.
pub struct WindowHandle {
/// An actual window.
pub window: Window<ConceptFrame>,
pub window: Window<FallbackFrame>,
/// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>,
@@ -150,14 +161,23 @@ pub struct WindowHandle {
/// Text inputs on the current surface.
text_inputs: Vec<TextInputHandler>,
/// XdgActivation object.
xdg_activation: Option<Attached<XdgActivationV1>>,
/// Indicator whether user attention is requested.
attention_requested: Cell<bool>,
}
impl WindowHandle {
pub fn new(
window: Window<ConceptFrame>,
env: &Environment<WinitEnv>,
window: Window<FallbackFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>();
Self {
window,
size,
@@ -167,6 +187,8 @@ impl WindowHandle {
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
xdg_activation,
attention_requested: Cell::new(false),
}
}
@@ -181,13 +203,55 @@ impl WindowHandle {
for pointer in self.pointers.iter() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(&surface);
pointer.confine(surface);
} else {
pointer.unconfine();
}
}
}
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
None => return,
Some(xdg_activation) => xdg_activation,
};
// Urgency is only removed by the compositor and there's no need to raise urgency when it
// was already raised.
if request_type.is_none() || self.attention_requested.get() {
return;
}
let xdg_activation_token = xdg_activation.get_activation_token();
let surface = self.window.surface();
let window_id = wayland::make_wid(surface);
let xdg_activation = xdg_activation.clone();
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
let token = match event {
xdg_activation_token_v1::Event::Done { token } => token,
_ => return,
};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let surface = window_handle.window.surface();
xdg_activation.activate(token, surface);
// Mark that attention request was done and drop the token.
window_handle.attention_requested.replace(false);
xdg_token.destroy();
});
xdg_activation_token.set_surface(surface);
xdg_activation_token.commit();
self.attention_requested.replace(true);
}
/// Pointer appeared over the window.
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
let position = self.pointers.iter().position(|p| *p == pointer);
@@ -195,7 +259,7 @@ impl WindowHandle {
if position.is_none() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(&surface);
pointer.confine(surface);
}
self.pointers.push(pointer);
}
@@ -219,12 +283,7 @@ impl WindowHandle {
}
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
if self
.text_inputs
.iter()
.find(|t| *t == &text_input)
.is_none()
{
if !self.text_inputs.iter().any(|t| *t == text_input) {
self.text_inputs.push(text_input);
}
}
@@ -268,6 +327,12 @@ impl WindowHandle {
pointer.set_cursor(Some(cursor_icon));
}
}
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
}
#[inline]
@@ -299,6 +364,9 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
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();
@@ -318,35 +386,35 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
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();
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();
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();
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();
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();
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::FrameSize(size) => {
@@ -354,20 +422,16 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
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();
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type);
}
WindowRequest::Redraw => {
let window_update = window_updates.get_mut(&window_id).unwrap();
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);

View File

@@ -45,7 +45,7 @@ impl<T: 'static> EventProcessor<T> {
let mut devices = self.devices.borrow_mut();
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
for info in info.iter() {
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
devices.insert(DeviceId(info.deviceid), Device::new(self, info));
}
}
}
@@ -709,7 +709,7 @@ impl<T: 'static> EventProcessor<T> {
event: MouseInput {
device_id,
state,
button: Other(x as u8),
button: Other(x as u16),
modifiers,
},
}),
@@ -925,7 +925,7 @@ impl<T: 'static> EventProcessor<T> {
// Issue key press events for all pressed keys
Self::handle_pressed_keys(
&wt,
wt,
window_id,
ElementState::Pressed,
&self.mod_keymap,
@@ -949,7 +949,7 @@ impl<T: 'static> EventProcessor<T> {
// Issue key release events for all pressed keys
Self::handle_pressed_keys(
&wt,
wt,
window_id,
ElementState::Released,
&self.mod_keymap,
@@ -1164,68 +1164,64 @@ impl<T: 'static> EventProcessor<T> {
if let Some(prev_list) = prev_list {
let new_list = wt.xconn.available_monitors();
for new_monitor in new_list {
prev_list
// Previous list may be empty, in case of disconnecting and
// reconnecting the only one monitor. We still need to emit events in
// this case.
let maybe_prev_scale_factor = prev_list
.iter()
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
.map(|prev_monitor| {
if new_monitor.scale_factor != prev_monitor.scale_factor {
for (window_id, window) in wt.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.current_monitor();
if monitor.name == new_monitor.name {
let (width, height) =
window.inner_size_physical();
let (new_width, new_height) = window
.adjust_for_dpi(
prev_monitor.scale_factor,
new_monitor.scale_factor,
width,
height,
&*window.shared_state.lock(),
);
.map(|prev_monitor| prev_monitor.scale_factor);
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
for (window_id, window) in wt.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.current_monitor();
if monitor.name == new_monitor.name {
let (width, height) = window.inner_size_physical();
let (new_width, new_height) = window.adjust_for_dpi(
// If there all monitors are closed before, scale
// factor would be already changed to 1.0.
maybe_prev_scale_factor.unwrap_or(1.0),
new_monitor.scale_factor,
width,
height,
&*window.shared_state.lock(),
);
let window_id = crate::window::WindowId(
crate::platform_impl::platform::WindowId::X(
*window_id,
),
);
let old_inner_size =
PhysicalSize::new(width, height);
let mut new_inner_size =
PhysicalSize::new(new_width, new_height);
let window_id = crate::window::WindowId(
crate::platform_impl::platform::WindowId::X(
*window_id,
),
);
let old_inner_size = PhysicalSize::new(width, height);
let mut new_inner_size =
PhysicalSize::new(new_width, new_height);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
new_inner_size: &mut new_inner_size,
},
});
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
new_inner_size: &mut new_inner_size,
},
});
if new_inner_size != old_inner_size {
let (new_width, new_height) =
new_inner_size.into();
window.set_inner_size_physical(
new_width, new_height,
);
}
}
if new_inner_size != old_inner_size {
let (new_width, new_height) = new_inner_size.into();
window
.set_inner_size_physical(new_width, new_height);
}
}
}
});
}
}
}
}
}
}
}
match self.ime_receiver.try_recv() {
Ok((window_id, x, y)) => {
wt.ime.borrow_mut().send_xim_spot(window_id, x, y);
}
Err(_) => (),
if let Ok((window_id, x, y)) = self.ime_receiver.try_recv() {
wt.ime.borrow_mut().send_xim_spot(window_id, x, y);
}
}

View File

@@ -9,7 +9,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_Linefeed => VirtualKeyCode::Linefeed,
//ffi::XK_Clear => VirtualKeyCode::Clear,
ffi::XK_Return => VirtualKeyCode::Return,
//ffi::XK_Pause => VirtualKeyCode::Pause,
ffi::XK_Pause => VirtualKeyCode::Pause,
//ffi::XK_Scroll_Lock => VirtualKeyCode::Scroll_lock,
//ffi::XK_Sys_Req => VirtualKeyCode::Sys_req,
ffi::XK_Escape => VirtualKeyCode::Escape,
@@ -59,10 +59,10 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_Break => VirtualKeyCode::Break,
//ffi::XK_Mode_switch => VirtualKeyCode::Mode_switch,
//ffi::XK_script_switch => VirtualKeyCode::Script_switch,
//ffi::XK_Num_Lock => VirtualKeyCode::Num_lock,
ffi::XK_Num_Lock => VirtualKeyCode::Numlock,
//ffi::XK_KP_Space => VirtualKeyCode::Kp_space,
//ffi::XK_KP_Tab => VirtualKeyCode::Kp_tab,
//ffi::XK_KP_Enter => VirtualKeyCode::Kp_enter,
ffi::XK_KP_Enter => VirtualKeyCode::NumpadEnter,
//ffi::XK_KP_F1 => VirtualKeyCode::Kp_f1,
//ffi::XK_KP_F2 => VirtualKeyCode::Kp_f2,
//ffi::XK_KP_F3 => VirtualKeyCode::Kp_f3,
@@ -83,7 +83,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals,
ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd,
//ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator,
ffi::XK_KP_Separator => VirtualKeyCode::NumpadComma,
ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract,
ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal,
ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide,

View File

@@ -1,4 +1,9 @@
use x11_dl::xmd::CARD32;
pub use x11_dl::{
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
xrandr::*, xrender::*,
};
// Isn't defined by x11_dl
#[allow(non_upper_case_globals)]
pub const IconicState: CARD32 = 3;

View File

@@ -62,7 +62,7 @@ pub unsafe fn set_destroy_callback(
inner: &ImeInner,
) -> Result<(), XError> {
xim_set_callback(
&xconn,
xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
@@ -70,6 +70,7 @@ pub unsafe fn set_destroy_callback(
}
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
enum ReplaceImError {
MethodOpenFailed(PotentialInputMethods),
ContextCreationFailed(ImeContextCreationError),
@@ -136,13 +137,17 @@ pub unsafe extern "C" fn xim_instantiate_callback(
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
let xconn = &(*inner).xconn;
let result = replace_im(inner);
if result.is_ok() {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
} else if result.is_err() && (*inner).is_destroyed {
// We have no usable input methods!
result.expect("Failed to reopen input method");
match replace_im(inner) {
Ok(()) => {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
}
Err(err) => {
if (*inner).is_destroyed {
// We have no usable input methods!
panic!("Failed to reopen input method: {:?}", err);
}
}
}
}
}
@@ -163,12 +168,12 @@ pub unsafe extern "C" fn xim_destroy_callback(
if !(*inner).is_fallback {
let _ = set_instantiate_callback(xconn, client_data);
// Attempt to open fallback input method.
let result = replace_im(inner);
if result.is_ok() {
(*inner).is_fallback = true;
} else {
// We have no usable input methods!
result.expect("Failed to open fallback input method");
match replace_im(inner) {
Ok(()) => (*inner).is_fallback = true,
Err(err) => {
// We have no usable input methods!
panic!("Failed to open fallback input method: {:?}", err);
}
}
}
}

View File

@@ -58,7 +58,7 @@ impl ImeContext {
Ok(ImeContext {
ic,
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
ic_spot: ic_spot.unwrap_or(ffi::XPoint { x: 0, y: 0 }),
})
}

View File

@@ -58,10 +58,8 @@ impl ImeInner {
}
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
for context in self.contexts.values() {
if let &Some(ref context) = context {
self.destroy_ic_if_necessary(context.ic)?;
}
for context in self.contexts.values().flatten() {
self.destroy_ic_if_necessary(context.ic)?;
}
Ok(!self.is_destroyed)
}

View File

@@ -42,12 +42,12 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
#[derive(Debug)]
pub struct InputMethod {
pub im: ffi::XIM,
name: String,
_name: String,
}
impl InputMethod {
fn new(im: ffi::XIM, name: String) -> Self {
InputMethod { im, name }
InputMethod { im, _name: name }
}
}
@@ -63,11 +63,7 @@ pub enum InputMethodResult {
impl InputMethodResult {
pub fn is_fallback(&self) -> bool {
if let &InputMethodResult::Fallback(_) = self {
true
} else {
false
}
matches!(self, InputMethodResult::Fallback(_))
}
pub fn ok(self) -> Option<InputMethod> {
@@ -249,7 +245,7 @@ impl PotentialInputMethods {
pub fn open_im(
&mut self,
xconn: &Arc<XConnection>,
callback: Option<&dyn Fn() -> ()>,
callback: Option<&dyn Fn()>,
) -> InputMethodResult {
use self::InputMethodResult::*;
@@ -259,10 +255,8 @@ impl PotentialInputMethods {
let im = input_method.open_im(xconn);
if let Some(im) = im {
return XModifiers(im);
} else {
if let Some(ref callback) = callback {
callback();
}
} else if let Some(ref callback) = callback {
callback();
}
}

View File

@@ -32,15 +32,14 @@ use std::{
ptr,
rc::Rc,
slice,
sync::{mpsc, Arc, Mutex, Weak},
sync::mpsc::{Receiver, Sender},
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, SendError, Sender};
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
use self::{
dnd::{Dnd, DndState},
@@ -57,7 +56,12 @@ use crate::{
};
const X_TOKEN: Token = Token(0);
const USER_TOKEN: Token = Token(1);
const USER_REDRAW_TOKEN: Token = Token(1);
struct WakeSender<T> {
sender: Sender<T>,
waker: Arc<Waker>,
}
pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
@@ -67,26 +71,30 @@ pub struct EventLoopWindowTarget<T> {
root: ffi::Window,
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
pending_redraws: Arc<Mutex<HashSet<WindowId>>>,
redraw_sender: WakeSender<WindowId>,
_marker: ::std::marker::PhantomData<T>,
}
pub struct EventLoop<T: 'static> {
poll: Poll,
waker: Arc<Waker>,
event_processor: EventProcessor<T>,
user_channel: Receiver<T>,
redraw_channel: Receiver<WindowId>,
user_channel: Receiver<T>, //waker.wake needs to be called whenever something gets sent
user_sender: Sender<T>,
target: Rc<RootELW<T>>,
}
pub struct EventLoopProxy<T: 'static> {
user_sender: Sender<T>,
waker: Arc<Waker>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_sender: self.user_sender.clone(),
waker: self.waker.clone(),
}
}
}
@@ -129,7 +137,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 +182,19 @@ 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());
poll.registry()
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
.unwrap();
let (user_sender, user_channel) = std::sync::mpsc::channel();
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime,
@@ -189,31 +205,14 @@ impl<T: 'static> EventLoop<T> {
xconn,
wm_delete_window,
net_wm_ping,
pending_redraws: pending_redraws.clone(),
redraw_sender: WakeSender {
sender: redraw_sender, // not used again so no clone
waker: waker.clone(),
},
}),
_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,
@@ -237,20 +236,21 @@ impl<T: 'static> EventLoop<T> {
event_processor.init_device(ffi::XIAllDevices);
let result = EventLoop {
EventLoop {
poll,
waker,
event_processor,
redraw_channel,
user_channel,
user_sender,
event_processor,
target,
};
result
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_sender: self.user_sender.clone(),
waker: self.waker.clone(),
}
}
@@ -277,8 +277,6 @@ impl<T: 'static> EventLoop<T> {
// 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() {
@@ -301,12 +299,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,
@@ -358,7 +360,11 @@ impl<T: 'static> EventLoop<T> {
// 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();
if let Err(e) = self.poll.poll(&mut events, timeout) {
if e.raw_os_error() != Some(libc::EINTR) {
panic!("epoll returned an error: {:?}", e);
}
}
events.clear();
}
@@ -393,7 +399,6 @@ impl<T: 'static> EventLoop<T> {
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
@@ -408,7 +413,8 @@ impl<T: 'static> EventLoop<T> {
super::WindowId::X(wid),
)) = event
{
wt.pending_redraws.lock().unwrap().insert(wid);
wt.redraw_sender.sender.send(wid).unwrap();
wt.redraw_sender.waker.wake().unwrap();
} else {
callback(event, window_target, control_flow);
}
@@ -437,13 +443,10 @@ impl<T> EventLoopWindowTarget<T> {
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
self.user_sender
.send(event)
.map_err(|e| EventLoopClosed(e.0))
.map(|_| self.waker.wake().unwrap())
}
}
@@ -491,7 +494,7 @@ impl<'a> Deref for DeviceInfo<'a> {
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
WindowId(0)
}
}
@@ -500,7 +503,7 @@ impl WindowId {
pub struct DeviceId(c_int);
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(0)
}
}
@@ -521,7 +524,7 @@ impl Window {
attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?);
let window = Arc::new(UnownedWindow::new(event_loop, attribs, pl_attribs)?);
event_loop
.windows
.borrow_mut()
@@ -589,7 +592,7 @@ fn mkdid(w: c_int) -> crate::event::DeviceId {
#[derive(Debug)]
struct Device {
name: String,
_name: String,
scroll_axes: Vec<(i32, ScrollAxis)>,
// For master devices, this is the paired device (pointer <-> keyboard).
// For slave devices, this is the master.
@@ -631,31 +634,28 @@ impl Device {
// Identify scroll axes
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
match class._type {
ffi::XIScrollClass => {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
};
scroll_axes.push((
info.number,
ScrollAxis {
increment: info.increment,
orientation: match info.scroll_type {
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => unreachable!(),
},
position: 0.0,
if class._type == ffi::XIScrollClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
};
scroll_axes.push((
info.number,
ScrollAxis {
increment: info.increment,
orientation: match info.scroll_type {
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => unreachable!(),
},
));
}
_ => {}
position: 0.0,
},
));
}
}
}
let mut device = Device {
name: name.into_owned(),
_name: name.into_owned(),
scroll_axes,
attachment: info.attachment,
};
@@ -667,20 +667,17 @@ impl Device {
if Device::physical_device(info) {
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
match class._type {
ffi::XIValuatorClass => {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
};
if let Some(&mut (_, ref mut axis)) = self
.scroll_axes
.iter_mut()
.find(|&&mut (axis, _)| axis == info.number)
{
axis.position = info.value;
}
if class._type == ffi::XIValuatorClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
};
if let Some(&mut (_, ref mut axis)) = self
.scroll_axes
.iter_mut()
.find(|&&mut (axis, _)| axis == info.number)
{
axis.position = info.value;
}
_ => {}
}
}
}

View File

@@ -90,7 +90,7 @@ impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
Some(self.cmp(other))
}
}
@@ -204,7 +204,7 @@ impl XConnection {
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
if overlapping_area > largest_overlap {
largest_overlap = overlapping_area;
matched_monitor = &monitor;
matched_monitor = monitor;
}
}
@@ -242,8 +242,11 @@ impl XConnection {
if is_active {
let is_primary = *(*crtc).outputs.offset(0) == primary;
has_primary |= is_primary;
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
.map(|monitor_id| available.push(monitor_id));
if let Some(monitor_id) =
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
{
available.push(monitor_id)
}
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}

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

@@ -104,8 +104,8 @@ impl XConnection {
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),

View File

@@ -23,9 +23,9 @@ impl Format {
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
&Format::Short => mem::size_of::<c_short>(),
&Format::Long => mem::size_of::<c_long>(),
Format::Char => mem::size_of::<c_char>(),
Format::Short => mem::size_of::<c_short>(),
Format::Long => mem::size_of::<c_long>(),
}
}
}

View File

@@ -248,7 +248,7 @@ impl XConnection {
);
// The list of children isn't used
if children != ptr::null_mut() {
if !children.is_null() {
(self.xlib.XFree)(children as *mut _);
}

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

@@ -27,7 +27,7 @@ impl Icon {
data.push(rgba_icon.height as Cardinal);
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
let pixel = unsafe { &*pixels.add(pixel_index) };
data.push(pixel.to_packed_argb());
}
data

View File

@@ -36,7 +36,7 @@ impl Iterator for KeymapIter<'_> {
fn next(&mut self) -> Option<ffi::KeyCode> {
if self.item.is_none() {
while let Some((index, &item)) = self.iter.next() {
for (index, &item) in self.iter.by_ref() {
if item != 0 {
self.index = index;
self.item = Some(item);

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

@@ -151,7 +151,7 @@ impl ModifierKeyState {
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
if let Some(modifier) = self.keys.remove(&keycode) {
if self.keys.values().find(|&&m| m == modifier).is_none() {
if !self.keys.values().any(|&m| m == modifier) {
set_modifier(&mut self.state, modifier, false);
}
}

View File

@@ -27,7 +27,11 @@ pub fn calc_dpi_factor(
// Quantize 1/12 step size
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
assert!(validate_scale_factor(dpi_factor));
dpi_factor
if dpi_factor <= 20. {
dpi_factor
} else {
1.
}
}
impl XConnection {
@@ -35,15 +39,15 @@ impl XConnection {
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)();
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
if resource_manager_str == ptr::null_mut() {
if resource_manager_str.is_null() {
return None;
}
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
let name: &str = "Xft.dpi:\t";
for pair in res.split("\n") {
for pair in res.split('\n') {
if pair.starts_with(&name) {
let res = &pair[name.len()..];
return f64::from_str(&res).ok();
return f64::from_str(res).ok();
}
}
}

View File

@@ -97,7 +97,7 @@ impl XConnection {
quantity_returned,
new_data,
);*/
data.extend_from_slice(&new_data);
data.extend_from_slice(new_data);
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
} else {

View File

@@ -62,11 +62,7 @@ impl XConnection {
let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
wm_check?
};
// Querying the same property on the child window we were given, we should get this child
@@ -76,11 +72,7 @@ impl XConnection {
let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
wm_check?
};
// These values should be the same.

View File

@@ -1,8 +1,6 @@
use raw_window_handle::unix::XlibHandle;
use raw_window_handle::XlibHandle;
use std::{
cmp,
collections::HashSet,
env,
cmp, env,
ffi::CString,
mem::{self, replace, MaybeUninit},
os::raw::*,
@@ -10,6 +8,7 @@ use std::{
ptr, slice,
sync::Arc,
};
use x11_dl::xlib::TrueColor;
use libc;
use parking_lot::Mutex;
@@ -23,10 +22,12 @@ 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};
use super::{
ffi, util, EventLoopWindowTarget, ImeSender, WakeSender, WindowId, XConnection, XError,
};
#[derive(Debug)]
pub struct SharedState {
@@ -104,7 +105,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: WakeSender<WindowId>,
}
impl UnownedWindow {
@@ -147,6 +148,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));
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
@@ -177,6 +182,39 @@ impl UnownedWindow {
};
// creating
let (visual, depth, require_colormap) = match pl_attribs.visual_infos {
Some(vi) => (vi.visual, vi.depth, false),
None if window_attrs.transparent => {
// Find a suitable visual
let mut vinfo = MaybeUninit::uninit();
let vinfo_initialized = unsafe {
(xconn.xlib.XMatchVisualInfo)(
xconn.display,
screen_id,
32,
TrueColor,
vinfo.as_mut_ptr(),
) != 0
};
if vinfo_initialized {
let vinfo = unsafe { vinfo.assume_init() };
(vinfo.visual, vinfo.depth, true)
} else {
debug!("Could not set transparency, because XMatchVisualInfo returned zero for the required parameters");
(
ffi::CopyFromParent as *mut ffi::Visual,
ffi::CopyFromParent,
false,
)
}
}
_ => (
ffi::CopyFromParent as *mut ffi::Visual,
ffi::CopyFromParent,
false,
),
};
let mut set_win_attr = {
let mut swa: ffi::XSetWindowAttributes = unsafe { mem::zeroed() };
swa.colormap = if let Some(vi) = pl_attribs.visual_infos {
@@ -184,6 +222,8 @@ impl UnownedWindow {
let visual = vi.visual;
(xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone)
}
} else if require_colormap {
unsafe { (xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone) }
} else {
0
};
@@ -212,28 +252,14 @@ 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,
match pl_attribs.visual_infos {
Some(vi) => vi.depth,
None => ffi::CopyFromParent,
},
depth,
ffi::InputOutput as c_uint,
// TODO: If window wants transparency and `visual_infos` is None,
// we need to find our own visual which has an `alphaMask` which
// is > 0, like we do in glutin.
//
// It is non obvious which masks, if any, we should pass to
// `XGetVisualInfo`. winit doesn't receive any info about what
// properties the user wants. Users should consider choosing the
// visual themselves as glutin does.
match pl_attribs.visual_infos {
Some(vi) => vi.visual,
None => ffi::CopyFromParent as *mut ffi::Visual,
},
visual,
window_attributes,
&mut set_win_attr,
)
@@ -249,7 +275,10 @@ 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: WakeSender {
waker: event_loop.redraw_sender.waker.clone(),
sender: event_loop.redraw_sender.sender.clone(),
},
};
// Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
@@ -312,7 +341,9 @@ impl UnownedWindow {
} //.queue();
}
window.set_pid().map(|flusher| flusher.queue());
if let Some(flusher) = window.set_pid() {
flusher.queue()
}
window.set_window_types(pl_attribs.x11_window_types).queue();
@@ -345,6 +376,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));
@@ -400,8 +432,7 @@ impl UnownedWindow {
}
// Select XInput2 events
let mask = {
let mask = ffi::XI_MotionMask
let mask = ffi::XI_MotionMask
| ffi::XI_ButtonPressMask
| ffi::XI_ButtonReleaseMask
//| ffi::XI_KeyPressMask
@@ -413,8 +444,6 @@ impl UnownedWindow {
| ffi::XI_TouchBeginMask
| ffi::XI_TouchUpdateMask
| ffi::XI_TouchEndMask;
mask
};
xconn
.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask)
.queue();
@@ -437,9 +466,16 @@ impl UnownedWindow {
window.set_maximized_inner(window_attrs.maximized).queue();
}
if window_attrs.fullscreen.is_some() {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.map(|flusher| flusher.queue());
if let Some(flusher) = window.set_fullscreen_inner(window_attrs.fullscreen.clone())
{
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 +560,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,
@@ -786,6 +805,30 @@ impl UnownedWindow {
.expect("Failed to change window minimization");
}
#[inline]
pub fn is_maximized(&self) -> bool {
let state_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE\0") };
let state = self
.xconn
.get_property(self.xwindow, state_atom, ffi::XA_ATOM);
let horz_atom = unsafe {
self.xconn
.get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_HORZ\0")
};
let vert_atom = unsafe {
self.xconn
.get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_VERT\0")
};
match state {
Ok(atoms) => {
let horz_maximized = atoms.iter().any(|atom: &ffi::Atom| *atom == horz_atom);
let vert_maximized = atoms.iter().any(|atom: &ffi::Atom| *atom == vert_atom);
horz_maximized && vert_maximized
}
_ => false,
}
}
fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> {
let horz_atom = unsafe {
self.xconn
@@ -1063,7 +1106,7 @@ impl UnownedWindow {
fn update_normal_hints<F>(&self, callback: F) -> Result<(), XError>
where
F: FnOnce(&mut util::NormalHints<'_>) -> (),
F: FnOnce(&mut util::NormalHints<'_>),
{
let mut normal_hints = self.xconn.get_normal_hints(self.xwindow)?;
callback(&mut normal_hints);
@@ -1144,7 +1187,7 @@ impl UnownedWindow {
)
} else {
let window_size = Some(Size::from(self.inner_size()));
(window_size.clone(), window_size)
(window_size, window_size)
};
self.set_maximizable_inner(resizable).queue();
@@ -1294,6 +1337,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
@@ -1307,6 +1390,58 @@ impl UnownedWindow {
self.set_ime_position_physical(x, y);
}
#[inline]
pub fn focus_window(&self) {
let state_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_STATE\0") };
let state_type_atom = unsafe { self.xconn.get_atom_unchecked(b"CARD32\0") };
let is_minimized = if let Ok(state) =
self.xconn
.get_property(self.xwindow, state_atom, state_type_atom)
{
state.contains(&(ffi::IconicState as c_ulong))
} else {
false
};
let is_visible = match self.shared_state.lock().visibility {
Visibility::Yes => true,
Visibility::YesWait | Visibility::No => false,
};
if is_visible && !is_minimized {
let atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0") };
let flusher = self.xconn.send_client_msg(
self.xwindow,
self.root,
atom,
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
[1, ffi::CurrentTime as c_long, 0, 0, 0],
);
if let Err(e) = flusher.flush() {
log::error!(
"`flush` returned an error when focusing the window. Error was: {}",
e
);
}
}
}
#[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)
@@ -1314,18 +1449,18 @@ impl UnownedWindow {
#[inline]
pub fn request_redraw(&self) {
self.pending_redraws
.lock()
.unwrap()
.insert(WindowId(self.xwindow));
self.redraw_sender
.sender
.send(WindowId(self.xwindow))
.unwrap();
self.redraw_sender.waker.wake().unwrap();
}
#[inline]
pub fn raw_window_handle(&self) -> XlibHandle {
XlibHandle {
window: self.xwindow,
display: self.xconn.display as _,
..XlibHandle::empty()
}
let mut handle = XlibHandle::empty();
handle.window = self.xlib_window();
handle.display = self.xlib_display();
handle
}
}

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,30 @@ use std::{
};
use cocoa::{
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
appkit::{NSApp, NSApplication, NSWindow},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize},
foundation::NSSize,
};
use objc::{
rc::autoreleasepool,
runtime::{Object, YES},
};
use objc::runtime::YES;
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 +61,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 +97,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;
});
}
}
@@ -229,20 +262,12 @@ 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,
}));
@@ -255,9 +280,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,
@@ -265,8 +303,13 @@ 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.");
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() {
return;
}
let start = HANDLER.get_start_time().unwrap();
@@ -302,6 +345,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) {
@@ -318,32 +369,34 @@ 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.");
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() {
return;
}
if !HANDLER.get_in_callback() {
HANDLER.set_in_callback(true);
HANDLER.handle_user_events();
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
for window_id in HANDLER.should_redraw() {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
window_id,
)));
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
HANDLER.set_in_callback(false);
HANDLER.set_in_callback(true);
HANDLER.handle_user_events();
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
for window_id in HANDLER.should_redraw() {
HANDLER
.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
HANDLER.set_in_callback(false);
if HANDLER.should_exit() {
unsafe {
let app: id = NSApp();
let windows: id = msg_send![app, windows];
let window: id = msg_send![windows, objectAtIndex:0];
let window_count: usize = msg_send![windows, count];
assert_ne!(window, nil);
let dialog_open = if window_count > 1 {
let dialog: id = msg_send![windows, lastObject];
@@ -354,35 +407,26 @@ impl AppState {
};
let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst);
let pool = NSAutoreleasePool::new(nil);
if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst)
&& !dialog_open
&& !dialog_is_closing
{
let _: () = msg_send![app, stop: nil];
autoreleasepool(|| {
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);
}
});
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];
}
pool.drain();
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);
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);
}
}
};
}
@@ -396,3 +440,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

@@ -1,13 +1,22 @@
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::NSPoint,
};
use objc::rc::autoreleasepool;
use crate::{
event::Event,
@@ -23,6 +32,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>,
@@ -48,9 +85,33 @@ impl<T: 'static> EventLoopWindowTarget<T> {
}
}
impl<T> EventLoopWindowTarget<T> {
pub(crate) fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
}
pub(crate) 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] }
}
}
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> {
@@ -67,18 +128,21 @@ impl<T> EventLoop<T> {
let app: id = msg_send![APP_CLASS.0, sharedApplication];
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
let pool = NSAutoreleasePool::new(nil);
let _: () = msg_send![app, setDelegate:*delegate];
let _: () = msg_send![pool, drain];
autoreleasepool(|| {
let _: () = msg_send![app, setDelegate:*delegate];
});
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,
}
}
@@ -98,15 +162,38 @@ impl<T> EventLoop<T> {
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let pool = NSAutoreleasePool::new(nil);
// 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));
autoreleasepool(|| unsafe {
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() {
drop(self._callback.take());
resume_unwind(panic);
}
AppState::exit();
pool.drain();
}
});
drop(self._callback.take());
}
pub fn create_proxy(&self) -> Proxy<T> {
@@ -114,6 +201,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,

View File

@@ -2,6 +2,8 @@
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
use std::ffi::c_void;
use cocoa::{
base::id,
foundation::{NSInteger, NSUInteger},
@@ -159,7 +161,28 @@ pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut libc::c_void;
pub type CGDisplayModeRef = *mut c_void;
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13.
//
// Since we want to support older versions, we can't link to `ColorSync`
// directly. Fortunately, it has always been available as a subframework of
// `ApplicationServices`, see:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
//
// TODO: Remove the WINIT_LINK_COLORSYNC hack, it is probably not needed.
#[cfg_attr(
not(use_colorsync_cgdisplaycreateuuidfromdisplayid),
link(name = "ApplicationServices", 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" {
@@ -189,7 +212,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,115 @@
use super::util::IdRef;
use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use cocoa::base::{nil, selector};
use cocoa::foundation::{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 = IdRef::new(NSMenu::new(nil));
let app_menu_item = IdRef::new(NSMenuItem::new(nil));
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},
@@ -24,6 +25,7 @@ pub use self::{
use crate::{
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
};
use objc::rc::autoreleasepool;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
@@ -31,7 +33,7 @@ pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}
@@ -68,7 +70,7 @@ impl Window {
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
let (window, _delegate) = UnownedWindow::new(attributes, pl_attribs)?;
let (window, _delegate) = autoreleasepool(|| UnownedWindow::new(attributes, pl_attribs))?;
Ok(Window { window, _delegate })
}
}

View File

@@ -162,6 +162,7 @@ impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API
#[derive(Debug)]
#[allow(dead_code)]
struct MonitorHandle {
name: Option<String>,
native_identifier: u32,

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::{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
@@ -60,9 +61,7 @@ impl Drop for IdRef {
fn drop(&mut self) {
if self.0 != nil {
unsafe {
let pool = NSAutoreleasePool::new(nil);
let () = msg_send![self.0, release];
pool.drain();
};
}
}
@@ -91,10 +90,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

@@ -55,7 +55,6 @@ pub(super) struct ViewState {
pub cursor_state: Arc<Mutex<CursorState>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
pub(super) modifiers: ModifiersState,
tracking_rect: Option<NSInteger>,
}
@@ -74,7 +73,6 @@ pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
cursor_state,
ime_spot: None,
raw_characters: None,
is_key_down: false,
modifiers: Default::default(),
tracking_rect: None,
};
@@ -255,6 +253,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();
@@ -285,7 +287,7 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i
let notification_center: &Object =
msg_send![class!(NSNotificationCenter), defaultCenter];
let notification_name =
NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification");
IdRef::new(NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification"));
let _: () = msg_send![
notification_center,
addObserver: this
@@ -346,7 +348,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 +390,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
}
}
@@ -512,7 +514,6 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran
let slice =
slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len());
let string = str::from_utf8_unchecked(slice);
state.is_key_down = true;
// We don't need this now, but it's here if that changes.
//let event: id = msg_send![NSApp(), currentEvent];
@@ -671,7 +672,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
let pass_along = {
AppState::queue_event(EventWrapper::StaticEvent(window_event));
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down {
if is_repeat {
for character in characters.chars().filter(|c| !is_corporate_character(*c)) {
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
@@ -701,8 +702,6 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
state.is_key_down = false;
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
@@ -1078,3 +1077,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

@@ -1,4 +1,4 @@
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
use raw_window_handle::{AppKitHandle, RawWindowHandle};
use std::{
collections::VecDeque,
f64,
@@ -16,7 +16,7 @@ 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,
@@ -28,20 +28,22 @@ 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},
foundation::{NSDictionary, NSPoint, NSRect, NSSize},
};
use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{
declare::ClassDecl,
rc::autoreleasepool,
runtime::{Class, Object, Sel, BOOL, NO, YES},
};
@@ -49,7 +51,7 @@ use objc::{
pub struct Id(pub usize);
impl Id {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
Id(0)
}
}
@@ -62,7 +64,6 @@ pub fn get_window_id(window_cocoa_id: id) -> Id {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub activation_policy: ActivationPolicy,
pub movable_by_window_background: bool,
pub titlebar_transparent: bool,
pub title_hidden: bool,
@@ -78,7 +79,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
#[inline]
fn default() -> Self {
Self {
activation_policy: Default::default(),
movable_by_window_background: false,
titlebar_transparent: false,
title_hidden: false,
@@ -92,24 +92,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
}
}
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)
}
}
}
unsafe fn create_view(
ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
@@ -129,8 +111,6 @@ unsafe fn create_view(
ns_view.setWantsLayer(YES);
}
ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*ns_view);
(ns_view, cursor_state)
})
}
@@ -139,8 +119,7 @@ fn create_window(
attrs: &WindowAttributes,
pl_attrs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<IdRef> {
unsafe {
let pool = NSAutoreleasePool::new(nil);
autoreleasepool(|| unsafe {
let screen = match attrs.fullscreen {
Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor })))
| Some(Fullscreen::Exclusive(RootVideoMode {
@@ -164,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))
}
};
@@ -247,13 +236,13 @@ fn create_window(
if !pl_attrs.has_shadow {
ns_window.setHasShadow_(NO);
}
ns_window.center();
if attrs.position.is_none() {
ns_window.center();
}
ns_window
});
pool.drain();
res
}
})
}
struct WindowClass(*const Class);
@@ -292,7 +281,10 @@ pub struct SharedState {
is_simple_fullscreen: bool,
pub saved_style: Option<NSWindowStyleMask>,
/// Presentation options saved before entering `set_simple_fullscreen`, and
/// restored upon exiting it
/// restored upon exiting it. Also used when transitioning from Borderless to
/// Exclusive fullscreen in `set_fullscreen` because we need to disable the menu
/// bar in exclusive fullscreen but want to restore the original options when
/// transitioning back to borderless fullscreen.
save_presentation_opts: Option<NSApplicationPresentationOptions>,
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
}
@@ -344,24 +336,19 @@ 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_window = create_window(&win_attribs, &pl_attribs)
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?;
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_view, cursor_state) = unsafe { create_view(*ns_window, &pl_attribs) }
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSView`")))?;
let ns_window = create_window(&win_attribs, &pl_attribs).ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
})?;
let (ns_view, cursor_state) =
unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
unsafe { pool.drain() };
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) };
@@ -373,7 +360,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)
@@ -423,20 +409,15 @@ 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 {
window.set_maximized(maximized);
}
unsafe { pool.drain() };
Ok((window, delegate))
}
@@ -494,17 +475,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));
}
}
@@ -634,6 +606,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.
@@ -653,7 +635,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 {
@@ -728,6 +710,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`");
@@ -798,6 +785,15 @@ impl UnownedWindow {
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) {
unsafe {
let app = NSApp();
trace!("Locked shared state in `set_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.save_presentation_opts = Some(app.presentationOptions_());
}
}
unsafe {
// Fade to black (and wait for the fade to complete) to hide the
// flicker from capturing the display and switching display mode
@@ -848,7 +844,6 @@ impl UnownedWindow {
trace!("Locked shared state in `set_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = fullscreen.clone();
trace!("Unlocked shared state in `set_fullscreen`");
INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst);
@@ -889,16 +884,42 @@ impl UnownedWindow {
// of the menu bar, and this looks broken, so we must make sure
// that the menu bar is disabled. This is done in the window
// delegate in `window:willUseFullScreenPresentationOptions:`.
let app = NSApp();
trace!("Locked shared state in `set_fullscreen`");
shared_state_lock.save_presentation_opts = Some(app.presentationOptions_());
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions_(presentation_options);
let () = msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1];
},
(
&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })),
&Some(Fullscreen::Borderless(_)),
) => unsafe {
let presentation_options =
shared_state_lock.save_presentation_opts.unwrap_or_else(|| {
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
});
NSApp().setPresentationOptions_(presentation_options);
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
let () = msg_send![
*self.ns_window,
setLevel: ffi::NSWindowLevel::NSNormalWindowLevel
];
},
_ => INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst),
}
};
trace!("Unlocked shared state in `set_fullscreen`");
}
#[inline]
@@ -977,6 +998,34 @@ impl UnownedWindow {
}
}
#[inline]
pub fn focus_window(&self) {
let is_minimized: BOOL = unsafe { msg_send![*self.ns_window, isMiniaturized] };
let is_minimized = is_minimized == YES;
let is_visible: BOOL = unsafe { msg_send![*self.ns_window, isVisible] };
let is_visible = is_visible == YES;
if !is_minimized && is_visible {
unsafe {
NSApp().activateIgnoringOtherApps_(YES);
util::make_key_and_order_front_async(*self.ns_window);
}
}
}
#[inline]
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 {
@@ -1010,12 +1059,10 @@ impl UnownedWindow {
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = MacOSHandle {
ns_window: *self.ns_window as *mut _,
ns_view: *self.ns_view as *mut _,
..MacOSHandle::empty()
};
RawWindowHandle::MacOS(handle)
let mut handle = AppKitHandle::empty();
handle.ns_window = *self.ns_window as *mut _;
handle.ns_view = *self.ns_view as *mut _;
RawWindowHandle::AppKit(handle)
}
}
@@ -1030,18 +1077,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();
@@ -1150,7 +1185,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()) };
}
}
}
@@ -1168,14 +1203,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)
}
}
@@ -1192,13 +1227,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

@@ -7,10 +7,11 @@ use std::{
use cocoa::{
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
base::{id, nil},
foundation::{NSAutoreleasePool, NSUInteger},
foundation::NSUInteger,
};
use objc::{
declare::ClassDecl,
rc::autoreleasepool,
runtime::{Class, Object, Sel, BOOL, NO, YES},
};
@@ -274,11 +275,11 @@ extern "C" fn window_will_close(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillClose:`");
with_state(this, |state| unsafe {
// `setDelegate:` retains the previous value and then autoreleases it
let pool = NSAutoreleasePool::new(nil);
// Since El Capitan, we need to be careful that delegate methods can't
// be called after the window closes.
let () = msg_send![*state.ns_window, setDelegate: nil];
pool.drain();
autoreleasepool(|| {
// Since El Capitan, we need to be careful that delegate methods can't
// be called after the window closes.
let () = msg_send![*state.ns_window, setDelegate: nil];
});
state.emit_event(WindowEvent::Destroyed);
});
trace!("Completed `windowWillClose:`");
@@ -481,10 +482,10 @@ extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
}
extern "C" fn window_will_use_fullscreen_presentation_options(
_this: &Object,
this: &Object,
_: Sel,
_: id,
_proposed_options: NSUInteger,
proposed_options: NSUInteger,
) -> NSUInteger {
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
@@ -494,10 +495,22 @@ extern "C" fn window_will_use_fullscreen_presentation_options(
// still want to make this configurable for borderless fullscreen. Right now
// we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen.
(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
.bits()
let mut options: NSUInteger = proposed_options;
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_will_use_fullscreen_presentation_options`");
let shared_state = window.shared_state.lock().unwrap();
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
.bits();
}
trace!("Unlocked shared state in `window_will_use_fullscreen_presentation_options`");
})
});
options
}
/// Invoked when entered fullscreen

View File

@@ -2,7 +2,7 @@
pub struct Id(pub i32);
impl Id {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
Id(0)
}
}

View File

@@ -1,6 +1,8 @@
use super::{super::monitor, backend, device, proxy::Proxy, runner, window};
use crate::dpi::{PhysicalSize, Size};
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event::{
DeviceEvent, DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent,
};
use crate::event_loop::ControlFlow;
use crate::monitor::MonitorHandle as RootMH;
use crate::window::{Theme, WindowId};
@@ -130,7 +132,7 @@ impl<T> WindowTarget<T> {
});
let runner = self.runner.clone();
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
canvas.on_cursor_move(move |pointer_id, position, delta, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorMoved {
@@ -139,6 +141,12 @@ impl<T> WindowTarget<T> {
modifiers,
},
});
runner.send_event(Event::DeviceEvent {
device_id: DeviceId(device::Id(pointer_id)),
event: DeviceEvent::MouseMotion {
delta: (delta.x, delta.y),
},
});
});
let runner = self.runner.clone();

View File

@@ -1,5 +1,5 @@
// Brief introduction to the internals of the web backend:
// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the
// The web backend used to support both wasm-bindgen and stdweb as methods of binding to the
// environment. Because they are both supporting the same underlying APIs, the actual web bindings
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
//
@@ -23,17 +23,9 @@ mod event_loop;
mod monitor;
mod window;
#[cfg(feature = "web-sys")]
#[path = "web_sys/mod.rs"]
mod backend;
#[cfg(feature = "stdweb")]
#[path = "stdweb/mod.rs"]
mod backend;
#[cfg(not(any(feature = "web-sys", feature = "stdweb")))]
compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`");
pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub use self::event_loop::{

View File

@@ -1,307 +0,0 @@
use super::event;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
use std::cell::RefCell;
use std::rc::Rc;
use stdweb::js;
use stdweb::traits::IPointerEvent;
use stdweb::unstable::TryInto;
use stdweb::web::event::{
BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, IEvent, KeyDownEvent,
KeyPressEvent, KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent,
PointerOutEvent, PointerOverEvent, PointerUpEvent,
};
use stdweb::web::html_element::CanvasElement;
use stdweb::web::{
document, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement,
};
pub struct Canvas {
/// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: CanvasElement,
on_focus: Option<EventListenerHandle>,
on_blur: Option<EventListenerHandle>,
on_keyboard_release: Option<EventListenerHandle>,
on_keyboard_press: Option<EventListenerHandle>,
on_received_character: Option<EventListenerHandle>,
on_cursor_leave: Option<EventListenerHandle>,
on_cursor_enter: Option<EventListenerHandle>,
on_cursor_move: Option<EventListenerHandle>,
on_mouse_press: Option<EventListenerHandle>,
on_mouse_release: Option<EventListenerHandle>,
on_mouse_wheel: Option<EventListenerHandle>,
on_fullscreen_change: Option<EventListenerHandle>,
wants_fullscreen: Rc<RefCell<bool>>,
}
impl Canvas {
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
let canvas = match attr.canvas {
Some(canvas) => canvas,
None => document()
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.try_into()
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?,
};
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
// sequential keyboard navigation, but its order is defined by the
// document's source order.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
canvas
.set_attribute("tabindex", "0")
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
Ok(Canvas {
raw: canvas,
on_blur: None,
on_focus: None,
on_keyboard_release: None,
on_keyboard_press: None,
on_received_character: None,
on_cursor_leave: None,
on_cursor_enter: None,
on_cursor_move: None,
on_mouse_release: None,
on_mouse_press: None,
on_mouse_wheel: None,
on_fullscreen_change: None,
wants_fullscreen: Rc::new(RefCell::new(false)),
})
}
pub fn set_attribute(&self, attribute: &str, value: &str) {
self.raw
.set_attribute(attribute, value)
.expect(&format!("Set attribute: {}", attribute));
}
pub fn position(&self) -> LogicalPosition<f64> {
let bounds = self.raw.get_bounding_client_rect();
LogicalPosition {
x: bounds.get_x(),
y: bounds.get_y(),
}
}
pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: self.raw.width() as u32,
height: self.raw.height() as u32,
}
}
pub fn raw(&self) -> &CanvasElement {
&self.raw
}
pub fn on_blur<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_blur = Some(self.add_event(move |_: BlurEvent| {
handler();
}));
}
pub fn on_focus<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_focus = Some(self.add_event(move |_: FocusEvent| {
handler();
}));
}
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| {
event.prevent_default();
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| {
event.prevent_default();
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
pub fn on_received_character<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(char),
{
// TODO: Use `beforeinput`.
//
// The `keypress` event is deprecated, but there does not seem to be a
// viable/compatible alternative as of now. `beforeinput` is still widely
// unsupported.
self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| {
handler(event::codepoint(&event));
}));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
handler(event.pointer_id());
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32),
{
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
handler(event.pointer_id());
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
{
let canvas = self.raw.clone();
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
canvas
.set_pointer_capture(event.pointer_id())
.expect("Failed to set pointer capture");
}));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
{
// todo
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
event.prevent_default();
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
}));
}
pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler()));
}
pub fn on_dark_mode<F>(&mut self, handler: F)
where
F: 'static + FnMut(bool),
{
// TODO: upstream to stdweb
js! {
var handler = @{handler};
if (window.matchMedia) {
window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) {
handler(event.matches)
});
}
}
}
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
where
E: ConcreteEvent,
F: 'static + FnMut(E),
{
self.raw.add_event_listener(move |event: E| {
event.stop_propagation();
event.cancel_bubble();
handler(event);
})
}
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
fn add_user_event<E, F>(&self, mut handler: F) -> EventListenerHandle
where
E: ConcreteEvent,
F: 'static + FnMut(E),
{
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
self.add_event(move |event: E| {
handler(event);
if *wants_fullscreen.borrow() {
canvas.request_fullscreen();
*wants_fullscreen.borrow_mut() = false;
}
})
}
pub fn request_fullscreen(&self) {
*self.wants_fullscreen.borrow_mut() = true;
}
pub fn is_fullscreen(&self) -> bool {
super::is_fullscreen(&self.raw)
}
pub fn remove_listeners(&mut self) {
// TODO: Stub, unimplemented (see web_sys for reference).
}
}

View File

@@ -1,232 +0,0 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
use stdweb::{js, unstable::TryInto, JsSerialize};
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
match event.button() {
stdweb::web::event::MouseButton::Left => MouseButton::Left,
stdweb::web::event::MouseButton::Right => MouseButton::Right,
stdweb::web::event::MouseButton::Wheel => MouseButton::Middle,
stdweb::web::event::MouseButton::Button4 => MouseButton::Other(0),
stdweb::web::event::MouseButton::Button5 => MouseButton::Other(1),
}
}
pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition<f64> {
LogicalPosition {
x: event.offset_x() as f64,
y: event.offset_y() as f64,
}
}
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
let x = event.delta_x();
let y = -event.delta_y();
match event.delta_mode() {
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
MouseWheelDeltaMode::Pixel => {
let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor());
Some(MouseScrollDelta::PixelDelta(delta))
}
MouseWheelDeltaMode::Page => None,
}
}
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
let key_code = js! ( return @{event}.keyCode; );
key_code
.try_into()
.expect("The which value should be a number")
}
pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
Some(match &event.code()[..] {
"Digit1" => VirtualKeyCode::Key1,
"Digit2" => VirtualKeyCode::Key2,
"Digit3" => VirtualKeyCode::Key3,
"Digit4" => VirtualKeyCode::Key4,
"Digit5" => VirtualKeyCode::Key5,
"Digit6" => VirtualKeyCode::Key6,
"Digit7" => VirtualKeyCode::Key7,
"Digit8" => VirtualKeyCode::Key8,
"Digit9" => VirtualKeyCode::Key9,
"Digit0" => VirtualKeyCode::Key0,
"KeyA" => VirtualKeyCode::A,
"KeyB" => VirtualKeyCode::B,
"KeyC" => VirtualKeyCode::C,
"KeyD" => VirtualKeyCode::D,
"KeyE" => VirtualKeyCode::E,
"KeyF" => VirtualKeyCode::F,
"KeyG" => VirtualKeyCode::G,
"KeyH" => VirtualKeyCode::H,
"KeyI" => VirtualKeyCode::I,
"KeyJ" => VirtualKeyCode::J,
"KeyK" => VirtualKeyCode::K,
"KeyL" => VirtualKeyCode::L,
"KeyM" => VirtualKeyCode::M,
"KeyN" => VirtualKeyCode::N,
"KeyO" => VirtualKeyCode::O,
"KeyP" => VirtualKeyCode::P,
"KeyQ" => VirtualKeyCode::Q,
"KeyR" => VirtualKeyCode::R,
"KeyS" => VirtualKeyCode::S,
"KeyT" => VirtualKeyCode::T,
"KeyU" => VirtualKeyCode::U,
"KeyV" => VirtualKeyCode::V,
"KeyW" => VirtualKeyCode::W,
"KeyX" => VirtualKeyCode::X,
"KeyY" => VirtualKeyCode::Y,
"KeyZ" => VirtualKeyCode::Z,
"Escape" => VirtualKeyCode::Escape,
"F1" => VirtualKeyCode::F1,
"F2" => VirtualKeyCode::F2,
"F3" => VirtualKeyCode::F3,
"F4" => VirtualKeyCode::F4,
"F5" => VirtualKeyCode::F5,
"F6" => VirtualKeyCode::F6,
"F7" => VirtualKeyCode::F7,
"F8" => VirtualKeyCode::F8,
"F9" => VirtualKeyCode::F9,
"F10" => VirtualKeyCode::F10,
"F11" => VirtualKeyCode::F11,
"F12" => VirtualKeyCode::F12,
"F13" => VirtualKeyCode::F13,
"F14" => VirtualKeyCode::F14,
"F15" => VirtualKeyCode::F15,
"F16" => VirtualKeyCode::F16,
"F17" => VirtualKeyCode::F17,
"F18" => VirtualKeyCode::F18,
"F19" => VirtualKeyCode::F19,
"F20" => VirtualKeyCode::F20,
"F21" => VirtualKeyCode::F21,
"F22" => VirtualKeyCode::F22,
"F23" => VirtualKeyCode::F23,
"F24" => VirtualKeyCode::F24,
"PrintScreen" => VirtualKeyCode::Snapshot,
"ScrollLock" => VirtualKeyCode::Scroll,
"Pause" => VirtualKeyCode::Pause,
"Insert" => VirtualKeyCode::Insert,
"Home" => VirtualKeyCode::Home,
"Delete" => VirtualKeyCode::Delete,
"End" => VirtualKeyCode::End,
"PageDown" => VirtualKeyCode::PageDown,
"PageUp" => VirtualKeyCode::PageUp,
"ArrowLeft" => VirtualKeyCode::Left,
"ArrowUp" => VirtualKeyCode::Up,
"ArrowRight" => VirtualKeyCode::Right,
"ArrowDown" => VirtualKeyCode::Down,
"Backspace" => VirtualKeyCode::Back,
"Enter" => VirtualKeyCode::Return,
"Space" => VirtualKeyCode::Space,
"Compose" => VirtualKeyCode::Compose,
"Caret" => VirtualKeyCode::Caret,
"NumLock" => VirtualKeyCode::Numlock,
"Numpad0" => VirtualKeyCode::Numpad0,
"Numpad1" => VirtualKeyCode::Numpad1,
"Numpad2" => VirtualKeyCode::Numpad2,
"Numpad3" => VirtualKeyCode::Numpad3,
"Numpad4" => VirtualKeyCode::Numpad4,
"Numpad5" => VirtualKeyCode::Numpad5,
"Numpad6" => VirtualKeyCode::Numpad6,
"Numpad7" => VirtualKeyCode::Numpad7,
"Numpad8" => VirtualKeyCode::Numpad8,
"Numpad9" => VirtualKeyCode::Numpad9,
"AbntC1" => VirtualKeyCode::AbntC1,
"AbntC2" => VirtualKeyCode::AbntC2,
"NumpadAdd" => VirtualKeyCode::NumpadAdd,
"Quote" => VirtualKeyCode::Apostrophe,
"Apps" => VirtualKeyCode::Apps,
"At" => VirtualKeyCode::At,
"Ax" => VirtualKeyCode::Ax,
"Backslash" => VirtualKeyCode::Backslash,
"Calculator" => VirtualKeyCode::Calculator,
"Capital" => VirtualKeyCode::Capital,
"Semicolon" => VirtualKeyCode::Semicolon,
"Comma" => VirtualKeyCode::Comma,
"Convert" => VirtualKeyCode::Convert,
"NumpadDecimal" => VirtualKeyCode::NumpadDecimal,
"NumpadDivide" => VirtualKeyCode::NumpadDivide,
"Equal" => VirtualKeyCode::Equals,
"Backquote" => VirtualKeyCode::Grave,
"Kana" => VirtualKeyCode::Kana,
"Kanji" => VirtualKeyCode::Kanji,
"AltLeft" => VirtualKeyCode::LAlt,
"BracketLeft" => VirtualKeyCode::LBracket,
"ControlLeft" => VirtualKeyCode::LControl,
"ShiftLeft" => VirtualKeyCode::LShift,
"MetaLeft" => VirtualKeyCode::LWin,
"Mail" => VirtualKeyCode::Mail,
"MediaSelect" => VirtualKeyCode::MediaSelect,
"MediaStop" => VirtualKeyCode::MediaStop,
"Minus" => VirtualKeyCode::Minus,
"NumpadMultiply" => VirtualKeyCode::NumpadMultiply,
"Mute" => VirtualKeyCode::Mute,
"LaunchMyComputer" => VirtualKeyCode::MyComputer,
"NavigateForward" => VirtualKeyCode::NavigateForward,
"NavigateBackward" => VirtualKeyCode::NavigateBackward,
"NextTrack" => VirtualKeyCode::NextTrack,
"NoConvert" => VirtualKeyCode::NoConvert,
"NumpadComma" => VirtualKeyCode::NumpadComma,
"NumpadEnter" => VirtualKeyCode::NumpadEnter,
"NumpadEquals" => VirtualKeyCode::NumpadEquals,
"OEM102" => VirtualKeyCode::OEM102,
"Period" => VirtualKeyCode::Period,
"PlayPause" => VirtualKeyCode::PlayPause,
"Power" => VirtualKeyCode::Power,
"PrevTrack" => VirtualKeyCode::PrevTrack,
"AltRight" => VirtualKeyCode::RAlt,
"BracketRight" => VirtualKeyCode::RBracket,
"ControlRight" => VirtualKeyCode::RControl,
"ShiftRight" => VirtualKeyCode::RShift,
"MetaRight" => VirtualKeyCode::RWin,
"Slash" => VirtualKeyCode::Slash,
"Sleep" => VirtualKeyCode::Sleep,
"Stop" => VirtualKeyCode::Stop,
"NumpadSubtract" => VirtualKeyCode::NumpadSubtract,
"Sysrq" => VirtualKeyCode::Sysrq,
"Tab" => VirtualKeyCode::Tab,
"Underline" => VirtualKeyCode::Underline,
"Unlabeled" => VirtualKeyCode::Unlabeled,
"AudioVolumeDown" => VirtualKeyCode::VolumeDown,
"AudioVolumeUp" => VirtualKeyCode::VolumeUp,
"Wake" => VirtualKeyCode::Wake,
"WebBack" => VirtualKeyCode::WebBack,
"WebFavorites" => VirtualKeyCode::WebFavorites,
"WebForward" => VirtualKeyCode::WebForward,
"WebHome" => VirtualKeyCode::WebHome,
"WebRefresh" => VirtualKeyCode::WebRefresh,
"WebSearch" => VirtualKeyCode::WebSearch,
"WebStop" => VirtualKeyCode::WebStop,
"Yen" => VirtualKeyCode::Yen,
_ => return None,
})
}
pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState {
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
pub fn codepoint(event: &impl IKeyboardEvent) -> char {
// `event.key()` always returns a non-empty `String`. Therefore, this should
// never panic.
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}

View File

@@ -1,96 +0,0 @@
#![deprecated(since = "0.23.0", note = "Please migrate to web-sys over stdweb")]
mod canvas;
mod event;
mod scaling;
mod timeout;
pub use self::canvas::Canvas;
pub use self::scaling::ScaleChangeDetector;
pub use self::timeout::{AnimationFrameRequest, Timeout};
use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtStdweb;
use crate::window::Window;
use stdweb::js;
use stdweb::unstable::TryInto;
use stdweb::web::event::BeforeUnloadEvent;
use stdweb::web::window;
use stdweb::web::IEventTarget;
use stdweb::web::{document, html_element::CanvasElement, Element};
pub fn throw(msg: &str) {
js! { throw @{msg} }
}
pub fn exit_fullscreen() {
document().exit_fullscreen();
}
pub type UnloadEventHandle = ();
pub fn on_unload(mut handler: impl FnMut() + 'static) -> UnloadEventHandle {
window().add_event_listener(move |_: BeforeUnloadEvent| handler());
}
impl WindowExtStdweb for Window {
fn canvas(&self) -> CanvasElement {
self.window.canvas().raw().clone()
}
fn is_dark_mode(&self) -> bool {
// TODO: upstream to stdweb
let is_dark_mode = js! {
return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)
};
is_dark_mode.try_into().expect("should return a bool")
}
}
pub fn window_size() -> LogicalSize<f64> {
let window = window();
let width = window.inner_width() as f64;
let height = window.inner_height() as f64;
LogicalSize { width, height }
}
pub fn scale_factor() -> f64 {
let window = window();
window.device_pixel_ratio()
}
pub fn set_canvas_size(raw: &CanvasElement, size: Size) {
use stdweb::*;
let scale_factor = scale_factor();
let physical_size = size.to_physical::<u32>(scale_factor);
let logical_size = size.to_logical::<f64>(scale_factor);
raw.set_width(physical_size.width);
raw.set_height(physical_size.height);
set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width));
set_canvas_style_property(raw, "height", &format!("{}px", logical_size.height));
}
pub fn set_canvas_style_property(raw: &CanvasElement, style_attribute: &str, value: &str) {
js! {
@{raw.as_ref()}.style[@{style_attribute}] = @{value};
}
}
pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
match document().fullscreen_element() {
Some(elem) => {
let raw: Element = canvas.clone().into();
raw == elem
}
None => false,
}
}
pub type RawCanvasType = CanvasElement;

View File

@@ -1,13 +0,0 @@
use super::super::ScaleChangeArgs;
pub struct ScaleChangeDetector(());
impl ScaleChangeDetector {
pub(crate) fn new<F>(_handler: F) -> Self
where
F: 'static + FnMut(ScaleChangeArgs),
{
// TODO: Stub, unimplemented (see web_sys for reference).
Self(())
}
}

View File

@@ -1,63 +0,0 @@
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle};
#[derive(Debug)]
pub struct Timeout {
handle: Option<TimeoutHandle>,
}
impl Timeout {
pub fn new<F>(f: F, duration: Duration) -> Timeout
where
F: 'static + FnMut(),
{
Timeout {
handle: Some(window().set_clearable_timeout(f, duration.as_millis() as u32)),
}
}
}
impl Drop for Timeout {
fn drop(&mut self) {
let handle = self.handle.take().unwrap();
handle.clear();
}
}
#[derive(Debug)]
pub struct AnimationFrameRequest {
handle: Option<RequestAnimationFrameHandle>,
// track callback state, because `cancelAnimationFrame` is slow
fired: Rc<Cell<bool>>,
}
impl AnimationFrameRequest {
pub fn new<F>(mut f: F) -> AnimationFrameRequest
where
F: 'static + FnMut(),
{
let fired = Rc::new(Cell::new(false));
let c_fired = fired.clone();
let handle = window().request_animation_frame(move |_| {
(*c_fired).set(true);
f();
});
AnimationFrameRequest {
handle: Some(handle),
fired,
}
}
}
impl Drop for AnimationFrameRequest {
fn drop(&mut self) {
if !(*self.fired).get() {
if let Some(handle) = self.handle.take() {
handle.cancel();
}
}
}
}

View File

@@ -18,6 +18,7 @@ use web_sys::{
mod mouse_handler;
mod pointer_handler;
#[allow(dead_code)]
pub struct Canvas {
common: Common,
on_focus: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
@@ -88,6 +89,20 @@ impl Canvas {
})
}
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), RootOE> {
if grab {
self.raw().request_pointer_lock();
} else {
let window = web_sys::window()
.ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
let document = window
.document()
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
document.exit_pointer_lock();
}
Ok(())
}
pub fn set_attribute(&self, attribute: &str, value: &str) {
self.common
.raw
@@ -157,7 +172,17 @@ impl Canvas {
self.on_keyboard_press = Some(self.common.add_user_event(
"keydown",
move |event: KeyboardEvent| {
event.prevent_default();
// event.prevent_default() would suppress subsequent on_received_character() calls. That
// supression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to
// scroll, etc. We should not do it for key sequences that result in meaningful character
// input though.
let event_key = &event.key();
let is_key_string = event_key.len() == 1 || !event_key.is_ascii();
let is_shortcut_modifiers =
(event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr");
if !is_key_string || is_shortcut_modifiers {
event.prevent_default();
}
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
@@ -179,6 +204,8 @@ impl Canvas {
self.on_received_character = Some(self.common.add_user_event(
"keypress",
move |event: KeyboardEvent| {
// Supress further handling to stop keys like the space key from scrolling the page.
event.prevent_default();
handler(event::codepoint(&event));
},
));
@@ -226,7 +253,7 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
{
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler),

View File

@@ -8,6 +8,7 @@ use std::rc::Rc;
use web_sys::{EventTarget, MouseEvent};
#[allow(dead_code)]
pub(super) struct MouseHandler {
on_mouse_leave: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
on_mouse_enter: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
@@ -160,7 +161,7 @@ impl MouseHandler {
pub fn on_cursor_move<F>(&mut self, canvas_common: &super::Common, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
{
let mouse_capture_state = self.mouse_capture_state.clone();
let canvas = canvas_common.raw.clone();
@@ -190,9 +191,11 @@ impl MouseHandler {
// use `offsetX`/`offsetY`.
event::mouse_position_by_client(&event, &canvas)
};
let mouse_delta = event::mouse_delta(&event);
handler(
0,
mouse_pos.to_physical(super::super::scale_factor()),
mouse_delta.to_physical(super::super::scale_factor()),
event::mouse_modifiers(&event),
);
}

View File

@@ -5,6 +5,7 @@ use crate::event::{ModifiersState, MouseButton};
use web_sys::PointerEvent;
#[allow(dead_code)]
pub(super) struct PointerHandler {
on_cursor_leave: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
on_cursor_enter: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
@@ -78,16 +79,18 @@ impl PointerHandler {
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
canvas
.set_pointer_capture(event.pointer_id())
.expect("Failed to set pointer capture");
// Error is swallowed here since the error would occur every time the mouse is
// clicked when the cursor is grabbed, and there is probably not a situation where
// this could fail, that we care if it fails.
let _e = canvas.set_pointer_capture(event.pointer_id());
},
));
}
pub fn on_cursor_move<F>(&mut self, canvas_common: &super::Common, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
{
self.on_cursor_move = Some(canvas_common.add_event(
"pointermove",
@@ -95,6 +98,7 @@ impl PointerHandler {
handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::super::scale_factor()),
event::mouse_delta(&event).to_physical(super::super::scale_factor()),
event::mouse_modifiers(&event),
);
},

View File

@@ -29,6 +29,13 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
}
}
pub fn mouse_delta(event: &MouseEvent) -> LogicalPosition<f64> {
LogicalPosition {
x: event.movement_x() as f64,
y: event.movement_y() as f64,
}
}
pub fn mouse_position_by_client(
event: &MouseEvent,
canvas: &HtmlCanvasElement,

View File

@@ -59,9 +59,9 @@ impl ScaleChangeDetectorInternal {
// We add 0.0001 to the lower and upper bounds such that it won't fail
// due to floating point precision limitations.
let media_query = format!(
"(min-resolution: {:.4}dppx) and (max-resolution: {:.4}dppx)",
current_scale - 0.0001,
current_scale + 0.0001,
"(min-resolution: {min_scale:.4}dppx) and (max-resolution: {max_scale:.4}dppx),
(-webkit-min-device-pixel-ratio: {min_scale:.4}) and (-webkit-max-device-pixel-ratio: {max_scale:.4})",
min_scale = current_scale - 0.0001, max_scale= current_scale + 0.0001,
);
let mql = MediaQueryListHandle::new(&media_query, closure);
if let Some(mql) = &mql {

View File

@@ -3,9 +3,11 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::event;
use crate::icon::Icon;
use crate::monitor::MonitorHandle as RootMH;
use crate::window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWI};
use crate::window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI,
};
use raw_window_handle::web::WebHandle;
use raw_window_handle::{RawWindowHandle, WebHandle};
use super::{backend, monitor, EventLoopWindowTarget};
@@ -205,8 +207,11 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
self.canvas
.borrow()
.set_cursor_grab(grab)
.map_err(|e| ExternalError::Os(e))
}
#[inline]
@@ -220,6 +225,11 @@ impl Window {
}
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {
// Intentionally a no-op, as canvases cannot be 'minimized'
@@ -230,6 +240,12 @@ impl Window {
// Intentionally a no-op, as canvases cannot be 'maximized'
}
#[inline]
pub fn is_maximized(&self) -> bool {
// Canvas cannot be 'maximized'
false
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
if self.canvas.borrow().is_fullscreen() {
@@ -268,6 +284,16 @@ impl Window {
// Currently a no-op as it does not seem there is good support for this on web
}
#[inline]
pub fn focus_window(&self) {
// Currently a no-op as it does not seem there is good support for this on web
}
#[inline]
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
// Currently an intentional no-op
}
#[inline]
// Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> RootMH {
@@ -299,13 +325,10 @@ impl Window {
}
#[inline]
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let handle = WebHandle {
id: self.id.0,
..WebHandle::empty()
};
raw_window_handle::RawWindowHandle::Web(handle)
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = WebHandle::empty();
handle.id = self.id.0;
RawWindowHandle::Web(handle)
}
}
@@ -321,7 +344,7 @@ impl Drop for Window {
pub struct Id(pub(crate) u32);
impl Id {
pub unsafe fn dummy() -> Id {
pub const unsafe fn dummy() -> Id {
Id(0)
}
}

View File

@@ -6,36 +6,24 @@ use std::os::windows::ffi::OsStrExt;
use winapi::{
shared::{
basetsd::SIZE_T,
minwindef::{BOOL, DWORD, FALSE, UINT, ULONG, WORD},
ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR},
minwindef::{BOOL, DWORD, FALSE, WORD},
ntdef::{NTSTATUS, NT_SUCCESS, PVOID},
windef::HWND,
winerror::S_OK,
},
um::{libloaderapi, uxtheme, winuser},
um::{libloaderapi, uxtheme, winnt, winuser},
};
use crate::window::Theme;
lazy_static! {
static ref WIN10_BUILD_VERSION: Option<DWORD> = {
// FIXME: RtlGetVersion is a documented windows API,
// should be part of winapi!
#[allow(non_snake_case)]
#[repr(C)]
struct OSVERSIONINFOW {
dwOSVersionInfoSize: ULONG,
dwMajorVersion: ULONG,
dwMinorVersion: ULONG,
dwBuildNumber: ULONG,
dwPlatformId: ULONG,
szCSDVersion: [WCHAR; 128],
}
type RtlGetVersion = unsafe extern "system" fn (*mut OSVERSIONINFOW) -> NTSTATUS;
type RtlGetVersion = unsafe extern "system" fn (*mut winnt::OSVERSIONINFOW) -> NTSTATUS;
let handle = get_function!("ntdll.dll", RtlGetVersion);
if let Some(rtl_get_version) = handle {
unsafe {
let mut vi = OSVERSIONINFOW {
let mut vi = winnt::OSVERSIONINFOW {
dwOSVersionInfoSize: 0,
dwMajorVersion: 0,
dwMinorVersion: 0,
@@ -70,24 +58,33 @@ lazy_static! {
static ref LIGHT_THEME_NAME: Vec<u16> = widestring("");
}
/// Attempt to set dark mode on a window, if necessary.
/// Returns true if dark mode was set, false if not.
pub fn try_dark_mode(hwnd: HWND) -> bool {
/// Attempt to set a theme on a window, if necessary.
/// Returns the theme that was picked
pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
if *DARK_MODE_SUPPORTED {
let is_dark_mode = should_use_dark_mode();
let is_dark_mode = match preferred_theme {
Some(theme) => theme == Theme::Dark,
None => should_use_dark_mode(),
};
let theme_name = if is_dark_mode {
DARK_THEME_NAME.as_ptr()
let theme = if is_dark_mode {
Theme::Dark
} else {
LIGHT_THEME_NAME.as_ptr()
Theme::Light
};
let theme_name = match theme {
Theme::Dark => DARK_THEME_NAME.as_ptr(),
Theme::Light => LIGHT_THEME_NAME.as_ptr(),
};
let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) };
status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode)
} else {
false
if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
return theme;
}
}
Theme::Light
}
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
@@ -97,11 +94,12 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
type SetWindowCompositionAttribute =
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
#[allow(non_snake_case)]
#[allow(clippy::upper_case_acronyms)]
type WINDOWCOMPOSITIONATTRIB = u32;
const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26;
#[allow(non_snake_case)]
#[allow(clippy::upper_case_acronyms)]
#[repr(C)]
struct WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WINDOWCOMPOSITIONATTRIB,
@@ -170,21 +168,8 @@ fn should_apps_use_dark_mode() -> bool {
.unwrap_or(false)
}
// FIXME: This definition was missing from winapi. Can remove from
// here and use winapi once the following PR is released:
// https://github.com/retep998/winapi-rs/pull/815
#[repr(C)]
#[allow(non_snake_case)]
struct HIGHCONTRASTA {
cbSize: UINT,
dwFlags: DWORD,
lpszDefaultScheme: LPSTR,
}
const HCF_HIGHCONTRASTON: DWORD = 1;
fn is_high_contrast() -> bool {
let mut hc = HIGHCONTRASTA {
let mut hc = winuser::HIGHCONTRASTA {
cbSize: 0,
dwFlags: 0,
lpszDefaultScheme: std::ptr::null_mut(),
@@ -199,7 +184,7 @@ fn is_high_contrast() -> bool {
)
};
ok != FALSE && (HCF_HIGHCONTRASTON & hc.dwFlags) == 1
ok != FALSE && (winuser::HCF_HIGHCONTRASTON & hc.dwFlags) == 1
}
fn widestring(src: &'static str) -> Vec<u16> {

View File

@@ -181,7 +181,7 @@ impl FileDropHandler {
},
};
let mut drop_format = FORMATETC {
let drop_format = FORMATETC {
cfFormat: CF_HDROP as CLIPFORMAT,
ptd: ptr::null(),
dwAspect: DVASPECT_CONTENT,
@@ -190,7 +190,7 @@ impl FileDropHandler {
};
let mut medium = std::mem::zeroed();
let get_data_result = (*data_obj).GetData(&mut drop_format, &mut medium);
let get_data_result = (*data_obj).GetData(&drop_format, &mut medium);
if SUCCEEDED(get_data_result) {
let hglobal = (*medium.u).hGlobal();
let hdrop = (*hglobal) as shellapi::HDROP;
@@ -213,15 +213,15 @@ impl FileDropHandler {
callback(OsString::from_wide(&path_buf[0..character_count]).into());
}
return Some(hdrop);
Some(hdrop)
} else if get_data_result == DV_E_FORMATETC {
// If the dropped item is not a file this error will occur.
// In this case it is OK to return without taking further action.
debug!("Error occured while processing dropped/hovered item: item is not a file.");
return None;
None
} else {
debug!("Unexpected error occured while processing dropped/hovered item.");
return None;
None
}
}
}

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