Compare commits

...

44 Commits

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

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

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

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

* Use more grammar friendly naming

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

* Run cargo fmt

* Update changelog

* Fixes and tweaks from review

* Correct comment in app_state.rs

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

* Update the changelog

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

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

* MacOS: Remove activation hack

* Stop unnecessarily calling `makeKeyWindow` on initially hidden windows

You can't make hidden windows the key window

* Add new, simpler activation hack

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

* fix: ran fmt

* extracted function into variable

* idiomatic formatting

* Set the default menu only during app startup

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

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

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

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

* Update CHANGELOG.md

* Update CHANGELOG.md

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

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

* Possible improvement

* Remove leftover

* Wrong rebase

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

* Update changelog

* Fix for compilation warnings

* Apply suggestions from code review

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

* Improve code quality

* Change Arc<Mutex> to Rc<RefCell>

* Panicking in the user callback is now well defined

* Address feedback

* Fix nightly warning

* The panic info is now not a global.

* Apply suggestions from code review

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

* Address feedback

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

* Introduce example.

* Wayland implementation.

* Windows implementation.

* Improve Wayland seat passing.

* MacOS implementation.

* Correct windows implementation per specification.

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

* Fixed blocking thread in windows implementation.

* Add multi-window example.

* Move Wayland to a different PR.

* Fix CHANGELOG.

* Improve example.

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

* Rename `set_drag_window` to `begin_drag`.

* Improve example.

* Fix CHANGELOG.

* Fix CHANGELOG.

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

* Rename to `drag_window`.

* Fix typo.

* Re-introduce Wayland implementation.

* Fixing Wayland build.

* Fixing Wayland build.

* Move SCTK to 0.12.3.

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

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

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

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

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

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

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

* Remove useless WS_EX_LAYERED from transparent windows on Windows

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

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

* Update changelog

* Add support for stdweb too

* Add mouse_delta to stdweb

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

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

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

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

The issue was introduced in #1797 (932cbe4).
2021-01-19 17:41:02 +01:00
Marijn Suijten
05fe983757 android: Use event identifier instead of userdata pointer (#1826)
ndk-glue currently sets both the `ident` field and user-data pointer to
`0` or `1` for the event pipe and input queue respectively, to tell
these sources apart. While it works to reinterpret this `data` pointer
as integer identifier it shouldn't be abused for that, in particular
when one may wish to provide extra information with an event in the
future; then the `data` field is used as pointer (or abused as abstract
value) for that.
2021-01-13 23:02:55 +01:00
alula
d1a7749df5 Android: Do not mark unhandled events as handled. (#1820) 2021-01-12 08:25:56 +01:00
Markus Røyset
9d63fc7ca0 On Windows, set the cursor icon when the cursor first enters a window (#1807) 2021-01-05 17:39:13 +01:00
Markus Røyset
38fccebe1f On Windows, change the default window size (#1805) 2020-12-20 17:59:46 +01:00
Markus Røyset
c05952b813 On Windows, improve handling of window destruction (#1798) 2020-12-20 12:54:42 +01:00
Samuel
932cbe40bf On Windows, fix bug causing mouse capture to not be released. (#1797) 2020-12-15 07:31:13 +01:00
relrelb
39573d65d0 Windows: Preserve minimized/maximized state in fullscreen (#1784) 2020-12-13 19:06:53 +01:00
53 changed files with 1522 additions and 617 deletions

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,25 +1,58 @@
# 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 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 `with_min_inner_size` in `WindowBuilder`.
- 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 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 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`.
- **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.
@@ -34,7 +67,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`.
@@ -107,7 +140,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.24.0"
version = "0.25.0"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -20,7 +20,7 @@ targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux
default = ["x11", "wayland"]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
stdweb = ["std_web", "instant/stdweb"]
x11 = ["x11-dl", "mio", "mio-extras", "percent-encoding", "parking_lot"]
x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "sctk"]
[dependencies]
@@ -31,15 +31,16 @@ log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.3"
bitflags = "1"
mint = { version = "0.5.6", optional = true }
[dev-dependencies]
image = "0.23.12"
simple_logger = "1.9"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2.0"
ndk = "0.3"
ndk-sys = "0.2.0"
ndk-glue = "0.2.0"
ndk-glue = "0.3"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc = "0.2.7"
@@ -49,6 +50,7 @@ cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
scopeguard = "1.1"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
version = "0.1.4"
@@ -85,9 +87,9 @@ features = [
[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 = { version = "0.6", optional = true }
mio-extras = { version = "2.0", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true }
mio = { version = "0.7", features = ["os-ext"], optional = true }
mio-misc = { version = "1.0", optional = true }
x11-dl = { version = "2.18.5", optional = true }
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.11.0", optional = true }

View File

@@ -116,6 +116,7 @@ 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 or set a preferred theme
@@ -208,6 +209,7 @@ Legend:
|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

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.24.0"
winit = "0.25.0"
```
## [Documentation](https://docs.rs/winit)
@@ -66,6 +66,7 @@ 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
@@ -110,3 +111,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.

10
build.rs Normal file
View File

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

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

@@ -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;

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

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

@@ -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.
@@ -163,7 +164,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
@@ -227,8 +228,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(mint: mint::Point2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(winit: LogicalPosition<P>) -> Self {
mint::Point2 {
x: winit.x,
y: winit.y,
}
}
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
@@ -292,8 +310,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(mint: mint::Point2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(winit: PhysicalPosition<P>) -> Self {
mint::Point2 {
x: winit.x,
y: winit.y,
}
}
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
@@ -357,8 +392,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(mint: mint::Vector2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(winit: LogicalSize<P>) -> Self {
mint::Vector2 {
x: winit.width,
y: winit.height,
}
}
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
@@ -419,6 +471,23 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(mint: mint::Vector2<P>) -> Self {
Self::new(mint.x, mint.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(winit: PhysicalSize<P>) -> Self {
mint::Vector2 {
x: winit.width,
y: winit.height,
}
}
}
/// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

View File

@@ -4,8 +4,9 @@ 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},
};
@@ -100,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;
@@ -122,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,
@@ -186,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.

View File

@@ -5,14 +5,14 @@ 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},
platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
};
@@ -78,6 +78,21 @@ pub trait WindowExtWindows {
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> *mut libc::c_void;
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
/// If a child window is disabled, it is ignored when the system tries to determine which
/// window should receive mouse messages.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
fn set_enable(&self, enabled: bool);
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
@@ -96,6 +111,13 @@ impl WindowExtWindows for Window {
self.window.hwnd() as *mut _
}
#[inline]
fn set_enable(&self, enabled: bool) {
unsafe {
winapi::um::winuser::EnableWindow(self.hwnd() as _, enabled as _);
}
}
#[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
@@ -110,8 +132,34 @@ impl WindowExtWindows for Window {
/// 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,7 +171,7 @@ 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.
@@ -133,7 +181,19 @@ pub trait WindowBuilderExtWindows {
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
}

View File

@@ -8,7 +8,7 @@ use crate::{
};
use ndk::{
configuration::Configuration,
event::{InputEvent, MotionAction},
event::{InputEvent, KeyAction, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
};
use ndk_glue::{Event, Rect};
@@ -30,9 +30,9 @@ enum EventSource {
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,
@@ -176,6 +176,7 @@ impl<T: 'static> EventLoop<T> {
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
while let Some(event) = input_queue.get_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 {
@@ -191,7 +192,10 @@ impl<T: 'static> EventLoop<T> {
MotionAction::Cancel => {
Some(event::TouchPhase::Cancelled)
}
_ => None, // TODO mouse events
_ => {
handled = false;
None // TODO mouse events
}
};
if let Some(phase) = phase {
let pointers: Box<
@@ -235,9 +239,35 @@ impl<T: 'static> EventLoop<T> {
}
}
}
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);
}
}
}
@@ -489,6 +519,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");
}
@@ -523,6 +557,12 @@ impl Window {
pub fn set_cursor_visible(&self, _: bool) {}
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
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 _ }

View File

@@ -182,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")
}
@@ -190,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 {

View File

@@ -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,12 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
}
#[inline]
pub fn is_maximized(&self) -> bool {
// TODO: Not implemented
false
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
@@ -598,11 +609,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

@@ -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;
@@ -28,6 +29,7 @@ pub(super) fn handle_pointer(
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();
@@ -59,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);
@@ -101,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);

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::{ConceptFrame, 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 {
@@ -144,6 +148,10 @@ impl WinitPointer {
confined_pointer.destroy();
}
}
pub fn drag_window(&self, window: &Window<ConceptFrame>) {
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
}
/// A pointer wrapper for easy releasing and managing pointers.
@@ -172,11 +180,18 @@ 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(),
);
},
);

View File

@@ -586,6 +586,18 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
let drag_window_request = WindowRequest::DragWindow;
self.window_requests
.lock()
.unwrap()
.push(drag_window_request);
self.event_loop_awakener.ping();
Ok(())
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64;

View File

@@ -34,6 +34,9 @@ pub enum WindowRequest {
/// Grab cursor.
GrabCursor(bool),
/// Drag window.
DragWindow,
/// Maximize the window.
Maximize(bool),
@@ -268,6 +271,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 +308,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();

View File

@@ -32,15 +32,20 @@ use std::{
ptr,
rc::Rc,
slice,
sync::mpsc::Receiver,
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
use mio_extras::channel::{channel, Receiver, SendError, Sender};
use mio_misc::{
channel::{channel, SendError, Sender},
queue::NotificationQueue,
NotificationId,
};
use self::{
dnd::{Dnd, DndState},
@@ -57,8 +62,7 @@ use crate::{
};
const X_TOKEN: Token = Token(0);
const USER_TOKEN: Token = Token(1);
const REDRAW_TOKEN: Token = Token(2);
const USER_REDRAW_TOKEN: Token = Token(1);
pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
@@ -131,7 +135,7 @@ impl<T: 'static> EventLoop<T> {
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn));
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
panic!(format!("Failed to open input method: {:#?}", state));
panic!("Failed to open input method: {:#?}", state);
}
result.expect("Failed to set input method destruction callback")
});
@@ -180,33 +184,16 @@ impl<T: 'static> EventLoop<T> {
mod_keymap.reset_from_x_connection(&xconn);
let poll = Poll::new().unwrap();
let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap());
let queue = Arc::new(NotificationQueue::new(waker));
let (user_sender, user_channel) = channel();
let (redraw_sender, redraw_channel) = channel();
poll.registry()
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
.unwrap();
poll.register(
&EventedFd(&xconn.x11_fd),
X_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let (user_sender, user_channel) = channel(queue.clone(), NotificationId::gen_next());
poll.register(
&user_channel,
USER_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
poll.register(
&redraw_channel,
REDRAW_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let (redraw_sender, redraw_channel) = channel(queue, NotificationId::gen_next());
let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ use std::{
};
use libc;
use mio_extras::channel::Sender;
use mio_misc::channel::Sender;
use parking_lot::Mutex;
use crate::{
@@ -146,6 +146,10 @@ impl UnownedWindow {
.min_inner_size
.map(|size| size.to_physical::<u32>(scale_factor).into());
let position = window_attrs
.position
.map(|position| position.to_physical::<i32>(scale_factor).into());
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
@@ -211,8 +215,8 @@ impl UnownedWindow {
(xconn.xlib.XCreateWindow)(
xconn.display,
root,
0,
0,
position.map_or(0, |p: PhysicalPosition<i32>| p.x as c_int),
position.map_or(0, |p: PhysicalPosition<i32>| p.y as c_int),
dimensions.0 as c_uint,
dimensions.1 as c_uint,
0,
@@ -344,6 +348,7 @@ impl UnownedWindow {
}
let mut normal_hints = util::NormalHints::new(xconn);
normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y)));
normal_hints.set_size(Some(dimensions));
normal_hints.set_min_size(min_inner_size.map(Into::into));
normal_hints.set_max_size(max_inner_size.map(Into::into));
@@ -439,6 +444,12 @@ impl UnownedWindow {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.map(|flusher| flusher.queue());
if let Some(PhysicalPosition { x, y }) = position {
let shared_state = window.shared_state.get_mut();
shared_state.restore_position = Some((x, y));
}
}
if window_attrs.always_on_top {
window
@@ -1276,6 +1287,46 @@ impl UnownedWindow {
self.set_cursor_position_physical(x, y)
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
let pointer = self
.xconn
.query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
let window = self.inner_position().map_err(ExternalError::NotSupported)?;
let message = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_MOVERESIZE\0") };
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
// if the cursor isn't currently grabbed
let mut grabbed_lock = self.cursor_grabbed.lock();
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
self.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
*grabbed_lock = false;
// we keep the lock until we are done
self.xconn
.send_client_msg(
self.xwindow,
self.root,
message,
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
[
(window.x as c_long + pointer.win_x as c_long),
(window.y as c_long + pointer.win_y as c_long),
8, // _NET_WM_MOVERESIZE_MOVE
ffi::Button1 as c_long,
1,
],
)
.flush()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
}
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
let _ = self
.ime_sender

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -207,7 +207,10 @@ pub unsafe fn set_title_async(ns_window: id, title: String) {
// `close:` is thread-safe, but we want the event to be triggered from the main
// thread. Though, it's a good idea to look into that more...
pub unsafe fn close_async(ns_window: id) {
//
// ArturKovacs: It's important that this operation keeps the underlying window alive
// through the `IdRef` because otherwise it would dereference free'd memory
pub unsafe fn close_async(ns_window: IdRef) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
autoreleasepool(move || {

View File

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

View File

@@ -255,6 +255,10 @@ lazy_static! {
sel!(frameDidChange:),
frame_did_change as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(acceptsFirstMouse:),
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
@@ -346,7 +350,7 @@ extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
AppState::queue_redraw(WindowId(get_window_id(state.ns_window)));
AppState::handle_redraw(WindowId(get_window_id(state.ns_window)));
let superclass = util::superclass(this);
let () = msg_send![super(this, superclass), drawRect: rect];
@@ -1078,3 +1082,7 @@ extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
extern "C" fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL {
YES
}
extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL {
YES
}

View File

@@ -16,7 +16,7 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform::macos::{ActivationPolicy, WindowExtMacOS},
platform::macos::WindowExtMacOS,
platform_impl::platform::{
app_state::AppState,
app_state::INTERRUPT_EVENT_LOOP_EXIT,
@@ -34,9 +34,8 @@ use crate::{
};
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},
@@ -64,7 +63,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,
@@ -80,7 +78,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,
@@ -94,24 +91,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,
@@ -131,8 +110,6 @@ unsafe fn create_view(
ns_view.setWantsLayer(YES);
}
ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*ns_view);
(ns_view, cursor_state)
})
}
@@ -166,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))
}
};
@@ -249,8 +236,9 @@ 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();
@@ -346,14 +334,9 @@ impl UnownedWindow {
panic!("Windows can only be created on the main thread on macOS");
}
}
trace!("Creating new window");
let pool = unsafe { NSAutoreleasePool::new(nil) };
let ns_app = create_app(pl_attribs.activation_policy).ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSApplication`"))
})?;
let ns_window = create_window(&win_attribs, &pl_attribs).ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
@@ -365,6 +348,12 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSView`"))
})?;
// Configure the new view as the "key view" for the window
unsafe {
ns_window.setContentView_(*ns_view);
ns_window.setInitialFirstResponder_(*ns_view);
}
let input_context = unsafe { util::create_input_context(*ns_view) };
let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
@@ -375,7 +364,6 @@ impl UnownedWindow {
ns_window.setBackgroundColor_(NSColor::clearColor(nil));
}
ns_app.activateIgnoringOtherApps_(YES);
win_attribs.min_inner_size.map(|dim| {
let logical_dim = dim.to_logical(scale_factor);
set_min_inner_size(*ns_window, logical_dim)
@@ -425,12 +413,9 @@ impl UnownedWindow {
// Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size
// before it transitions.
unsafe {
if visible {
window.ns_window.makeKeyAndOrderFront_(nil);
} else {
window.ns_window.makeKeyWindow();
}
if visible {
// Tightly linked with `app_state::window_activation_hack`
unsafe { window.ns_window.makeKeyAndOrderFront_(nil) };
}
if maximized {
@@ -496,17 +481,8 @@ impl UnownedWindow {
pub fn set_outer_position(&self, position: Position) {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
let dummy = NSRect::new(
NSPoint::new(
position.x,
// While it's true that we're setting the top-left position,
// it still needs to be in a bottom-left coordinate system.
CGDisplay::main().pixels_high() as f64 - position.y,
),
NSSize::new(0f64, 0f64),
);
unsafe {
util::set_frame_top_left_point_async(*self.ns_window, dummy.origin);
util::set_frame_top_left_point_async(*self.ns_window, util::window_position(position));
}
}
@@ -636,6 +612,16 @@ impl UnownedWindow {
Ok(())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
unsafe {
let event: id = msg_send![NSApp(), currentEvent];
let _: () = msg_send![*self.ns_window, performWindowDragWithEvent: event];
}
Ok(())
}
pub(crate) fn is_zoomed(&self) -> bool {
// because `isZoomed` doesn't work if the window's borderless,
// we make it resizable temporalily.
@@ -730,6 +716,11 @@ impl UnownedWindow {
shared_state_lock.fullscreen.clone()
}
#[inline]
pub fn is_maximized(&self) -> bool {
self.is_zoomed()
}
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
trace!("Locked shared state in `set_fullscreen`");
@@ -1153,7 +1144,7 @@ impl Drop for UnownedWindow {
trace!("Dropping `UnownedWindow` ({:?})", self as *mut _);
// Close the window if it has not yet been closed.
if *self.ns_window != nil {
unsafe { util::close_async(*self.ns_window) };
unsafe { util::close_async(self.ns_window.clone()) };
}
}
}

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

@@ -17,6 +17,7 @@ use stdweb::web::event::{
use stdweb::web::html_element::CanvasElement;
use stdweb::web::{document, EventListenerHandle, IElement, IEventTarget, IHtmlElement};
#[allow(dead_code)]
pub struct Canvas {
/// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: CanvasElement,
@@ -222,13 +223,14 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, 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_delta(&event).to_physical(super::scale_factor()),
event::mouse_modifiers(&event),
);
}));

View File

@@ -30,6 +30,13 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition<f64> {
}
}
pub fn mouse_delta(event: &impl IMouseEvent) -> LogicalPosition<f64> {
LogicalPosition {
x: event.movement_x() as f64,
y: event.movement_y() as f64,
}
}
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
let x = event.delta_x();
let y = -event.delta_y();

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)>>,
@@ -238,7 +239,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)>>,
@@ -87,7 +88,7 @@ impl PointerHandler {
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 +96,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

@@ -222,6 +222,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'
@@ -232,6 +237,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() {

View File

@@ -81,16 +81,20 @@ pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
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()) };
if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
return Theme::Dark;
return theme;
}
}

View File

@@ -4,6 +4,7 @@ mod runner;
use parking_lot::Mutex;
use std::{
cell::Cell,
collections::VecDeque,
marker::PhantomData,
mem, panic, ptr,
@@ -19,7 +20,7 @@ use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR};
use winapi::{
shared::{
minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WPARAM},
minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WORD, WPARAM},
windef::{HWND, POINT, RECT},
windowsx, winerror,
},
@@ -86,6 +87,8 @@ pub(crate) struct SubclassInput<T: 'static> {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: EventLoopRunnerShared<T>,
pub file_drop_handler: Option<FileDropHandler>,
pub subclass_removed: Cell<bool>,
pub recurse_depth: Cell<u32>,
}
impl<T> SubclassInput<T> {
@@ -616,18 +619,31 @@ fn subclass_event_target_window<T>(
}
}
fn remove_event_target_window_subclass<T: 'static>(window: HWND) {
let removal_result = unsafe {
commctrl::RemoveWindowSubclass(
window,
Some(thread_event_target_callback::<T>),
THREAD_EVENT_TARGET_SUBCLASS_ID,
)
};
assert_eq!(removal_result, 1);
}
/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of
/// the window.
unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) {
window_state.mouse.buttons_down += 1;
window_state.mouse.capture_count += 1;
winuser::SetCapture(window);
}
/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor
/// is outside the window.
unsafe fn release_mouse(window_state: &mut WindowState) {
window_state.mouse.buttons_down = window_state.mouse.buttons_down.saturating_sub(1);
if window_state.mouse.buttons_down == 0 {
unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowState>) {
window_state.mouse.capture_count = window_state.mouse.capture_count.saturating_sub(1);
if window_state.mouse.capture_count == 0 {
// ReleaseCapture() causes a WM_CAPTURECHANGED where we lock the window_state.
drop(window_state);
winuser::ReleaseCapture();
}
}
@@ -648,6 +664,17 @@ pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>)
assert_eq!(subclass_result, 1);
}
fn remove_window_subclass<T: 'static>(window: HWND) {
let removal_result = unsafe {
commctrl::RemoveWindowSubclass(
window,
Some(public_window_callback::<T>),
WINDOW_SUBCLASS_ID,
)
};
assert_eq!(removal_result, 1);
}
fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
match pressure {
1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)),
@@ -750,11 +777,41 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
_: UINT_PTR,
uidsubclass: UINT_PTR,
subclass_input_ptr: DWORD_PTR,
) -> LRESULT {
let subclass_input = &*(subclass_input_ptr as *const SubclassInput<T>);
let subclass_input_ptr = subclass_input_ptr as *mut SubclassInput<T>;
let (result, subclass_removed, recurse_depth) = {
let subclass_input = &*subclass_input_ptr;
subclass_input
.recurse_depth
.set(subclass_input.recurse_depth.get() + 1);
let result =
public_window_callback_inner(window, msg, wparam, lparam, uidsubclass, subclass_input);
let subclass_removed = subclass_input.subclass_removed.get();
let recurse_depth = subclass_input.recurse_depth.get() - 1;
subclass_input.recurse_depth.set(recurse_depth);
(result, subclass_removed, recurse_depth)
};
if subclass_removed && recurse_depth == 0 {
Box::from_raw(subclass_input_ptr);
}
result
}
unsafe fn public_window_callback_inner<T: 'static>(
window: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
_: UINT_PTR,
subclass_input: &SubclassInput<T>,
) -> LRESULT {
winuser::RedrawWindow(
subclass_input.event_loop_runner.thread_msg_target(),
ptr::null(),
@@ -788,7 +845,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
}
winuser::WM_NCLBUTTONDOWN => {
if wparam == winuser::HTCAPTION as _ {
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, lparam);
}
commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
@@ -814,8 +871,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
}
winuser::WM_NCDESTROY => {
drop(subclass_input);
Box::from_raw(subclass_input_ptr as *mut SubclassInput<T>);
remove_window_subclass::<T>(window);
subclass_input.subclass_removed.set(true);
0
}
@@ -1192,7 +1249,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
ElementState::Released, MouseButton::Left, WindowEvent::MouseInput,
};
release_mouse(&mut *subclass_input.window_state.lock());
release_mouse(subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
@@ -1234,7 +1291,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
ElementState::Released, MouseButton::Right, WindowEvent::MouseInput,
};
release_mouse(&mut *subclass_input.window_state.lock());
release_mouse(subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
@@ -1276,7 +1333,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput,
};
release_mouse(&mut *subclass_input.window_state.lock());
release_mouse(subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
@@ -1320,7 +1377,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
};
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
release_mouse(&mut *subclass_input.window_state.lock());
release_mouse(subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
@@ -1336,6 +1393,17 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
0
}
winuser::WM_CAPTURECHANGED => {
// lparam here is a handle to the window which is gaining mouse capture.
// If it is the same as our window, then we're essentially retaining the capture. This
// can happen if `SetCapture` is called on our window when it already has the mouse
// capture.
if lparam != window as isize {
subclass_input.window_state.lock().mouse.capture_count = 0;
}
0
}
winuser::WM_TOUCH => {
let pcount = LOWORD(wparam as DWORD) as usize;
let mut inputs = Vec::with_capacity(pcount);
@@ -1601,11 +1669,11 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
winuser::WM_SETCURSOR => {
let set_cursor_to = {
let window_state = subclass_input.window_state.lock();
if window_state
.mouse
.cursor_flags()
.contains(CursorFlags::IN_WINDOW)
{
// The return value for the preceding `WM_NCHITTEST` message is conveniently
// provided through the low-order word of lParam. We use that here since
// `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement.
let in_client_area = LOWORD(lparam as DWORD) == winuser::HTCLIENT as WORD;
if in_client_area {
Some(window_state.mouse.cursor)
} else {
None
@@ -1923,8 +1991,7 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
_: UINT_PTR,
subclass_input_ptr: DWORD_PTR,
) -> LRESULT {
let subclass_input = &mut *(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);
let runner = subclass_input.event_loop_runner.clone();
let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);
if msg != winuser::WM_PAINT {
winuser::RedrawWindow(
@@ -1935,13 +2002,15 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
);
}
let mut subclass_removed = false;
// I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing
// the closure to catch_unwind directly so that the match body indendation wouldn't change and
// the git blame and history would be preserved.
let callback = || match msg {
winuser::WM_NCDESTROY => {
Box::from_raw(subclass_input);
drop(subclass_input);
remove_event_target_window_subclass::<T>(window);
subclass_removed = true;
0
}
// Because WM_PAINT comes after all other messages, we use it during modal loops to detect
@@ -2032,11 +2101,12 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
}
if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
let delta =
mouse.usButtonData as SHORT as f32 / winuser::WHEEL_DELTA as f32;
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: MouseWheel {
delta: LineDelta(0.0, delta as f32),
delta: LineDelta(0.0, delta),
},
});
}
@@ -2148,5 +2218,14 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
_ => commctrl::DefSubclassProc(window, msg, wparam, lparam),
};
runner.catch_unwind(callback).unwrap_or(-1)
let result = subclass_input
.event_loop_runner
.catch_unwind(callback)
.unwrap_or(-1);
if subclass_removed {
mem::drop(subclass_input);
} else {
Box::into_raw(subclass_input);
}
result
}

View File

@@ -1,6 +1,6 @@
#![cfg(target_os = "windows")]
use winapi::{self, shared::windef::HWND};
use winapi::{self, shared::windef::HMENU, shared::windef::HWND};
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
@@ -15,9 +15,17 @@ use crate::event::DeviceId as RootDeviceId;
use crate::icon::Icon;
use crate::window::Theme;
#[derive(Clone)]
pub enum Parent {
None,
ChildOf(HWND),
OwnedBy(HWND),
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub parent: Option<HWND>,
pub parent: Parent,
pub menu: Option<HMENU>,
pub taskbar_icon: Option<Icon>,
pub no_redirection_bitmap: bool,
pub drag_and_drop: bool,
@@ -27,7 +35,8 @@ pub struct PlatformSpecificWindowBuilderAttributes {
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
parent: None,
parent: Parent::None,
menu: None,
taskbar_icon: None,
no_redirection_bitmap: false,
drag_and_drop: true,

View File

@@ -14,8 +14,8 @@ use std::{
use winapi::{
ctypes::c_int,
shared::{
minwindef::{HINSTANCE, UINT},
windef::{HWND, POINT, RECT},
minwindef::{HINSTANCE, LPARAM, UINT, WPARAM},
windef::{HWND, POINT, POINTS, RECT},
},
um::{
combaseapi, dwmapi,
@@ -25,7 +25,8 @@ use winapi::{
ole2,
oleidl::LPDROPTARGET,
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
winnt::LPCWSTR,
wingdi::{CreateRectRgn, DeleteObject},
winnt::{LPCWSTR, SHORT},
winuser,
},
};
@@ -43,7 +44,7 @@ use crate::{
icon::{self, IconType},
monitor, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId,
Parent, PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes},
};
@@ -114,6 +115,8 @@ impl Window {
window_state: win.window_state.clone(),
event_loop_runner: event_loop.runner_shared.clone(),
file_drop_handler,
subclass_removed: Cell::new(false),
recurse_depth: Cell::new(0),
};
event_loop::subclass_window(win.window.0, subclass_input);
@@ -276,7 +279,7 @@ impl Window {
#[inline]
pub fn hinstance(&self) -> HINSTANCE {
unsafe { winuser::GetWindowLongW(self.hwnd(), winuser::GWL_HINSTANCE) as *mut _ }
unsafe { winuser::GetWindowLongPtrW(self.hwnd(), winuser::GWLP_HINSTANCE) as *mut _ }
}
#[inline]
@@ -354,6 +357,30 @@ impl Window {
Ok(())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
unsafe {
let points = {
let mut pos = mem::zeroed();
winuser::GetCursorPos(&mut pos);
pos
};
let points = POINTS {
x: points.x as SHORT,
y: points.y as SHORT,
};
winuser::ReleaseCapture();
winuser::PostMessageW(
self.window.0,
winuser::WM_NCLBUTTONDOWN,
winuser::HTCAPTION as WPARAM,
&points as *const _ as LPARAM,
);
}
Ok(())
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.window.0)
@@ -383,6 +410,12 @@ impl Window {
});
}
#[inline]
pub fn is_maximized(&self) -> bool {
let window_state = self.window_state.lock();
window_state.window_flags.contains(WindowFlags::MAXIMIZED)
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
let window_state = self.window_state.lock();
@@ -403,20 +436,6 @@ impl Window {
drop(window_state_lock);
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
// Save window bounds before entering fullscreen
match (&old_fullscreen, &fullscreen) {
(&None, &Some(_)) => {
let client_rect = util::get_client_rect(window.0).unwrap();
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
scale_factor: window_state_lock.scale_factor,
});
}
_ => (),
}
// Change video mode if we're transitioning to or from exclusive
// fullscreen
match (&old_fullscreen, &fullscreen) {
@@ -490,7 +509,7 @@ impl Window {
}
// Update window style
WindowState::set_window_flags(window_state_lock, window.0, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
@@ -504,6 +523,15 @@ impl Window {
// Update window bounds
match &fullscreen {
Some(fullscreen) => {
// Save window bounds before entering fullscreen
let placement = unsafe {
let mut placement = mem::zeroed();
winuser::GetWindowPlacement(window.0, &mut placement);
placement
};
window_state.lock().saved_window = Some(SavedWindow { placement });
let monitor = match &fullscreen {
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
@@ -530,27 +558,10 @@ impl Window {
}
None => {
let mut window_state_lock = window_state.lock();
if let Some(SavedWindow {
client_rect,
scale_factor,
}) = window_state_lock.saved_window.take()
{
window_state_lock.scale_factor = scale_factor;
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
drop(window_state_lock);
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
unsafe {
winuser::SetWindowPos(
window.0,
ptr::null_mut(),
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOACTIVATE,
);
winuser::SetWindowPlacement(window.0, &placement);
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
}
}
@@ -722,8 +733,24 @@ unsafe fn init<T: 'static>(
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some());
window_flags.set(WindowFlags::ON_TASKBAR, true);
let parent = match pl_attribs.parent {
Parent::ChildOf(parent) => {
window_flags.set(WindowFlags::CHILD, true);
if pl_attribs.menu.is_some() {
warn!("Setting a menu on a child window is unsupported");
}
Some(parent)
}
Parent::OwnedBy(parent) => {
window_flags.set(WindowFlags::POPUP, true);
Some(parent)
}
Parent::None => {
window_flags.set(WindowFlags::ON_TASKBAR, true);
None
}
};
// creating the real window this time, by using the functions in `extra_functions`
let real_window = {
@@ -737,8 +764,8 @@ unsafe fn init<T: 'static>(
winuser::CW_USEDEFAULT,
winuser::CW_USEDEFAULT,
winuser::CW_USEDEFAULT,
pl_attribs.parent.unwrap_or(ptr::null_mut()),
ptr::null_mut(),
parent.unwrap_or(ptr::null_mut()),
pl_attribs.menu.unwrap_or(ptr::null_mut()),
libloaderapi::GetModuleHandleW(ptr::null()),
ptr::null_mut(),
);
@@ -763,20 +790,18 @@ unsafe fn init<T: 'static>(
// making the window transparent
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
// Empty region for the blur effect, so the window is fully transparent
let region = CreateRectRgn(0, 0, -1, -1);
let bb = dwmapi::DWM_BLURBEHIND {
dwFlags: dwmapi::DWM_BB_ENABLE,
dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION,
fEnable: 1,
hRgnBlur: ptr::null_mut(),
hRgnBlur: region,
fTransitionOnMaximized: 0,
};
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
if attributes.decorations {
let opacity = 255;
winuser::SetLayeredWindowAttributes(real_window.0, 0, opacity, winuser::LWA_ALPHA);
}
DeleteObject(region as _);
}
// If the system theme is dark, we need to set the window theme now
@@ -805,7 +830,7 @@ unsafe fn init<T: 'static>(
let dimensions = attributes
.inner_size
.unwrap_or_else(|| PhysicalSize::new(1024, 768).into());
.unwrap_or_else(|| PhysicalSize::new(800, 600).into());
win.set_inner_size(dimensions);
if attributes.maximized {
// Need to set MAXIMIZED after setting `inner_size` as
@@ -819,6 +844,10 @@ unsafe fn init<T: 'static>(
force_window_active(win.window.0);
}
if let Some(position) = attributes.position {
win.set_outer_position(position);
}
Ok(win)
}

View File

@@ -34,19 +34,18 @@ pub struct WindowState {
pub current_theme: Theme,
pub preferred_theme: Option<Theme>,
pub high_surrogate: Option<u16>,
window_flags: WindowFlags,
pub window_flags: WindowFlags,
}
#[derive(Clone)]
pub struct SavedWindow {
pub client_rect: RECT,
pub scale_factor: f64,
pub placement: winuser::WINDOWPLACEMENT,
}
#[derive(Clone)]
pub struct MouseProperties {
pub cursor: CursorIcon,
pub buttons_down: u32,
pub capture_count: u32,
cursor_flags: CursorFlags,
pub last_position: Option<PhysicalPosition<f64>>,
}
@@ -69,6 +68,7 @@ bitflags! {
const TRANSPARENT = 1 << 6;
const CHILD = 1 << 7;
const MAXIMIZED = 1 << 8;
const POPUP = 1 << 14;
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
/// included here to make masking easier.
@@ -86,11 +86,6 @@ bitflags! {
const MINIMIZED = 1 << 12;
const FULLSCREEN_AND_MASK = !(
WindowFlags::DECORATIONS.bits |
WindowFlags::RESIZABLE.bits |
WindowFlags::MAXIMIZED.bits
);
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
@@ -108,7 +103,7 @@ impl WindowState {
WindowState {
mouse: MouseProperties {
cursor: CursorIcon::default(),
buttons_down: 0,
capture_count: 0,
cursor_flags: CursorFlags::empty(),
last_position: None,
},
@@ -181,10 +176,7 @@ impl MouseProperties {
impl WindowFlags {
fn mask(mut self) -> WindowFlags {
if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK;
self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK;
} else if self.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK;
}
if !self.contains(WindowFlags::VISIBLE) {
self &= WindowFlags::INVISIBLE_AND_MASK;
@@ -219,12 +211,12 @@ impl WindowFlags {
if self.contains(WindowFlags::NO_BACK_BUFFER) {
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
}
if self.contains(WindowFlags::TRANSPARENT) && self.contains(WindowFlags::DECORATIONS) {
style_ex |= WS_EX_LAYERED;
}
if self.contains(WindowFlags::CHILD) {
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
}
if self.contains(WindowFlags::POPUP) {
style |= WS_POPUP;
}
if self.contains(WindowFlags::MINIMIZED) {
style |= WS_MINIMIZE;
}
@@ -235,6 +227,12 @@ impl WindowFlags {
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
style_ex |= WS_EX_ACCEPTFILES;
if self.intersects(
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN,
) {
style &= !WS_OVERLAPPEDWINDOW;
}
(style, style_ex)
}

View File

@@ -116,6 +116,31 @@ pub struct WindowAttributes {
/// The default is `None`.
pub max_inner_size: Option<Size>,
/// The desired position of the window. If this is `None`, some platform-specific position
/// will be chosen.
///
/// The default is `None`.
///
/// ## Platform-specific
///
/// - **macOS**: The top left corner position of the window content, the window's "inner"
/// position. The window title bar will be placed above it.
/// The window will be positioned such that it fits on screen, maintaining
/// set `inner_size` if any.
/// If you need to precisely position the top left corner of the whole window you have to
/// use [`Window::set_outer_position`] after creating the window.
/// - **Windows**: The top left corner position of the window title bar, the window's "outer"
/// position.
/// There may be a small gap between this position and the window due to the specifics of the
/// Window Manager.
/// - **X11**: The top left corner of the window, the window's "outer" position.
/// - **Others**: Ignored.
///
/// See [`Window::set_outer_position`].
///
/// [`Window::set_outer_position`]: crate::window::Window::set_outer_position
pub position: Option<Position>,
/// Whether the window is resizable or not.
///
/// The default is `true`.
@@ -170,6 +195,7 @@ impl Default for WindowAttributes {
inner_size: None,
min_inner_size: None,
max_inner_size: None,
position: None,
resizable: true,
title: "winit window".to_owned(),
maximized: false,
@@ -223,6 +249,17 @@ impl WindowBuilder {
self
}
/// Sets a desired initial position for the window.
///
/// See [`WindowAttributes::position`] for details.
///
/// [`WindowAttributes::position`]: crate::window::WindowAttributes::position
#[inline]
pub fn with_position<P: Into<Position>>(mut self, position: P) -> Self {
self.window.position = Some(position.into());
self
}
/// Sets whether the window is resizable or not.
///
/// See [`Window::set_resizable`] for details.
@@ -526,7 +563,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **iOS / Andraid / Web:** Unsupported.
/// - **iOS / Android / Web:** Unsupported.
#[inline]
pub fn set_max_inner_size<S: Into<Size>>(&self, max_size: Option<S>) {
self.window.set_max_inner_size(max_size.map(|s| s.into()))
@@ -597,6 +634,17 @@ impl Window {
self.window.set_maximized(maximized)
}
/// Gets the window's current maximized state.
///
/// ## Platform-specific
///
/// - **Wayland / X11:** Not implemented.
/// - **iOS / Android / Web:** Unsupported.
#[inline]
pub fn is_maximized(&self) -> bool {
self.window.is_maximized()
}
/// Sets the window to fullscreen or back.
///
/// ## Platform-specific
@@ -753,6 +801,22 @@ impl Window {
pub fn set_cursor_visible(&self, visible: bool) {
self.window.set_cursor_visible(visible)
}
/// Moves the window with the left mouse button until the button is released.
///
/// There's no guarantee that this will work unless the left mouse button was pressed
/// immediately before this function is called.
///
/// ## Platform-specific
///
/// - **X11:** Un-grabs the cursor.
/// - **Wayland:** Requires the cursor to be inside the window to be dragged.
/// - **macOS:** May prevent the button release event to be triggered.
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window.drag_window()
}
}
/// Monitor info functions.