mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
40 Commits
notgull/in
...
madsmtm/ja
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48974ed7ec | ||
|
|
82021cc5c7 | ||
|
|
39c0862198 | ||
|
|
aa8ebdc795 | ||
|
|
ea68916055 | ||
|
|
e26b831f23 | ||
|
|
8c3e69c08b | ||
|
|
46879429ed | ||
|
|
0c89ea7386 | ||
|
|
6c0e3c3b15 | ||
|
|
675582bd46 | ||
|
|
4d6fe7e35c | ||
|
|
f9912baf09 | ||
|
|
f290619dce | ||
|
|
23011c6b0a | ||
|
|
3a39a6ddb0 | ||
|
|
90cf9a3398 | ||
|
|
05d8fa0b91 | ||
|
|
d7d20507ed | ||
|
|
f6e66a71f8 | ||
|
|
c09160d1a8 | ||
|
|
2230e71093 | ||
|
|
5e6350d142 | ||
|
|
a6998af997 | ||
|
|
5c48ec7977 | ||
|
|
953d9b4268 | ||
|
|
f5dcd2aabe | ||
|
|
77f1c73f06 | ||
|
|
24c226416e | ||
|
|
69382fda9a | ||
|
|
ee245c569d | ||
|
|
5462f27dda | ||
|
|
927deb030f | ||
|
|
5ea81efc74 | ||
|
|
6896de5b73 | ||
|
|
d736763216 | ||
|
|
e316a89847 | ||
|
|
f0d8689039 | ||
|
|
4d5e68c6e2 | ||
|
|
5835c9102e |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -74,15 +74,19 @@ jobs:
|
||||
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
||||
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||
exclude:
|
||||
# Web on nightly needs extra arguments
|
||||
# Web on nightly needs extra arguments
|
||||
|
||||
- toolchain: nightly
|
||||
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
|
||||
platform: { name: 'Web' }
|
||||
# Rustup is broken.
|
||||
- toolchain: nightly
|
||||
platform: { name: 'Windows 32bit GNU' }
|
||||
# Android is tested on stable-3
|
||||
- toolchain: '1.73'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
platform: { name: 'Android' }
|
||||
# Redox OS doesn't follow MSRV
|
||||
- toolchain: '1.73'
|
||||
platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
|
||||
platform: { name: 'Redox OS' }
|
||||
include:
|
||||
- toolchain: '1.73'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
|
||||
@@ -19,6 +19,11 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
|
||||
life during review it's recommended to check the "give contributors write access
|
||||
to the branch" checkbox.
|
||||
|
||||
We use unstable Rustfmt options across the project, so please run
|
||||
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
|
||||
the maintainers can do it for you before merging, just state so in your pull
|
||||
request description.
|
||||
|
||||
#### Handling review
|
||||
|
||||
During the review process certain events could require an action from your side,
|
||||
|
||||
65
Cargo.toml
65
Cargo.toml
@@ -21,7 +21,7 @@ name = "winit"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.30.5"
|
||||
version = "0.30.9"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
@@ -104,14 +104,15 @@ ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
|
||||
|
||||
# AppKit or UIKit
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
block2 = "0.5.1"
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.5.2"
|
||||
block2 = "0.6.0"
|
||||
dispatch2 = { version = "0.2.0", default-features = false, features = ["std", "objc2"] }
|
||||
objc2 = "0.6.0"
|
||||
|
||||
# AppKit
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.23.1"
|
||||
objc2-app-kit = { version = "0.2.2", features = [
|
||||
objc2-app-kit = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"objc2-core-foundation",
|
||||
"NSAppearance",
|
||||
"NSApplication",
|
||||
"NSBitmapImageRep",
|
||||
@@ -128,6 +129,7 @@ objc2-app-kit = { version = "0.2.2", features = [
|
||||
"NSMenu",
|
||||
"NSMenuItem",
|
||||
"NSOpenGLView",
|
||||
"NSPanel",
|
||||
"NSPasteboard",
|
||||
"NSResponder",
|
||||
"NSRunningApplication",
|
||||
@@ -140,9 +142,37 @@ objc2-app-kit = { version = "0.2.2", features = [
|
||||
"NSWindowScripting",
|
||||
"NSWindowTabGroup",
|
||||
] }
|
||||
objc2-foundation = { version = "0.2.2", features = [
|
||||
objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"dispatch",
|
||||
"CFBase",
|
||||
"CFCGTypes",
|
||||
"CFData",
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
"CFUUID",
|
||||
] }
|
||||
objc2-core-graphics = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"libc",
|
||||
"CGDirectDisplay",
|
||||
"CGDisplayConfiguration",
|
||||
"CGDisplayFade",
|
||||
"CGError",
|
||||
"CGRemoteOperation",
|
||||
"CGWindowLevel",
|
||||
] }
|
||||
objc2-core-video = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"objc2-core-graphics",
|
||||
"CVBase",
|
||||
"CVReturn",
|
||||
"CVDisplayLink",
|
||||
] }
|
||||
objc2-foundation = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"objc2-core-foundation",
|
||||
"NSArray",
|
||||
"NSAttributedString",
|
||||
"NSData",
|
||||
@@ -164,20 +194,29 @@ objc2-foundation = { version = "0.2.2", features = [
|
||||
|
||||
# UIKit
|
||||
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
|
||||
objc2-foundation = { version = "0.2.2", features = [
|
||||
objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"CFCGTypes",
|
||||
"CFBase",
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
] }
|
||||
objc2-foundation = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"dispatch",
|
||||
"objc2-core-foundation",
|
||||
"NSArray",
|
||||
"NSEnumerator",
|
||||
"NSGeometry",
|
||||
"NSObjCRuntime",
|
||||
"NSOperation",
|
||||
"NSString",
|
||||
"NSProcessInfo",
|
||||
"NSThread",
|
||||
"NSSet",
|
||||
] }
|
||||
objc2-ui-kit = { version = "0.2.2", features = [
|
||||
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
|
||||
"std",
|
||||
"objc2-core-foundation",
|
||||
"UIApplication",
|
||||
"UIDevice",
|
||||
"UIEvent",
|
||||
@@ -203,7 +242,7 @@ objc2-ui-kit = { version = "0.2.2", features = [
|
||||
# Windows
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
unicode-segmentation = "1.7.1"
|
||||
windows-sys = { version = "0.52.0", features = [
|
||||
windows-sys = { version = "0.59.0", features = [
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.30.5"
|
||||
winit = "0.30.9"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
1241
examples/application.rs
Normal file
1241
examples/application.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,21 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
struct WindowData {
|
||||
window: Box<dyn Window>,
|
||||
color: u32,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
fn new(window: Box<dyn Window>, color: u32) -> Self {
|
||||
Self { window, color }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Application {
|
||||
parent_window_id: Option<WindowId>,
|
||||
windows: HashMap<WindowId, Box<dyn Window>>,
|
||||
windows: HashMap<WindowId, WindowData>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Application {
|
||||
@@ -26,11 +37,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
|
||||
let window = event_loop.create_window(attributes).unwrap();
|
||||
|
||||
println!("Parent window id: {:?})", window.id());
|
||||
self.parent_window_id = Some(window.id());
|
||||
|
||||
self.windows.insert(window.id(), window);
|
||||
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -56,15 +66,24 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event: KeyEvent { state: ElementState::Pressed, .. },
|
||||
..
|
||||
} => {
|
||||
let child_index = self.windows.len() - 1;
|
||||
let child_color =
|
||||
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
|
||||
|
||||
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
|
||||
let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
|
||||
let child_window =
|
||||
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
|
||||
let child_id = child_window.id();
|
||||
println!("Child window created with id: {child_id:?}");
|
||||
self.windows.insert(child_id, child_window);
|
||||
self.windows.insert(child_id, WindowData::new(child_window, child_color));
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = self.windows.get(&window_id) {
|
||||
fill::fill_window(window.as_ref());
|
||||
if window_id == self.parent_window_id.unwrap() {
|
||||
fill::fill_window(window.window.as_ref());
|
||||
} else {
|
||||
fill::fill_window_with_color(window.window.as_ref(), window.color);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
@@ -75,12 +94,20 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
fn spawn_child_window(
|
||||
parent: &dyn Window,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
child_count: usize,
|
||||
) -> Box<dyn Window> {
|
||||
let parent = parent.raw_window_handle().unwrap();
|
||||
|
||||
// As child count increases, x goes from 0*128 to 5*128 and then repeats
|
||||
let x: f64 = child_count.rem_euclid(5) as f64 * 128.0;
|
||||
|
||||
// After 5 windows have been put side by side horizontally, a new row starts
|
||||
let y: f64 = (child_count / 5) as f64 * 96.0;
|
||||
|
||||
let mut window_attributes = WindowAttributes::default()
|
||||
.with_title("child window")
|
||||
.with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_surface_size(LogicalSize::new(128.0f32, 96.0))
|
||||
.with_position(Position::Logical(LogicalPosition::new(x, y)))
|
||||
.with_visible(true);
|
||||
// `with_parent_window` is unsafe. Parent window must be a valid window.
|
||||
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
|
||||
|
||||
64
examples/dnd.rs
Normal file
64
examples/dnd.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::error::Error;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
#[path = "util/tracing.rs"]
|
||||
mod tracing;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
tracing::init();
|
||||
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
let app = Application::new();
|
||||
Ok(event_loop.run_app(app)?)
|
||||
}
|
||||
|
||||
/// Application state and event handling.
|
||||
struct Application {
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn new() -> Self {
|
||||
Self { window: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Application {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes =
|
||||
WindowAttributes::default().with_title("Drag and drop files on me!");
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
_window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
match event {
|
||||
WindowEvent::DragLeft { .. }
|
||||
| WindowEvent::DragEntered { .. }
|
||||
| WindowEvent::DragMoved { .. }
|
||||
| WindowEvent::DragDropped { .. } => {
|
||||
println!("{:?}", event);
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
let window = self.window.as_ref().unwrap();
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
WindowEvent::CloseRequested => {
|
||||
event_loop.exit();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window_with_color;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod platform {
|
||||
@@ -70,7 +73,7 @@ mod platform {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_window(window: &dyn Window) {
|
||||
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
|
||||
GC.with(|gc| {
|
||||
let size = window.surface_size();
|
||||
let (Some(width), Some(height)) =
|
||||
@@ -84,17 +87,21 @@ mod platform {
|
||||
let surface =
|
||||
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xff181818;
|
||||
// Fill a buffer with a solid color
|
||||
|
||||
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer.fill(color);
|
||||
buffer.present().expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window(window: &dyn Window) {
|
||||
fill_window_with_color(window, 0xff181818);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &dyn Window) {
|
||||
GC.with(|gc| {
|
||||
@@ -112,6 +119,11 @@ mod platform {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &dyn winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
|
||||
1286
examples/window.rs
1286
examples/window.rs
File diff suppressed because it is too large
Load Diff
@@ -59,23 +59,19 @@ changelog entry.
|
||||
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
|
||||
and `Serialize` on many types.
|
||||
- Add `MonitorHandle::current_video_mode()`.
|
||||
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
|
||||
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
|
||||
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
|
||||
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
|
||||
- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`
|
||||
to use a larger style of titlebar.
|
||||
- Add `WindowId::into_raw()` and `from_raw()`.
|
||||
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
|
||||
pointer events as part of the pointer event overhaul.
|
||||
- Add `DeviceId::into_raw()` and `from_raw()`.
|
||||
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
|
||||
variables to test the respective modifiers of window creation.
|
||||
- Added `Window::surface_position`, which is the position of the surface inside the window.
|
||||
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
|
||||
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
|
||||
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
|
||||
- On Windows, add `IconExtWindows::from_resource_name`.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -165,6 +161,34 @@ changelog entry.
|
||||
- On macOS, no longer emit `Focused` upon window creation.
|
||||
- On iOS, emit more events immediately, instead of queuing them.
|
||||
- Update `smol_str` to version `0.3`
|
||||
- Rename `VideoModeHandle` to `VideoMode`, now it only stores plain data.
|
||||
- Make `Fullscreen::Exclusive` contain `(MonitorHandle, VideoMode)`.
|
||||
- Reworked the file drag-and-drop API.
|
||||
|
||||
The `WindowEvent::DroppedFile`, `WindowEvent::HoveredFile` and `WindowEvent::HoveredFileCancelled`
|
||||
events have been removed, and replaced with `WindowEvent::DragEntered`, `WindowEvent::DragMoved`,
|
||||
`WindowEvent::DragDropped` and `WindowEvent::DragLeft`.
|
||||
|
||||
The old drag-and-drop events were emitted once per file. This occurred when files were *first*
|
||||
hovered over the window, dropped, or left the window. The new drag-and-drop events are emitted
|
||||
once per set of files dragged, and include a list of all dragged files. They also include the
|
||||
pointer position.
|
||||
|
||||
The rough correspondence is:
|
||||
- `WindowEvent::HoveredFile` -> `WindowEvent::DragEntered`
|
||||
- `WindowEvent::DroppedFile` -> `WindowEvent::DragDropped`
|
||||
- `WindowEvent::HoveredFileCancelled` -> `WindowEvent::DragLeft`
|
||||
|
||||
The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves
|
||||
whilst files are being dragged over the window. It doesn't contain any file paths, just the
|
||||
pointer position.
|
||||
- Updated `objc2` to `v0.6`.
|
||||
- Updated `windows-sys` to `v0.59`.
|
||||
- To match the corresponding changes in `windows-sys`, the `HWND`, `HMONITOR`, and `HMENU` types
|
||||
now alias to `*mut c_void` instead of `isize`.
|
||||
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
|
||||
- Removed `KeyEventExtModifierSupplement`, and made the fields `text_with_all_modifiers` and
|
||||
`key_without_modifiers` public on `KeyEvent` instead.
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -201,18 +225,8 @@ changelog entry.
|
||||
### Fixed
|
||||
|
||||
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name.
|
||||
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
|
||||
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
|
||||
default activation policy, unless explicitly provided during initialization.
|
||||
- On macOS, fix crash when calling `drag_window()` without a left click present.
|
||||
- On X11, key events forward to IME anyway, even when it's disabled.
|
||||
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
|
||||
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
|
||||
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
|
||||
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
|
||||
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
|
||||
- On macOS, fixed the scancode conversion for audio volume keys.
|
||||
- On macOS, fixed the scancode conversion for `IntlBackslash`.
|
||||
- On macOS, fixed redundant `SurfaceResized` event at window creation.
|
||||
- On macOS, fix crash when pressing Caps Lock in certain configurations.
|
||||
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
|
||||
- On macOS, fixed the behaviour of `pump_app_events` to match what the system expects.
|
||||
|
||||
@@ -1,3 +1,59 @@
|
||||
## 0.30.9
|
||||
|
||||
### Changed
|
||||
|
||||
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, fix crash with uim.
|
||||
- On X11, fix modifiers for keys that were sent by the same X11 request.
|
||||
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
|
||||
|
||||
## 0.30.8
|
||||
|
||||
### Added
|
||||
|
||||
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
|
||||
- On X11, add a workaround for disabling IME on GNOME.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Windows, fixed the event loop not waking on accessibility requests.
|
||||
- On X11, fixed cursor grab mode state tracking on error.
|
||||
|
||||
## 0.30.7
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, fixed KeyboardInput delivered twice when IME enabled.
|
||||
|
||||
## 0.30.6
|
||||
|
||||
### Added
|
||||
|
||||
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
|
||||
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
|
||||
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
|
||||
variables to test the respective modifiers of window creation.
|
||||
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
|
||||
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
|
||||
default activation policy, unless explicitly provided during initialization.
|
||||
- On macOS, fix crash when calling `drag_window()` without a left click present.
|
||||
- On X11, key events forward to IME anyway, even when it's disabled.
|
||||
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
|
||||
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
|
||||
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
|
||||
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
|
||||
- On macOS, fix crash when pressing Caps Lock in certain configurations.
|
||||
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
|
||||
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
|
||||
|
||||
## 0.30.5
|
||||
|
||||
### Added
|
||||
|
||||
358
src/event.rs
358
src/event.rs
@@ -49,70 +49,9 @@ use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::error::RequestError;
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
|
||||
use crate::platform_impl;
|
||||
#[cfg(doc)]
|
||||
use crate::window::Window;
|
||||
use crate::window::{ActivationToken, Theme, WindowId};
|
||||
|
||||
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
|
||||
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
|
||||
// wrap `Event` in some other structure.
|
||||
/// Describes a generic event.
|
||||
///
|
||||
/// See the module-level docs for more information on the event loop manages each event.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum Event {
|
||||
/// See [`ApplicationHandler::new_events()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events()
|
||||
NewEvents(StartCause),
|
||||
|
||||
/// See [`ApplicationHandler::window_event()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event()
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
WindowEvent { window_id: WindowId, event: WindowEvent },
|
||||
|
||||
/// See [`ApplicationHandler::device_event()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event()
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
|
||||
|
||||
/// See [`ApplicationHandler::suspended()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
|
||||
Suspended,
|
||||
|
||||
/// See [`ApplicationHandler::can_create_surfaces()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces()
|
||||
CreateSurfaces,
|
||||
|
||||
/// See [`ApplicationHandler::resumed()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
|
||||
Resumed,
|
||||
|
||||
/// See [`ApplicationHandler::about_to_wait()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
|
||||
AboutToWait,
|
||||
|
||||
/// See [`ApplicationHandler::exiting()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting()
|
||||
LoopExiting,
|
||||
|
||||
/// See [`ApplicationHandler::memory_warning()`] for details.
|
||||
///
|
||||
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
|
||||
MemoryWarning,
|
||||
|
||||
/// User requested a wake up.
|
||||
UserWakeUp,
|
||||
}
|
||||
use crate::window::{ActivationToken, Theme};
|
||||
|
||||
/// Describes the reason the event loop is resuming.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -175,28 +114,42 @@ pub enum WindowEvent {
|
||||
/// The window has been destroyed.
|
||||
Destroyed,
|
||||
|
||||
/// A file is being hovered over the window.
|
||||
///
|
||||
/// When the user hovers multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
HoveredFile(PathBuf),
|
||||
|
||||
/// A file has been dropped into the window.
|
||||
///
|
||||
/// When the user drops multiple files at once, this event will be emitted for each file
|
||||
/// separately.
|
||||
///
|
||||
/// The support for this is known to be incomplete, see [#720] for more
|
||||
/// information.
|
||||
///
|
||||
/// [#720]: https://github.com/rust-windowing/winit/issues/720
|
||||
DroppedFile(PathBuf),
|
||||
|
||||
/// A file was hovered, but has exited the window.
|
||||
///
|
||||
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
|
||||
/// hovered.
|
||||
HoveredFileCancelled,
|
||||
/// A file drag operation has entered the window.
|
||||
DragEntered {
|
||||
/// List of paths that are being dragged onto the window.
|
||||
paths: Vec<PathBuf>,
|
||||
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
|
||||
/// negative on some platforms if something is dragged over a window's decorations (title
|
||||
/// bar, frame, etc).
|
||||
position: PhysicalPosition<f64>,
|
||||
},
|
||||
/// A file drag operation has moved over the window.
|
||||
DragMoved {
|
||||
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
|
||||
/// negative on some platforms if something is dragged over a window's decorations (title
|
||||
/// bar, frame, etc).
|
||||
position: PhysicalPosition<f64>,
|
||||
},
|
||||
/// The file drag operation has dropped file(s) on the window.
|
||||
DragDropped {
|
||||
/// List of paths that are being dragged onto the window.
|
||||
paths: Vec<PathBuf>,
|
||||
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
|
||||
/// negative on some platforms if something is dragged over a window's decorations (title
|
||||
/// bar, frame, etc).
|
||||
position: PhysicalPosition<f64>,
|
||||
},
|
||||
/// The file drag operation has been cancelled or left the window.
|
||||
DragLeft {
|
||||
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
|
||||
/// negative on some platforms if something is dragged over a window's decorations (title
|
||||
/// bar, frame, etc).
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows:** Always emits [`None`].
|
||||
position: Option<PhysicalPosition<f64>>,
|
||||
},
|
||||
|
||||
/// The window gained or lost focus.
|
||||
///
|
||||
@@ -569,7 +522,7 @@ pub enum PointerSource {
|
||||
/// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`].
|
||||
/// - **Android:** Will never be [`None`]. If the device doesn't support pressure
|
||||
/// sensitivity, force will either be 0.0 or 1.0. Also see the
|
||||
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
|
||||
/// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity,
|
||||
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
|
||||
force: Option<Force>,
|
||||
@@ -778,12 +731,6 @@ pub struct KeyEvent {
|
||||
/// you somehow see this in the wild, we'd like to know :)
|
||||
pub physical_key: keyboard::PhysicalKey,
|
||||
|
||||
// Allowing `broken_intra_doc_links` for `logical_key`, because
|
||||
// `key_without_modifiers` is not available on all platforms
|
||||
#[cfg_attr(
|
||||
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
|
||||
allow(rustdoc::broken_intra_doc_links)
|
||||
)]
|
||||
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
|
||||
///
|
||||
/// This has two use cases:
|
||||
@@ -799,7 +746,7 @@ pub struct KeyEvent {
|
||||
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
|
||||
/// browser/OS.
|
||||
///
|
||||
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
|
||||
/// [`key_without_modifiers`]: Self::key_without_modifiers
|
||||
pub logical_key: keyboard::Key,
|
||||
|
||||
/// Contains the text produced by this keypress.
|
||||
@@ -820,7 +767,7 @@ pub struct KeyEvent {
|
||||
/// This is `None` if the current keypress cannot
|
||||
/// be interpreted as text.
|
||||
///
|
||||
/// See also: `text_with_all_modifiers()`
|
||||
/// See also [`text_with_all_modifiers`][Self::text_with_all_modifiers].
|
||||
pub text: Option<SmolStr>,
|
||||
|
||||
/// Contains the location of this key on the keyboard.
|
||||
@@ -876,13 +823,33 @@ pub struct KeyEvent {
|
||||
/// ```
|
||||
pub repeat: bool,
|
||||
|
||||
/// Platform-specific key event information.
|
||||
/// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd>.
|
||||
///
|
||||
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
|
||||
/// all modifiers applied.
|
||||
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
|
||||
///
|
||||
/// On Android, iOS, Redox and Web, this type is a no-op.
|
||||
pub(crate) platform_specific: platform_impl::KeyEventExtra,
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android:** Unimplemented, this field is always the same value as `text`.
|
||||
/// - **iOS:** Unimplemented, this field is always the same value as `text`.
|
||||
/// - **Web:** Unsupported, this field is always the same value as `text`.
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
|
||||
/// This value ignores all modifiers including, but not limited to <kbd>Shift</kbd>,
|
||||
/// <kbd>Caps Lock</kbd>, and <kbd>Ctrl</kbd>. In most cases this means that the
|
||||
/// unicode character in the resulting string is lowercase.
|
||||
///
|
||||
/// This is useful for key-bindings / shortcut key combinations.
|
||||
///
|
||||
/// In case [`logical_key`][Self::logical_key] reports [`Dead`][keyboard::Key::Dead],
|
||||
/// this will still report the key as `Character` according to the current keyboard
|
||||
/// layout. This value cannot be `Dead`.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android:** Unimplemented, this field is always the same value as `logical_key`.
|
||||
/// - **iOS:** Unimplemented, this field is always the same value as `logical_key`.
|
||||
/// - **Web:** Unsupported, this field is always the same value as `logical_key`.
|
||||
pub key_without_modifiers: keyboard::Key,
|
||||
}
|
||||
|
||||
/// Describes keyboard modifiers event.
|
||||
@@ -1193,123 +1160,108 @@ mod tests {
|
||||
|
||||
macro_rules! foreach_event {
|
||||
($closure:expr) => {{
|
||||
foreach_event!(window: $closure);
|
||||
foreach_event!(device: $closure);
|
||||
}};
|
||||
(window: $closure:expr) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut x = $closure;
|
||||
let mut with_window_event: &mut dyn FnMut(event::WindowEvent) = &mut $closure;
|
||||
let fid = event::FingerId::from_raw(0);
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
use crate::event::Event::*;
|
||||
use crate::event::Ime::Enabled;
|
||||
use crate::event::WindowEvent::*;
|
||||
use crate::event::{PointerKind, PointerSource};
|
||||
use crate::window::WindowId;
|
||||
use crate::event::Ime::Enabled;
|
||||
use crate::event::WindowEvent::*;
|
||||
use crate::event::{PointerKind, PointerSource};
|
||||
|
||||
// Mainline events.
|
||||
let wid = WindowId::from_raw(0);
|
||||
x(NewEvents(event::StartCause::Init));
|
||||
x(AboutToWait);
|
||||
x(LoopExiting);
|
||||
x(Suspended);
|
||||
x(Resumed);
|
||||
with_window_event(CloseRequested);
|
||||
with_window_event(Destroyed);
|
||||
with_window_event(Focused(true));
|
||||
with_window_event(Moved((0, 0).into()));
|
||||
with_window_event(SurfaceResized((0, 0).into()));
|
||||
with_window_event(DragEntered { paths: vec!["x.txt".into()], position: (0, 0).into() });
|
||||
with_window_event(DragMoved { position: (0, 0).into() });
|
||||
with_window_event(DragDropped { paths: vec!["x.txt".into()], position: (0, 0).into() });
|
||||
with_window_event(DragLeft { position: Some((0, 0).into()) });
|
||||
with_window_event(Ime(Enabled));
|
||||
with_window_event(PointerMoved {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position: (0, 0).into(),
|
||||
source: PointerSource::Mouse,
|
||||
});
|
||||
with_window_event(ModifiersChanged(event::Modifiers::default()));
|
||||
with_window_event(PointerEntered {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position: (0, 0).into(),
|
||||
kind: PointerKind::Mouse,
|
||||
});
|
||||
with_window_event(PointerLeft {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position: Some((0, 0).into()),
|
||||
kind: PointerKind::Mouse,
|
||||
});
|
||||
with_window_event(MouseWheel {
|
||||
device_id: None,
|
||||
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(PointerButton {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
state: event::ElementState::Pressed,
|
||||
position: (0, 0).into(),
|
||||
button: event::MouseButton::Other(0).into(),
|
||||
});
|
||||
with_window_event(PointerButton {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
state: event::ElementState::Released,
|
||||
position: (0, 0).into(),
|
||||
button: event::ButtonSource::Touch {
|
||||
finger_id: fid,
|
||||
force: Some(event::Force::Normalized(0.0)),
|
||||
},
|
||||
});
|
||||
with_window_event(PinchGesture {
|
||||
device_id: None,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(DoubleTapGesture { device_id: None });
|
||||
with_window_event(RotationGesture {
|
||||
device_id: None,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(PanGesture {
|
||||
device_id: None,
|
||||
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
|
||||
with_window_event(ThemeChanged(crate::window::Theme::Light));
|
||||
with_window_event(Occluded(true));
|
||||
}};
|
||||
(device: $closure:expr) => {{
|
||||
use event::DeviceEvent::*;
|
||||
|
||||
// Window events.
|
||||
let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
|
||||
#[allow(unused_mut)]
|
||||
let mut with_device_event: &mut dyn FnMut(event::DeviceEvent) = &mut $closure;
|
||||
|
||||
with_window_event(CloseRequested);
|
||||
with_window_event(Destroyed);
|
||||
with_window_event(Focused(true));
|
||||
with_window_event(Moved((0, 0).into()));
|
||||
with_window_event(SurfaceResized((0, 0).into()));
|
||||
with_window_event(DroppedFile("x.txt".into()));
|
||||
with_window_event(HoveredFile("x.txt".into()));
|
||||
with_window_event(HoveredFileCancelled);
|
||||
with_window_event(Ime(Enabled));
|
||||
with_window_event(PointerMoved {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position: (0, 0).into(),
|
||||
source: PointerSource::Mouse,
|
||||
});
|
||||
with_window_event(ModifiersChanged(event::Modifiers::default()));
|
||||
with_window_event(PointerEntered {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
position: (0, 0).into(),
|
||||
kind: PointerKind::Mouse,
|
||||
});
|
||||
with_window_event(PointerLeft {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position: Some((0, 0).into()),
|
||||
kind: PointerKind::Mouse,
|
||||
});
|
||||
with_window_event(MouseWheel {
|
||||
device_id: None,
|
||||
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(PointerButton {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
state: event::ElementState::Pressed,
|
||||
position: (0, 0).into(),
|
||||
button: event::MouseButton::Other(0).into(),
|
||||
});
|
||||
with_window_event(PointerButton {
|
||||
device_id: None,
|
||||
primary: true,
|
||||
state: event::ElementState::Released,
|
||||
position: (0, 0).into(),
|
||||
button: event::ButtonSource::Touch {
|
||||
finger_id: fid,
|
||||
force: Some(event::Force::Normalized(0.0)),
|
||||
},
|
||||
});
|
||||
with_window_event(PinchGesture {
|
||||
device_id: None,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(DoubleTapGesture { device_id: None });
|
||||
with_window_event(RotationGesture {
|
||||
device_id: None,
|
||||
delta: 0.0,
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(PanGesture {
|
||||
device_id: None,
|
||||
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
|
||||
phase: event::TouchPhase::Started,
|
||||
});
|
||||
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
|
||||
with_window_event(ThemeChanged(crate::window::Theme::Light));
|
||||
with_window_event(Occluded(true));
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
use event::DeviceEvent::*;
|
||||
|
||||
let with_device_event =
|
||||
|dev_ev| x(event::Event::DeviceEvent { device_id: None, event: dev_ev });
|
||||
|
||||
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
|
||||
with_device_event(MouseWheel {
|
||||
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
|
||||
});
|
||||
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
|
||||
}
|
||||
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
|
||||
with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0) });
|
||||
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_clone)]
|
||||
#[allow(clippy::clone_on_copy)]
|
||||
#[test]
|
||||
fn test_event_clone() {
|
||||
foreach_event!(|event: event::Event| {
|
||||
foreach_event!(|event| {
|
||||
let event2 = event.clone();
|
||||
assert_eq!(event, event2);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1327,7 +1279,7 @@ mod tests {
|
||||
#[allow(clippy::clone_on_copy)]
|
||||
#[test]
|
||||
fn ensure_attrs_do_not_panic() {
|
||||
foreach_event!(|event: event::Event| {
|
||||
foreach_event!(|event| {
|
||||
let _ = format!("{event:?}");
|
||||
});
|
||||
let _ = event::StartCause::Init.clone();
|
||||
|
||||
56
src/lib.rs
56
src/lib.rs
@@ -1,59 +1,5 @@
|
||||
//! Winit is a cross-platform window creation and event loop management library.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! `winit` can be added to `Cargo.toml` as a dependency. It can be added via `cargo add`.
|
||||
//!
|
||||
//! ```bash
|
||||
//! $ cargo add winit
|
||||
//! ```
|
||||
//!
|
||||
//! To only enable the X11 backend on Free Unix[^unix] systems, disable default features
|
||||
//! and enable the `x11` feature.
|
||||
//!
|
||||
//! ```bash
|
||||
//! $ cargo add winit --no-default-features --features x11
|
||||
//! ```
|
||||
//!
|
||||
//! To only enable the Wayland backend on Free Unix systems, disable default features
|
||||
//! and enable the `wayland` feature.
|
||||
//!
|
||||
//! ```bash
|
||||
//! $ cargo add winit --no-default-features --features wayland
|
||||
//! ```
|
||||
//!
|
||||
//! These features have no effect on systems that are not Free Unix.
|
||||
//!
|
||||
//! ## Dependencies
|
||||
//!
|
||||
//! Dependencies on non-system libraries is managed through Cargo. For the X11
|
||||
//! backend, the following Ubuntu packages or their equivalents must[^must] be installed.
|
||||
//!
|
||||
//! - `libx11-dev`
|
||||
//! - `libxcb1-dev`
|
||||
//! - `libxi-dev`
|
||||
//! - `libxcbcommon-dev`
|
||||
//! - `libxcbcommon-x11-dev`
|
||||
//!
|
||||
//! For the Wayland backend, the following Ubuntu packages or their equivalents
|
||||
//! must be installed.
|
||||
//!
|
||||
//! - `libwayland-dev`
|
||||
//! - `libxcbcommon-dev`
|
||||
//! - `libfontconfig` (only with `sctk-adwaita` feature)
|
||||
//! - `freetype` (only with `sctk-adwaita` feature)
|
||||
//!
|
||||
//! The "dev" packages are only needed for building binaries that use `winit`. On
|
||||
//! deployed system the non-`dev` equivalents need to be installed.
|
||||
//!
|
||||
//! The other backends (Windows, macOS, etc) do not have any dependencies on system libraries
|
||||
//! that don't already come with the operating system. However, note that the Windows backend
|
||||
//! only supports Windows 10 and above, and the macOS backend only supports macOS
|
||||
//! 10.14 and above.
|
||||
//!
|
||||
//! [^unix]: Unix systems outside of Android and Apple, like Linux or FreeBSD.
|
||||
//! [^must]: This is not a "must" when the "dlopen" features are enabled
|
||||
//!
|
||||
//! # Building windows
|
||||
//!
|
||||
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
|
||||
@@ -311,12 +257,10 @@
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowAttributes`]: window::WindowAttributes
|
||||
//! [window_new]: window::Window::new
|
||||
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`Event::UserEvent`]: event::Event::UserEvent
|
||||
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
|
||||
@@ -1,86 +1,55 @@
|
||||
//! Types useful for interacting with a user's monitors.
|
||||
use std::fmt;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl;
|
||||
|
||||
/// A handle to a fullscreen video mode of a specific monitor.
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// This can be acquired with [`MonitorHandle::video_modes`].
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) video_mode: platform_impl::VideoModeHandle,
|
||||
/// Can be retrieved with [`MonitorHandle::video_modes()`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) bit_depth: Option<NonZeroU16>,
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.video_mode.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VideoModeHandle {
|
||||
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VideoModeHandle {
|
||||
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
self.size()
|
||||
.cmp(&other.size())
|
||||
.then(
|
||||
self.refresh_rate_millihertz()
|
||||
.cmp(&other.refresh_rate_millihertz())
|
||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||
)
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
impl VideoMode {
|
||||
/// Returns the resolution of this video mode. This **must not** be used to create your
|
||||
/// rendering surface. Use [`Window::surface_size()`] instead.
|
||||
///
|
||||
/// [`Window::surface_size()`]: crate::window::Window::surface_size
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.video_mode.size()
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
||||
/// available per color. This is generally 24 bits or 32 bits on modern
|
||||
/// systems, depending on whether the alpha channel is counted or not.
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.video_mode.bit_depth()
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
/// Returns the refresh rate of this video mode in mHz.
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.video_mode.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||
/// a separate set of valid video modes.
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle { inner: self.video_mode.monitor() }
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} {}{}",
|
||||
self.size().width,
|
||||
self.size().height,
|
||||
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
|
||||
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
|
||||
)
|
||||
impl fmt::Display for VideoMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}x{}", self.size.width, self.size.height)?;
|
||||
|
||||
if let Some(refresh_rate) = self.refresh_rate_millihertz {
|
||||
write!(f, "@{refresh_rate}mHz")?;
|
||||
}
|
||||
|
||||
if let Some(bit_depth) = self.bit_depth {
|
||||
write!(f, " ({bit_depth} bpp)")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,13 +157,13 @@ impl MonitorHandle {
|
||||
|
||||
/// Returns the currently active video mode of this monitor.
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
self.inner.current_video_mode()
|
||||
}
|
||||
|
||||
/// Returns all fullscreen video modes supported by this monitor.
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.inner.video_modes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
|
||||
//! with `cargo apk`, then the minimal changes would be:
|
||||
//! 1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.9",
|
||||
//! features = [ "android-native-activity" ] }`
|
||||
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
|
||||
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
|
||||
|
||||
@@ -107,7 +107,7 @@ use std::os::raw::c_void;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::monitor::{MonitorHandle, VideoModeHandle};
|
||||
use crate::monitor::{MonitorHandle, VideoMode};
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to iOS.
|
||||
@@ -384,23 +384,23 @@ pub trait MonitorHandleExtIOS {
|
||||
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
||||
fn ui_screen(&self) -> *mut c_void;
|
||||
|
||||
/// Returns the preferred [`VideoModeHandle`] for this monitor.
|
||||
/// Returns the preferred [`VideoMode`] for this monitor.
|
||||
///
|
||||
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
|
||||
fn preferred_video_mode(&self) -> VideoModeHandle;
|
||||
fn preferred_video_mode(&self) -> VideoMode;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
// SAFETY: The marker is only used to get the pointer of the screen
|
||||
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
||||
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
|
||||
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn preferred_video_mode(&self) -> VideoModeHandle {
|
||||
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
|
||||
fn preferred_video_mode(&self) -> VideoMode {
|
||||
self.inner.preferred_video_mode()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,26 +21,21 @@
|
||||
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
|
||||
//! use objc2::rc::Retained;
|
||||
//! use objc2::runtime::ProtocolObject;
|
||||
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
|
||||
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
|
||||
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
|
||||
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol};
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//!
|
||||
//! declare_class!(
|
||||
//! define_class!(
|
||||
//! #[unsafe(super(NSObject))]
|
||||
//! #[thread_kind = MainThreadOnly]
|
||||
//! #[name = "AppDelegate"]
|
||||
//! struct AppDelegate;
|
||||
//!
|
||||
//! unsafe impl ClassType for AppDelegate {
|
||||
//! type Super = NSObject;
|
||||
//! type Mutability = mutability::MainThreadOnly;
|
||||
//! const NAME: &'static str = "MyAppDelegate";
|
||||
//! }
|
||||
//!
|
||||
//! impl DeclaredClass for AppDelegate {}
|
||||
//!
|
||||
//! unsafe impl NSObjectProtocol for AppDelegate {}
|
||||
//!
|
||||
//! unsafe impl NSApplicationDelegate for AppDelegate {
|
||||
//! #[method(application:openURLs:)]
|
||||
//! #[unsafe(method(application:openURLs:))]
|
||||
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
|
||||
//! // Note: To specifically get `application:openURLs:` to work, you _might_
|
||||
//! // have to bundle your application. This is not done in this example.
|
||||
@@ -51,7 +46,7 @@
|
||||
//!
|
||||
//! impl AppDelegate {
|
||||
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
|
||||
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
|
||||
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
@@ -332,6 +327,13 @@ pub trait WindowAttributesExtMacOS {
|
||||
fn with_borderless_game(self, borderless_game: bool) -> Self;
|
||||
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
|
||||
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
|
||||
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
|
||||
/// [`NSWindow`].
|
||||
///
|
||||
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
|
||||
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
|
||||
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
|
||||
fn with_panel(self, panel: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowAttributesExtMacOS for WindowAttributes {
|
||||
@@ -412,6 +414,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
|
||||
self.platform_specific.unified_titlebar = unified_titlebar;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_panel(mut self, panel: bool) -> Self {
|
||||
self.platform_specific.panel = panel;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopBuilderExtMacOS {
|
||||
@@ -505,7 +513,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
// SAFETY: We only use the marker to get a pointer
|
||||
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
||||
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
|
||||
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,15 +42,5 @@ pub mod run_on_demand;
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
orbital_platform,
|
||||
docsrs
|
||||
))]
|
||||
pub mod modifier_supplement;
|
||||
|
||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
|
||||
pub mod scancode;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
use crate::event::KeyEvent;
|
||||
use crate::keyboard::Key;
|
||||
|
||||
/// Additional methods for the `KeyEvent` which cannot be implemented on all
|
||||
/// platforms.
|
||||
pub trait KeyEventExtModifierSupplement {
|
||||
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
|
||||
///
|
||||
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
|
||||
fn text_with_all_modifiers(&self) -> Option<&str>;
|
||||
|
||||
/// This value ignores all modifiers including,
|
||||
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
|
||||
/// and <kbd>Ctrl</kbd>. In most cases this means that the
|
||||
/// unicode character in the resulting string is lowercase.
|
||||
///
|
||||
/// This is useful for key-bindings / shortcut key combinations.
|
||||
///
|
||||
/// In case `logical_key` reports `Dead`, this will still report the
|
||||
/// key as `Character` according to the current keyboard layout. This value
|
||||
/// cannot be `Dead`.
|
||||
fn key_without_modifiers(&self) -> Key;
|
||||
}
|
||||
|
||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
#[inline]
|
||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
||||
self.platform_specific.text_with_all_modifiers.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn key_without_modifiers(&self) -> Key {
|
||||
self.platform_specific.key_without_modifiers.clone()
|
||||
}
|
||||
}
|
||||
@@ -83,22 +83,18 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// - **Windows**: The implementation will use `PeekMessage` when checking for window messages
|
||||
/// to avoid blocking your external event loop.
|
||||
///
|
||||
/// - **MacOS**: The implementation works in terms of stopping the global application whenever
|
||||
/// the application `RunLoop` indicates that it is preparing to block and wait for new events.
|
||||
/// - **MacOS**: Certain actions like resizing the window will enter a "modal" state, where
|
||||
/// `pump_app_events` will process events internally, and block until the resize is over.
|
||||
///
|
||||
/// This is very different to the polling APIs that are available on other
|
||||
/// platforms (the lower level polling primitives on MacOS are private
|
||||
/// implementation details for `NSApplication` which aren't accessible to
|
||||
/// application developers)
|
||||
/// Thus, if you render or run your game code outside of `ApplicationHandler`, your
|
||||
/// application will freeze while the window resizes. The recommended approach is to render
|
||||
/// inside [`WindowEvent::RedrawRequested`] instead.
|
||||
///
|
||||
/// It's likely this will be less efficient than polling on other OSs and
|
||||
/// it also means the `NSApplication` is stopped while outside of the Winit
|
||||
/// event loop - and that's observable (for example to crates like `rfd`)
|
||||
/// because the `NSApplication` is global state.
|
||||
/// Furthermore, when pumping events the `NSApplication` is still considered stopped to
|
||||
/// crates like `rfd` that inspect [`-[NSApplication isRunning]`][isrunning].
|
||||
///
|
||||
/// If you render outside of Winit you are likely to see window resizing artifacts
|
||||
/// since MacOS expects applications to render synchronously during any `drawRect`
|
||||
/// callback.
|
||||
/// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested
|
||||
/// [isrunning]: https://developer.apple.com/documentation/appkit/nsapplication/isrunning?language=objc
|
||||
fn pump_app_events<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
timeout: Option<Duration>,
|
||||
|
||||
@@ -65,9 +65,9 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
|
||||
let _is_wayland = self.is_wayland();
|
||||
|
||||
if _is_wayland {
|
||||
env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
|
||||
env::var(WAYLAND_VAR).ok().map(ActivationToken::from_raw)
|
||||
} else {
|
||||
env::var(X11_VAR).ok().map(ActivationToken::_new)
|
||||
env::var(X11_VAR).ok().map(ActivationToken::from_raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,6 @@ pub fn reset_activation_token_env() {
|
||||
///
|
||||
/// This could be used before running daemon processes.
|
||||
pub fn set_activation_token_env(token: ActivationToken) {
|
||||
env::set_var(X11_VAR, &token._token);
|
||||
env::set_var(WAYLAND_VAR, token._token);
|
||||
env::set_var(X11_VAR, &token.token);
|
||||
env::set_var(WAYLAND_VAR, token.token);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ use crate::monitor::MonitorHandle;
|
||||
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
|
||||
|
||||
/// Window Handle type used by Win32 API
|
||||
pub type HWND = isize;
|
||||
pub type HWND = *mut c_void;
|
||||
/// Menu Handle type used by Win32 API
|
||||
pub type HMENU = isize;
|
||||
pub type HMENU = *mut c_void;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = isize;
|
||||
pub type HMONITOR = *mut c_void;
|
||||
|
||||
/// Describes a system-drawn backdrop material of a window.
|
||||
///
|
||||
@@ -660,6 +660,17 @@ impl DeviceIdExtWindows for DeviceId {
|
||||
}
|
||||
|
||||
/// Additional methods on `Icon` that are specific to Windows.
|
||||
///
|
||||
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
|
||||
///
|
||||
/// The `ICON` resource definition statement use the following syntax:
|
||||
/// ```rc
|
||||
/// nameID ICON filename
|
||||
/// ```
|
||||
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
|
||||
/// `filename` is the name of the file that contains the resource.
|
||||
///
|
||||
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
|
||||
pub trait IconExtWindows: Sized {
|
||||
/// Create an icon from a file path.
|
||||
///
|
||||
@@ -671,7 +682,12 @@ pub trait IconExtWindows: Sized {
|
||||
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
|
||||
-> Result<Self, BadIcon>;
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library.
|
||||
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
|
||||
///
|
||||
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
|
||||
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
|
||||
///
|
||||
/// [`from_resource_name`]: IconExtWindows::from_resource_name
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
@@ -679,6 +695,55 @@ pub trait IconExtWindows: Sized {
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library by its name.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Consider the following resource definition statements:
|
||||
/// ```rc
|
||||
/// app ICON "app.ico"
|
||||
/// 1 ICON "a.ico"
|
||||
/// 0027 ICON "custom.ico"
|
||||
/// 0 ICON "alt.ico"
|
||||
/// ```
|
||||
///
|
||||
/// Due to some internal implementation details of the resource embedding/loading process on
|
||||
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
|
||||
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
|
||||
/// [`from_resource`]:
|
||||
///
|
||||
/// [`from_resource`]: IconExtWindows::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use winit::platform::windows::IconExtWindows;
|
||||
/// use winit::window::Icon;
|
||||
///
|
||||
/// assert!(Icon::from_resource_name("app", None).is_ok());
|
||||
/// assert!(Icon::from_resource(1, None).is_ok());
|
||||
/// assert!(Icon::from_resource(27, None).is_ok());
|
||||
/// assert!(Icon::from_resource_name("27", None).is_err());
|
||||
/// assert!(Icon::from_resource_name("0027", None).is_err());
|
||||
/// ```
|
||||
///
|
||||
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
|
||||
/// name:
|
||||
///
|
||||
/// [`from_resource`]: IconExtWindows::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use winit::platform::windows::IconExtWindows;
|
||||
/// # use winit::window::Icon;
|
||||
/// assert!(Icon::from_resource_name("0", None).is_ok());
|
||||
/// assert!(Icon::from_resource(0, None).is_err());
|
||||
/// ```
|
||||
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
}
|
||||
|
||||
impl IconExtWindows for Icon {
|
||||
@@ -694,4 +759,9 @@ impl IconExtWindows for Icon {
|
||||
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::cell::Cell;
|
||||
use std::hash::Hash;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -21,7 +20,7 @@ use crate::event_loop::{
|
||||
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
|
||||
OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
use crate::window::{
|
||||
self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
|
||||
@@ -97,9 +96,6 @@ impl RedrawRequester {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
|
||||
pub struct EventLoop {
|
||||
pub(crate) android_app: AndroidApp,
|
||||
window_target: ActiveEventLoop,
|
||||
@@ -479,7 +475,8 @@ impl EventLoop {
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: None,
|
||||
platform_specific: KeyEventExtra {},
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: keycodes::to_logical(key_char, keycode),
|
||||
},
|
||||
is_synthetic: false,
|
||||
};
|
||||
@@ -1021,32 +1018,11 @@ impl MonitorHandle {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> std::iter::Empty<VideoModeHandle> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct VideoModeHandle;
|
||||
|
||||
impl VideoModeHandle {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
pub fn video_modes(&self) -> std::iter::Empty<VideoMode> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,101 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::runtime::{Imp, Sel};
|
||||
use objc2::sel;
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
|
||||
use super::app_state::AppState;
|
||||
use crate::event::{DeviceEvent, ElementState};
|
||||
|
||||
declare_class!(
|
||||
pub(super) struct WinitApplication;
|
||||
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);
|
||||
|
||||
unsafe impl ClassType for WinitApplication {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplication";
|
||||
}
|
||||
static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
|
||||
// SAFETY: Creating in a `const` context, where there is no concept of the main thread.
|
||||
MainThreadBound::new(Cell::new(None), unsafe { MainThreadMarker::new_unchecked() })
|
||||
};
|
||||
|
||||
impl DeclaredClass for WinitApplication {}
|
||||
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
|
||||
let mtm = MainThreadMarker::from(app);
|
||||
|
||||
unsafe impl WinitApplication {
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
#[method(sendEvent:)]
|
||||
fn send_event(&self, event: &NSEvent) {
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
} else {
|
||||
let app_state = AppState::get(MainThreadMarker::from(self));
|
||||
maybe_dispatch_device_event(&app_state, event);
|
||||
unsafe { msg_send![super(self), sendEvent: event] }
|
||||
}
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
//
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) {
|
||||
if let Some(key_window) = app.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
// Events are generally scoped to the window level, so the best way
|
||||
// to get device events is to listen for them on NSApplication.
|
||||
let app_state = AppState::get(mtm);
|
||||
maybe_dispatch_device_event(&app_state, event);
|
||||
|
||||
let original = ORIGINAL.get(mtm).get().expect("no existing sendEvent: handler set");
|
||||
original(app, sel, event)
|
||||
}
|
||||
|
||||
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
|
||||
///
|
||||
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
|
||||
/// give the user full control over their `NSApplication`, so we override the method here using
|
||||
/// method swizzling instead.
|
||||
///
|
||||
/// This _should_ also allow two versions of Winit to exist in the same application.
|
||||
///
|
||||
/// See the following links for more info on method swizzling:
|
||||
/// - <https://nshipster.com/method-swizzling/>
|
||||
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
|
||||
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
|
||||
///
|
||||
/// NOTE: This function assumes that the passed in application object is the one returned from
|
||||
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
|
||||
/// For testing though, we allow it to be a different object.
|
||||
pub(crate) fn override_send_event(global_app: &NSApplication) {
|
||||
let mtm = MainThreadMarker::from(global_app);
|
||||
let class = global_app.class();
|
||||
|
||||
let method =
|
||||
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
|
||||
|
||||
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
|
||||
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
|
||||
|
||||
// If we've already overridden the method, don't do anything.
|
||||
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
|
||||
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
|
||||
if overridden == method.implementation() {
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFETY: Our implementation has:
|
||||
// 1. The same signature as `sendEvent:`.
|
||||
// 2. Does not impose extra safety requirements on callers.
|
||||
let original = unsafe { method.set_implementation(overridden) };
|
||||
|
||||
// SAFETY: This is the actual signature of `sendEvent:`.
|
||||
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
|
||||
|
||||
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
|
||||
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
|
||||
// stored here.
|
||||
//
|
||||
// It is only usable from the main thread, however, so we're good!
|
||||
ORIGINAL.get(mtm).set(Some(original));
|
||||
}
|
||||
|
||||
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
let event_type = unsafe { event.r#type() };
|
||||
@@ -87,3 +137,52 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{define_class, msg_send, ClassType};
|
||||
use objc2_app_kit::NSResponder;
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_override() {
|
||||
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
|
||||
let Some(mtm) = MainThreadMarker::new() else { return };
|
||||
|
||||
// Create a new application, without making it the shared application.
|
||||
let app = unsafe { NSApplication::new(mtm) };
|
||||
override_send_event(&app);
|
||||
// Test calling twice works.
|
||||
override_send_event(&app);
|
||||
|
||||
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
|
||||
// unsafe {
|
||||
// let event = super::super::event::dummy_event().unwrap();
|
||||
// app.sendEvent(&event)
|
||||
// }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_class() {
|
||||
let Some(_mtm) = MainThreadMarker::new() else { return };
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSApplication, NSResponder, NSObject))]
|
||||
#[name = "TestApplication"]
|
||||
pub(super) struct TestApplication;
|
||||
|
||||
impl TestApplication {
|
||||
#[unsafe(method(sendEvent:))]
|
||||
fn send_event(&self, _event: &NSEvent) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let app: Retained<TestApplication> = unsafe { msg_send![TestApplication::class(), new] };
|
||||
override_send_event(&app);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::cell::{Cell, OnceCell, RefCell};
|
||||
use std::mem;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::atomic::Ordering as AtomicOrdering;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotification};
|
||||
use objc2_foundation::NSNotification;
|
||||
|
||||
use super::super::event_handler::EventHandler;
|
||||
use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
|
||||
use super::super::event_loop_proxy::EventLoopProxy;
|
||||
use super::event_loop::{stop_app_immediately, ActiveEventLoop};
|
||||
use super::menu;
|
||||
use super::observer::{EventLoopWaker, RunLoop};
|
||||
use crate::application::ApplicationHandler;
|
||||
@@ -26,41 +28,22 @@ pub(super) struct AppState {
|
||||
run_loop: RunLoop,
|
||||
event_loop_proxy: Arc<EventLoopProxy>,
|
||||
event_handler: EventHandler,
|
||||
stop_on_launch: Cell<bool>,
|
||||
stop_before_wait: Cell<bool>,
|
||||
stop_after_wait: Cell<bool>,
|
||||
stop_on_redraw: Cell<bool>,
|
||||
/// Whether `applicationDidFinishLaunching:` has been run or not.
|
||||
/// Whether `NSApplicationDidFinishLaunchingNotification` has been sent.
|
||||
is_launched: Cell<bool>,
|
||||
/// Whether an `EventLoop` is currently running.
|
||||
is_running: Cell<bool>,
|
||||
/// Whether the user has requested the event loop to exit.
|
||||
exit: Cell<bool>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
waker: RefCell<EventLoopWaker>,
|
||||
start_time: Cell<Option<Instant>>,
|
||||
wait_timeout: Cell<Option<Instant>>,
|
||||
pending_redraw: RefCell<Vec<WindowId>>,
|
||||
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
|
||||
// as such should be careful to not add fields that, in turn, strongly reference those.
|
||||
}
|
||||
|
||||
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
|
||||
struct StaticMainThreadBound<T>(T);
|
||||
|
||||
impl<T> StaticMainThreadBound<T> {
|
||||
const fn get(&self, _mtm: MainThreadMarker) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for StaticMainThreadBound<T> {}
|
||||
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
|
||||
|
||||
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
|
||||
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
|
||||
// main thread.
|
||||
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
|
||||
StaticMainThreadBound(OnceCell::new());
|
||||
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
|
||||
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
|
||||
|
||||
impl AppState {
|
||||
pub(super) fn setup_global(
|
||||
@@ -69,25 +52,23 @@ impl AppState {
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Rc<Self> {
|
||||
let this = Rc::new(AppState {
|
||||
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
|
||||
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
||||
}));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
mtm,
|
||||
activation_policy,
|
||||
event_loop_proxy: Arc::new(EventLoopProxy::new()),
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
run_loop: RunLoop::main(mtm),
|
||||
event_loop_proxy,
|
||||
event_handler: EventHandler::new(),
|
||||
stop_on_launch: Cell::new(false),
|
||||
stop_before_wait: Cell::new(false),
|
||||
stop_after_wait: Cell::new(false),
|
||||
stop_on_redraw: Cell::new(false),
|
||||
is_launched: Cell::new(false),
|
||||
is_running: Cell::new(false),
|
||||
exit: Cell::new(false),
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
waker: RefCell::new(EventLoopWaker::new()),
|
||||
start_time: Cell::new(None),
|
||||
wait_timeout: Cell::new(None),
|
||||
pending_redraw: RefCell::new(vec![]),
|
||||
});
|
||||
|
||||
@@ -103,15 +84,17 @@ impl AppState {
|
||||
.clone()
|
||||
}
|
||||
|
||||
// NOTE: This notification will, globally, only be emitted once,
|
||||
// no matter how many `EventLoop`s the user creates.
|
||||
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
|
||||
trace_scope!("NSApplicationDidFinishLaunchingNotification");
|
||||
// NOTE: This notification will, globally, only be emitted once,
|
||||
// no matter how many `EventLoop`s the user creates. There is no other
|
||||
// way to know this information, other than to keep track of it
|
||||
// ourselves.
|
||||
self.is_launched.set(true);
|
||||
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// We need to delay setting the activation policy and activating the app until
|
||||
// `NSApplicationDidFinishLaunchingNotification` has been sent. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
if let Some(activation_policy) = self.activation_policy {
|
||||
app.setActivationPolicy(activation_policy);
|
||||
@@ -141,23 +124,7 @@ impl AppState {
|
||||
|
||||
self.waker.borrow_mut().start();
|
||||
|
||||
self.set_is_running(true);
|
||||
self.dispatch_init_events();
|
||||
|
||||
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
|
||||
// want to stop the app once it is launched (and return to the external loop)
|
||||
//
|
||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||
// so we call `start_running()` above.
|
||||
if self.stop_on_launch.get() {
|
||||
// NOTE: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`-[NSApplication run]`
|
||||
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
||||
//
|
||||
// So we return from `pump_events` by stopping the application.
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
|
||||
@@ -180,57 +147,16 @@ impl AppState {
|
||||
&self.event_loop_proxy
|
||||
}
|
||||
|
||||
/// If `pump_events` is called to progress the event loop then we
|
||||
/// bootstrap the event loop via `-[NSApplication run]` but will use
|
||||
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
|
||||
pub fn set_stop_on_launch(&self) {
|
||||
self.stop_on_launch.set(true);
|
||||
}
|
||||
|
||||
pub fn set_stop_before_wait(&self, value: bool) {
|
||||
self.stop_before_wait.set(value)
|
||||
}
|
||||
|
||||
pub fn set_stop_after_wait(&self, value: bool) {
|
||||
self.stop_after_wait.set(value)
|
||||
}
|
||||
|
||||
pub fn set_stop_on_redraw(&self, value: bool) {
|
||||
self.stop_on_redraw.set(value)
|
||||
}
|
||||
|
||||
pub fn set_wait_timeout(&self, value: Option<Instant>) {
|
||||
self.wait_timeout.set(value)
|
||||
}
|
||||
|
||||
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
|
||||
///
|
||||
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
|
||||
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
||||
pub fn internal_exit(self: &Rc<Self>) {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.exiting(event_loop);
|
||||
});
|
||||
|
||||
self.set_is_running(false);
|
||||
self.set_stop_on_redraw(false);
|
||||
self.set_stop_before_wait(false);
|
||||
self.set_stop_after_wait(false);
|
||||
self.set_wait_timeout(None);
|
||||
}
|
||||
|
||||
pub fn is_launched(&self) -> bool {
|
||||
self.is_launched.get()
|
||||
}
|
||||
|
||||
pub fn set_is_running(&self, value: bool) {
|
||||
self.is_running.set(value)
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.is_running.get()
|
||||
}
|
||||
|
||||
pub fn exit(&self) {
|
||||
self.exit.set(true)
|
||||
}
|
||||
@@ -258,14 +184,6 @@ impl AppState {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||
});
|
||||
|
||||
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
|
||||
// events as a way to ensure that `pump_events` can't block an external loop
|
||||
// indefinitely
|
||||
if self.stop_on_redraw.get() {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,21 +235,12 @@ impl AppState {
|
||||
}
|
||||
|
||||
// Called by RunLoopObserver after finishing waiting for new events
|
||||
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
|
||||
pub fn wakeup(self: &Rc<Self>) {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
||||
if !self.event_handler.ready() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.stop_after_wait.get() {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
|
||||
let start = self.start_time.get().unwrap();
|
||||
let cause = match self.control_flow() {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
@@ -349,22 +258,14 @@ impl AppState {
|
||||
}
|
||||
|
||||
// Called by RunLoopObserver before waiting for new events
|
||||
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
|
||||
pub fn cleared(self: &Rc<Self>) {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
||||
// we're about to return to the `CFRunLoop` to poll for new events?
|
||||
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
||||
if !self.event_handler.ready() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.event_loop_proxy.wake_up.swap(false, AtomicOrdering::Relaxed) {
|
||||
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
||||
}
|
||||
|
||||
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
|
||||
for window_id in redraw {
|
||||
self.with_handler(|app, event_loop| {
|
||||
@@ -380,24 +281,12 @@ impl AppState {
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
|
||||
if self.stop_before_wait.get() {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
self.start_time.set(Some(Instant::now()));
|
||||
let wait_timeout = self.wait_timeout.get(); // configured by pump_events
|
||||
let app_timeout = match self.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Instant::now()),
|
||||
ControlFlow::WaitUntil(instant) => Some(instant),
|
||||
};
|
||||
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
|
||||
self.waker.borrow_mut().start_at(app_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum `Option<Instant>`, taking into account that `None`
|
||||
/// equates to an infinite timeout, not a zero timeout (so can't just use
|
||||
/// `Option::min`)
|
||||
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
|
||||
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ use std::sync::OnceLock;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType};
|
||||
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
|
||||
use objc2_foundation::{
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
|
||||
NSString,
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
|
||||
};
|
||||
|
||||
use crate::cursor::{CursorImage, OnlyCursorImageSource};
|
||||
@@ -67,8 +66,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
|
||||
|
||||
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
|
||||
let cls = NSCursor::class();
|
||||
if cls.responds_to(sel) {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
if unsafe { msg_send![cls, respondsToSelector: sel] } {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
|
||||
Some(cursor)
|
||||
} else {
|
||||
tracing::warn!("cursor `{sel}` appears to be invalid");
|
||||
@@ -130,25 +129,21 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
msg_send![
|
||||
<NSDictionary<NSObject, NSObject>>::class(),
|
||||
dictionaryWithContentsOfFile: &*info_path,
|
||||
]
|
||||
};
|
||||
let mut x = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
x = unsafe { &*ptr }.as_cgfloat()
|
||||
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
|
||||
if let Ok(n) = n.downcast::<NSNumber>() {
|
||||
x = n.as_cgfloat();
|
||||
}
|
||||
}
|
||||
let mut y = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
y = unsafe { &*ptr }.as_cgfloat()
|
||||
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
|
||||
if let Ok(n) = n.downcast::<NSNumber>() {
|
||||
y = n.as_cgfloat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +183,7 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
|
||||
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
match icon {
|
||||
CursorIcon::Default => default_cursor(),
|
||||
@@ -208,7 +204,9 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
|
||||
CursorIcon::Help => _helpCursor(),
|
||||
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
|
||||
CursorIcon::ZoomIn => _zoomInCursor(),
|
||||
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
|
||||
CursorIcon::ZoomOut => _zoomOutCursor(),
|
||||
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use core_foundation::base::CFRelease;
|
||||
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
|
||||
use dispatch2::run_on_main;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
|
||||
use objc2_foundation::{run_on_main, NSPoint};
|
||||
use objc2_core_foundation::{CFData, CFDataGetBytePtr, CFRetained};
|
||||
use objc2_foundation::NSPoint;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use super::ffi;
|
||||
@@ -14,37 +14,29 @@ use crate::keyboard::{
|
||||
PhysicalKey,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
pub key_without_modifiers: Key,
|
||||
}
|
||||
|
||||
/// Ignores ALL modifiers.
|
||||
pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
let mut string = [0; 16];
|
||||
let input_source;
|
||||
let layout;
|
||||
unsafe {
|
||||
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if input_source.is_null() {
|
||||
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
let layout_data =
|
||||
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
|
||||
if layout_data.is_null() {
|
||||
CFRelease(input_source as *mut c_void);
|
||||
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
|
||||
}
|
||||
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
|
||||
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
};
|
||||
let input_source = unsafe { CFRetained::from_raw(ptr) };
|
||||
|
||||
let layout_data = unsafe {
|
||||
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData)
|
||||
};
|
||||
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
|
||||
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
};
|
||||
|
||||
let layout = unsafe { CFDataGetBytePtr(layout_data).cast() };
|
||||
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
|
||||
|
||||
let mut result_len = 0;
|
||||
let mut dead_keys = 0;
|
||||
let modifiers = 0;
|
||||
let mut string = [0; 16];
|
||||
let translate_result = unsafe {
|
||||
ffi::UCKeyTranslate(
|
||||
layout,
|
||||
@@ -59,9 +51,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
string.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
CFRelease(input_source as *mut c_void);
|
||||
}
|
||||
if translate_result != 0 {
|
||||
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
@@ -123,8 +112,8 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
|
||||
let key_without_modifiers = get_modifierless_char(scancode);
|
||||
|
||||
let modifiers = unsafe { ns_event.modifierFlags() };
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
|
||||
@@ -162,7 +151,8 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
|
||||
repeat: is_repeat,
|
||||
state,
|
||||
text,
|
||||
platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +162,9 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
|
||||
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
|
||||
};
|
||||
|
||||
// Roughly same handling as Firefox and Chromium:
|
||||
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMKeyName.h
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/010a75a426c4a2292955a52f480e9251cacf750e/ui/events/keycodes/keyboard_code_conversion_mac.mm#100
|
||||
Key::Named(match code {
|
||||
KeyCode::Enter => NamedKey::Enter,
|
||||
KeyCode::Tab => NamedKey::Tab,
|
||||
@@ -186,14 +179,17 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
|
||||
KeyCode::ShiftRight => NamedKey::Shift,
|
||||
KeyCode::AltRight => NamedKey::Alt,
|
||||
KeyCode::ControlRight => NamedKey::Control,
|
||||
KeyCode::CapsLock => NamedKey::CapsLock,
|
||||
|
||||
KeyCode::NumLock => NamedKey::NumLock,
|
||||
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
|
||||
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
|
||||
KeyCode::AudioVolumeMute => NamedKey::AudioVolumeMute,
|
||||
|
||||
// Other numpad keys all generate text on macOS (if I understand correctly)
|
||||
KeyCode::NumpadEnter => NamedKey::Enter,
|
||||
|
||||
KeyCode::Fn => NamedKey::Fn,
|
||||
KeyCode::F1 => NamedKey::F1,
|
||||
KeyCode::F2 => NamedKey::F2,
|
||||
KeyCode::F3 => NamedKey::F3,
|
||||
@@ -214,17 +210,27 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
|
||||
KeyCode::F18 => NamedKey::F18,
|
||||
KeyCode::F19 => NamedKey::F19,
|
||||
KeyCode::F20 => NamedKey::F20,
|
||||
KeyCode::F21 => NamedKey::F21,
|
||||
KeyCode::F22 => NamedKey::F22,
|
||||
KeyCode::F23 => NamedKey::F23,
|
||||
KeyCode::F24 => NamedKey::F24,
|
||||
|
||||
KeyCode::Insert => NamedKey::Insert,
|
||||
KeyCode::Home => NamedKey::Home,
|
||||
KeyCode::PageUp => NamedKey::PageUp,
|
||||
KeyCode::Delete => NamedKey::Delete,
|
||||
KeyCode::End => NamedKey::End,
|
||||
KeyCode::Help => NamedKey::Help,
|
||||
KeyCode::PageDown => NamedKey::PageDown,
|
||||
KeyCode::ArrowLeft => NamedKey::ArrowLeft,
|
||||
KeyCode::ArrowRight => NamedKey::ArrowRight,
|
||||
KeyCode::ArrowDown => NamedKey::ArrowDown,
|
||||
KeyCode::ArrowUp => NamedKey::ArrowUp,
|
||||
KeyCode::ContextMenu => NamedKey::ContextMenu,
|
||||
|
||||
KeyCode::Lang2 => NamedKey::Eisu,
|
||||
KeyCode::Lang1 => NamedKey::KanjiMode,
|
||||
|
||||
_ => return Key::Unidentified(NativeKey::MacOS(scancode)),
|
||||
})
|
||||
}
|
||||
@@ -308,26 +314,19 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
let mut state = ModifiersState::empty();
|
||||
let mut pressed_mods = ModifiersKeys::empty();
|
||||
|
||||
state
|
||||
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
|
||||
state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift));
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::CONTROL,
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
|
||||
);
|
||||
state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control));
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
|
||||
|
||||
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
|
||||
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option));
|
||||
pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::SUPER,
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
|
||||
);
|
||||
state.set(ModifiersState::SUPER, flags.contains(NSEventModifierFlags::Command));
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
|
||||
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
use std::any::Any;
|
||||
use std::cell::Cell;
|
||||
use std::os::raw::c_void;
|
||||
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
|
||||
use std::ptr;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
use core_foundation::base::{CFIndex, CFRelease};
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{available, MainThreadMarker};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
|
||||
NSApplicationWillTerminateNotification, NSWindow,
|
||||
NSApplicationWillTerminateNotification, NSEventMask, NSWindow,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
NSDate, NSDefaultRunLoopMode, NSNotificationCenter, NSObjectProtocol, NSTimeInterval,
|
||||
};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
|
||||
use super::super::notification_center::create_observer;
|
||||
use super::app::WinitApplication;
|
||||
use super::app::override_send_event;
|
||||
use super::app_state::AppState;
|
||||
use super::cursor::CustomCursor;
|
||||
use super::event::dummy_event;
|
||||
@@ -33,8 +25,7 @@ use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, RequestError};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
|
||||
OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::platform::macos::ActivationPolicy;
|
||||
@@ -42,36 +33,6 @@ use crate::platform::pump_events::PumpStatus;
|
||||
use crate::platform_impl::Window;
|
||||
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
|
||||
|
||||
#[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 current 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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveEventLoop {
|
||||
pub(super) app_state: Rc<AppState>,
|
||||
@@ -129,7 +90,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn system_theme(&self) -> Option<Theme> {
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
|
||||
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
// Dark appearance was introduced in macOS 10.14
|
||||
if available!(macos = 10.14) {
|
||||
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
|
||||
} else {
|
||||
Some(Theme::Light)
|
||||
@@ -176,15 +138,17 @@ pub struct EventLoop {
|
||||
app: Retained<NSApplication>,
|
||||
app_state: Rc<AppState>,
|
||||
|
||||
/// Whether an outer event loop is running.
|
||||
pump_has_sent_init: bool,
|
||||
|
||||
window_target: ActiveEventLoop,
|
||||
panic_info: Rc<PanicInfo>,
|
||||
|
||||
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
|
||||
// the system instead cleans it up next time it would have posted a notification to it.
|
||||
//
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<NSObject>,
|
||||
_will_terminate_observer: Retained<NSObject>,
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -207,16 +171,6 @@ impl EventLoop {
|
||||
let mtm = MainThreadMarker::new()
|
||||
.expect("on macOS, `EventLoop` must be created on the main thread!");
|
||||
|
||||
let app: Retained<NSApplication> =
|
||||
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
|
||||
|
||||
if !app.is_kind_of::<WinitApplication>() {
|
||||
panic!(
|
||||
"`winit` requires control over the principal class. You must create the event \
|
||||
loop before other parts of your application initialize NSApplication"
|
||||
);
|
||||
}
|
||||
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
None => None,
|
||||
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
|
||||
@@ -231,6 +185,21 @@ impl EventLoop {
|
||||
attributes.activate_ignoring_other_apps,
|
||||
);
|
||||
|
||||
// Initialize the application (if it has not already been).
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
// Override `sendEvent:` on the application to forward to our application state.
|
||||
override_send_event(&app);
|
||||
|
||||
// Queue `NSApplicationDidFinishLaunchingNotification` and generally
|
||||
// make sure the application is fully initialized (once the run loop
|
||||
// starts).
|
||||
//
|
||||
// This is technically only necessary when using `pump_app_events`
|
||||
// (`app.run()` will do it for us in `run_app_on_demand`), but we
|
||||
// might as well do it everywhere.
|
||||
unsafe { app.finishLaunching() };
|
||||
|
||||
let center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||
|
||||
let weak_app_state = Rc::downgrade(&app_state);
|
||||
@@ -257,14 +226,13 @@ impl EventLoop {
|
||||
},
|
||||
);
|
||||
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
||||
setup_control_flow_observers(mtm);
|
||||
|
||||
Ok(EventLoop {
|
||||
app,
|
||||
app_state: app_state.clone(),
|
||||
pump_has_sent_init: false,
|
||||
window_target: ActiveEventLoop { app_state, mtm },
|
||||
panic_info,
|
||||
_did_finish_launching_observer,
|
||||
_will_terminate_observer,
|
||||
})
|
||||
@@ -278,10 +246,6 @@ impl EventLoop {
|
||||
self.run_app_on_demand(app)
|
||||
}
|
||||
|
||||
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
|
||||
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
|
||||
// time and so a layered implementation would end up using a lot of CPU due to
|
||||
// redundant wake ups.
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
@@ -289,29 +253,21 @@ impl EventLoop {
|
||||
self.app_state.clear_exit();
|
||||
self.app_state.set_event_handler(&mut app, || {
|
||||
autoreleasepool(|_| {
|
||||
// clear / normalize pump_events state
|
||||
self.app_state.set_wait_timeout(None);
|
||||
self.app_state.set_stop_before_wait(false);
|
||||
self.app_state.set_stop_after_wait(false);
|
||||
self.app_state.set_stop_on_redraw(false);
|
||||
|
||||
if self.app_state.is_launched() {
|
||||
debug_assert!(!self.app_state.is_running());
|
||||
self.app_state.set_is_running(true);
|
||||
// The `NSApplicationDidFinishLaunchingNotification` notification is globally
|
||||
// only delivered once, but for the purpose of our events, we want to act
|
||||
// as-if an entirely new event loop has been started on each invocation of
|
||||
// `run_app_on_demand`.
|
||||
self.app_state.dispatch_init_events();
|
||||
}
|
||||
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
// to avoid unwinding across an objective-c ffi boundary, which
|
||||
// will lead to us stopping the `NSApplication` and saving the
|
||||
// `PanicInfo` so that we can resume the unwind at a controlled,
|
||||
// safe point in time.
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
resume_unwind(panic);
|
||||
}
|
||||
// NOTE: We don't base this on `pump_events` because
|
||||
// `nextEventMatchingMask:untilDate:inMode:dequeue:` is worse supported,
|
||||
// especially as the top-level handler. In part because this sets the `isRunning`
|
||||
// flag (which is used by crates like `rfd`), while `nextEventMatchingMask` won't.
|
||||
//
|
||||
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
|
||||
self.app.run();
|
||||
|
||||
self.app_state.internal_exit()
|
||||
})
|
||||
@@ -327,60 +283,43 @@ impl EventLoop {
|
||||
) -> PumpStatus {
|
||||
self.app_state.set_event_handler(&mut app, || {
|
||||
autoreleasepool(|_| {
|
||||
// As a special case, if the application hasn't been launched yet then we at least
|
||||
// run the loop until it has fully launched.
|
||||
if !self.app_state.is_launched() {
|
||||
debug_assert!(!self.app_state.is_running());
|
||||
|
||||
self.app_state.set_stop_on_launch();
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
|
||||
// has launched
|
||||
} else if !self.app_state.is_running() {
|
||||
// Even though the application may have been launched, it's possible we aren't
|
||||
// running if the `EventLoop` was run before and has since
|
||||
// exited. This indicates that we just starting to re-run
|
||||
// the same `EventLoop` again.
|
||||
self.app_state.set_is_running(true);
|
||||
if self.app_state.is_launched() && !self.pump_has_sent_init {
|
||||
// If the application is already launched, we won't get the re-initialization
|
||||
// events. Dispatch them here instead.
|
||||
self.app_state.dispatch_init_events();
|
||||
} else {
|
||||
// Only run for as long as the given `Duration` allows so we don't block the
|
||||
// external loop.
|
||||
match timeout {
|
||||
Some(Duration::ZERO) => {
|
||||
self.app_state.set_wait_timeout(None);
|
||||
self.app_state.set_stop_before_wait(true);
|
||||
},
|
||||
Some(duration) => {
|
||||
self.app_state.set_stop_before_wait(false);
|
||||
let timeout = Instant::now() + duration;
|
||||
self.app_state.set_wait_timeout(Some(timeout));
|
||||
self.app_state.set_stop_after_wait(true);
|
||||
},
|
||||
None => {
|
||||
self.app_state.set_wait_timeout(None);
|
||||
self.app_state.set_stop_before_wait(false);
|
||||
self.app_state.set_stop_after_wait(true);
|
||||
},
|
||||
}
|
||||
self.app_state.set_stop_on_redraw(true);
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
}
|
||||
self.pump_has_sent_init = true;
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
// to avoid unwinding across an objective-c ffi boundary, which
|
||||
// will lead to us stopping the application and saving the
|
||||
// `PanicInfo` so that we can resume the unwind at a controlled,
|
||||
// safe point in time.
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
resume_unwind(panic);
|
||||
// Only run for as long as the given `Duration` allows so we don't block the
|
||||
// external loop.
|
||||
let expiration_date = match timeout {
|
||||
Some(Duration::ZERO) => unsafe { NSDate::distantPast() },
|
||||
Some(duration) => unsafe {
|
||||
NSDate::dateWithTimeIntervalSinceNow(
|
||||
duration.as_secs_f64() as NSTimeInterval
|
||||
)
|
||||
},
|
||||
None => unsafe { NSDate::distantFuture() },
|
||||
};
|
||||
|
||||
// Wait for an event to arrive within the specified duration,
|
||||
// and let the application handle it if one did.
|
||||
let event = unsafe {
|
||||
self.app.nextEventMatchingMask_untilDate_inMode_dequeue(
|
||||
NSEventMask::Any,
|
||||
Some(&expiration_date),
|
||||
NSDefaultRunLoopMode,
|
||||
true,
|
||||
)
|
||||
};
|
||||
if let Some(event) = event {
|
||||
unsafe { self.app.sendEvent(&event) };
|
||||
}
|
||||
|
||||
if self.app_state.exiting() {
|
||||
self.app_state.internal_exit();
|
||||
// If we start again, we'll emit a new set of initialization events.
|
||||
self.pump_has_sent_init = false;
|
||||
PumpStatus::Exit(0)
|
||||
} else {
|
||||
PumpStatus::Continue
|
||||
@@ -407,88 +346,3 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
|
||||
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
||||
});
|
||||
}
|
||||
|
||||
/// 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>(
|
||||
mtm: MainThreadMarker,
|
||||
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);
|
||||
}
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
stop_app_immediately(&app);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopProxy {
|
||||
pub(crate) wake_up: AtomicBool,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl Drop for EventLoopProxy {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub(crate) fn new() -> Self {
|
||||
unsafe {
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
// process user events through the normal OS EventLoop mechanisms.
|
||||
let rl = CFRunLoopGetMain();
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
info: ptr::null_mut(),
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
equal: None,
|
||||
hash: None,
|
||||
schedule: None,
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
EventLoopProxy { wake_up: AtomicBool::new(false), source }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxyProvider for EventLoopProxy {
|
||||
fn wake_up(&self) {
|
||||
self.wake_up.store(true, AtomicOrdering::Relaxed);
|
||||
unsafe {
|
||||
// Let the main thread know there's a new event.
|
||||
CFRunLoopSourceSignal(self.source);
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,10 @@
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use core_foundation::array::CFArrayRef;
|
||||
use core_foundation::dictionary::CFDictionaryRef;
|
||||
use core_foundation::string::CFStringRef;
|
||||
use core_foundation::uuid::CFUUIDRef;
|
||||
use core_graphics::base::CGError;
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
|
||||
use objc2::ffi::NSInteger;
|
||||
use objc2::runtime::AnyObject;
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
pub type CGDisplayBlendFraction = f32;
|
||||
use objc2_core_foundation::{cf_type, CFString, CFUUID};
|
||||
use objc2_core_graphics::CGDirectDisplayID;
|
||||
|
||||
pub const kCGDisplayBlendNormal: f32 = 0.0;
|
||||
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
||||
@@ -23,22 +15,6 @@ pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
||||
pub type CGDisplayFadeReservationToken = u32;
|
||||
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
|
||||
|
||||
pub type Boolean = u8;
|
||||
pub const FALSE: Boolean = 0;
|
||||
pub const TRUE: Boolean = 1;
|
||||
|
||||
pub const kCGErrorSuccess: i32 = 0;
|
||||
pub const kCGErrorFailure: i32 = 1000;
|
||||
pub const kCGErrorIllegalArgument: i32 = 1001;
|
||||
pub const kCGErrorInvalidConnection: i32 = 1002;
|
||||
pub const kCGErrorInvalidContext: i32 = 1003;
|
||||
pub const kCGErrorCannotComplete: i32 = 1004;
|
||||
pub const kCGErrorNotImplemented: i32 = 1006;
|
||||
pub const kCGErrorRangeCheck: i32 = 1007;
|
||||
pub const kCGErrorTypeCheck: i32 = 1008;
|
||||
pub const kCGErrorInvalidOperation: i32 = 1010;
|
||||
pub const kCGErrorNoneAvailable: i32 = 1011;
|
||||
|
||||
pub const IO1BitIndexedPixels: &str = "P";
|
||||
pub const IO2BitIndexedPixels: &str = "PP";
|
||||
pub const IO4BitIndexedPixels: &str = "PPPP";
|
||||
@@ -55,9 +31,6 @@ pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
|
||||
pub const IOYUV422Pixels: &str = "Y4U2V2";
|
||||
pub const IO8BitOverlayPixels: &str = "O8";
|
||||
|
||||
pub type CGWindowLevel = i32;
|
||||
pub type CGDisplayModeRef = *mut c_void;
|
||||
|
||||
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
|
||||
// However, that framework was only introduced "publicly" in macOS 10.13.
|
||||
//
|
||||
@@ -67,54 +40,11 @@ pub type CGDisplayModeRef = *mut c_void;
|
||||
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
|
||||
}
|
||||
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGRestorePermanentDisplayConfiguration();
|
||||
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
|
||||
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
|
||||
pub fn CGConfigureDisplayFadeEffect(
|
||||
config: CGDisplayConfigRef,
|
||||
fadeOutSeconds: CGDisplayFadeInterval,
|
||||
fadeInSeconds: CGDisplayFadeInterval,
|
||||
fadeRed: f32,
|
||||
fadeGreen: f32,
|
||||
fadeBlue: f32,
|
||||
) -> CGError;
|
||||
pub fn CGAcquireDisplayFadeReservation(
|
||||
seconds: CGDisplayReservationInterval,
|
||||
token: *mut CGDisplayFadeReservationToken,
|
||||
) -> CGError;
|
||||
pub fn CGDisplayFade(
|
||||
token: CGDisplayFadeReservationToken,
|
||||
duration: CGDisplayFadeInterval,
|
||||
startBlend: CGDisplayBlendFraction,
|
||||
endBlend: CGDisplayBlendFraction,
|
||||
redBlend: f32,
|
||||
greenBlend: f32,
|
||||
blueBlend: f32,
|
||||
synchronous: Boolean,
|
||||
) -> CGError;
|
||||
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
|
||||
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
|
||||
pub fn CGDisplaySetDisplayMode(
|
||||
display: CGDirectDisplayID,
|
||||
mode: CGDisplayModeRef,
|
||||
options: CFDictionaryRef,
|
||||
) -> CGError;
|
||||
pub fn CGDisplayCopyAllDisplayModes(
|
||||
display: CGDirectDisplayID,
|
||||
options: CFDictionaryRef,
|
||||
) -> CFArrayRef;
|
||||
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
|
||||
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
|
||||
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
|
||||
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
|
||||
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
||||
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
||||
|
||||
// Wildly used private APIs; Apple uses them for their Terminal.app.
|
||||
pub fn CGSMainConnectionID() -> *mut AnyObject;
|
||||
pub fn CGSSetWindowBackgroundBlurRadius(
|
||||
@@ -124,50 +54,13 @@ extern "C" {
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
mod core_video {
|
||||
use super::*;
|
||||
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
extern "C" {}
|
||||
|
||||
// CVBase.h
|
||||
|
||||
pub type CVTimeFlags = i32; // int32_t
|
||||
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CVTime {
|
||||
pub time_value: i64, // int64_t
|
||||
pub time_scale: i32, // int32_t
|
||||
pub flags: i32, // int32_t
|
||||
}
|
||||
|
||||
// CVReturn.h
|
||||
|
||||
pub type CVReturn = i32; // int32_t
|
||||
pub const kCVReturnSuccess: CVReturn = 0;
|
||||
|
||||
// CVDisplayLink.h
|
||||
|
||||
pub type CVDisplayLinkRef = *mut c_void;
|
||||
|
||||
extern "C" {
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
displayID: CGDirectDisplayID,
|
||||
displayLinkOut: *mut CVDisplayLinkRef,
|
||||
) -> CVReturn;
|
||||
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
|
||||
displayLink: CVDisplayLinkRef,
|
||||
) -> CVTime;
|
||||
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
|
||||
}
|
||||
}
|
||||
|
||||
pub use core_video::*;
|
||||
#[repr(transparent)]
|
||||
pub struct TISInputSource(std::ffi::c_void);
|
||||
pub type TISInputSourceRef = *mut TISInputSource;
|
||||
|
||||
cf_type!(
|
||||
#[encoding_name = "__TISInputSource"]
|
||||
unsafe impl TISInputSource {}
|
||||
);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct UCKeyboardLayout(std::ffi::c_void);
|
||||
@@ -184,15 +77,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
|
||||
|
||||
#[link(name = "Carbon", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
|
||||
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TISGetInputSourceProperty(
|
||||
inputSource: TISInputSourceRef,
|
||||
propertyKey: CFStringRef,
|
||||
inputSource: &TISInputSource,
|
||||
propertyKey: &CFString,
|
||||
) -> *mut c_void;
|
||||
|
||||
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
|
||||
|
||||
pub fn LMGetKbdType() -> u8;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::sel;
|
||||
use objc2::{sel, MainThreadMarker};
|
||||
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
|
||||
use objc2_foundation::{ns_string, NSProcessInfo, NSString};
|
||||
|
||||
struct KeyEquivalent<'a> {
|
||||
key: &'a NSString,
|
||||
@@ -48,10 +48,7 @@ pub fn initialize(app: &NSApplication) {
|
||||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSEventModifierFlagOption
|
||||
| NSEventModifierFlags::NSEventModifierFlagCommand,
|
||||
),
|
||||
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ mod window;
|
||||
mod window_delegate;
|
||||
|
||||
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
|
||||
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub(crate) use self::event_loop::{
|
||||
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
|
||||
pub(crate) use self::monitor::MonitorHandle;
|
||||
pub(crate) use self::window::Window;
|
||||
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::ptr::NonNull;
|
||||
use std::{fmt, ptr};
|
||||
|
||||
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
|
||||
use core_foundation::base::{CFRelease, TCFType};
|
||||
use core_foundation::string::CFString;
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use dispatch2::run_on_main;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::NSScreen;
|
||||
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
|
||||
use objc2_core_foundation::{
|
||||
CFArrayGetCount, CFArrayGetValueAtIndex, CFRetained, CFUUIDGetUUIDBytes,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
use objc2_core_graphics::{
|
||||
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
|
||||
CGDisplayMode, CGDisplayModeCopyPixelEncoding, CGDisplayModeGetPixelHeight,
|
||||
CGDisplayModeGetPixelWidth, CGDisplayModeGetRefreshRate, CGDisplayModelNumber,
|
||||
CGGetActiveDisplayList, CGMainDisplayID,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
use objc2_core_video::{
|
||||
kCVReturnSuccess, CVDisplayLinkCreateWithCGDisplay,
|
||||
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVTimeFlags,
|
||||
};
|
||||
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
|
||||
|
||||
use super::ffi;
|
||||
use super::util::cgerr;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::VideoMode;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VideoModeHandle {
|
||||
size: PhysicalSize<u32>,
|
||||
bit_depth: Option<NonZeroU16>,
|
||||
refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) mode: VideoMode,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
pub(crate) native_mode: NativeDisplayMode,
|
||||
}
|
||||
|
||||
impl PartialEq for VideoModeHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.size == other.size
|
||||
&& self.bit_depth == other.bit_depth
|
||||
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
|
||||
&& self.monitor == other.monitor
|
||||
self.monitor == other.monitor && self.mode == other.mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,56 +48,35 @@ impl Eq for VideoModeHandle {}
|
||||
|
||||
impl std::hash::Hash for VideoModeHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.size.hash(state);
|
||||
self.bit_depth.hash(state);
|
||||
self.refresh_rate_millihertz.hash(state);
|
||||
self.monitor.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("VideoModeHandle")
|
||||
.field("size", &self.size)
|
||||
.field("bit_depth", &self.bit_depth)
|
||||
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
|
||||
f.debug_struct("VideoMode")
|
||||
.field("mode", &self.mode)
|
||||
.field("monitor", &self.monitor)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
|
||||
|
||||
unsafe impl Send for NativeDisplayMode {}
|
||||
unsafe impl Sync for NativeDisplayMode {}
|
||||
|
||||
impl Drop for NativeDisplayMode {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffi::CGDisplayModeRelease(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NativeDisplayMode {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
ffi::CGDisplayModeRetain(self.0);
|
||||
}
|
||||
NativeDisplayMode(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(
|
||||
monitor: MonitorHandle,
|
||||
mode: NativeDisplayMode,
|
||||
native_mode: NativeDisplayMode,
|
||||
refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
#[allow(deprecated)]
|
||||
let pixel_encoding =
|
||||
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0))
|
||||
.to_string();
|
||||
CGDisplayModeCopyPixelEncoding(Some(&native_mode.0)).unwrap().to_string();
|
||||
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
|
||||
32
|
||||
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
|
||||
@@ -100,48 +87,38 @@ impl VideoModeHandle {
|
||||
unimplemented!()
|
||||
};
|
||||
|
||||
VideoModeHandle {
|
||||
let mode = VideoMode {
|
||||
size: PhysicalSize::new(
|
||||
ffi::CGDisplayModeGetPixelWidth(mode.0) as u32,
|
||||
ffi::CGDisplayModeGetPixelHeight(mode.0) as u32,
|
||||
CGDisplayModeGetPixelWidth(Some(&native_mode.0)) as u32,
|
||||
CGDisplayModeGetPixelHeight(Some(&native_mode.0)) as u32,
|
||||
),
|
||||
refresh_rate_millihertz,
|
||||
bit_depth: NonZeroU16::new(bit_depth),
|
||||
monitor: monitor.clone(),
|
||||
native_mode: mode,
|
||||
}
|
||||
};
|
||||
|
||||
VideoModeHandle { mode, monitor: monitor.clone(), native_mode }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorHandle(CGDirectDisplayID);
|
||||
|
||||
impl MonitorHandle {
|
||||
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
|
||||
fn uuid(&self) -> [u8; 16] {
|
||||
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
let cf_uuid = unsafe { CFRetained::from_raw(NonNull::new(ptr).unwrap()) };
|
||||
unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into()
|
||||
}
|
||||
}
|
||||
|
||||
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
|
||||
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
|
||||
// unique identifier that persists even across system reboots
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
|
||||
}
|
||||
self.uuid() == other.uuid()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,35 +132,43 @@ impl PartialOrd for MonitorHandle {
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
|
||||
}
|
||||
self.uuid().cmp(&other.uuid())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for MonitorHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
|
||||
}
|
||||
self.uuid().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for display in displays {
|
||||
monitors.push_back(MonitorHandle(display));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
VecDeque::with_capacity(0)
|
||||
let mut expected_count = 0;
|
||||
let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
|
||||
if res.is_err() {
|
||||
return VecDeque::with_capacity(0);
|
||||
}
|
||||
|
||||
let mut displays: Vec<CGDirectDisplayID> = vec![0; expected_count as usize];
|
||||
let mut actual_count = 0;
|
||||
let res = cgerr(unsafe {
|
||||
CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count)
|
||||
});
|
||||
displays.truncate(actual_count as usize);
|
||||
|
||||
if res.is_err() {
|
||||
return VecDeque::with_capacity(0);
|
||||
}
|
||||
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for display in displays {
|
||||
monitors.push_back(MonitorHandle(display));
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
pub fn primary_monitor() -> MonitorHandle {
|
||||
MonitorHandle(CGDisplay::main().id)
|
||||
MonitorHandle(unsafe { CGMainDisplayID() })
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
@@ -205,8 +190,7 @@ impl MonitorHandle {
|
||||
// TODO: Be smarter about this:
|
||||
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let MonitorHandle(display_id) = *self;
|
||||
let screen_num = CGDisplay::new(display_id).model_number();
|
||||
let screen_num = unsafe { CGDisplayModelNumber(self.0) };
|
||||
Some(format!("Monitor #{screen_num}"))
|
||||
}
|
||||
|
||||
@@ -220,7 +204,7 @@ impl MonitorHandle {
|
||||
// This is already in screen coordinates. If we were using `NSScreen`,
|
||||
// then a conversion would've been needed:
|
||||
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
|
||||
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
|
||||
let bounds = unsafe { CGDisplayBounds(self.0) };
|
||||
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
|
||||
Some(position.to_physical(self.scale_factor()))
|
||||
}
|
||||
@@ -236,38 +220,40 @@ impl MonitorHandle {
|
||||
|
||||
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
let current_display_mode =
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
|
||||
refresh_rate_millihertz(self.0, ¤t_display_mode)
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
|
||||
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz))
|
||||
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.video_modes_handles().map(|handle| handle.mode)
|
||||
}
|
||||
|
||||
pub(crate) fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
let refresh_rate_millihertz = self.refresh_rate_millihertz();
|
||||
let monitor = self.clone();
|
||||
|
||||
unsafe {
|
||||
let modes = {
|
||||
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
||||
assert!(!array.is_null(), "failed to get list of display modes");
|
||||
let array_count = CFArrayGetCount(array);
|
||||
let array = CGDisplayCopyAllDisplayModes(self.0, None)
|
||||
.expect("failed to get list of display modes");
|
||||
let array_count = CFArrayGetCount(&array);
|
||||
let modes: Vec<_> = (0..array_count)
|
||||
.map(move |i| {
|
||||
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||
ffi::CGDisplayModeRetain(mode);
|
||||
mode
|
||||
let mode = CFArrayGetValueAtIndex(&array, i) as *mut CGDisplayMode;
|
||||
CFRetained::retain(NonNull::new(mode).unwrap())
|
||||
})
|
||||
.collect();
|
||||
CFRelease(array as *const _);
|
||||
modes
|
||||
};
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
|
||||
let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64;
|
||||
|
||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
||||
// isn't a CRT
|
||||
@@ -287,13 +273,11 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
|
||||
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
let uuid = self.uuid();
|
||||
NSScreen::screens(mtm).into_iter().find(|screen| {
|
||||
let other_native_id = get_display_id(screen);
|
||||
let other_uuid = unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
|
||||
};
|
||||
uuid == other_uuid
|
||||
let other = MonitorHandle::new(other_native_id);
|
||||
uuid == other.uuid()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -306,15 +290,14 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
|
||||
|
||||
// Retrieve the CGDirectDisplayID associated with this screen
|
||||
//
|
||||
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for `deviceDescription` for details:
|
||||
// The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for details:
|
||||
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
|
||||
let obj = device_description
|
||||
.get(key)
|
||||
.expect("failed getting screen display id from device description");
|
||||
let obj: *const AnyObject = obj;
|
||||
let obj: *const NSNumber = obj.cast();
|
||||
let obj: &NSNumber = unsafe { &*obj };
|
||||
.objectForKey(key)
|
||||
.expect("failed getting screen display id from device description")
|
||||
.downcast::<NSNumber>()
|
||||
.expect("NSScreenNumber must be NSNumber");
|
||||
|
||||
obj.as_u32()
|
||||
})
|
||||
@@ -335,7 +318,7 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
|
||||
// It is intentional that we use `CGMainDisplayID` (as opposed to
|
||||
// `NSScreen::mainScreen`), because that's what the screen coordinates
|
||||
// are relative to, no matter which display the window is currently on.
|
||||
let main_screen_height = CGDisplay::main().bounds().size.height;
|
||||
let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height;
|
||||
|
||||
let y = main_screen_height - frame.size.height - frame.origin.y;
|
||||
NSPoint::new(frame.origin.x, y)
|
||||
@@ -343,25 +326,29 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
|
||||
|
||||
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
|
||||
unsafe {
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0);
|
||||
let refresh_rate = CGDisplayModeGetRefreshRate(Some(&mode.0));
|
||||
if refresh_rate > 0.0 {
|
||||
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
let mut display_link = std::ptr::null_mut();
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess {
|
||||
#[allow(deprecated)]
|
||||
if CVDisplayLinkCreateWithCGDisplay(id, NonNull::from(&mut display_link))
|
||||
!= kCVReturnSuccess
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
|
||||
ffi::CVDisplayLinkRelease(display_link);
|
||||
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
|
||||
#[allow(deprecated)]
|
||||
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(&display_link);
|
||||
|
||||
// This value is indefinite if an invalid display link was specified
|
||||
if time.flags & ffi::kCVTimeIsIndefinite != 0 {
|
||||
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
(time.time_scale as i64)
|
||||
.checked_div(time.time_value)
|
||||
(time.timeScale as i64)
|
||||
.checked_div(time.timeValue)
|
||||
.map(|v| (v * 1000) as u32)
|
||||
.and_then(NonZeroU32::new)
|
||||
}
|
||||
|
||||
@@ -4,132 +4,78 @@
|
||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||
use std::cell::Cell;
|
||||
use std::ffi::c_void;
|
||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
use std::ptr;
|
||||
use std::rc::Weak;
|
||||
use std::time::Instant;
|
||||
|
||||
use block2::Block;
|
||||
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
|
||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
|
||||
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
|
||||
CFRunLoop, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
CFRunLoopObserver, CFRunLoopObserverCallBack, CFRunLoopObserverContext,
|
||||
CFRunLoopObserverCreate, CFRunLoopPerformBlock, CFRunLoopTimer, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
use tracing::error;
|
||||
|
||||
use super::app_state::AppState;
|
||||
use super::event_loop::{stop_app_on_panic, PanicInfo};
|
||||
use super::ffi;
|
||||
|
||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
||||
where
|
||||
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
|
||||
{
|
||||
let info_from_raw = unsafe { 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);
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
|
||||
let _ = &panic_info;
|
||||
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,
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
panic_info: *mut c_void,
|
||||
_info: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => {
|
||||
// trace!("Triggered `CFRunLoopAfterWaiting`");
|
||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
||||
// trace!("Completed `CFRunLoopAfterWaiting`");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
match activity {
|
||||
CFRunLoopActivity::AfterWaiting => {
|
||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
// without that, LoopExiting would get sent after AboutToWait
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
panic_info: *mut c_void,
|
||||
_info: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => {
|
||||
// trace!("Triggered `CFRunLoopBeforeWaiting`");
|
||||
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
||||
// trace!("Completed `CFRunLoopBeforeWaiting`");
|
||||
},
|
||||
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => {
|
||||
AppState::get(MainThreadMarker::new().unwrap()).cleared();
|
||||
},
|
||||
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RunLoop(CFRunLoopRef);
|
||||
|
||||
impl Default for RunLoop {
|
||||
fn default() -> Self {
|
||||
Self(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
pub struct RunLoop(CFRetained<CFRunLoop>);
|
||||
|
||||
impl RunLoop {
|
||||
pub fn main(mtm: MainThreadMarker) -> Self {
|
||||
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
|
||||
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
|
||||
let _ = mtm;
|
||||
RunLoop(unsafe { CFRunLoopGetMain() })
|
||||
RunLoop(unsafe { CFRunLoopGetMain() }.unwrap())
|
||||
}
|
||||
|
||||
pub fn wakeup(&self) {
|
||||
unsafe { CFRunLoopWakeUp(self.0) }
|
||||
unsafe { CFRunLoopWakeUp(&self.0) }
|
||||
}
|
||||
|
||||
unsafe fn add_observer(
|
||||
&self,
|
||||
flags: CFOptionFlags,
|
||||
flags: CFRunLoopActivity,
|
||||
// The lower the value, the sooner this will run
|
||||
priority: CFIndex,
|
||||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
flags,
|
||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
handler,
|
||||
context,
|
||||
)
|
||||
};
|
||||
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
|
||||
let observer =
|
||||
unsafe { CFRunLoopObserverCreate(None, flags.0, true, priority, handler, context) }
|
||||
.unwrap();
|
||||
unsafe { CFRunLoopAddObserver(&self.0, Some(&observer), kCFRunLoopCommonModes) };
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
@@ -166,10 +112,6 @@ impl RunLoop {
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
extern "C" {
|
||||
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
|
||||
}
|
||||
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
@@ -195,33 +137,33 @@ impl RunLoop {
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
|
||||
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
|
||||
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
|
||||
unsafe { CFRunLoopPerformBlock(&self.0, Some(mode), Some(&block)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
|
||||
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
|
||||
let run_loop = RunLoop::main(mtm);
|
||||
unsafe {
|
||||
let mut context = CFRunLoopObserverContext {
|
||||
info: Weak::into_raw(panic_info) as *mut _,
|
||||
info: ptr::null_mut(),
|
||||
version: 0,
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
};
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopAfterWaiting,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
Some(control_flow_begin_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
Some(control_flow_end_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
}
|
||||
@@ -229,7 +171,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopWaker {
|
||||
timer: CFRunLoopTimerRef,
|
||||
timer: CFRetained<CFRunLoopTimer>,
|
||||
|
||||
/// An arbitrary instant in the past, that will trigger an immediate wake
|
||||
/// We save this as the `next_fire_date` for consistency so we can
|
||||
@@ -244,30 +186,28 @@ pub struct EventLoopWaker {
|
||||
|
||||
impl Drop for EventLoopWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopTimerInvalidate(self.timer);
|
||||
CFRelease(self.timer as _);
|
||||
}
|
||||
unsafe { CFRunLoopTimerInvalidate(&self.timer) };
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
pub(crate) fn new() -> Self {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
||||
// It is initially setup with a first fire time really far into the
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
wakeup_main_loop,
|
||||
Some(wakeup_main_loop),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddTimer(&CFRunLoopGetMain().unwrap(), Some(&timer), kCFRunLoopCommonModes);
|
||||
Self { timer, start_instant: Instant::now(), next_fire_date: None }
|
||||
}
|
||||
}
|
||||
@@ -275,14 +215,14 @@ impl EventLoopWaker {
|
||||
pub fn stop(&mut self) {
|
||||
if self.next_fire_date.is_some() {
|
||||
self.next_fire_date = None;
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.next_fire_date != Some(self.start_instant) {
|
||||
self.next_fire_date = Some(self.start_instant);
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +240,7 @@ impl EventLoopWaker {
|
||||
let duration = instant - now;
|
||||
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
|
||||
+ duration.as_secs() as f64;
|
||||
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
|
||||
CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use objc2_core_graphics::CGError;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::error::OsError;
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate =
|
||||
@@ -26,3 +29,12 @@ impl Drop for TraceGuard {
|
||||
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
|
||||
if err == CGError::Success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(os_error!(format!("CGError {err:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,23 @@ use std::rc::Rc;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView,
|
||||
NSTrackingRectTag, NSView, NSWindow,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
|
||||
NSSize, NSString, NSUInteger,
|
||||
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
|
||||
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
|
||||
use super::app_state::AppState;
|
||||
use super::cursor::{default_cursor, invisible_cursor};
|
||||
use super::event::{
|
||||
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
|
||||
scancode_to_physicalkey, KeyEventExtra,
|
||||
scancode_to_physicalkey,
|
||||
};
|
||||
use super::window::WinitWindow;
|
||||
use super::window::window_id;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
||||
@@ -138,28 +137,21 @@ pub struct ViewState {
|
||||
option_as_alt: Cell<OptionAsAlt>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSView, NSResponder, NSObject))]
|
||||
#[ivars = ViewState]
|
||||
#[name = "WinitView"]
|
||||
pub(super) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = ViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(isFlipped)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(isFlipped))]
|
||||
fn is_flipped(&self) -> bool {
|
||||
// `winit` uses the upper-left corner as the origin.
|
||||
true
|
||||
}
|
||||
|
||||
#[method(viewDidMoveToWindow)]
|
||||
#[unsafe(method(viewDidMoveToWindow))]
|
||||
fn view_did_move_to_window(&self) {
|
||||
trace_scope!("viewDidMoveToWindow");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
@@ -175,7 +167,7 @@ declare_class!(
|
||||
}
|
||||
|
||||
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
|
||||
#[method(viewFrameDidChangeNotification:)]
|
||||
#[unsafe(method(viewFrameDidChangeNotification:))]
|
||||
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
|
||||
trace_scope!("NSViewFrameDidChangeNotification");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
@@ -190,38 +182,45 @@ declare_class!(
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
|
||||
// Emit resize event here rather than from windowDidResize because:
|
||||
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
|
||||
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
|
||||
// 1. When a new window is created as a tab, the frame size may change without a window
|
||||
// resize occurring.
|
||||
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
|
||||
// size (includes tab height).
|
||||
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
|
||||
let size = logical_size.to_physical::<u32>(self.scale_factor());
|
||||
self.queue_event(WindowEvent::SurfaceResized(size));
|
||||
}
|
||||
|
||||
#[method(drawRect:)]
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, _rect: NSRect) {
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
self.ivars().app_state.handle_redraw(self.window().id());
|
||||
self.ivars().app_state.handle_redraw(window_id(&self.window()));
|
||||
|
||||
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
||||
}
|
||||
|
||||
#[method(acceptsFirstResponder)]
|
||||
#[unsafe(method(acceptsFirstResponder))]
|
||||
fn accepts_first_responder(&self) -> bool {
|
||||
trace_scope!("acceptsFirstResponder");
|
||||
true
|
||||
}
|
||||
|
||||
// This is necessary to prevent a beefy terminal error on MacBook Pros:
|
||||
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
|
||||
// TODO: Add an API extension for using `NSTouchBar`
|
||||
#[method_id(touchBar)]
|
||||
// IMKInputSession [0x7fc573576ff0
|
||||
// presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self
|
||||
// textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error
|
||||
// Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this
|
||||
// process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from
|
||||
// this process.}, com.apple.inputmethod.EmojiFunctionRowItem TODO: Add an API
|
||||
// extension for using `NSTouchBar`
|
||||
#[unsafe(method_id(touchBar))]
|
||||
fn touch_bar(&self) -> Option<Retained<NSObject>> {
|
||||
trace_scope!("touchBar");
|
||||
None
|
||||
}
|
||||
|
||||
#[method(resetCursorRects)]
|
||||
#[unsafe(method(resetCursorRects))]
|
||||
fn reset_cursor_rects(&self) {
|
||||
trace_scope!("resetCursorRects");
|
||||
let bounds = self.bounds();
|
||||
@@ -236,13 +235,13 @@ declare_class!(
|
||||
}
|
||||
|
||||
unsafe impl NSTextInputClient for WinitView {
|
||||
#[method(hasMarkedText)]
|
||||
#[unsafe(method(hasMarkedText))]
|
||||
fn has_marked_text(&self) -> bool {
|
||||
trace_scope!("hasMarkedText");
|
||||
self.ivars().marked_text.borrow().length() > 0
|
||||
}
|
||||
|
||||
#[method(markedRange)]
|
||||
#[unsafe(method(markedRange))]
|
||||
fn marked_range(&self) -> NSRange {
|
||||
trace_scope!("markedRange");
|
||||
let length = self.ivars().marked_text.borrow().length();
|
||||
@@ -254,14 +253,14 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(selectedRange)]
|
||||
#[unsafe(method(selectedRange))]
|
||||
fn selected_range(&self) -> NSRange {
|
||||
trace_scope!("selectedRange");
|
||||
// Documented to return `{NSNotFound, 0}` if there is no selection.
|
||||
NSRange::new(NSNotFound as NSUInteger, 0)
|
||||
}
|
||||
|
||||
#[method(setMarkedText:selectedRange:replacementRange:)]
|
||||
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
|
||||
fn set_marked_text(
|
||||
&self,
|
||||
string: &NSObject,
|
||||
@@ -271,23 +270,15 @@ declare_class!(
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("setMarkedText:selectedRange:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_attributed_nsstring(string),
|
||||
string.string(),
|
||||
)
|
||||
let (marked_text, string) = if let Some(string) =
|
||||
string.downcast_ref::<NSAttributedString>()
|
||||
{
|
||||
(NSMutableAttributedString::from_attributed_nsstring(string), string.string())
|
||||
} else if let Some(string) = string.downcast_ref::<NSString>() {
|
||||
(NSMutableAttributedString::from_nsstring(string), string.copy())
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_nsstring(string),
|
||||
string.copy(),
|
||||
)
|
||||
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
panic!("unexpected text {string:?}")
|
||||
};
|
||||
|
||||
// Update marked text.
|
||||
@@ -323,7 +314,7 @@ declare_class!(
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
|
||||
}
|
||||
|
||||
#[method(unmarkText)]
|
||||
#[unsafe(method(unmarkText))]
|
||||
fn unmark_text(&self) {
|
||||
trace_scope!("unmarkText");
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
@@ -340,13 +331,13 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method_id(validAttributesForMarkedText)]
|
||||
#[unsafe(method_id(validAttributesForMarkedText))]
|
||||
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
|
||||
trace_scope!("validAttributesForMarkedText");
|
||||
NSArray::new()
|
||||
}
|
||||
|
||||
#[method_id(attributedSubstringForProposedRange:actualRange:)]
|
||||
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
|
||||
fn attributed_substring_for_proposed_range(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
@@ -356,42 +347,36 @@ declare_class!(
|
||||
None
|
||||
}
|
||||
|
||||
#[method(characterIndexForPoint:)]
|
||||
#[unsafe(method(characterIndexForPoint:))]
|
||||
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
|
||||
trace_scope!("characterIndexForPoint:");
|
||||
0
|
||||
}
|
||||
|
||||
#[method(firstRectForCharacterRange:actualRange:)]
|
||||
#[unsafe(method(firstRectForCharacterRange:actualRange:))]
|
||||
fn first_rect_for_character_range(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut NSRange,
|
||||
) -> NSRect {
|
||||
trace_scope!("firstRectForCharacterRange:actualRange:");
|
||||
let rect = NSRect::new(
|
||||
self.ivars().ime_position.get(),
|
||||
self.ivars().ime_size.get()
|
||||
);
|
||||
let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get());
|
||||
// Return value is expected to be in screen coordinates, so we need a conversion here
|
||||
self.window()
|
||||
.convertRectToScreen(self.convertRect_toView(rect, None))
|
||||
self.window().convertRectToScreen(self.convertRect_toView(rect, None))
|
||||
}
|
||||
|
||||
#[method(insertText:replacementRange:)]
|
||||
#[unsafe(method(insertText:replacementRange:))]
|
||||
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("insertText:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let string = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
unsafe { &*string }.string().to_string()
|
||||
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
|
||||
string.string().to_string()
|
||||
} else if let Some(string) = string.downcast_ref::<NSString>() {
|
||||
string.to_string()
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSString = string.cast();
|
||||
unsafe { &*string }.to_string()
|
||||
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
panic!("unexpected text {string:?}")
|
||||
};
|
||||
|
||||
let is_control = string.chars().next().is_some_and(|c| c.is_control());
|
||||
@@ -404,15 +389,16 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
|
||||
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
#[method(doCommandBySelector:)]
|
||||
// Basically, we're sent this message whenever a keyboard event that doesn't generate a
|
||||
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
#[unsafe(method(doCommandBySelector:))]
|
||||
fn do_command_by_selector(&self, command: Sel) {
|
||||
trace_scope!("doCommandBySelector:");
|
||||
|
||||
// We shouldn't forward any character from just committed text, since we'll end up sending
|
||||
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
|
||||
// which is not desired given it was used to confirm IME input.
|
||||
// We shouldn't forward any character from just committed text, since we'll end up
|
||||
// sending it twice with some IMEs like Korean one. We'll also always send
|
||||
// `Enter` in that case, which is not desired given it was used to confirm
|
||||
// IME input.
|
||||
if self.ivars().ime_state.get() == ImeState::Committed {
|
||||
return;
|
||||
}
|
||||
@@ -426,10 +412,14 @@ declare_class!(
|
||||
}
|
||||
|
||||
// Send command action to user if they requested it.
|
||||
let window_id = self.window().id();
|
||||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
if let Some(handler) = app.macos_handler() {
|
||||
handler.standard_key_binding(event_loop, window_id, command.name());
|
||||
handler.standard_key_binding(
|
||||
event_loop,
|
||||
window_id,
|
||||
command.name().to_str().unwrap(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -439,8 +429,9 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(keyDown:)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(keyDown:))]
|
||||
fn key_down(&self, event: &NSEvent) {
|
||||
trace_scope!("keyDown:");
|
||||
{
|
||||
@@ -483,7 +474,7 @@ declare_class!(
|
||||
// Allow normal input after the commit.
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
true
|
||||
}
|
||||
},
|
||||
ImeState::Preedit => true,
|
||||
// `key_down` could result in preedit clear, so compare old and current state.
|
||||
_ => old_ime_state != self.ivars().ime_state.get(),
|
||||
@@ -499,7 +490,7 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(keyUp:)]
|
||||
#[unsafe(method(keyUp:))]
|
||||
fn key_up(&self, event: &NSEvent) {
|
||||
trace_scope!("keyUp:");
|
||||
|
||||
@@ -507,10 +498,7 @@ declare_class!(
|
||||
self.update_modifiers(&event, false);
|
||||
|
||||
// We want to send keyboard input when we are currently in the ground state.
|
||||
if matches!(
|
||||
self.ivars().ime_state.get(),
|
||||
ImeState::Ground | ImeState::Disabled
|
||||
) {
|
||||
if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: None,
|
||||
event: create_key_event(&event, false, false),
|
||||
@@ -519,14 +507,14 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(flagsChanged:)]
|
||||
#[unsafe(method(flagsChanged:))]
|
||||
fn flags_changed(&self, event: &NSEvent) {
|
||||
trace_scope!("flagsChanged:");
|
||||
|
||||
self.update_modifiers(event, true);
|
||||
}
|
||||
|
||||
#[method(insertTab:)]
|
||||
#[unsafe(method(insertTab:))]
|
||||
fn insert_tab(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("insertTab:");
|
||||
let window = self.window();
|
||||
@@ -537,7 +525,7 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(insertBackTab:)]
|
||||
#[unsafe(method(insertBackTab:))]
|
||||
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("insertBackTab:");
|
||||
let window = self.window();
|
||||
@@ -550,7 +538,7 @@ declare_class!(
|
||||
|
||||
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
|
||||
#[method(cancelOperation:)]
|
||||
#[unsafe(method(cancelOperation:))]
|
||||
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
trace_scope!("cancelOperation:");
|
||||
@@ -580,42 +568,42 @@ declare_class!(
|
||||
//
|
||||
// See https://github.com/rust-windowing/winit/pull/1490 for history.
|
||||
|
||||
#[method(mouseDown:)]
|
||||
#[unsafe(method(mouseDown:))]
|
||||
fn mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(mouseUp:)]
|
||||
#[unsafe(method(mouseUp:))]
|
||||
fn mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[method(rightMouseDown:)]
|
||||
#[unsafe(method(rightMouseDown:))]
|
||||
fn right_mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("rightMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(rightMouseUp:)]
|
||||
#[unsafe(method(rightMouseUp:))]
|
||||
fn right_mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("rightMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[method(otherMouseDown:)]
|
||||
#[unsafe(method(otherMouseDown:))]
|
||||
fn other_mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("otherMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(otherMouseUp:)]
|
||||
#[unsafe(method(otherMouseUp:))]
|
||||
fn other_mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("otherMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
@@ -624,27 +612,27 @@ declare_class!(
|
||||
|
||||
// No tracing on these because that would be overly verbose
|
||||
|
||||
#[method(mouseMoved:)]
|
||||
#[unsafe(method(mouseMoved:))]
|
||||
fn mouse_moved(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(mouseDragged:)]
|
||||
#[unsafe(method(mouseDragged:))]
|
||||
fn mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(rightMouseDragged:)]
|
||||
#[unsafe(method(rightMouseDragged:))]
|
||||
fn right_mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(otherMouseDragged:)]
|
||||
#[unsafe(method(otherMouseDragged:))]
|
||||
fn other_mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(mouseEntered:)]
|
||||
#[unsafe(method(mouseEntered:))]
|
||||
fn mouse_entered(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseEntered:");
|
||||
|
||||
@@ -658,7 +646,7 @@ declare_class!(
|
||||
});
|
||||
}
|
||||
|
||||
#[method(mouseExited:)]
|
||||
#[unsafe(method(mouseExited:))]
|
||||
fn mouse_exited(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseExited:");
|
||||
|
||||
@@ -672,7 +660,7 @@ declare_class!(
|
||||
});
|
||||
}
|
||||
|
||||
#[method(scrollWheel:)]
|
||||
#[unsafe(method(scrollWheel:))]
|
||||
fn scroll_wheel(&self, event: &NSEvent) {
|
||||
trace_scope!("scrollWheel:");
|
||||
|
||||
@@ -689,9 +677,9 @@ declare_class!(
|
||||
};
|
||||
|
||||
// The "momentum phase," if any, has higher priority than touch phase (the two should
|
||||
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
|
||||
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
|
||||
// report the touch phase.
|
||||
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no
|
||||
// momentum phase is recorded (or rather, the started/ended cases of the
|
||||
// momentum phase) then we report the touch phase.
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.momentumPhase() } {
|
||||
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
|
||||
@@ -705,17 +693,13 @@ declare_class!(
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop|
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
|
||||
);
|
||||
self.queue_event(WindowEvent::MouseWheel {
|
||||
device_id: None,
|
||||
delta,
|
||||
phase,
|
||||
});
|
||||
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
|
||||
}
|
||||
|
||||
#[method(magnifyWithEvent:)]
|
||||
#[unsafe(method(magnifyWithEvent:))]
|
||||
fn magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("magnifyWithEvent:");
|
||||
|
||||
@@ -737,18 +721,16 @@ declare_class!(
|
||||
});
|
||||
}
|
||||
|
||||
#[method(smartMagnifyWithEvent:)]
|
||||
#[unsafe(method(smartMagnifyWithEvent:))]
|
||||
fn smart_magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("smartMagnifyWithEvent:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
self.queue_event(WindowEvent::DoubleTapGesture {
|
||||
device_id: None,
|
||||
});
|
||||
self.queue_event(WindowEvent::DoubleTapGesture { device_id: None });
|
||||
}
|
||||
|
||||
#[method(rotateWithEvent:)]
|
||||
#[unsafe(method(rotateWithEvent:))]
|
||||
fn rotate_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("rotateWithEvent:");
|
||||
|
||||
@@ -770,7 +752,7 @@ declare_class!(
|
||||
});
|
||||
}
|
||||
|
||||
#[method(pressureChangeWithEvent:)]
|
||||
#[unsafe(method(pressureChangeWithEvent:))]
|
||||
fn pressure_change_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("pressureChangeWithEvent:");
|
||||
|
||||
@@ -784,13 +766,13 @@ declare_class!(
|
||||
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
|
||||
// Note that this *doesn't* help with any missing Cmd inputs.
|
||||
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
|
||||
#[method(_wantsKeyDownForEvent:)]
|
||||
#[unsafe(method(_wantsKeyDownForEvent:))]
|
||||
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("_wantsKeyDownForEvent:");
|
||||
true
|
||||
}
|
||||
|
||||
#[method(acceptsFirstMouse:)]
|
||||
#[unsafe(method(acceptsFirstMouse:))]
|
||||
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("acceptsFirstMouse:");
|
||||
self.ivars().accepts_first_mouse
|
||||
@@ -821,26 +803,19 @@ impl WinitView {
|
||||
accepts_first_mouse,
|
||||
option_as_alt: Cell::new(option_as_alt),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
|
||||
|
||||
*this.ivars().input_source.borrow_mut() = this.current_input_source();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Retained<WinitWindow> {
|
||||
let window = (**self).window().expect("view must be installed in a window");
|
||||
|
||||
if !window.isKindOfClass(WinitWindow::class()) {
|
||||
unreachable!("view installed in non-WinitWindow");
|
||||
}
|
||||
|
||||
// SAFETY: Just checked that the window is `WinitWindow`
|
||||
unsafe { Retained::cast(window) }
|
||||
fn window(&self) -> Retained<NSWindow> {
|
||||
(**self).window().expect("view must be installed in a window")
|
||||
}
|
||||
|
||||
fn queue_event(&self, event: WindowEvent) {
|
||||
let window_id = self.window().id();
|
||||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
@@ -965,10 +940,8 @@ impl WinitView {
|
||||
// We'll correct this later.
|
||||
state: Pressed,
|
||||
text: None,
|
||||
platform_specific: KeyEventExtra {
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: logical_key.clone(),
|
||||
},
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: logical_key.clone(),
|
||||
};
|
||||
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use dpi::{Position, Size};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSResponder, NSWindow};
|
||||
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
|
||||
use objc2::{define_class, MainThreadMarker, Message};
|
||||
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::event_loop::ActiveEventLoop;
|
||||
use super::window_delegate::WindowDelegate;
|
||||
@@ -16,7 +17,7 @@ use crate::window::{
|
||||
};
|
||||
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Retained<WinitWindow>>,
|
||||
window: MainThreadBound<Retained<NSWindow>>,
|
||||
/// The window only keeps a weak reference to this, so we must keep it around here.
|
||||
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
||||
}
|
||||
@@ -64,7 +65,7 @@ impl Window {
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
// Restore the video mode.
|
||||
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
|
||||
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_, _))) {
|
||||
self.set_fullscreen(None);
|
||||
}
|
||||
|
||||
@@ -332,27 +333,21 @@ impl CoreWindow for Window {
|
||||
}
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSWindow, NSResponder, NSObject))]
|
||||
#[name = "WinitWindow"]
|
||||
#[derive(Debug)]
|
||||
pub struct WinitWindow;
|
||||
|
||||
unsafe impl ClassType for WinitWindow {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSWindow;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitWindow {}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
#[method(canBecomeMainWindow)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitWindow {
|
||||
#[unsafe(method(canBecomeMainWindow))]
|
||||
fn can_become_main_window(&self) -> bool {
|
||||
trace_scope!("canBecomeMainWindow");
|
||||
true
|
||||
}
|
||||
|
||||
#[method(canBecomeKeyWindow)]
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
@@ -360,8 +355,24 @@ declare_class!(
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitWindow {
|
||||
pub(super) fn id(&self) -> WindowId {
|
||||
WindowId::from_raw(self as *const Self as usize)
|
||||
define_class!(
|
||||
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
|
||||
#[name = "WinitPanel"]
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPanel;
|
||||
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitPanel {
|
||||
// although NSPanel can become key window
|
||||
// it doesn't if window doesn't have NSWindowStyleMask::Titled
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
pub(super) fn window_id(window: &NSWindow) -> WindowId {
|
||||
WindowId::from_raw(window as *const _ as usize)
|
||||
}
|
||||
|
||||
@@ -6,25 +6,32 @@ use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use core_graphics::display::CGDisplay;
|
||||
use monitor::VideoModeHandle;
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::runtime::{AnyObject, ProtocolObject};
|
||||
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2::{
|
||||
available, define_class, msg_send, sel, ClassType, DefinedClass, MainThreadMarker,
|
||||
MainThreadOnly, Message,
|
||||
};
|
||||
use objc2_app_kit::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
|
||||
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
|
||||
NSColor, NSDraggingDestination, NSDraggingInfo, NSFilenamesPboardType,
|
||||
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
|
||||
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
|
||||
};
|
||||
use objc2_core_foundation::{CGFloat, CGPoint};
|
||||
use objc2_core_graphics::{
|
||||
CGAcquireDisplayFadeReservation, CGAssociateMouseAndMouseCursorPosition, CGDisplayCapture,
|
||||
CGDisplayFade, CGDisplayRelease, CGDisplaySetDisplayMode, CGReleaseDisplayFadeReservation,
|
||||
CGRestorePermanentDisplayConfiguration, CGShieldingWindowLevel, CGWarpMouseCursorPosition,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
|
||||
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
|
||||
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
|
||||
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
|
||||
ns_string, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
|
||||
NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSNotificationCenter, NSObject,
|
||||
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
|
||||
NSRect, NSSize, NSString,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
@@ -32,8 +39,9 @@ use super::app_state::AppState;
|
||||
use super::cursor::cursor_from_icon;
|
||||
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
||||
use super::observer::RunLoop;
|
||||
use super::util::cgerr;
|
||||
use super::view::WinitView;
|
||||
use super::window::WinitWindow;
|
||||
use super::window::{window_id, WinitPanel, WinitWindow};
|
||||
use super::{ffi, Fullscreen, MonitorHandle};
|
||||
use crate::dpi::{
|
||||
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
|
||||
@@ -62,6 +70,7 @@ pub struct PlatformSpecificWindowAttributes {
|
||||
pub option_as_alt: OptionAsAlt,
|
||||
pub borderless_game: bool,
|
||||
pub unified_titlebar: bool,
|
||||
pub panel: bool,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowAttributes {
|
||||
@@ -81,6 +90,7 @@ impl Default for PlatformSpecificWindowAttributes {
|
||||
option_as_alt: Default::default(),
|
||||
borderless_game: false,
|
||||
unified_titlebar: false,
|
||||
panel: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +100,7 @@ pub(crate) struct State {
|
||||
/// Strong reference to the global application state.
|
||||
app_state: Rc<AppState>,
|
||||
|
||||
window: Retained<WinitWindow>,
|
||||
window: Retained<NSWindow>,
|
||||
|
||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
||||
//
|
||||
@@ -132,30 +142,24 @@ pub(crate) struct State {
|
||||
is_borderless_game: Cell<bool>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSObject))]
|
||||
#[thread_kind = MainThreadOnly]
|
||||
#[name = "WinitWindowDelegate"]
|
||||
#[ivars = State]
|
||||
pub(crate) struct WindowDelegate;
|
||||
|
||||
unsafe impl ClassType for WindowDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindowDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WindowDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for WindowDelegate {}
|
||||
|
||||
unsafe impl NSWindowDelegate for WindowDelegate {
|
||||
#[method(windowShouldClose:)]
|
||||
#[unsafe(method(windowShouldClose:))]
|
||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
||||
trace_scope!("windowShouldClose:");
|
||||
self.queue_event(WindowEvent::CloseRequested);
|
||||
false
|
||||
}
|
||||
|
||||
#[method(windowWillClose:)]
|
||||
#[unsafe(method(windowWillClose:))]
|
||||
fn window_will_close(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillClose:");
|
||||
// `setDelegate:` retains the previous value and then autoreleases it
|
||||
@@ -167,14 +171,14 @@ declare_class!(
|
||||
self.queue_event(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
#[method(windowDidResize:)]
|
||||
#[unsafe(method(windowDidResize:))]
|
||||
fn window_did_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidResize:");
|
||||
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[method(windowWillStartLiveResize:)]
|
||||
#[unsafe(method(windowWillStartLiveResize:))]
|
||||
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillStartLiveResize:");
|
||||
|
||||
@@ -182,20 +186,20 @@ declare_class!(
|
||||
self.set_resize_increments_inner(increments);
|
||||
}
|
||||
|
||||
#[method(windowDidEndLiveResize:)]
|
||||
#[unsafe(method(windowDidEndLiveResize:))]
|
||||
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEndLiveResize:");
|
||||
self.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
}
|
||||
|
||||
// This won't be triggered if the move was part of a resize.
|
||||
#[method(windowDidMove:)]
|
||||
#[unsafe(method(windowDidMove:))]
|
||||
fn window_did_move(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidMove:");
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[method(windowDidChangeBackingProperties:)]
|
||||
#[unsafe(method(windowDidChangeBackingProperties:))]
|
||||
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeBackingProperties:");
|
||||
let scale_factor = self.scale_factor();
|
||||
@@ -211,7 +215,7 @@ declare_class!(
|
||||
});
|
||||
}
|
||||
|
||||
#[method(windowDidBecomeKey:)]
|
||||
#[unsafe(method(windowDidBecomeKey:))]
|
||||
fn window_did_become_key(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidBecomeKey:");
|
||||
// TODO: center the cursor if the window had mouse grab when it
|
||||
@@ -219,7 +223,7 @@ declare_class!(
|
||||
self.queue_event(WindowEvent::Focused(true));
|
||||
}
|
||||
|
||||
#[method(windowDidResignKey:)]
|
||||
#[unsafe(method(windowDidResignKey:))]
|
||||
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidResignKey:");
|
||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
||||
@@ -235,7 +239,7 @@ declare_class!(
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
#[unsafe(method(windowWillEnterFullScreen:))]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
@@ -245,7 +249,7 @@ declare_class!(
|
||||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_)) => (),
|
||||
Some(Fullscreen::Exclusive(..)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
@@ -261,14 +265,14 @@ declare_class!(
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
#[unsafe(method(windowWillExitFullScreen:))]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
self.ivars().in_fullscreen_transition.set(true);
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
#[unsafe(method(window:willUseFullScreenPresentationOptions:))]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
@@ -285,17 +289,17 @@ declare_class!(
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let fullscreen = self.ivars().fullscreen.borrow();
|
||||
if let Some(Fullscreen::Exclusive(_)) = &*fullscreen {
|
||||
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
if let Some(Fullscreen::Exclusive(..)) = &*fullscreen {
|
||||
options = NSApplicationPresentationOptions::FullScreen
|
||||
| NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
#[unsafe(method(windowDidEnterFullScreen:))]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.ivars().initial_fullscreen.set(false);
|
||||
@@ -306,7 +310,7 @@ declare_class!(
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
#[unsafe(method(windowDidExitFullScreen:))]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
@@ -333,7 +337,7 @@ declare_class!(
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[method(windowDidFailToEnterFullScreen:)]
|
||||
#[unsafe(method(windowDidFailToEnterFullScreen:))]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
self.ivars().in_fullscreen_transition.set(false);
|
||||
@@ -352,14 +356,14 @@ declare_class!(
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
#[unsafe(method(windowDidChangeOcclusionState:))]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
#[unsafe(method(windowDidChangeScreen:))]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
|
||||
@@ -373,67 +377,114 @@ declare_class!(
|
||||
|
||||
unsafe impl NSDraggingDestination for WindowDelegate {
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[method(draggingEntered:)]
|
||||
fn dragging_entered(&self, sender: &NSObject) -> bool {
|
||||
#[unsafe(method(draggingEntered:))]
|
||||
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
trace_scope!("draggingEntered:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
let pb = unsafe { sender.draggingPasteboard() };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap()
|
||||
.downcast::<NSArray>()
|
||||
.unwrap();
|
||||
let paths = filenames
|
||||
.into_iter()
|
||||
.map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
|
||||
.collect();
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
self.queue_event(WindowEvent::HoveredFile(path));
|
||||
});
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
let dl = self.view().convertPoint_fromView(dl, None);
|
||||
let position =
|
||||
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
|
||||
|
||||
self.queue_event(WindowEvent::DragEntered { paths, position });
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(wantsPeriodicDraggingUpdates))]
|
||||
fn wants_periodic_dragging_updates(&self) -> bool {
|
||||
trace_scope!("wantsPeriodicDraggingUpdates:");
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked periodically as the image is held within the destination area, allowing
|
||||
/// modification of the dragging operation or mouse-pointer position.
|
||||
#[unsafe(method(draggingUpdated:))]
|
||||
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
trace_scope!("draggingUpdated:");
|
||||
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
let dl = self.view().convertPoint_fromView(dl, None);
|
||||
let position =
|
||||
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
|
||||
|
||||
self.queue_event(WindowEvent::DragMoved { position });
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked when the image is released
|
||||
#[method(prepareForDragOperation:)]
|
||||
#[unsafe(method(prepareForDragOperation:))]
|
||||
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
|
||||
trace_scope!("prepareForDragOperation:");
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked after the released image has been removed from the screen
|
||||
#[method(performDragOperation:)]
|
||||
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
|
||||
#[unsafe(method(performDragOperation:))]
|
||||
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
trace_scope!("performDragOperation:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
let pb = unsafe { sender.draggingPasteboard() };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap()
|
||||
.downcast::<NSArray>()
|
||||
.unwrap();
|
||||
let paths = filenames
|
||||
.into_iter()
|
||||
.map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
|
||||
.collect();
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
self.queue_event(WindowEvent::DroppedFile(path));
|
||||
});
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
let dl = self.view().convertPoint_fromView(dl, None);
|
||||
let position =
|
||||
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
|
||||
|
||||
self.queue_event(WindowEvent::DragDropped { paths, position });
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is complete
|
||||
#[method(concludeDragOperation:)]
|
||||
#[unsafe(method(concludeDragOperation:))]
|
||||
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
|
||||
trace_scope!("concludeDragOperation:");
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is cancelled
|
||||
#[method(draggingExited:)]
|
||||
fn dragging_exited(&self, _sender: Option<&NSObject>) {
|
||||
#[unsafe(method(draggingExited:))]
|
||||
fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
|
||||
trace_scope!("draggingExited:");
|
||||
self.queue_event(WindowEvent::HoveredFileCancelled);
|
||||
|
||||
let position = sender.map(|sender| {
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
let dl = self.view().convertPoint_fromView(dl, None);
|
||||
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor())
|
||||
});
|
||||
|
||||
self.queue_event(WindowEvent::DragLeft { position });
|
||||
}
|
||||
}
|
||||
|
||||
// Key-Value Observing
|
||||
unsafe impl WindowDelegate {
|
||||
#[method(observeValueForKeyPath:ofObject:change:context:)]
|
||||
/// Key-Value Observing
|
||||
impl WindowDelegate {
|
||||
#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
|
||||
fn observe_value(
|
||||
&self,
|
||||
key_path: Option<&NSString>,
|
||||
@@ -449,19 +500,15 @@ declare_class!(
|
||||
"requested a change dictionary in `addObserver`, but none was provided",
|
||||
);
|
||||
let old = change
|
||||
.get(unsafe { NSKeyValueChangeOldKey })
|
||||
.objectForKey(unsafe { NSKeyValueChangeOldKey })
|
||||
.expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
|
||||
let new = change
|
||||
.get(unsafe { NSKeyValueChangeNewKey })
|
||||
.objectForKey(unsafe { NSKeyValueChangeNewKey })
|
||||
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
|
||||
|
||||
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
|
||||
let old: *const AnyObject = old;
|
||||
let old: *const NSAppearance = old.cast();
|
||||
let old: &NSAppearance = unsafe { &*old };
|
||||
let new: *const AnyObject = new;
|
||||
let new: *const NSAppearance = new.cast();
|
||||
let new: &NSAppearance = unsafe { &*new };
|
||||
// The value of `effectiveAppearance` is `NSAppearance`
|
||||
let old = old.downcast::<NSAppearance>().unwrap();
|
||||
let new = new.downcast::<NSAppearance>().unwrap();
|
||||
|
||||
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
|
||||
|
||||
@@ -472,8 +519,8 @@ declare_class!(
|
||||
return;
|
||||
}
|
||||
|
||||
let old = appearance_to_theme(old);
|
||||
let new = appearance_to_theme(new);
|
||||
let old = appearance_to_theme(&old);
|
||||
let new = appearance_to_theme(&new);
|
||||
// Check that the theme changed in Winit's terms (the theme might have changed on
|
||||
// other parameters, such as level of contrast, but the event should not be emitted
|
||||
// in those cases).
|
||||
@@ -501,11 +548,11 @@ fn new_window(
|
||||
app_state: &Rc<AppState>,
|
||||
attrs: &WindowAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Option<Retained<WinitWindow>> {
|
||||
) -> Option<Retained<NSWindow>> {
|
||||
autoreleasepool(|_| {
|
||||
let screen = match attrs.fullscreen.clone().map(Into::into) {
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
| Some(Fullscreen::Exclusive(VideoModeHandle { monitor, .. })) => {
|
||||
| Some(Fullscreen::Exclusive(monitor, _)) => {
|
||||
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
|
||||
},
|
||||
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
|
||||
@@ -584,16 +631,33 @@ fn new_window(
|
||||
// confusing issues with the window not being properly activated.
|
||||
//
|
||||
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
||||
msg_send_id![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
let window: Retained<NSWindow> = if attrs.platform_specific.panel {
|
||||
masks |= NSWindowStyleMask::NonactivatingPanel;
|
||||
|
||||
let window: Option<Retained<WinitPanel>> = unsafe {
|
||||
msg_send![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::Buffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
||||
window?.as_super().as_super().retain()
|
||||
} else {
|
||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
||||
msg_send![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::Buffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
||||
window?.as_super().retain()
|
||||
};
|
||||
let window = window?;
|
||||
|
||||
// It is very important for correct memory management that we
|
||||
// disable the extra release that would otherwise happen when
|
||||
@@ -609,22 +673,22 @@ fn new_window(
|
||||
}
|
||||
|
||||
if attrs.content_protected {
|
||||
window.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
||||
window.setSharingType(NSWindowSharingType::None);
|
||||
}
|
||||
|
||||
if attrs.platform_specific.titlebar_transparent {
|
||||
window.setTitlebarAppearsTransparent(true);
|
||||
}
|
||||
if attrs.platform_specific.title_hidden {
|
||||
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden);
|
||||
window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
|
||||
}
|
||||
if attrs.platform_specific.titlebar_buttons_hidden {
|
||||
for titlebar_button in &[
|
||||
#[allow(deprecated)]
|
||||
NSWindowFullScreenButton,
|
||||
NSWindowButton::NSWindowMiniaturizeButton,
|
||||
NSWindowButton::NSWindowCloseButton,
|
||||
NSWindowButton::NSWindowZoomButton,
|
||||
NSWindowButton::MiniaturizeButton,
|
||||
NSWindowButton::CloseButton,
|
||||
NSWindowButton::ZoomButton,
|
||||
] {
|
||||
if let Some(button) = window.standardWindowButton(*titlebar_button) {
|
||||
button.setHidden(true);
|
||||
@@ -644,7 +708,7 @@ fn new_window(
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
|
||||
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) {
|
||||
if let Some(button) = window.standardWindowButton(NSWindowButton::ZoomButton) {
|
||||
button.setEnabled(false);
|
||||
}
|
||||
}
|
||||
@@ -706,10 +770,7 @@ fn new_window(
|
||||
}
|
||||
|
||||
// register for drag and drop operations.
|
||||
window
|
||||
.registerForDraggedTypes(&NSArray::from_id_slice(&[
|
||||
unsafe { NSFilenamesPboardType }.copy()
|
||||
]));
|
||||
window.registerForDraggedTypes(&NSArray::from_slice(&[unsafe { NSFilenamesPboardType }]));
|
||||
|
||||
Some(window)
|
||||
})
|
||||
@@ -737,9 +798,7 @@ impl WindowDelegate {
|
||||
// SAFETY: We know that there are no parent -> child -> parent cycles since the only
|
||||
// place in `winit` where we allow making a window a child window is
|
||||
// right here, just after it's been created.
|
||||
unsafe {
|
||||
parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove)
|
||||
};
|
||||
unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) };
|
||||
},
|
||||
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
|
||||
None => (),
|
||||
@@ -780,7 +839,7 @@ impl WindowDelegate {
|
||||
saved_style: Cell::new(None),
|
||||
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
|
||||
});
|
||||
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
|
||||
let delegate: Retained<WindowDelegate> = unsafe { msg_send![super(delegate), init] };
|
||||
|
||||
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
|
||||
@@ -791,8 +850,7 @@ impl WindowDelegate {
|
||||
window.addObserver_forKeyPath_options_context(
|
||||
&delegate,
|
||||
ns_string!("effectiveAppearance"),
|
||||
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
|
||||
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
|
||||
NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
@@ -836,22 +894,22 @@ impl WindowDelegate {
|
||||
|
||||
#[track_caller]
|
||||
pub(super) fn view(&self) -> Retained<WinitView> {
|
||||
// SAFETY: The view inside WinitWindow is always `WinitView`
|
||||
unsafe { Retained::cast(self.window().contentView().unwrap()) }
|
||||
// The view inside WinitWindow should always be set and be `WinitView`.
|
||||
self.window().contentView().unwrap().downcast().unwrap()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(super) fn window(&self) -> &WinitWindow {
|
||||
pub(super) fn window(&self) -> &NSWindow {
|
||||
&self.ivars().window
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn id(&self) -> WindowId {
|
||||
self.window().id()
|
||||
window_id(self.window())
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let window_id = self.window().id();
|
||||
let window_id = window_id(self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
@@ -950,7 +1008,7 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
self.ivars().app_state.queue_redraw(self.window().id());
|
||||
self.ivars().app_state.queue_redraw(window_id(self.window()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1157,8 +1215,7 @@ impl WindowDelegate {
|
||||
// We edit the button directly instead of using `NSResizableWindowMask`,
|
||||
// since that mask also affect the resizability of the window (which is
|
||||
// controllable by other means in `winit`).
|
||||
if let Some(button) = self.window().standardWindowButton(NSWindowButton::NSWindowZoomButton)
|
||||
{
|
||||
if let Some(button) = self.window().standardWindowButton(NSWindowButton::ZoomButton) {
|
||||
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
|
||||
}
|
||||
}
|
||||
@@ -1171,7 +1228,7 @@ impl WindowDelegate {
|
||||
}
|
||||
if self
|
||||
.window()
|
||||
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
|
||||
.standardWindowButton(NSWindowButton::ZoomButton)
|
||||
.map(|b| b.isEnabled())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
@@ -1210,8 +1267,9 @@ impl WindowDelegate {
|
||||
};
|
||||
|
||||
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
|
||||
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
|
||||
.map_err(|status| os_error!(format!("CGError {status}")).into())
|
||||
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(associate_mouse_cursor) })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1233,14 +1291,12 @@ impl WindowDelegate {
|
||||
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
|
||||
let window_position = flip_window_screen_coordinates(content_rect);
|
||||
let cursor_position = cursor_position.to_logical::<CGFloat>(self.scale_factor());
|
||||
let point = core_graphics::display::CGPoint {
|
||||
let point = CGPoint {
|
||||
x: window_position.x + cursor_position.x,
|
||||
y: window_position.y + cursor_position.y,
|
||||
};
|
||||
CGDisplay::warp_mouse_cursor_position(point)
|
||||
.map_err(|status| os_error!(format!("CGError {status}")))?;
|
||||
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
|
||||
.map_err(|status| os_error!(format!("CGError {status}")))?;
|
||||
cgerr(unsafe { CGWarpMouseCursorPosition(point) })?;
|
||||
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(true) })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1407,7 +1463,7 @@ impl WindowDelegate {
|
||||
return;
|
||||
}
|
||||
},
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
Fullscreen::Exclusive(monitor, _) => monitor.clone(),
|
||||
}
|
||||
.ns_screen(mtm)
|
||||
.unwrap();
|
||||
@@ -1418,7 +1474,7 @@ impl WindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
|
||||
if let Some(Fullscreen::Exclusive(ref monitor, ref video_mode)) = fullscreen {
|
||||
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
|
||||
// same thing as we're doing here (captures the display, sets the
|
||||
// video mode, and hides the menu bar and dock), with the exception
|
||||
@@ -1431,7 +1487,7 @@ impl WindowDelegate {
|
||||
// parameter, which is not consistent with the docs saying that it
|
||||
// takes a `NSDictionary`..
|
||||
|
||||
let display_id = video_mode.monitor().native_identifier();
|
||||
let display_id = monitor.native_identifier();
|
||||
|
||||
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
|
||||
|
||||
@@ -1442,10 +1498,8 @@ impl WindowDelegate {
|
||||
unsafe {
|
||||
// Fade to black (and wait for the fade to complete) to hide the
|
||||
// flicker from capturing the display and switching display mode
|
||||
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
|
||||
== ffi::kCGErrorSuccess
|
||||
{
|
||||
ffi::CGDisplayFade(
|
||||
if cgerr(CGAcquireDisplayFadeReservation(5.0, &mut fade_token)).is_ok() {
|
||||
CGDisplayFade(
|
||||
fade_token,
|
||||
0.3,
|
||||
ffi::kCGDisplayBlendNormal,
|
||||
@@ -1453,25 +1507,27 @@ impl WindowDelegate {
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
ffi::TRUE,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
|
||||
cgerr(CGDisplayCapture(display_id)).unwrap();
|
||||
}
|
||||
|
||||
let video_mode =
|
||||
match monitor.video_modes_handles().find(|mode| &mode.mode == video_mode) {
|
||||
Some(video_mode) => video_mode,
|
||||
None => return,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let result = ffi::CGDisplaySetDisplayMode(
|
||||
display_id,
|
||||
video_mode.native_mode.0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
|
||||
cgerr(CGDisplaySetDisplayMode(display_id, Some(&video_mode.native_mode.0), None))
|
||||
.expect("failed to set video mode");
|
||||
|
||||
// After the display has been configured, fade back in
|
||||
// asynchronously
|
||||
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
|
||||
ffi::CGDisplayFade(
|
||||
CGDisplayFade(
|
||||
fade_token,
|
||||
0.6,
|
||||
ffi::kCGDisplayBlendSolidColor,
|
||||
@@ -1479,16 +1535,16 @@ impl WindowDelegate {
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
ffi::FALSE,
|
||||
false,
|
||||
);
|
||||
ffi::CGReleaseDisplayFadeReservation(fade_token);
|
||||
CGReleaseDisplayFadeReservation(fade_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.ivars().fullscreen.replace(fullscreen.clone());
|
||||
|
||||
fn toggle_fullscreen(window: &WinitWindow) {
|
||||
fn toggle_fullscreen(window: &NSWindow) {
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
@@ -1513,8 +1569,8 @@ impl WindowDelegate {
|
||||
// `window:willUseFullScreenPresentationOptions` because for some reason
|
||||
// the menu bar remains interactable despite being hidden.
|
||||
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
|
||||
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
}
|
||||
|
||||
@@ -1524,11 +1580,11 @@ impl WindowDelegate {
|
||||
// State is restored by `window_did_exit_fullscreen`
|
||||
toggle_fullscreen(self.window());
|
||||
},
|
||||
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
|
||||
restore_and_release_display(&video_mode.monitor());
|
||||
(Some(Fullscreen::Exclusive(ref monitor, _)), None) => {
|
||||
restore_and_release_display(monitor);
|
||||
toggle_fullscreen(self.window());
|
||||
},
|
||||
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
|
||||
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(..))) => {
|
||||
// If we're already in fullscreen mode, calling
|
||||
// `CGDisplayCapture` will place the shielding window on top of
|
||||
// our window, which results in a black display and is not what
|
||||
@@ -1539,24 +1595,23 @@ impl WindowDelegate {
|
||||
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
|
||||
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationOptions::FullScreen
|
||||
| NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
|
||||
let window_level = unsafe { CGShieldingWindowLevel() } as NSWindowLevel + 1;
|
||||
self.window().setLevel(window_level);
|
||||
},
|
||||
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
|
||||
(Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => {
|
||||
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
||||
NSApplicationPresentationOptions::FullScreen
|
||||
| NSApplicationPresentationOptions::AutoHideDock
|
||||
| NSApplicationPresentationOptions::AutoHideMenuBar,
|
||||
);
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
restore_and_release_display(&video_mode.monitor());
|
||||
restore_and_release_display(monitor);
|
||||
|
||||
// Restore the normal window level following the Borderless fullscreen
|
||||
// `CGShieldingWindowLevel() + 1` hack.
|
||||
@@ -1664,8 +1719,8 @@ impl WindowDelegate {
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let ns_request_type = request_type.map(|ty| match ty {
|
||||
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
|
||||
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
|
||||
UserAttentionType::Critical => NSRequestUserAttentionType::CriticalRequest,
|
||||
UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest,
|
||||
});
|
||||
if let Some(ty) = ns_request_type {
|
||||
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
|
||||
@@ -1725,7 +1780,7 @@ impl WindowDelegate {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
if available!(macos = 10.14) {
|
||||
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
|
||||
} else {
|
||||
Some(Theme::Light)
|
||||
@@ -1740,9 +1795,9 @@ impl WindowDelegate {
|
||||
#[inline]
|
||||
pub fn set_content_protected(&self, protected: bool) {
|
||||
self.window().setSharingType(if protected {
|
||||
NSWindowSharingType::NSWindowSharingNone
|
||||
NSWindowSharingType::None
|
||||
} else {
|
||||
NSWindowSharingType::NSWindowSharingReadOnly
|
||||
NSWindowSharingType::ReadOnly
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1759,8 +1814,8 @@ fn restore_and_release_display(monitor: &MonitorHandle) {
|
||||
let available_monitors = monitor::available_monitors();
|
||||
if available_monitors.contains(monitor) {
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
|
||||
CGRestorePermanentDisplayConfiguration();
|
||||
cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap();
|
||||
};
|
||||
} else {
|
||||
warn!(
|
||||
@@ -1805,9 +1860,8 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
self.ivars().is_simple_fullscreen.set(true);
|
||||
|
||||
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationOptions::AutoHideDock
|
||||
| NSApplicationPresentationOptions::AutoHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
// Hide the titlebar
|
||||
@@ -1888,10 +1942,14 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
|
||||
#[inline]
|
||||
fn select_tab_at_index(&self, index: usize) {
|
||||
if !available!(macos = 10.13) {
|
||||
tracing::warn!("window tab groups are only available on macOS 10.13+");
|
||||
return;
|
||||
}
|
||||
if let Some(group) = self.window().tabGroup() {
|
||||
if let Some(windows) = unsafe { self.window().tabbedWindows() } {
|
||||
if index < windows.len() {
|
||||
group.setSelectedWindow(Some(&windows[index]));
|
||||
group.setSelectedWindow(Some(&windows.objectAtIndex(index)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1964,9 +2022,9 @@ fn dark_appearance_name() -> &'static NSString {
|
||||
}
|
||||
|
||||
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
|
||||
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
unsafe { NSAppearanceNameAqua.copy() },
|
||||
dark_appearance_name().copy(),
|
||||
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[
|
||||
unsafe { NSAppearanceNameAqua },
|
||||
dark_appearance_name(),
|
||||
]));
|
||||
if let Some(best_match) = best_match {
|
||||
if *best_match == *dark_appearance_name() {
|
||||
|
||||
126
src/platform_impl/apple/event_loop_proxy.rs
Normal file
126
src/platform_impl/apple/event_loop_proxy.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::Arc;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopAddSource, CFRunLoopGetMain,
|
||||
CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate,
|
||||
CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
|
||||
use crate::event_loop::EventLoopProxyProvider;
|
||||
|
||||
/// A waker that signals a `CFRunLoopSource` on the main thread.
|
||||
///
|
||||
/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an
|
||||
/// atomic around that we check on each iteration of the event loop).
|
||||
///
|
||||
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct EventLoopProxy {
|
||||
source: CFRetained<CFRunLoopSource>,
|
||||
/// Cached value of `CFRunLoopGetMain`.
|
||||
main_loop: CFRetained<CFRunLoop>,
|
||||
}
|
||||
|
||||
// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`.
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl EventLoopProxy {
|
||||
/// Create a new proxy, registering it to be performed on the main thread.
|
||||
///
|
||||
/// The provided closure should call `proxy_wake_up` on the application.
|
||||
pub(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
|
||||
// We use an `Arc` here to make sure that the reference-counting of the signal container is
|
||||
// atomic (`Retained`/`CFRetained` would be valid alternatives too).
|
||||
let signaller = Arc::new(signaller);
|
||||
|
||||
unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {
|
||||
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
||||
unsafe { Arc::increment_strong_count(info.cast::<F>()) };
|
||||
info
|
||||
}
|
||||
unsafe extern "C-unwind" fn release<F>(info: *const c_void) {
|
||||
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
||||
unsafe { Arc::decrement_strong_count(info.cast::<F>()) };
|
||||
}
|
||||
|
||||
// Pointer equality / hashing.
|
||||
extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 {
|
||||
(info1 == info2) as u8
|
||||
}
|
||||
extern "C-unwind" fn hash(info: *const c_void) -> usize {
|
||||
info as usize
|
||||
}
|
||||
|
||||
// Call the provided closure.
|
||||
unsafe extern "C-unwind" fn perform<F: Fn()>(info: *mut c_void) {
|
||||
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
|
||||
let signaller = unsafe { &*info.cast::<F>() };
|
||||
(signaller)();
|
||||
}
|
||||
|
||||
// Fire last.
|
||||
let order = CFIndex::MAX - 1;
|
||||
|
||||
// This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the
|
||||
// information is copied, and not actually mutated.
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
// This is retained on creation.
|
||||
info: Arc::as_ptr(&signaller) as *mut c_void,
|
||||
retain: Some(retain::<F>),
|
||||
release: Some(release::<F>),
|
||||
copyDescription: None,
|
||||
equal: Some(equal),
|
||||
hash: Some(hash),
|
||||
schedule: None,
|
||||
cancel: None,
|
||||
perform: Some(perform::<F>),
|
||||
};
|
||||
|
||||
// SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and
|
||||
// `equal`/`hash` only access a pointer).
|
||||
//
|
||||
// Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but
|
||||
// that's okay, since we are on the main thread, and the source is only added to the main
|
||||
// run loop (below), and hence only performed there.
|
||||
//
|
||||
// Keeping the closure alive beyond this scope is fine, because `F: 'static`.
|
||||
let source = unsafe {
|
||||
let _ = mtm;
|
||||
CFRunLoopSourceCreate(None, order, &mut context).unwrap()
|
||||
};
|
||||
|
||||
// Register the source to be performed on the main thread.
|
||||
let main_loop = unsafe { CFRunLoopGetMain() }.unwrap();
|
||||
unsafe { CFRunLoopAddSource(&main_loop, Some(&source), kCFRunLoopCommonModes) };
|
||||
|
||||
Self { source, main_loop }
|
||||
}
|
||||
|
||||
// FIXME(madsmtm): Use this on macOS too.
|
||||
// More difficult there, since the user can re-start the event loop.
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
pub(crate) fn invalidate(&self) {
|
||||
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that
|
||||
// we only need to register a single source even if there's multiple proxies in use.
|
||||
unsafe { CFRunLoopSourceInvalidate(&self.source) };
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxyProvider for EventLoopProxy {
|
||||
fn wake_up(&self) {
|
||||
// Signal the source, which ends up later invoking `perform` on the main thread.
|
||||
//
|
||||
// Multiple signals in quick succession are automatically coalesced into a single signal.
|
||||
unsafe { CFRunLoopSourceSignal(&self.source) };
|
||||
|
||||
// Let the main thread know there's a new event.
|
||||
//
|
||||
// This is required since we may be (probably are) running on a different thread, and the
|
||||
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
|
||||
unsafe { CFRunLoopWakeUp(&self.main_loop) };
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod appkit;
|
||||
mod event_handler;
|
||||
mod event_loop_proxy;
|
||||
mod notification_center;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mod uikit;
|
||||
|
||||
@@ -2,7 +2,10 @@ use std::ptr::NonNull;
|
||||
|
||||
use block2::RcBlock;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2_foundation::{
|
||||
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
|
||||
};
|
||||
|
||||
/// Observe the given notification.
|
||||
///
|
||||
@@ -12,7 +15,7 @@ pub fn create_observer(
|
||||
center: &NSNotificationCenter,
|
||||
name: &NSNotificationName,
|
||||
handler: impl Fn(&NSNotification) + 'static,
|
||||
) -> Retained<NSObject> {
|
||||
) -> Retained<ProtocolObject<dyn NSObjectProtocol>> {
|
||||
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
|
||||
handler(unsafe { notification.as_ref() });
|
||||
});
|
||||
|
||||
@@ -3,28 +3,24 @@
|
||||
use std::cell::{OnceCell, RefCell, RefMut};
|
||||
use std::collections::HashSet;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use core_foundation::base::CFRelease;
|
||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::sel;
|
||||
use objc2_foundation::{
|
||||
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
|
||||
NSProcessInfo,
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopAddTimer,
|
||||
CFRunLoopGetMain, CFRunLoopTimer, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerSetNextFireDate, CGRect, CGSize,
|
||||
};
|
||||
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
|
||||
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView};
|
||||
|
||||
use super::super::event_handler::EventHandler;
|
||||
use super::super::event_loop_proxy::EventLoopProxy;
|
||||
use super::window::WinitUIWindow;
|
||||
use super::{ActiveEventLoop, EventLoopProxy};
|
||||
use super::ActiveEventLoop;
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
@@ -48,22 +44,10 @@ macro_rules! bug_assert {
|
||||
/// This is stored separately from AppState, since AppState needs to be accessible while the handler
|
||||
/// is executing.
|
||||
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
|
||||
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
|
||||
struct StaticMainThreadBound<T>(T);
|
||||
|
||||
impl<T> StaticMainThreadBound<T> {
|
||||
const fn get(&self, _mtm: MainThreadMarker) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for StaticMainThreadBound<T> {}
|
||||
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
|
||||
|
||||
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
|
||||
// of the main thread.
|
||||
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
|
||||
StaticMainThreadBound(OnceCell::new());
|
||||
static GLOBAL: MainThreadBound<OnceCell<EventHandler>> =
|
||||
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
|
||||
|
||||
GLOBAL.get(mtm).get_or_init(EventHandler::new)
|
||||
}
|
||||
@@ -118,7 +102,7 @@ pub(crate) struct AppState {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
|
||||
pub(crate) fn get_mut(mtm: MainThreadMarker) -> RefMut<'static, AppState> {
|
||||
// basically everything in UIKit requires the main thread, so it's pointless to use the
|
||||
// std::sync APIs.
|
||||
// must be mut because plain `static` requires `Sync`
|
||||
@@ -130,17 +114,21 @@ impl AppState {
|
||||
if guard.is_none() {
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
|
||||
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
|
||||
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>, mtm: MainThreadMarker) {
|
||||
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain().unwrap() });
|
||||
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
|
||||
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
|
||||
}));
|
||||
|
||||
**guard = Some(AppState {
|
||||
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
|
||||
control_flow: ControlFlow::default(),
|
||||
waker,
|
||||
event_loop_proxy: Arc::new(EventLoopProxy::new()),
|
||||
event_loop_proxy,
|
||||
queued_events: Vec::new(),
|
||||
});
|
||||
}
|
||||
init_guard(&mut guard);
|
||||
init_guard(&mut guard, mtm);
|
||||
}
|
||||
RefMut::map(guard, |state| state.as_mut().unwrap())
|
||||
}
|
||||
@@ -255,6 +243,7 @@ impl AppState {
|
||||
(ControlFlow::Wait, ControlFlow::Wait) => {
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting { start });
|
||||
self.waker.stop()
|
||||
},
|
||||
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
|
||||
if old_instant == new_instant =>
|
||||
@@ -409,13 +398,8 @@ fn handle_user_events(mtm: MainThreadMarker) {
|
||||
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
|
||||
bug!("user events attempted to be sent out while `ProcessingRedraws`");
|
||||
}
|
||||
let event_loop_proxy = this.event_loop_proxy().clone();
|
||||
drop(this);
|
||||
|
||||
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
|
||||
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let queued_events = mem::take(&mut this.queued_events);
|
||||
@@ -427,10 +411,6 @@ fn handle_user_events(mtm: MainThreadMarker) {
|
||||
for event in queued_events {
|
||||
handle_wrapped_event(mtm, event);
|
||||
}
|
||||
|
||||
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
|
||||
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,13 +420,7 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
|
||||
let mut events = Vec::new();
|
||||
#[allow(deprecated)]
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
if let Ok(window) = window.downcast::<WinitUIWindow>() {
|
||||
events.push(EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::Occluded(occluded),
|
||||
@@ -510,13 +484,7 @@ pub(crate) fn terminated(application: &UIApplication) {
|
||||
let mut events = Vec::new();
|
||||
#[allow(deprecated)]
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
if let Ok(window) = window.downcast::<WinitUIWindow>() {
|
||||
events.push(EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::Destroyed,
|
||||
@@ -530,6 +498,10 @@ pub(crate) fn terminated(application: &UIApplication) {
|
||||
drop(this);
|
||||
|
||||
get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
|
||||
|
||||
let this = AppState::get_mut(mtm);
|
||||
// Prevent EventLoopProxy from firing again.
|
||||
this.event_loop_proxy.invalidate();
|
||||
}
|
||||
|
||||
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
|
||||
@@ -569,46 +541,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
|
||||
}
|
||||
|
||||
struct EventLoopWaker {
|
||||
timer: CFRunLoopTimerRef,
|
||||
timer: CFRetained<CFRunLoopTimer>,
|
||||
}
|
||||
|
||||
impl Drop for EventLoopWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopTimerInvalidate(self.timer);
|
||||
CFRelease(self.timer as _);
|
||||
CFRunLoopTimerInvalidate(&self.timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
fn new(rl: CFRetained<CFRunLoop>) -> EventLoopWaker {
|
||||
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
||||
// It is initially setup with a first fire time really far into the
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
wakeup_main_loop,
|
||||
Some(wakeup_main_loop),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddTimer(&rl, Some(&timer), kCFRunLoopCommonModes);
|
||||
|
||||
EventLoopWaker { timer }
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) }
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) }
|
||||
}
|
||||
|
||||
fn start_at(&mut self, instant: Instant) {
|
||||
@@ -621,94 +593,8 @@ impl EventLoopWaker {
|
||||
let duration = instant - now;
|
||||
let fsecs =
|
||||
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
|
||||
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
|
||||
CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! os_capabilities {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$error_name:ident: $objc_call:literal,
|
||||
$name:ident: $major:literal-$minor:literal
|
||||
),*
|
||||
$(,)*
|
||||
) => {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OSCapabilities {
|
||||
$(
|
||||
pub $name: bool,
|
||||
)*
|
||||
|
||||
os_version: NSOperatingSystemVersion,
|
||||
}
|
||||
|
||||
impl OSCapabilities {
|
||||
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
|
||||
$(let $name = meets_requirements(os_version, $major, $minor);)*
|
||||
Self { $($name,)* os_version, }
|
||||
}
|
||||
}
|
||||
|
||||
impl OSCapabilities {$(
|
||||
$(#[$attr])*
|
||||
pub fn $error_name(&self, extra_msg: &str) {
|
||||
tracing::warn!(
|
||||
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
|
||||
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
|
||||
extra_msg
|
||||
)
|
||||
}
|
||||
)*}
|
||||
};
|
||||
}
|
||||
|
||||
os_capabilities! {
|
||||
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
|
||||
#[allow(unused)] // error message unused
|
||||
safe_area_err_msg: "-[UIView safeAreaInsets]",
|
||||
safe_area: 11-0,
|
||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
|
||||
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
|
||||
home_indicator_hidden: 11-0,
|
||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
|
||||
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
|
||||
defer_system_gestures: 11-0,
|
||||
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
|
||||
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
|
||||
maximum_frames_per_second: 10-3,
|
||||
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
|
||||
#[allow(unused)] // error message unused
|
||||
force_touch_err_msg: "-[UITouch force]",
|
||||
force_touch: 9-0,
|
||||
}
|
||||
|
||||
fn meets_requirements(
|
||||
version: NSOperatingSystemVersion,
|
||||
required_major: NSInteger,
|
||||
required_minor: NSInteger,
|
||||
) -> bool {
|
||||
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
|
||||
}
|
||||
|
||||
fn get_version() -> NSOperatingSystemVersion {
|
||||
let process_info = NSProcessInfo::processInfo();
|
||||
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
|
||||
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
|
||||
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
|
||||
// not support debugging on devices with an iOS version of less than 8. Another example, in
|
||||
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
|
||||
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
|
||||
process_info.operatingSystemVersion()
|
||||
}
|
||||
|
||||
pub fn os_capabilities() -> OSCapabilities {
|
||||
// Cache the version lookup for efficiency
|
||||
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
|
||||
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use core_foundation::base::{CFIndex, CFRelease};
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{msg_send, ClassType, MainThreadMarker};
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopDefaultMode, CFIndex, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopGetMain,
|
||||
CFRunLoopObserver, CFRunLoopObserverCreate,
|
||||
};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UIApplicationDidBecomeActiveNotification,
|
||||
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
|
||||
@@ -29,8 +26,7 @@ use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, NotSupportedError, RequestError};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
|
||||
OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::platform_impl::Window;
|
||||
@@ -128,13 +124,13 @@ pub struct EventLoop {
|
||||
// system instead cleans it up next time it would have posted a notification to it.
|
||||
//
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<NSObject>,
|
||||
_did_become_active_observer: Retained<NSObject>,
|
||||
_will_resign_active_observer: Retained<NSObject>,
|
||||
_will_enter_foreground_observer: Retained<NSObject>,
|
||||
_did_enter_background_observer: Retained<NSObject>,
|
||||
_will_terminate_observer: Retained<NSObject>,
|
||||
_did_receive_memory_warning_observer: Retained<NSObject>,
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_become_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_resign_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_enter_foreground_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -189,9 +185,9 @@ impl EventLoop {
|
||||
let app = unsafe { notification.object() }.expect(
|
||||
"UIApplicationWillEnterForegroundNotification to have application object",
|
||||
);
|
||||
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
|
||||
// documented to be `UIApplication`.
|
||||
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||
// The `object` in `UIApplicationWillEnterForegroundNotification` is documented to
|
||||
// be `UIApplication`.
|
||||
let app = app.downcast::<UIApplication>().unwrap();
|
||||
send_occluded_event_for_all_windows(&app, false);
|
||||
},
|
||||
);
|
||||
@@ -203,9 +199,9 @@ impl EventLoop {
|
||||
let app = unsafe { notification.object() }.expect(
|
||||
"UIApplicationDidEnterBackgroundNotification to have application object",
|
||||
);
|
||||
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
|
||||
// documented to be `UIApplication`.
|
||||
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||
// The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be
|
||||
// `UIApplication`.
|
||||
let app = app.downcast::<UIApplication>().unwrap();
|
||||
send_occluded_event_for_all_windows(&app, true);
|
||||
},
|
||||
);
|
||||
@@ -216,9 +212,9 @@ impl EventLoop {
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }
|
||||
.expect("UIApplicationWillTerminateNotification to have application object");
|
||||
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
|
||||
// (somewhat) documented to be `UIApplication`.
|
||||
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||
// The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented
|
||||
// to be `UIApplication`.
|
||||
let app = app.downcast::<UIApplication>().unwrap();
|
||||
app_state::terminated(&app);
|
||||
},
|
||||
);
|
||||
@@ -244,7 +240,7 @@ impl EventLoop {
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
|
||||
let application: Option<Retained<UIApplication>> =
|
||||
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
|
||||
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
||||
assert!(
|
||||
application.is_none(),
|
||||
"\
|
||||
@@ -277,78 +273,19 @@ impl EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy {
|
||||
pub(crate) wake_up: AtomicBool,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl Drop for EventLoopProxy {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopSourceInvalidate(self.source);
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub(crate) fn new() -> EventLoopProxy {
|
||||
unsafe {
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
// process user events through the normal OS EventLoop mechanisms.
|
||||
let rl = CFRunLoopGetMain();
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
info: ptr::null_mut(),
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
equal: None,
|
||||
hash: None,
|
||||
schedule: None,
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
EventLoopProxy { wake_up: AtomicBool::new(false), source }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxyProvider for EventLoopProxy {
|
||||
fn wake_up(&self) {
|
||||
self.wake_up.store(true, AtomicOrdering::Relaxed);
|
||||
unsafe {
|
||||
// let the main thread know there's a new event
|
||||
CFRunLoopSourceSignal(self.source);
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_control_flow_observers() {
|
||||
unsafe {
|
||||
// begin is queued with the highest priority to ensure it is processed before other
|
||||
// observers
|
||||
extern "C" fn control_flow_begin_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -364,65 +301,68 @@ fn setup_control_flow_observers() {
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
//
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
extern "C" fn control_flow_main_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_main_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
kCFRunLoopExit => {}, // may happen when running on macOS
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
kCFRunLoopExit => {}, // may happen when running on macOS
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoopGetMain();
|
||||
let main_loop = CFRunLoopGetMain().unwrap();
|
||||
|
||||
let begin_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopAfterWaiting,
|
||||
1, // repeat = true
|
||||
None,
|
||||
CFRunLoopActivity::AfterWaiting.0,
|
||||
true,
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
Some(control_flow_begin_handler),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddObserver(&main_loop, Some(&begin_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let main_end_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
0, // see comment on `control_flow_main_end_handler`
|
||||
control_flow_main_end_handler,
|
||||
Some(control_flow_main_end_handler),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddObserver(&main_loop, Some(&main_end_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let end_observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
Some(control_flow_end_handler),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddObserver(&main_loop, Some(&end_observer), kCFRunLoopDefaultMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ mod window;
|
||||
use std::fmt;
|
||||
|
||||
pub(crate) use self::event_loop::{
|
||||
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
|
||||
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
|
||||
pub(crate) use self::monitor::MonitorHandle;
|
||||
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
|
||||
pub(crate) use crate::cursor::{
|
||||
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
|
||||
@@ -20,9 +20,6 @@ pub(crate) use crate::cursor::{
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OsError {}
|
||||
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::collections::VecDeque;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{fmt, hash, ptr};
|
||||
|
||||
use objc2::mutability::IsRetainable;
|
||||
use dispatch2::{run_on_main, MainThreadBound};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::Message;
|
||||
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
|
||||
use objc2::{available, MainThreadMarker, Message};
|
||||
use objc2_foundation::NSInteger;
|
||||
use objc2_ui_kit::{UIScreen, UIScreenMode};
|
||||
|
||||
use super::app_state;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::monitor::VideoMode;
|
||||
|
||||
// Workaround for `MainThreadBound` implementing almost no traits
|
||||
#[derive(Debug)]
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
|
||||
|
||||
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
@@ -32,7 +31,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
@@ -40,14 +39,12 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
impl<T: Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) mode: VideoMode,
|
||||
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
@@ -58,30 +55,18 @@ impl VideoModeHandle {
|
||||
) -> VideoModeHandle {
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
||||
let size = screen_mode.size();
|
||||
VideoModeHandle {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
let mode = VideoMode {
|
||||
size: (size.width as u32, size.height as u32).into(),
|
||||
bit_depth: None,
|
||||
refresh_rate_millihertz,
|
||||
};
|
||||
|
||||
VideoModeHandle {
|
||||
mode,
|
||||
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
|
||||
monitor: MonitorHandle::new(uiscreen),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
|
||||
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> {
|
||||
self.screen_mode.0.get(mtm)
|
||||
}
|
||||
@@ -164,7 +149,7 @@ impl MonitorHandle {
|
||||
#[allow(deprecated)]
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == &**self.ui_screen(mtm))
|
||||
.position(|rhs| rhs == *self.ui_screen(mtm))
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
})
|
||||
@@ -179,52 +164,53 @@ impl MonitorHandle {
|
||||
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
Some(run_on_main(|mtm| {
|
||||
VideoModeHandle::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).currentMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
.mode
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
pub fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
run_on_main(|mtm| {
|
||||
let ui_screen = self.ui_screen(mtm);
|
||||
// Use Ord impl of RootVideoModeHandle
|
||||
|
||||
let modes: BTreeSet<_> = ui_screen
|
||||
ui_screen
|
||||
.availableModes()
|
||||
.into_iter()
|
||||
.map(|mode| RootVideoModeHandle {
|
||||
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm),
|
||||
})
|
||||
.collect();
|
||||
|
||||
modes.into_iter().map(|mode| mode.video_mode)
|
||||
.map(|mode| VideoModeHandle::new(ui_screen.clone(), mode, mtm))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.video_modes_handles().map(|handle| handle.mode)
|
||||
}
|
||||
|
||||
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
|
||||
self.ui_screen.get(mtm)
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoModeHandle {
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
run_on_main(|mtm| {
|
||||
VideoModeHandle::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).preferredMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
.mode
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
|
||||
let refresh_rate_millihertz: NSInteger = {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.maximum_frames_per_second {
|
||||
if available!(ios = 10.3, tvos = 10.2) {
|
||||
uiscreen.maximumFramesPerSecond()
|
||||
} else {
|
||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||
@@ -237,7 +223,9 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
|
||||
//
|
||||
// FIXME: earlier OSs could calculate the refresh rate using
|
||||
// `-[CADisplayLink duration]`.
|
||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
||||
tracing::warn!(
|
||||
"`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps"
|
||||
);
|
||||
60
|
||||
}
|
||||
};
|
||||
@@ -266,7 +254,7 @@ mod tests {
|
||||
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
|
||||
|
||||
let main = UIScreen::mainScreen(mtm);
|
||||
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
|
||||
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(&*screen, &*main)));
|
||||
|
||||
assert!(unsafe {
|
||||
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
|
||||
|
||||
@@ -3,8 +3,9 @@ use std::cell::{Cell, RefCell};
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
|
||||
use objc2::{available, define_class, msg_send, sel, DefinedClass, MainThreadMarker};
|
||||
use objc2_core_foundation::{CGFloat, CGPoint, CGRect};
|
||||
use objc2_foundation::{NSObject, NSSet, NSString};
|
||||
use objc2_ui_kit::{
|
||||
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
|
||||
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
|
||||
@@ -21,7 +22,6 @@ use crate::event::{
|
||||
WindowEvent,
|
||||
};
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
pub struct WinitViewState {
|
||||
@@ -39,36 +39,26 @@ pub struct WinitViewState {
|
||||
fingers: Cell<u8>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(UIView, UIResponder, NSObject))]
|
||||
#[name = "WinitUIView"]
|
||||
#[ivars = WinitViewState]
|
||||
pub(crate) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIView;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = WinitViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(drawRect:)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let window = self.window().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
},
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
|
||||
}
|
||||
|
||||
#[method(layoutSubviews)]
|
||||
#[unsafe(method(layoutSubviews))]
|
||||
fn layout_subviews(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
|
||||
@@ -82,16 +72,13 @@ declare_class!(
|
||||
.to_physical(scale_factor);
|
||||
|
||||
let window = self.window().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::SurfaceResized(size),
|
||||
},
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::SurfaceResized(size),
|
||||
});
|
||||
}
|
||||
|
||||
#[method(setContentScaleFactor:)]
|
||||
#[unsafe(method(setContentScaleFactor:))]
|
||||
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () =
|
||||
@@ -124,49 +111,46 @@ declare_class!(
|
||||
let window_id = window.id();
|
||||
app_state::handle_nonuser_events(
|
||||
mtm,
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(
|
||||
app_state::ScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
},
|
||||
))
|
||||
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
|
||||
window,
|
||||
scale_factor,
|
||||
suggested_size: size.to_physical(scale_factor),
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::Window {
|
||||
window_id,
|
||||
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
|
||||
},
|
||||
)),
|
||||
window_id,
|
||||
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
#[method(safeAreaInsetsDidChange)]
|
||||
#[unsafe(method(safeAreaInsetsDidChange))]
|
||||
fn safe_area_changed(&self) {
|
||||
debug!("safeAreaInsetsDidChange was called, requesting redraw");
|
||||
// When the safe area changes we want to make sure to emit a redraw event
|
||||
self.setNeedsDisplay();
|
||||
}
|
||||
|
||||
#[method(touchesBegan:withEvent:)]
|
||||
#[unsafe(method(touchesBegan:withEvent:))]
|
||||
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(touchesMoved:withEvent:)]
|
||||
#[unsafe(method(touchesMoved:withEvent:))]
|
||||
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(touchesEnded:withEvent:)]
|
||||
#[unsafe(method(touchesEnded:withEvent:))]
|
||||
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(touchesCancelled:withEvent:)]
|
||||
#[unsafe(method(touchesCancelled:withEvent:))]
|
||||
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[method(pinchGesture:)]
|
||||
#[unsafe(method(pinchGesture:))]
|
||||
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
@@ -174,46 +158,40 @@ declare_class!(
|
||||
UIGestureRecognizerState::Began => {
|
||||
self.ivars().pinch_last_delta.set(recognizer.scale());
|
||||
(TouchPhase::Started, 0.0)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Changed => {
|
||||
let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale());
|
||||
(TouchPhase::Moved, recognizer.scale() - last_scale)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Ended => {
|
||||
let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0);
|
||||
(TouchPhase::Moved, recognizer.scale() - last_scale)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
||||
self.ivars().rotation_last_delta.set(0.0);
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -recognizer.scale())
|
||||
}
|
||||
},
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::PinchGesture {
|
||||
device_id: None,
|
||||
delta: delta as f64,
|
||||
phase,
|
||||
},
|
||||
event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase },
|
||||
};
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
|
||||
#[method(doubleTapGesture:)]
|
||||
#[unsafe(method(doubleTapGesture:))]
|
||||
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
if recognizer.state() == UIGestureRecognizerState::Ended {
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::DoubleTapGesture {
|
||||
device_id: None,
|
||||
},
|
||||
event: WindowEvent::DoubleTapGesture { device_id: None },
|
||||
};
|
||||
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
@@ -221,7 +199,7 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
#[method(rotationGesture:)]
|
||||
#[unsafe(method(rotationGesture:))]
|
||||
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
@@ -230,23 +208,24 @@ declare_class!(
|
||||
self.ivars().rotation_last_delta.set(0.0);
|
||||
|
||||
(TouchPhase::Started, 0.0)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Changed => {
|
||||
let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation());
|
||||
let last_rotation =
|
||||
self.ivars().rotation_last_delta.replace(recognizer.rotation());
|
||||
|
||||
(TouchPhase::Moved, recognizer.rotation() - last_rotation)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Ended => {
|
||||
let last_rotation = self.ivars().rotation_last_delta.replace(0.0);
|
||||
|
||||
(TouchPhase::Ended, recognizer.rotation() - last_rotation)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
||||
self.ivars().rotation_last_delta.set(0.0);
|
||||
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -recognizer.rotation())
|
||||
}
|
||||
},
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
@@ -264,7 +243,7 @@ declare_class!(
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
|
||||
#[method(panGesture:)]
|
||||
#[unsafe(method(panGesture:))]
|
||||
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
@@ -275,7 +254,7 @@ declare_class!(
|
||||
self.ivars().pan_last_delta.set(translation);
|
||||
|
||||
(TouchPhase::Started, 0.0, 0.0)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Changed => {
|
||||
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation);
|
||||
|
||||
@@ -283,25 +262,26 @@ declare_class!(
|
||||
let dy = translation.y - last_pan.y;
|
||||
|
||||
(TouchPhase::Moved, dx, dy)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Ended => {
|
||||
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
|
||||
let last_pan: CGPoint =
|
||||
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
|
||||
|
||||
let dx = translation.x - last_pan.x;
|
||||
let dy = translation.y - last_pan.y;
|
||||
|
||||
(TouchPhase::Ended, dx, dy)
|
||||
}
|
||||
},
|
||||
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
|
||||
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
|
||||
let last_pan: CGPoint =
|
||||
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
|
||||
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
|
||||
}
|
||||
},
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
|
||||
let gesture_event = EventWrapper::Window {
|
||||
window_id: window.id(),
|
||||
event: WindowEvent::PanGesture {
|
||||
@@ -315,7 +295,7 @@ declare_class!(
|
||||
app_state::handle_nonuser_event(mtm, gesture_event);
|
||||
}
|
||||
|
||||
#[method(canBecomeFirstResponder)]
|
||||
#[unsafe(method(canBecomeFirstResponder))]
|
||||
fn can_become_first_responder(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -324,27 +304,30 @@ declare_class!(
|
||||
unsafe impl NSObjectProtocol for WinitView {}
|
||||
|
||||
unsafe impl UIGestureRecognizerDelegate for WinitView {
|
||||
#[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
|
||||
fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool {
|
||||
#[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))]
|
||||
fn should_recognize_simultaneously(
|
||||
&self,
|
||||
_gesture_recognizer: &UIGestureRecognizer,
|
||||
_other_gesture_recognizer: &UIGestureRecognizer,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl UITextInputTraits for WinitView {
|
||||
}
|
||||
unsafe impl UITextInputTraits for WinitView {}
|
||||
|
||||
unsafe impl UIKeyInput for WinitView {
|
||||
#[method(hasText)]
|
||||
#[unsafe(method(hasText))]
|
||||
fn has_text(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[method(insertText:)]
|
||||
#[unsafe(method(insertText:))]
|
||||
fn insert_text(&self, text: &NSString) {
|
||||
self.handle_insert_text(text)
|
||||
}
|
||||
|
||||
#[method(deleteBackward)]
|
||||
#[unsafe(method(deleteBackward))]
|
||||
fn delete_backward(&self) {
|
||||
self.handle_delete_backward()
|
||||
}
|
||||
@@ -370,7 +353,7 @@ impl WinitView {
|
||||
primary_finger: Cell::new(None),
|
||||
fingers: Cell::new(0),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
|
||||
|
||||
this.setMultipleTouchEnabled(true);
|
||||
|
||||
@@ -382,8 +365,8 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn window(&self) -> Option<Retained<WinitUIWindow>> {
|
||||
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
|
||||
(**self).window().map(|window| unsafe { Retained::cast(window) })
|
||||
// `WinitView`s should always be installed in a `WinitUIWindow`
|
||||
(**self).window().map(|window| window.downcast().unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
||||
@@ -478,13 +461,12 @@ impl WinitView {
|
||||
fn handle_touches(&self, touches: &NSSet<UITouch>) {
|
||||
let window = self.window().unwrap();
|
||||
let mut touch_events = Vec::new();
|
||||
let os_supports_force = app_state::os_capabilities().force_touch;
|
||||
for touch in touches {
|
||||
let logical_location = touch.locationInView(None);
|
||||
let touch_type = touch.r#type();
|
||||
let force = if let UITouchType::Pencil = touch_type {
|
||||
None
|
||||
} else if os_supports_force {
|
||||
} else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
|
||||
let trait_collection = self.traitCollection();
|
||||
let touch_capability = trait_collection.forceTouchCapability();
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
@@ -501,7 +483,7 @@ impl WinitView {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let touch_id = touch as *const UITouch as usize;
|
||||
let touch_id = Retained::as_ptr(&touch) as usize;
|
||||
let phase = touch.phase();
|
||||
let position = {
|
||||
let scale_factor = self.contentScaleFactor();
|
||||
@@ -661,7 +643,12 @@ impl WinitView {
|
||||
repeat: false,
|
||||
logical_key: Key::Character(text.clone()),
|
||||
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
|
||||
platform_specific: KeyEventExtra {},
|
||||
text_with_all_modifiers: if state == ElementState::Pressed {
|
||||
Some(text.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
key_without_modifiers: Key::Character(text.clone()),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
@@ -684,10 +671,11 @@ impl WinitView {
|
||||
state,
|
||||
logical_key: Key::Named(NamedKey::Backspace),
|
||||
physical_key: PhysicalKey::Code(KeyCode::Backspace),
|
||||
platform_specific: KeyEventExtra {},
|
||||
repeat: false,
|
||||
location: KeyLocation::Standard,
|
||||
text: None,
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: Key::Named(NamedKey::Backspace),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker};
|
||||
use objc2_foundation::NSObject;
|
||||
use objc2_ui_kit::{
|
||||
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
|
||||
UIUserInterfaceIdiom, UIView, UIViewController,
|
||||
};
|
||||
|
||||
use super::app_state::{self};
|
||||
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
@@ -20,51 +19,42 @@ pub struct ViewControllerState {
|
||||
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(UIViewController, UIResponder, NSObject))]
|
||||
#[name = "WinitUIViewController"]
|
||||
#[ivars = ViewControllerState]
|
||||
pub(crate) struct WinitViewController;
|
||||
|
||||
unsafe impl ClassType for WinitViewController {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIViewController;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIViewController";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitViewController {
|
||||
type Ivars = ViewControllerState;
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[method(shouldAutorotate)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitViewController {
|
||||
#[unsafe(method(shouldAutorotate))]
|
||||
fn should_autorotate(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[method(prefersStatusBarHidden)]
|
||||
#[unsafe(method(prefersStatusBarHidden))]
|
||||
fn prefers_status_bar_hidden(&self) -> bool {
|
||||
self.ivars().prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[method(preferredStatusBarStyle)]
|
||||
#[unsafe(method(preferredStatusBarStyle))]
|
||||
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
|
||||
self.ivars().preferred_status_bar_style.get()
|
||||
}
|
||||
|
||||
#[method(prefersHomeIndicatorAutoHidden)]
|
||||
#[unsafe(method(prefersHomeIndicatorAutoHidden))]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
self.ivars().prefers_home_indicator_auto_hidden.get()
|
||||
}
|
||||
|
||||
#[method(supportedInterfaceOrientations)]
|
||||
#[unsafe(method(supportedInterfaceOrientations))]
|
||||
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
|
||||
self.ivars().supported_orientations.get()
|
||||
}
|
||||
|
||||
#[method(preferredScreenEdgesDeferringSystemGestures)]
|
||||
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
|
||||
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
|
||||
self.ivars()
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.get()
|
||||
self.ivars().preferred_screen_edges_deferring_system_gestures.get()
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -87,11 +77,13 @@ impl WinitViewController {
|
||||
|
||||
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
|
||||
self.ivars().prefers_home_indicator_auto_hidden.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
if available!(ios = 11.0, visionos = 1.0) {
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
} else {
|
||||
os_capabilities.home_indicator_hidden_err_msg("ignoring")
|
||||
tracing::warn!(
|
||||
"`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \
|
||||
Ignoring"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +93,13 @@ impl WinitViewController {
|
||||
UIRectEdge(val.bits().into())
|
||||
};
|
||||
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.defer_system_gestures {
|
||||
if available!(ios = 11.0, visionos = 1.0) {
|
||||
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
|
||||
} else {
|
||||
os_capabilities.defer_system_gestures_err_msg("ignoring")
|
||||
tracing::warn!(
|
||||
"`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \
|
||||
visionOS. Ignoring"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +140,7 @@ impl WinitViewController {
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
|
||||
|
||||
this.set_prefers_status_bar_hidden(
|
||||
window_attributes.platform_specific.prefers_status_bar_hidden,
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{
|
||||
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol,
|
||||
};
|
||||
use objc2::{available, class, define_class, msg_send, MainThreadMarker};
|
||||
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
|
||||
use objc2_foundation::{NSObject, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
|
||||
UIScreenOverscanCompensation, UIViewController, UIWindow,
|
||||
@@ -32,43 +32,31 @@ use crate::window::{
|
||||
WindowAttributes, WindowButtons, WindowId, WindowLevel,
|
||||
};
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(UIWindow, UIResponder, NSObject))]
|
||||
#[name = "WinitUIWindow"]
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct WinitUIWindow;
|
||||
|
||||
unsafe impl ClassType for WinitUIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIWindow;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitUIWindow {}
|
||||
|
||||
unsafe impl WinitUIWindow {
|
||||
#[method(becomeKeyWindow)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitUIWindow {
|
||||
#[unsafe(method(becomeKeyWindow))]
|
||||
fn become_key_window(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::Focused(true),
|
||||
},
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::Focused(true),
|
||||
});
|
||||
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
|
||||
}
|
||||
|
||||
#[method(resignKeyWindow)]
|
||||
#[unsafe(method(resignKeyWindow))]
|
||||
fn resign_key_window(&self) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::Focused(false),
|
||||
},
|
||||
);
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::Focused(false),
|
||||
});
|
||||
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
|
||||
}
|
||||
}
|
||||
@@ -86,15 +74,18 @@ impl WinitUIWindow {
|
||||
// into very confusing issues with the window not being properly activated.
|
||||
//
|
||||
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
||||
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
|
||||
let this: Retained<Self> = unsafe { msg_send![mtm.alloc(), initWithFrame: frame] };
|
||||
|
||||
this.setRootViewController(Some(view_controller));
|
||||
|
||||
match window_attributes.fullscreen.clone().map(Into::into) {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let monitor = video_mode.monitor();
|
||||
Some(Fullscreen::Exclusive(ref monitor, ref video_mode)) => {
|
||||
let screen = monitor.ui_screen(mtm);
|
||||
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
if let Some(video_mode) =
|
||||
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
|
||||
{
|
||||
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
}
|
||||
this.setScreen(screen);
|
||||
},
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => {
|
||||
@@ -205,8 +196,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub fn safe_area(&self) -> PhysicalInsets<u32> {
|
||||
// Only available on iOS 11.0
|
||||
let insets = if app_state::os_capabilities().safe_area {
|
||||
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
|
||||
self.view.safeAreaInsets()
|
||||
} else {
|
||||
// Assume the status bar frame is the only thing that obscures the view
|
||||
@@ -312,9 +302,13 @@ impl Inner {
|
||||
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let uiscreen = match &monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.monitor.ui_screen(mtm);
|
||||
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
Some(Fullscreen::Exclusive(monitor, video_mode)) => {
|
||||
let uiscreen = monitor.ui_screen(mtm);
|
||||
if let Some(video_mode) =
|
||||
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
|
||||
{
|
||||
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
|
||||
}
|
||||
uiscreen.clone()
|
||||
},
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
|
||||
@@ -489,7 +483,7 @@ impl Window {
|
||||
let main_screen = UIScreen::mainScreen(mtm);
|
||||
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
|
||||
let screen = match fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Exclusive(ref monitor, _)) => monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
|
||||
Some(Fullscreen::Borderless(None)) | None => &main_screen,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@ use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle
|
||||
|
||||
use crate::event::{ElementState, KeyEvent};
|
||||
use crate::keyboard::{Key, KeyLocation};
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
use crate::utils::Lazy;
|
||||
|
||||
mod compose;
|
||||
@@ -198,9 +197,16 @@ impl KeyContext<'_> {
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
let text_with_all_modifiers = event.text_with_all_modifiers();
|
||||
|
||||
let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers };
|
||||
|
||||
KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific }
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
location,
|
||||
state,
|
||||
repeat,
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
|
||||
|
||||
@@ -4,26 +4,23 @@
|
||||
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
||||
|
||||
use std::env;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::time::Duration;
|
||||
#[cfg(x11_platform)]
|
||||
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
#[cfg(x11_platform)]
|
||||
use self::x11::{XConnection, XError, XNotSupported};
|
||||
use crate::application::ApplicationHandler;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::dpi::Size;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::error::{EventLoopError, NotSupportedError};
|
||||
use crate::event_loop::ActiveEventLoop;
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
use crate::keyboard::Key;
|
||||
use crate::monitor::VideoMode;
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
|
||||
@@ -165,52 +162,16 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum VideoModeHandle {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::VideoModeHandle),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::VideoModeHandle),
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.size())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
pub key_without_modifiers: Key,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) enum PlatformCustomCursor {
|
||||
#[cfg(wayland_platform)]
|
||||
@@ -329,9 +290,9 @@ impl EventLoop {
|
||||
// Create the display based on the backend.
|
||||
match backend {
|
||||
#[cfg(wayland_platform)]
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread(),
|
||||
#[cfg(x11_platform)]
|
||||
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
|
||||
Backend::X => EventLoop::new_x11_any_thread(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::application::ApplicationHandler;
|
||||
use crate::cursor::OnlyCursorImage;
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::error::{EventLoopError, OsError, RequestError};
|
||||
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event::{DeviceEvent, StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
@@ -38,6 +38,12 @@ pub use crate::event_loop::EventLoopProxy as CoreEventLoopProxy;
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Event {
|
||||
WindowEvent { window_id: WindowId, event: WindowEvent },
|
||||
DeviceEvent { event: DeviceEvent },
|
||||
}
|
||||
|
||||
/// The Wayland event loop.
|
||||
pub struct EventLoop {
|
||||
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
|
||||
@@ -383,10 +389,9 @@ impl EventLoop {
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
app.window_event(&self.active_event_loop, window_id, event)
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
app.device_event(&self.active_event_loop, device_id, event)
|
||||
Event::DeviceEvent { event } => {
|
||||
app.device_event(&self.active_event_loop, None, event)
|
||||
},
|
||||
_ => unreachable!("event which is neither device nor window event."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,10 +404,9 @@ impl EventLoop {
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
app.window_event(&self.active_event_loop, window_id, event)
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
app.device_event(&self.active_event_loop, device_id, event)
|
||||
Event::DeviceEvent { event } => {
|
||||
app.device_event(&self.active_event_loop, None, event)
|
||||
},
|
||||
_ => unreachable!("event which is neither device nor window event."),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
use std::vec::Drain;
|
||||
|
||||
use crate::event::{DeviceEvent, Event, WindowEvent};
|
||||
use super::Event;
|
||||
use crate::event::{DeviceEvent, WindowEvent};
|
||||
use crate::window::WindowId;
|
||||
|
||||
/// An event loop's sink to deliver events from the Wayland event callbacks
|
||||
@@ -26,7 +27,7 @@ impl EventSink {
|
||||
/// Add new device event to a queue.
|
||||
#[inline]
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent) {
|
||||
self.window_events.push(Event::DeviceEvent { event, device_id: None });
|
||||
self.window_events.push(Event::DeviceEvent { event });
|
||||
}
|
||||
|
||||
/// Add new window event to a queue.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
pub use event_loop::{ActiveEventLoop, EventLoop};
|
||||
pub use output::{MonitorHandle, VideoModeHandle};
|
||||
pub use output::MonitorHandle;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
pub use window::Window;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use sctk::output::{Mode, OutputData};
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition};
|
||||
use crate::monitor::VideoMode;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MonitorHandle {
|
||||
@@ -54,27 +54,19 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| {
|
||||
let mode = info.modes.iter().find(|mode| mode.current).cloned();
|
||||
|
||||
mode.map(|mode| {
|
||||
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode))
|
||||
})
|
||||
mode.map(wayland_mode_to_core_mode)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
let modes = output_data.with_output_info(|info| info.modes.clone());
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode))
|
||||
})
|
||||
modes.into_iter().map(wayland_mode_to_core_mode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,38 +96,11 @@ impl std::hash::Hash for MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(monitor: MonitorHandle, mode: Mode) -> Self {
|
||||
VideoModeHandle {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
|
||||
monitor: monitor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
/// Convert Wayland's [`Mode`] to winit's [`VideoMode`].
|
||||
fn wayland_mode_to_core_mode(mode: Mode) -> VideoMode {
|
||||
VideoMode {
|
||||
size: (mode.dimensions.0, mode.dimensions.1).into(),
|
||||
bit_depth: None,
|
||||
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@ use sctk::seat::pointer::{
|
||||
use sctk::seat::SeatState;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition};
|
||||
use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent};
|
||||
use crate::event::{
|
||||
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::{self, WindowId};
|
||||
|
||||
@@ -119,11 +119,15 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Clear preedit at the start of `Done`.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
// Clear preedit, unless all we'll be doing next is sending a new preedit.
|
||||
if text_input_data.pending_commit.is_some()
|
||||
|| text_input_data.pending_preedit.is_none()
|
||||
{
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = text_input_data.pending_commit.take() {
|
||||
|
||||
@@ -78,7 +78,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
|
||||
state.events_sink.push_window_event(
|
||||
crate::event::WindowEvent::ActivationTokenDone {
|
||||
serial: *serial,
|
||||
token: ActivationToken::_new(token),
|
||||
token: ActivationToken::from_raw(token),
|
||||
},
|
||||
*window_id,
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ impl Window {
|
||||
|
||||
// Set startup mode.
|
||||
match attributes.fullscreen.map(Into::into) {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
Some(Fullscreen::Exclusive(..)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
},
|
||||
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
|
||||
@@ -165,7 +165,7 @@ impl Window {
|
||||
if let (Some(xdg_activation), Some(token)) =
|
||||
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
|
||||
{
|
||||
xdg_activation.activate(token._token, &surface);
|
||||
xdg_activation.activate(token.token, &surface);
|
||||
}
|
||||
|
||||
// XXX Do initial commit.
|
||||
@@ -438,7 +438,7 @@ impl CoreWindow for Window {
|
||||
|
||||
fn set_fullscreen(&self, fullscreen: Option<CoreFullscreen>) {
|
||||
match fullscreen {
|
||||
Some(CoreFullscreen::Exclusive(_)) => {
|
||||
Some(CoreFullscreen::Exclusive(..)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
},
|
||||
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::str::Utf8Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dpi::PhysicalPosition;
|
||||
use percent_encoding::percent_decode;
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt};
|
||||
|
||||
@@ -45,13 +46,25 @@ pub struct Dnd {
|
||||
pub type_list: Option<Vec<xproto::Atom>>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub source_window: Option<xproto::Window>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub position: PhysicalPosition<f64>,
|
||||
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
|
||||
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
|
||||
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
|
||||
pub dragging: bool,
|
||||
}
|
||||
|
||||
impl Dnd {
|
||||
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
|
||||
Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None })
|
||||
Ok(Dnd {
|
||||
xconn,
|
||||
version: None,
|
||||
type_list: None,
|
||||
source_window: None,
|
||||
position: PhysicalPosition::default(),
|
||||
result: None,
|
||||
dragging: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
@@ -59,6 +72,7 @@ impl Dnd {
|
||||
self.type_list = None;
|
||||
self.source_window = None;
|
||||
self.result = None;
|
||||
self.dragging = false;
|
||||
}
|
||||
|
||||
pub unsafe fn send_status(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -122,19 +122,15 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let is_allowed =
|
||||
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
|
||||
|
||||
// We can't use the style from the old context here, since it may change on reload, so
|
||||
// pick style from the new XIM based on the old state.
|
||||
let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
|
||||
|
||||
let new_context = {
|
||||
let result = unsafe {
|
||||
ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
style,
|
||||
&new_im,
|
||||
*window,
|
||||
spot,
|
||||
(*inner).event_sender.clone(),
|
||||
is_allowed,
|
||||
)
|
||||
};
|
||||
if result.is_err() {
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{fmt, mem, ptr};
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
/// IME creation error.
|
||||
@@ -197,7 +197,7 @@ struct ImeContextClientData {
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) style: Style,
|
||||
pub(crate) allowed: bool,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
|
||||
// from there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
@@ -206,11 +206,11 @@ pub struct ImeContext {
|
||||
impl ImeContext {
|
||||
pub(crate) unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: Style,
|
||||
im: &InputMethod,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
event_sender: ImeEventSender,
|
||||
allowed: bool,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
@@ -219,20 +219,24 @@ impl ImeContext {
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let style = if allowed { im.preedit_style } else { im.none_style };
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => unsafe {
|
||||
ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im,
|
||||
im.im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
)
|
||||
},
|
||||
Style::Nothing(style) => unsafe {
|
||||
ImeContext::create_nothing_ic(xconn, im, style, window)
|
||||
ImeContext::create_nothing_ic(xconn, im.im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe {
|
||||
ImeContext::create_none_ic(xconn, im.im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
@@ -241,7 +245,7 @@ impl ImeContext {
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
style,
|
||||
allowed,
|
||||
_client_data: unsafe { Box::from_raw(client_data) },
|
||||
};
|
||||
|
||||
@@ -348,7 +352,7 @@ impl ImeContext {
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
!matches!(self.style, Style::None(_))
|
||||
self.allowed
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
|
||||
@@ -177,7 +177,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
|
||||
)
|
||||
.map_err(GetXimServersError::GetPropertyError)?
|
||||
.into_iter()
|
||||
.map(ffi::Atom::from)
|
||||
.map(|atom| atom as _)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||
|
||||
@@ -10,13 +10,12 @@ use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
use self::callbacks::*;
|
||||
use self::context::ImeContext;
|
||||
pub use self::context::ImeContextCreationError;
|
||||
use self::inner::{close_im, ImeInner};
|
||||
use self::input_method::{PotentialInputMethods, Style};
|
||||
use self::input_method::PotentialInputMethods;
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -113,39 +112,26 @@ impl Ime {
|
||||
pub fn create_context(
|
||||
&mut self,
|
||||
window: ffi::Window,
|
||||
with_preedit: bool,
|
||||
with_ime: bool,
|
||||
) -> Result<bool, ImeContextCreationError> {
|
||||
let context = if self.is_destroyed() {
|
||||
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
|
||||
None
|
||||
} else {
|
||||
let im = self.inner.im.as_ref().unwrap();
|
||||
let style = if with_preedit { im.preedit_style } else { im.none_style };
|
||||
|
||||
let context = unsafe {
|
||||
ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
im.im,
|
||||
style,
|
||||
im,
|
||||
window,
|
||||
None,
|
||||
self.inner.event_sender.clone(),
|
||||
with_ime,
|
||||
)?
|
||||
};
|
||||
|
||||
// Check the state on the context, since it could fail to enable or disable preedit.
|
||||
let event = if matches!(style, Style::None(_)) {
|
||||
if with_preedit {
|
||||
debug!("failed to create IME context with preedit support.")
|
||||
}
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
if !with_preedit {
|
||||
debug!("failed to create IME context without preedit support.")
|
||||
}
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
|
||||
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
|
||||
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
|
||||
|
||||
Some(context)
|
||||
|
||||
@@ -24,7 +24,7 @@ use x11rb::xcb_ffi::ReplyOrIdError;
|
||||
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, RequestError};
|
||||
use crate::event::{DeviceId, Event, StartCause, WindowEvent};
|
||||
use crate::event::{DeviceId, StartCause, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
|
||||
@@ -532,7 +532,7 @@ impl EventLoop {
|
||||
Some(Ok(token)) => {
|
||||
let event = WindowEvent::ActivationTokenDone {
|
||||
serial,
|
||||
token: crate::window::ActivationToken::_new(token),
|
||||
token: crate::window::ActivationToken::from_raw(token),
|
||||
};
|
||||
app.window_event(&self.event_processor.target, window_id, event);
|
||||
},
|
||||
@@ -574,22 +574,7 @@ impl EventLoop {
|
||||
|
||||
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
||||
let mut xev = unsafe { xev.assume_init() };
|
||||
self.event_processor.process_event(&mut xev, |window_target, event: Event| {
|
||||
if let Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } = event
|
||||
{
|
||||
window_target.redraw_sender.send(window_id);
|
||||
} else {
|
||||
match event {
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
app.window_event(window_target, window_id, event)
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
app.device_event(window_target, device_id, event)
|
||||
},
|
||||
_ => unreachable!("event which is neither device nor window event."),
|
||||
}
|
||||
}
|
||||
});
|
||||
self.event_processor.process_event(&mut xev, app);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use x11rb::connection::RequestConnection;
|
||||
use x11rb::protocol::randr::{self, ConnectionExt as _};
|
||||
use x11rb::protocol::xproto;
|
||||
|
||||
use super::{util, X11Error, XConnection};
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl::VideoModeHandle as PlatformVideoModeHandle;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::monitor::VideoMode;
|
||||
|
||||
// Used for testing. This should always be committed as false.
|
||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
||||
@@ -21,32 +21,14 @@ impl XConnection {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) current: bool,
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: Option<NonZeroU16>,
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) mode: VideoMode,
|
||||
pub(crate) native_mode: randr::Mode,
|
||||
pub(crate) monitor: Option<MonitorHandle>,
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone().unwrap()
|
||||
impl From<VideoModeHandle> for VideoMode {
|
||||
fn from(handle: VideoModeHandle) -> Self {
|
||||
handle.mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +47,7 @@ pub struct MonitorHandle {
|
||||
/// Used to determine which windows are on this monitor
|
||||
pub(crate) rect: util::AaRect,
|
||||
/// Supported video modes on this monitor
|
||||
video_modes: Vec<VideoModeHandle>,
|
||||
pub(crate) video_modes: Vec<VideoModeHandle>,
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
@@ -159,17 +141,13 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
|
||||
self.video_modes.iter().find(|mode| mode.current).cloned().map(PlatformVideoModeHandle::X)
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
self.video_modes.iter().find(|mode| mode.current).cloned().map(Into::into)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
|
||||
let monitor = self.clone();
|
||||
self.video_modes.clone().into_iter().map(move |mut x| {
|
||||
x.monitor = Some(monitor.clone());
|
||||
PlatformVideoModeHandle::X(x)
|
||||
})
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.video_modes.clone().into_iter().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ impl FrameExtentsHeuristic {
|
||||
|
||||
impl XConnection {
|
||||
// This is adequate for inner_position
|
||||
pub fn translate_coords(
|
||||
pub fn translate_coords_root(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
root: xproto::Window,
|
||||
@@ -103,6 +103,19 @@ impl XConnection {
|
||||
self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn translate_coords(
|
||||
&self,
|
||||
src_w: xproto::Window,
|
||||
dst_w: xproto::Window,
|
||||
src_x: i16,
|
||||
src_y: i16,
|
||||
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
|
||||
self.xcb_connection()
|
||||
.translate_coordinates(src_w, dst_w, src_x, src_y)?
|
||||
.reply()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
// This is adequate for surface_size
|
||||
pub fn get_geometry(
|
||||
&self,
|
||||
@@ -189,7 +202,7 @@ impl XConnection {
|
||||
// that, fullscreen windows often aren't nested.
|
||||
let (inner_y_rel_root, child) = {
|
||||
let coords = self
|
||||
.translate_coords(window, root)
|
||||
.translate_coords_root(window, root)
|
||||
.expect("Failed to translate window coordinates");
|
||||
(coords.dst_y, coords.child)
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ use x11rb::protocol::randr::{self, ConnectionExt as _};
|
||||
|
||||
use super::*;
|
||||
use crate::dpi::validate_scale_factor;
|
||||
use crate::monitor::VideoMode;
|
||||
use crate::platform_impl::platform::x11::{monitor, VideoModeHandle};
|
||||
|
||||
/// Represents values of `WINIT_HIDPI_FACTOR`.
|
||||
@@ -85,9 +86,11 @@ impl XConnection {
|
||||
.map(|mode| {
|
||||
VideoModeHandle {
|
||||
current: mode.id == current_mode,
|
||||
size: (mode.width.into(), mode.height.into()),
|
||||
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode),
|
||||
bit_depth: NonZeroU16::new(bit_depth as u16),
|
||||
mode: VideoMode {
|
||||
size: (mode.width as u32, mode.height as u32).into(),
|
||||
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode),
|
||||
bit_depth: NonZeroU16::new(bit_depth as u16),
|
||||
},
|
||||
native_mode: mode.id,
|
||||
// This is populated in `MonitorHandle::video_modes` as the
|
||||
// video mode is returned to the user
|
||||
|
||||
@@ -20,10 +20,11 @@ use super::util::{self, SelectedCursor};
|
||||
use super::{
|
||||
ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, XConnection,
|
||||
};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
|
||||
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::event::{Event, SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event::{SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform::x11::WindowType;
|
||||
use crate::platform_impl::x11::atoms::*;
|
||||
@@ -32,7 +33,6 @@ use crate::platform_impl::x11::{
|
||||
};
|
||||
use crate::platform_impl::{
|
||||
common, Fullscreen, MonitorHandle as PlatformMonitorHandle, PlatformCustomCursor, PlatformIcon,
|
||||
VideoModeHandle as PlatformVideoModeHandle,
|
||||
};
|
||||
use crate::window::{
|
||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
|
||||
@@ -327,7 +327,7 @@ impl Drop for Window {
|
||||
let xconn = &window.xconn;
|
||||
|
||||
// Restore the video mode on drop.
|
||||
if let Some(Fullscreen::Exclusive(_)) = window.fullscreen() {
|
||||
if let Some(Fullscreen::Exclusive(..)) = window.fullscreen() {
|
||||
window.set_fullscreen(None);
|
||||
}
|
||||
|
||||
@@ -880,7 +880,7 @@ impl UnownedWindow {
|
||||
|
||||
// Remove the startup notification if we have one.
|
||||
if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() {
|
||||
leap!(xconn.remove_activation_token(xwindow, &startup._token));
|
||||
leap!(xconn.remove_activation_token(xwindow, &startup.token));
|
||||
}
|
||||
|
||||
// We never want to give the user a broken window, since by then, it's too late to handle.
|
||||
@@ -1035,20 +1035,17 @@ impl UnownedWindow {
|
||||
// fullscreen, so we can restore it upon exit, as XRandR does not
|
||||
// provide a mechanism to set this per app-session or restore this
|
||||
// to the desktop video mode as macOS and Windows do
|
||||
(&None, &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))))
|
||||
| (
|
||||
&Some(Fullscreen::Borderless(_)),
|
||||
&Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))),
|
||||
) => {
|
||||
let monitor = video_mode.monitor.as_ref().unwrap();
|
||||
(&None, &Some(Fullscreen::Exclusive(ref monitor, _)))
|
||||
| (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref monitor, _))) => {
|
||||
let id = monitor.native_identifier();
|
||||
shared_state_lock.desktop_video_mode = Some((
|
||||
monitor.id,
|
||||
self.xconn.get_crtc_mode(monitor.id).expect("Failed to get desktop video mode"),
|
||||
id,
|
||||
self.xconn.get_crtc_mode(id).expect("Failed to get desktop video mode"),
|
||||
));
|
||||
},
|
||||
// Restore desktop video mode upon exiting exclusive fullscreen
|
||||
(&Some(Fullscreen::Exclusive(_)), &None)
|
||||
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||
(&Some(Fullscreen::Exclusive(..)), &None)
|
||||
| (&Some(Fullscreen::Exclusive(..)), &Some(Fullscreen::Borderless(_))) => {
|
||||
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
|
||||
self.xconn
|
||||
.set_crtc_config(monitor_id, mode_id)
|
||||
@@ -1072,8 +1069,8 @@ impl UnownedWindow {
|
||||
},
|
||||
Some(fullscreen) => {
|
||||
let (video_mode, monitor) = match fullscreen {
|
||||
Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => {
|
||||
(Some(video_mode), video_mode.monitor.clone().unwrap())
|
||||
Fullscreen::Exclusive(PlatformMonitorHandle::X(monitor), video_mode) => {
|
||||
(Some(video_mode), monitor.clone())
|
||||
},
|
||||
Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => {
|
||||
(None, monitor)
|
||||
@@ -1090,7 +1087,15 @@ impl UnownedWindow {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(video_mode) = video_mode {
|
||||
if let Some(native_mode) = video_mode.and_then(|requested| {
|
||||
monitor.video_modes.iter().find_map(|mode| {
|
||||
if mode.mode == requested {
|
||||
Some(mode.native_mode)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}) {
|
||||
// FIXME: this is actually not correct if we're setting the
|
||||
// video mode to a resolution higher than the current
|
||||
// desktop resolution, because XRandR does not automatically
|
||||
@@ -1117,7 +1122,7 @@ impl UnownedWindow {
|
||||
// this will make someone unhappy, but it's very unusual for
|
||||
// games to want to do this anyway).
|
||||
self.xconn
|
||||
.set_crtc_config(monitor.id, video_mode.native_mode)
|
||||
.set_crtc_config(monitor.id, native_mode)
|
||||
.expect("failed to set video mode");
|
||||
}
|
||||
|
||||
@@ -1207,7 +1212,8 @@ impl UnownedWindow {
|
||||
&self,
|
||||
new_monitor: &X11MonitorHandle,
|
||||
maybe_prev_scale_factor: Option<f64>,
|
||||
mut callback: impl FnMut(Event),
|
||||
app: &mut dyn ApplicationHandler,
|
||||
event_loop: &ActiveEventLoop,
|
||||
) {
|
||||
// Check if the self is on this monitor
|
||||
let monitor = self.shared_state_lock().last_monitor.clone();
|
||||
@@ -1227,12 +1233,9 @@ impl UnownedWindow {
|
||||
|
||||
let old_surface_size = PhysicalSize::new(width, height);
|
||||
let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
|
||||
callback(Event::WindowEvent {
|
||||
window_id: self.id(),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_monitor.scale_factor,
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
|
||||
},
|
||||
app.window_event(event_loop, self.id(), WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_monitor.scale_factor,
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
|
||||
});
|
||||
|
||||
let new_surface_size = *surface_size.lock().unwrap();
|
||||
@@ -1516,7 +1519,7 @@ impl UnownedWindow {
|
||||
// This should be okay to unwrap since the only error XTranslateCoordinates can return
|
||||
// is BadWindow, and if the window handle is bad we have bigger problems.
|
||||
self.xconn
|
||||
.translate_coords(self.xwindow, self.root)
|
||||
.translate_coords_root(self.xwindow, self.root)
|
||||
.map(|coords| (coords.dst_x.into(), coords.dst_y.into()))
|
||||
.unwrap()
|
||||
}
|
||||
@@ -1827,6 +1830,11 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
|
||||
// We don't support the locked cursor yet, so ignore it early on.
|
||||
if mode == CursorGrabMode::Locked {
|
||||
return Err(NotSupportedError::new("locked cursor is not implemented on X11").into());
|
||||
}
|
||||
|
||||
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
|
||||
if mode == *grabbed_lock {
|
||||
return Ok(());
|
||||
@@ -1838,6 +1846,7 @@ impl UnownedWindow {
|
||||
.xcb_connection()
|
||||
.ungrab_pointer(x11rb::CURRENT_TIME)
|
||||
.expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
|
||||
*grabbed_lock = CursorGrabMode::None;
|
||||
|
||||
let result = match mode {
|
||||
CursorGrabMode::None => self
|
||||
@@ -1845,34 +1854,33 @@ impl UnownedWindow {
|
||||
.flush_requests()
|
||||
.map_err(|err| RequestError::Os(os_error!(X11Error::Xlib(err)))),
|
||||
CursorGrabMode::Confined => {
|
||||
let result = {
|
||||
self.xconn
|
||||
.xcb_connection()
|
||||
.grab_pointer(
|
||||
true as _,
|
||||
self.xwindow,
|
||||
xproto::EventMask::BUTTON_PRESS
|
||||
| xproto::EventMask::BUTTON_RELEASE
|
||||
| xproto::EventMask::ENTER_WINDOW
|
||||
| xproto::EventMask::LEAVE_WINDOW
|
||||
| xproto::EventMask::POINTER_MOTION
|
||||
| xproto::EventMask::POINTER_MOTION_HINT
|
||||
| xproto::EventMask::BUTTON1_MOTION
|
||||
| xproto::EventMask::BUTTON2_MOTION
|
||||
| xproto::EventMask::BUTTON3_MOTION
|
||||
| xproto::EventMask::BUTTON4_MOTION
|
||||
| xproto::EventMask::BUTTON5_MOTION
|
||||
| xproto::EventMask::KEYMAP_STATE,
|
||||
xproto::GrabMode::ASYNC,
|
||||
xproto::GrabMode::ASYNC,
|
||||
self.xwindow,
|
||||
0u32,
|
||||
x11rb::CURRENT_TIME,
|
||||
)
|
||||
.expect("Failed to call `grab_pointer`")
|
||||
.reply()
|
||||
.expect("Failed to receive reply from `grab_pointer`")
|
||||
};
|
||||
let result = self
|
||||
.xconn
|
||||
.xcb_connection()
|
||||
.grab_pointer(
|
||||
true as _,
|
||||
self.xwindow,
|
||||
xproto::EventMask::BUTTON_PRESS
|
||||
| xproto::EventMask::BUTTON_RELEASE
|
||||
| xproto::EventMask::ENTER_WINDOW
|
||||
| xproto::EventMask::LEAVE_WINDOW
|
||||
| xproto::EventMask::POINTER_MOTION
|
||||
| xproto::EventMask::POINTER_MOTION_HINT
|
||||
| xproto::EventMask::BUTTON1_MOTION
|
||||
| xproto::EventMask::BUTTON2_MOTION
|
||||
| xproto::EventMask::BUTTON3_MOTION
|
||||
| xproto::EventMask::BUTTON4_MOTION
|
||||
| xproto::EventMask::BUTTON5_MOTION
|
||||
| xproto::EventMask::KEYMAP_STATE,
|
||||
xproto::GrabMode::ASYNC,
|
||||
xproto::GrabMode::ASYNC,
|
||||
self.xwindow,
|
||||
0u32,
|
||||
x11rb::CURRENT_TIME,
|
||||
)
|
||||
.expect("Failed to call `grab_pointer`")
|
||||
.reply()
|
||||
.expect("Failed to receive reply from `grab_pointer`");
|
||||
|
||||
match result.status {
|
||||
xproto::GrabStatus::SUCCESS => Ok(()),
|
||||
@@ -1892,11 +1900,7 @@ impl UnownedWindow {
|
||||
}
|
||||
.map_err(|err| RequestError::Os(os_error!(err)))
|
||||
},
|
||||
CursorGrabMode::Locked => {
|
||||
return Err(
|
||||
NotSupportedError::new("locked cursor is not implemented on X11").into()
|
||||
);
|
||||
},
|
||||
CursorGrabMode::Locked => return Ok(()),
|
||||
};
|
||||
|
||||
if result.is_ok() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
|
||||
use crate::window::Fullscreen as RootFullscreen;
|
||||
|
||||
#[cfg(android_platform)]
|
||||
@@ -30,17 +30,19 @@ use self::web as platform;
|
||||
use self::windows as platform;
|
||||
|
||||
/// Helper for converting between platform-specific and generic
|
||||
/// [`VideoModeHandle`]/[`MonitorHandle`]
|
||||
/// [`VideoMode`]/[`MonitorHandle`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Fullscreen {
|
||||
Exclusive(VideoModeHandle),
|
||||
Exclusive(MonitorHandle, VideoMode),
|
||||
Borderless(Option<MonitorHandle>),
|
||||
}
|
||||
|
||||
impl From<RootFullscreen> for Fullscreen {
|
||||
fn from(f: RootFullscreen) -> Self {
|
||||
match f {
|
||||
RootFullscreen::Exclusive(mode) => Self::Exclusive(mode.video_mode),
|
||||
RootFullscreen::Exclusive(handle, video_mode) => {
|
||||
Self::Exclusive(handle.inner, video_mode)
|
||||
},
|
||||
RootFullscreen::Borderless(Some(handle)) => Self::Borderless(Some(handle.inner)),
|
||||
RootFullscreen::Borderless(None) => Self::Borderless(None),
|
||||
}
|
||||
@@ -50,8 +52,8 @@ impl From<RootFullscreen> for Fullscreen {
|
||||
impl From<Fullscreen> for RootFullscreen {
|
||||
fn from(f: Fullscreen) -> Self {
|
||||
match f {
|
||||
Fullscreen::Exclusive(video_mode) => {
|
||||
Self::Exclusive(RootVideoModeHandle { video_mode })
|
||||
Fullscreen::Exclusive(inner, video_mode) => {
|
||||
Self::Exclusive(RootMonitorHandle { inner }, video_mode)
|
||||
},
|
||||
Fullscreen::Borderless(Some(inner)) => {
|
||||
Self::Borderless(Some(RootMonitorHandle { inner }))
|
||||
|
||||
@@ -12,8 +12,7 @@ use orbclient::{
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use super::{
|
||||
KeyEventExtra, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket,
|
||||
WindowProperties,
|
||||
MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowProperties,
|
||||
};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, NotSupportedError, RequestError};
|
||||
@@ -372,10 +371,8 @@ impl EventLoop {
|
||||
state: element_state(pressed),
|
||||
repeat: false,
|
||||
text,
|
||||
platform_specific: KeyEventExtra {
|
||||
key_without_modifiers,
|
||||
text_with_all_modifiers,
|
||||
},
|
||||
key_without_modifiers,
|
||||
text_with_all_modifiers,
|
||||
},
|
||||
is_synthetic: false,
|
||||
};
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#![cfg(target_os = "redox")]
|
||||
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::{fmt, str};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::keyboard::Key;
|
||||
mod event_loop;
|
||||
|
||||
pub use self::window::Window;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::monitor::VideoMode;
|
||||
|
||||
mod event_loop;
|
||||
mod window;
|
||||
|
||||
pub(crate) use crate::cursor::{
|
||||
@@ -151,43 +148,12 @@ impl MonitorHandle {
|
||||
1.0 // TODO
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
// (it is guaranteed to support 32 bit color though)
|
||||
Some(VideoModeHandle { monitor: self.clone() })
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
self.current_video_mode().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct VideoModeHandle {
|
||||
monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
// TODO
|
||||
PhysicalSize::default()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
// TODO
|
||||
None
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
std::iter::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub key_without_modifiers: Key,
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use super::{backend, HasMonitorPermissionFuture, MonitorPermissionFuture};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{EventLoopError, NotSupportedError};
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
|
||||
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
|
||||
|
||||
@@ -24,20 +23,20 @@ impl EventLoop {
|
||||
Ok(EventLoop { elw: ActiveEventLoop::new() })
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
|
||||
let event_loop = self.elw.clone();
|
||||
|
||||
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
|
||||
let handler: Box<dyn FnMut(Event)> =
|
||||
Box::new(|event| handle_event(&mut app, &event_loop, event));
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||
let app = Box::new(app);
|
||||
|
||||
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
|
||||
// because this function will never return and all resources not cleaned up by the point we
|
||||
// `throw` will leak, making this actually `'static`.
|
||||
let handler = unsafe {
|
||||
std::mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event) + 'static>>(handler)
|
||||
let app = unsafe {
|
||||
std::mem::transmute::<
|
||||
Box<dyn ApplicationHandler + '_>,
|
||||
Box<dyn ApplicationHandler + 'static>,
|
||||
>(app)
|
||||
};
|
||||
self.elw.run(handler, false);
|
||||
|
||||
self.elw.run(app, false);
|
||||
|
||||
// Throw an exception to break out of Rust execution and use unreachable to tell the
|
||||
// compiler this function won't return, giving it a return type of '!'
|
||||
@@ -48,9 +47,8 @@ impl EventLoop {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
pub fn spawn_app<A: ApplicationHandler + 'static>(self, mut app: A) {
|
||||
let event_loop = self.elw.clone();
|
||||
self.elw.run(Box::new(move |event| handle_event(&mut app, &event_loop, event)), true);
|
||||
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
|
||||
self.elw.run(Box::new(app), true);
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
||||
@@ -85,18 +83,3 @@ impl EventLoop {
|
||||
self.elw.runner.monitor().has_detailed_monitor_permission_async()
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event<A: ApplicationHandler>(app: &mut A, target: &ActiveEventLoop, event: Event) {
|
||||
match event {
|
||||
Event::NewEvents(cause) => app.new_events(target, cause),
|
||||
Event::WindowEvent { window_id, event } => app.window_event(target, window_id, event),
|
||||
Event::DeviceEvent { device_id, event } => app.device_event(target, device_id, event),
|
||||
Event::UserWakeUp => app.proxy_wake_up(target),
|
||||
Event::Suspended => app.suspended(target),
|
||||
Event::Resumed => app.resumed(target),
|
||||
Event::CreateSurfaces => app.can_create_surfaces(target),
|
||||
Event::AboutToWait => app.about_to_wait(target),
|
||||
Event::LoopExiting => app.exiting(target),
|
||||
Event::MemoryWarning => app.memory_warning(target),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ use web_time::{Duration, Instant};
|
||||
use super::super::event;
|
||||
use super::super::main_thread::MainThreadMarker;
|
||||
use super::super::monitor::MonitorHandler;
|
||||
use super::backend;
|
||||
use super::proxy::EventLoopProxy;
|
||||
use super::state::State;
|
||||
use super::{backend, ActiveEventLoop};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::event::{DeviceEvent, ElementState, Event, RawKeyEvent, StartCause, WindowEvent};
|
||||
use crate::event::{DeviceEvent, DeviceId, ElementState, RawKeyEvent, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ControlFlow, DeviceEvents};
|
||||
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
|
||||
use crate::platform_impl::platform::backend::{EventListenerHandle, SafeAreaHandle};
|
||||
@@ -27,8 +28,6 @@ use crate::window::WindowId;
|
||||
|
||||
pub struct Shared(Rc<Execution>);
|
||||
|
||||
pub(super) type EventHandler = dyn FnMut(Event);
|
||||
|
||||
impl Clone for Shared {
|
||||
fn clone(&self) -> Self {
|
||||
Shared(self.0.clone())
|
||||
@@ -47,7 +46,7 @@ struct Execution {
|
||||
runner: RefCell<RunnerEnum>,
|
||||
suspended: Cell<bool>,
|
||||
event_loop_recreation: Cell<bool>,
|
||||
events: RefCell<VecDeque<EventWrapper>>,
|
||||
events: RefCell<VecDeque<Event>>,
|
||||
id: Cell<usize>,
|
||||
window: web_sys::Window,
|
||||
navigator: Navigator,
|
||||
@@ -93,12 +92,13 @@ impl RunnerEnum {
|
||||
|
||||
struct Runner {
|
||||
state: State,
|
||||
event_handler: Box<EventHandler>,
|
||||
app: Box<dyn ApplicationHandler>,
|
||||
event_loop: ActiveEventLoop,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub fn new(event_handler: Box<EventHandler>) -> Self {
|
||||
Runner { state: State::Init, event_handler }
|
||||
pub fn new(app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) -> Self {
|
||||
Runner { state: State::Init, app, event_loop }
|
||||
}
|
||||
|
||||
/// Returns the corresponding `StartCause` for the current `state`, or `None`
|
||||
@@ -115,19 +115,33 @@ impl Runner {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_single_event(&mut self, runner: &Shared, event: impl Into<EventWrapper>) {
|
||||
match event.into() {
|
||||
EventWrapper::Event(event) => (self.event_handler)(event),
|
||||
EventWrapper::ScaleChange { canvas, size, scale } => {
|
||||
fn handle_single_event(&mut self, runner: &Shared, event: Event) {
|
||||
match event {
|
||||
Event::NewEvents(cause) => self.app.new_events(&self.event_loop, cause),
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
self.app.window_event(&self.event_loop, window_id, event)
|
||||
},
|
||||
Event::ScaleChange { canvas, size, scale } => {
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
canvas.handle_scale_change(
|
||||
runner,
|
||||
|event| (self.event_handler)(event),
|
||||
|window_id, event| {
|
||||
self.app.window_event(&self.event_loop, window_id, event);
|
||||
},
|
||||
size,
|
||||
scale,
|
||||
)
|
||||
}
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
self.app.device_event(&self.event_loop, device_id, event)
|
||||
},
|
||||
Event::UserWakeUp => self.app.proxy_wake_up(&self.event_loop),
|
||||
Event::Suspended => self.app.suspended(&self.event_loop),
|
||||
Event::Resumed => self.app.resumed(&self.event_loop),
|
||||
Event::CreateSurfaces => self.app.can_create_surfaces(&self.event_loop),
|
||||
Event::AboutToWait => self.app.about_to_wait(&self.event_loop),
|
||||
Event::LoopExiting => self.app.exiting(&self.event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,13 +230,13 @@ impl Shared {
|
||||
self.0.destroy_pending.borrow_mut().push_back(id);
|
||||
}
|
||||
|
||||
pub(crate) fn start(&self, event_handler: Box<EventHandler>) {
|
||||
pub(crate) fn start(&self, app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) {
|
||||
let mut runner = self.0.runner.borrow_mut();
|
||||
assert!(matches!(*runner, RunnerEnum::Pending));
|
||||
if self.0.monitor.is_initializing() {
|
||||
*runner = RunnerEnum::Initializing(Runner::new(event_handler));
|
||||
*runner = RunnerEnum::Initializing(Runner::new(app, event_loop));
|
||||
} else {
|
||||
*runner = RunnerEnum::Running(Runner::new(event_handler));
|
||||
*runner = RunnerEnum::Running(Runner::new(app, event_loop));
|
||||
|
||||
drop(runner);
|
||||
|
||||
@@ -445,7 +459,7 @@ impl Shared {
|
||||
|
||||
pub fn request_redraw(&self, id: WindowId) {
|
||||
self.0.redraw_pending.borrow_mut().insert(id);
|
||||
self.send_events::<EventWrapper>(iter::empty());
|
||||
self.send_events([]);
|
||||
}
|
||||
|
||||
fn init(&self) {
|
||||
@@ -473,7 +487,7 @@ impl Shared {
|
||||
// Add an event to the event loop runner, from the user or an event handler
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub(crate) fn send_event<E: Into<EventWrapper>>(&self, event: E) {
|
||||
pub(crate) fn send_event(&self, event: Event) {
|
||||
self.send_events(iter::once(event));
|
||||
}
|
||||
|
||||
@@ -514,7 +528,7 @@ impl Shared {
|
||||
// Add a series of events to the event loop runner
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub(crate) fn send_events<E: Into<EventWrapper>>(&self, events: impl IntoIterator<Item = E>) {
|
||||
pub(crate) fn send_events(&self, events: impl IntoIterator<Item = Event>) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
@@ -539,7 +553,7 @@ impl Shared {
|
||||
}
|
||||
if !process_immediately {
|
||||
// Queue these events to look at later
|
||||
self.0.events.borrow_mut().extend(events.into_iter().map(Into::into));
|
||||
self.0.events.borrow_mut().extend(events);
|
||||
return;
|
||||
}
|
||||
// At this point, we know this is a fresh set of events
|
||||
@@ -557,8 +571,7 @@ impl Shared {
|
||||
// Take the start event, then the events provided to this function, and run an iteration of
|
||||
// the event loop
|
||||
let start_event = Event::NewEvents(start_cause);
|
||||
let events =
|
||||
iter::once(EventWrapper::from(start_event)).chain(events.into_iter().map(Into::into));
|
||||
let events = iter::once(start_event).chain(events);
|
||||
self.run_until_cleared(events);
|
||||
}
|
||||
|
||||
@@ -579,9 +592,9 @@ impl Shared {
|
||||
// cleared
|
||||
//
|
||||
// This will also process any events that have been queued or that are queued during processing
|
||||
fn run_until_cleared<E: Into<EventWrapper>>(&self, events: impl Iterator<Item = E>) {
|
||||
fn run_until_cleared(&self, events: impl Iterator<Item = Event>) {
|
||||
for event in events {
|
||||
self.handle_event(event.into());
|
||||
self.handle_event(event);
|
||||
}
|
||||
self.process_destroy_pending_windows();
|
||||
|
||||
@@ -615,7 +628,7 @@ impl Shared {
|
||||
// handle_event takes in events and either queues them or applies a callback
|
||||
//
|
||||
// It should only ever be called from `run_until_cleared`.
|
||||
fn handle_event(&self, event: impl Into<EventWrapper>) {
|
||||
fn handle_event(&self, event: Event) {
|
||||
if self.is_closed() {
|
||||
self.exit();
|
||||
}
|
||||
@@ -625,7 +638,7 @@ impl Shared {
|
||||
},
|
||||
// If an event is being handled without a runner somehow, add it to the event queue so
|
||||
// it will eventually be processed
|
||||
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event.into()),
|
||||
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event),
|
||||
// If the Runner has been destroyed, there is nothing to do.
|
||||
RunnerEnum::Destroyed => return,
|
||||
// This function should never be called if we are still waiting for something.
|
||||
@@ -652,13 +665,7 @@ impl Shared {
|
||||
let mut events = self.0.events.borrow_mut();
|
||||
|
||||
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
|
||||
events.extend(
|
||||
self.0
|
||||
.event_loop_proxy
|
||||
.take()
|
||||
.then_some(Event::UserWakeUp)
|
||||
.map(EventWrapper::from),
|
||||
);
|
||||
events.extend(self.0.event_loop_proxy.take().then_some(Event::UserWakeUp));
|
||||
|
||||
events.pop_front()
|
||||
};
|
||||
@@ -845,13 +852,16 @@ impl WeakShared {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum EventWrapper {
|
||||
Event(Event),
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum Event {
|
||||
NewEvents(StartCause),
|
||||
WindowEvent { window_id: WindowId, event: WindowEvent },
|
||||
ScaleChange { canvas: Weak<backend::Canvas>, size: PhysicalSize<u32>, scale: f64 },
|
||||
}
|
||||
|
||||
impl From<Event> for EventWrapper {
|
||||
fn from(value: Event) -> Self {
|
||||
Self::Event(value)
|
||||
}
|
||||
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
|
||||
Suspended,
|
||||
CreateSurfaces,
|
||||
Resumed,
|
||||
AboutToWait,
|
||||
LoopExiting,
|
||||
UserWakeUp,
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ use std::sync::Arc;
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use super::super::lock;
|
||||
use super::super::monitor::MonitorPermissionFuture;
|
||||
use super::super::{lock, KeyEventExtra};
|
||||
use super::runner::EventWrapper;
|
||||
use super::runner::Event;
|
||||
use super::{backend, runner};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::error::{NotSupportedError, RequestError};
|
||||
use crate::event::{ElementState, Event, KeyEvent, TouchPhase, WindowEvent};
|
||||
use crate::event::{ElementState, KeyEvent, TouchPhase, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
@@ -54,13 +55,9 @@ impl ActiveEventLoop {
|
||||
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
|
||||
}
|
||||
|
||||
pub(crate) fn run(
|
||||
&self,
|
||||
event_handler: Box<runner::EventHandler>,
|
||||
event_loop_recreation: bool,
|
||||
) {
|
||||
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
|
||||
self.runner.event_loop_recreation(event_loop_recreation);
|
||||
self.runner.start(event_handler);
|
||||
self.runner.start(app, self.clone());
|
||||
}
|
||||
|
||||
pub fn generate_id(&self) -> WindowId {
|
||||
@@ -143,12 +140,13 @@ impl ActiveEventLoop {
|
||||
device_id: None,
|
||||
event: KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
logical_key: logical_key.clone(),
|
||||
text: text.clone(),
|
||||
location,
|
||||
state: ElementState::Pressed,
|
||||
repeat,
|
||||
platform_specific: KeyEventExtra,
|
||||
text_with_all_modifiers: text,
|
||||
key_without_modifiers: logical_key,
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
@@ -177,12 +175,13 @@ impl ActiveEventLoop {
|
||||
device_id: None,
|
||||
event: KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
logical_key: logical_key.clone(),
|
||||
text: text.clone(),
|
||||
location,
|
||||
state: ElementState::Released,
|
||||
repeat,
|
||||
platform_specific: KeyEventExtra,
|
||||
text_with_all_modifiers: text,
|
||||
key_without_modifiers: logical_key,
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
@@ -396,7 +395,7 @@ impl ActiveEventLoop {
|
||||
let canvas = canvas_clone.clone();
|
||||
|
||||
move |size, scale| {
|
||||
runner.send_event(EventWrapper::ScaleChange {
|
||||
runner.send_event(Event::ScaleChange {
|
||||
canvas: Rc::downgrade(&canvas),
|
||||
size,
|
||||
scale,
|
||||
|
||||
@@ -2,9 +2,6 @@ use smol_str::SmolStr;
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub(crate) struct KeyEventExtra;
|
||||
|
||||
impl Key {
|
||||
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
|
||||
Key::Named(match kav {
|
||||
|
||||
@@ -40,10 +40,8 @@ pub(crate) use cursor::{
|
||||
pub(crate) use self::event_loop::{
|
||||
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub(crate) use self::keyboard::KeyEventExtra;
|
||||
pub(crate) use self::monitor::{
|
||||
HasMonitorPermissionFuture, MonitorHandle, MonitorPermissionFuture, OrientationLockFuture,
|
||||
VideoModeHandle,
|
||||
};
|
||||
use self::web_sys as backend;
|
||||
pub use self::window::{PlatformSpecificWindowAttributes, Window};
|
||||
|
||||
@@ -3,9 +3,8 @@ use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::future::Future;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::{self, Once};
|
||||
use std::mem;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::num::NonZeroU16;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::pin::Pin;
|
||||
use std::rc::{Rc, Weak};
|
||||
@@ -29,7 +28,7 @@ use super::main_thread::MainThreadMarker;
|
||||
use super::r#async::{Dispatcher, Notified, Notifier};
|
||||
use super::web_sys::{Engine, EventListenerHandle};
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
|
||||
use crate::platform::web::{
|
||||
MonitorPermissionError, Orientation, OrientationData, OrientationLock, OrientationLockError,
|
||||
};
|
||||
@@ -59,12 +58,16 @@ impl MonitorHandle {
|
||||
self.inner.queue(|inner| inner.name())
|
||||
}
|
||||
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
Some(VideoModeHandle(self.clone()))
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
Some(VideoMode {
|
||||
size: self.inner.queue(|inner| inner.size()),
|
||||
bit_depth: self.inner.queue(|inner| inner.bit_depth()),
|
||||
refresh_rate_millihertz: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> Once<VideoModeHandle> {
|
||||
iter::once(VideoModeHandle(self.clone()))
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.current_video_mode().into_iter()
|
||||
}
|
||||
|
||||
pub fn orientation(&self) -> OrientationData {
|
||||
@@ -252,35 +255,6 @@ impl OrientationLockError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||
pub struct VideoModeHandle(MonitorHandle);
|
||||
|
||||
impl VideoModeHandle {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.0.inner.queue(|inner| inner.size())
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.0.inner.queue(|inner| inner.bit_depth())
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let (size, bit_depth) = self.0.inner.queue(|this| (this.size(), this.bit_depth()));
|
||||
|
||||
f.debug_struct("MonitorHandle").field("size", &size).field("bit_depth", &bit_depth).finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
window: WindowExt,
|
||||
engine: Option<Engine>,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9",
|
||||
"@types/eslint__js": "^8",
|
||||
"eslint": "^9",
|
||||
"typescript": "^5",
|
||||
"typescript-eslint": "^8"
|
||||
|
||||
@@ -12,6 +12,7 @@ use web_sys::{
|
||||
};
|
||||
|
||||
use super::super::cursor::CursorHandler;
|
||||
use super::super::event_loop::runner;
|
||||
use super::super::main_thread::MainThreadMarker;
|
||||
use super::animation_frame::AnimationFrameHandler;
|
||||
use super::event_handle::EventListenerHandle;
|
||||
@@ -23,7 +24,7 @@ use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::error::RequestError;
|
||||
use crate::event::{
|
||||
ButtonSource, DeviceId, ElementState, MouseScrollDelta, PointerKind, PointerSource,
|
||||
SurfaceSizeWriter,
|
||||
SurfaceSizeWriter, WindowEvent,
|
||||
};
|
||||
use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey};
|
||||
use crate::platform_impl::Fullscreen;
|
||||
@@ -487,7 +488,7 @@ impl Canvas {
|
||||
pub(crate) fn handle_scale_change(
|
||||
&self,
|
||||
runner: &super::super::event_loop::runner::Shared,
|
||||
event_handler: impl FnOnce(crate::event::Event),
|
||||
event_handler: impl FnOnce(WindowId, WindowEvent),
|
||||
current_size: PhysicalSize<u32>,
|
||||
scale: f64,
|
||||
) {
|
||||
@@ -495,12 +496,9 @@ impl Canvas {
|
||||
self.set_current_size(current_size);
|
||||
let new_size = {
|
||||
let new_size = Arc::new(Mutex::new(current_size));
|
||||
event_handler(crate::event::Event::WindowEvent {
|
||||
window_id: self.id,
|
||||
event: crate::event::WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: scale,
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_size)),
|
||||
},
|
||||
event_handler(self.id, WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: scale,
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_size)),
|
||||
});
|
||||
|
||||
let new_size = *new_size.lock().unwrap();
|
||||
@@ -523,7 +521,7 @@ impl Canvas {
|
||||
} else if self.old_size() != new_size {
|
||||
// Then we at least send a resized event.
|
||||
self.set_old_size(new_size);
|
||||
runner.send_event(crate::event::Event::WindowEvent {
|
||||
runner.send_event(runner::Event::WindowEvent {
|
||||
window_id: self.id,
|
||||
event: crate::event::WindowEvent::SurfaceResized(new_size),
|
||||
})
|
||||
|
||||
@@ -55,7 +55,7 @@ pub(crate) fn request_fullscreen(
|
||||
let canvas: &RequestFullscreen = canvas.unchecked_ref();
|
||||
|
||||
match fullscreen {
|
||||
Fullscreen::Exclusive(_) => error!("Exclusive full screen mode is not supported"),
|
||||
Fullscreen::Exclusive(..) => error!("Exclusive full screen mode is not supported"),
|
||||
Fullscreen::Borderless(Some(monitor)) => {
|
||||
if !monitor::has_screen_details_support(window) {
|
||||
error!(
|
||||
|
||||
@@ -133,7 +133,7 @@ fn should_apps_use_dark_mode() -> bool {
|
||||
|
||||
let module = LoadLibraryA("uxtheme.dll\0".as_ptr());
|
||||
|
||||
if module == 0 {
|
||||
if module.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use windows_sys::core::{IUnknown, GUID, HRESULT};
|
||||
use windows_sys::core::{GUID, HRESULT};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL};
|
||||
use windows_sys::Win32::System::Com::{
|
||||
IAdviseSink, IDataObject, IEnumFORMATETC, IEnumSTATDATA, FORMATETC, STGMEDIUM,
|
||||
};
|
||||
use windows_sys::Win32::System::Com::{FORMATETC, STGMEDIUM};
|
||||
|
||||
pub type IUnknown = *mut c_void;
|
||||
pub type IAdviseSink = *mut c_void;
|
||||
pub type IDataObject = *mut c_void;
|
||||
pub type IEnumFORMATETC = *mut c_void;
|
||||
pub type IEnumSTATDATA = *mut c_void;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IUnknownVtbl {
|
||||
@@ -72,13 +76,13 @@ pub struct IDropTargetVtbl {
|
||||
This: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
grfKeyState: u32,
|
||||
pt: *const POINTL,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT,
|
||||
pub DragOver: unsafe extern "system" fn(
|
||||
This: *mut IDropTarget,
|
||||
grfKeyState: u32,
|
||||
pt: *const POINTL,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT,
|
||||
pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT,
|
||||
@@ -86,7 +90,7 @@ pub struct IDropTargetVtbl {
|
||||
This: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
grfKeyState: u32,
|
||||
pt: *const POINTL,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||
|
||||
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
let hdc = unsafe { GetDC(hwnd) };
|
||||
if hdc == 0 {
|
||||
if hdc.is_null() {
|
||||
panic!("[winit] `GetDC` returned null!");
|
||||
}
|
||||
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||
@@ -85,7 +85,7 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) };
|
||||
if monitor == 0 {
|
||||
if monitor.is_null() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,27 +5,28 @@ use std::ptr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use tracing::debug;
|
||||
use windows_sys::core::{IUnknown, GUID, HRESULT};
|
||||
use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINTL, S_OK};
|
||||
use windows_sys::Win32::System::Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL};
|
||||
use windows_sys::core::{GUID, HRESULT};
|
||||
use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINT, POINTL, S_OK};
|
||||
use windows_sys::Win32::Graphics::Gdi::ScreenToClient;
|
||||
use windows_sys::Win32::System::Com::{DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL};
|
||||
use windows_sys::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE};
|
||||
use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP};
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::event::WindowEvent;
|
||||
use crate::platform_impl::platform::definitions::{
|
||||
IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl,
|
||||
IDataObject, IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknown, IUnknownVtbl,
|
||||
};
|
||||
use crate::window::WindowId;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FileDropHandlerData {
|
||||
pub interface: IDropTarget,
|
||||
refcount: AtomicUsize,
|
||||
window: HWND,
|
||||
send_event: Box<dyn Fn(Event)>,
|
||||
send_event: Box<dyn Fn(WindowEvent)>,
|
||||
cursor_effect: u32,
|
||||
hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any
|
||||
* `HoveredFileCancelled` emitted */
|
||||
valid: bool, /* If the currently hovered item is not valid there must not be any
|
||||
* `DragLeft` emitted */
|
||||
}
|
||||
|
||||
pub struct FileDropHandler {
|
||||
@@ -34,14 +35,14 @@ pub struct FileDropHandler {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl FileDropHandler {
|
||||
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(Event)>) -> FileDropHandler {
|
||||
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(WindowEvent)>) -> FileDropHandler {
|
||||
let data = Box::new(FileDropHandlerData {
|
||||
interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl },
|
||||
refcount: AtomicUsize::new(1),
|
||||
window,
|
||||
send_event,
|
||||
cursor_effect: DROPEFFECT_NONE,
|
||||
hovered_is_valid: false,
|
||||
valid: false,
|
||||
});
|
||||
FileDropHandler { data: Box::into_raw(data) }
|
||||
}
|
||||
@@ -77,22 +78,23 @@ impl FileDropHandler {
|
||||
this: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
_grfKeyState: u32,
|
||||
_pt: *const POINTL,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT {
|
||||
use crate::event::WindowEvent::HoveredFile;
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
let hdrop = unsafe {
|
||||
Self::iterate_filenames(pDataObj, |filename| {
|
||||
drop_handler.send_event(Event::WindowEvent {
|
||||
window_id: WindowId::from_raw(drop_handler.window as usize),
|
||||
event: HoveredFile(filename),
|
||||
});
|
||||
})
|
||||
};
|
||||
drop_handler.hovered_is_valid = hdrop.is_some();
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
unsafe {
|
||||
ScreenToClient(drop_handler.window, &mut pt);
|
||||
}
|
||||
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
|
||||
let mut paths = Vec::new();
|
||||
let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) };
|
||||
drop_handler.valid = hdrop.is_some();
|
||||
if drop_handler.valid {
|
||||
(drop_handler.send_event)(WindowEvent::DragEntered { paths, position });
|
||||
}
|
||||
drop_handler.cursor_effect =
|
||||
if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
|
||||
if drop_handler.valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
|
||||
unsafe {
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
}
|
||||
@@ -103,10 +105,18 @@ impl FileDropHandler {
|
||||
pub unsafe extern "system" fn DragOver(
|
||||
this: *mut IDropTarget,
|
||||
_grfKeyState: u32,
|
||||
_pt: *const POINTL,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT {
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
if drop_handler.valid {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
unsafe {
|
||||
ScreenToClient(drop_handler.window, &mut pt);
|
||||
}
|
||||
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
|
||||
(drop_handler.send_event)(WindowEvent::DragMoved { position });
|
||||
}
|
||||
unsafe {
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
}
|
||||
@@ -115,13 +125,9 @@ impl FileDropHandler {
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
|
||||
use crate::event::WindowEvent::HoveredFileCancelled;
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
if drop_handler.hovered_is_valid {
|
||||
drop_handler.send_event(Event::WindowEvent {
|
||||
window_id: WindowId::from_raw(drop_handler.window as usize),
|
||||
event: HoveredFileCancelled,
|
||||
});
|
||||
if drop_handler.valid {
|
||||
(drop_handler.send_event)(WindowEvent::DragLeft { position: None });
|
||||
}
|
||||
|
||||
S_OK
|
||||
@@ -131,21 +137,27 @@ impl FileDropHandler {
|
||||
this: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
_grfKeyState: u32,
|
||||
_pt: *const POINTL,
|
||||
_pdwEffect: *mut u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT {
|
||||
use crate::event::WindowEvent::DroppedFile;
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
let hdrop = unsafe {
|
||||
Self::iterate_filenames(pDataObj, |filename| {
|
||||
drop_handler.send_event(Event::WindowEvent {
|
||||
window_id: WindowId::from_raw(drop_handler.window as usize),
|
||||
event: DroppedFile(filename),
|
||||
});
|
||||
})
|
||||
};
|
||||
if let Some(hdrop) = hdrop {
|
||||
unsafe { DragFinish(hdrop) };
|
||||
if drop_handler.valid {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
unsafe {
|
||||
ScreenToClient(drop_handler.window, &mut pt);
|
||||
}
|
||||
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
|
||||
let mut paths = Vec::new();
|
||||
let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) };
|
||||
(drop_handler.send_event)(WindowEvent::DragDropped { paths, position });
|
||||
if let Some(hdrop) = hdrop {
|
||||
unsafe {
|
||||
DragFinish(hdrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
}
|
||||
|
||||
S_OK
|
||||
@@ -155,9 +167,9 @@ impl FileDropHandler {
|
||||
unsafe { &mut *(this as *mut _) }
|
||||
}
|
||||
|
||||
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<HDROP>
|
||||
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, mut callback: F) -> Option<HDROP>
|
||||
where
|
||||
F: Fn(PathBuf),
|
||||
F: FnMut(PathBuf),
|
||||
{
|
||||
let drop_format = FORMATETC {
|
||||
cfFormat: CF_HDROP,
|
||||
@@ -207,12 +219,6 @@ impl FileDropHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl FileDropHandlerData {
|
||||
fn send_event(&self, event: Event) {
|
||||
(self.send_event)(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileDropHandler {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,27 @@
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
use std::{mem, panic};
|
||||
|
||||
use windows_sys::Win32::Foundation::HWND;
|
||||
|
||||
use super::ControlFlow;
|
||||
use super::{ActiveEventLoop, ControlFlow, EventLoopThreadExecutor};
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event::{DeviceEvent, DeviceId, StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
|
||||
use crate::platform_impl::platform::event_loop::{WindowData, GWL_USERDATA};
|
||||
use crate::platform_impl::platform::get_window_long;
|
||||
use crate::window::WindowId;
|
||||
|
||||
type EventHandler = Cell<Option<Box<dyn FnMut(Event)>>>;
|
||||
type EventHandler = Cell<Option<&'static mut (dyn ApplicationHandler + 'static)>>;
|
||||
|
||||
pub(crate) struct EventLoopRunner {
|
||||
pub(super) thread_id: u32,
|
||||
|
||||
// The event loop's win32 handles
|
||||
pub(super) thread_msg_target: HWND,
|
||||
|
||||
@@ -29,8 +34,8 @@ pub(crate) struct EventLoopRunner {
|
||||
exit: Cell<Option<i32>>,
|
||||
runner_state: Cell<RunnerState>,
|
||||
last_events_cleared: Cell<Instant>,
|
||||
event_handler: EventHandler,
|
||||
event_buffer: RefCell<VecDeque<BufferedEvent>>,
|
||||
event_handler: Rc<EventHandler>,
|
||||
event_buffer: RefCell<VecDeque<Event>>,
|
||||
|
||||
panic_error: Cell<Option<PanicError>>,
|
||||
}
|
||||
@@ -51,14 +56,20 @@ pub(crate) enum RunnerState {
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
enum BufferedEvent {
|
||||
Event(Event),
|
||||
ScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Event {
|
||||
Device { device_id: DeviceId, event: DeviceEvent },
|
||||
Window { window_id: WindowId, event: WindowEvent },
|
||||
BufferedScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
|
||||
// FIXME(madsmtm): Coalesce these into a flag (or similar) instead of handling them as events.
|
||||
// https://github.com/rust-windowing/winit/pull/3687
|
||||
WakeUp,
|
||||
}
|
||||
|
||||
impl EventLoopRunner {
|
||||
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner {
|
||||
EventLoopRunner {
|
||||
pub(crate) fn new(thread_id: u32, thread_msg_target: HWND) -> Self {
|
||||
Self {
|
||||
thread_id,
|
||||
thread_msg_target,
|
||||
interrupt_msg_dispatch: Cell::new(false),
|
||||
runner_state: Cell::new(RunnerState::Uninitialized),
|
||||
@@ -66,40 +77,50 @@ impl EventLoopRunner {
|
||||
exit: Cell::new(None),
|
||||
panic_error: Cell::new(None),
|
||||
last_events_cleared: Cell::new(Instant::now()),
|
||||
event_handler: Cell::new(None),
|
||||
event_handler: Rc::new(Cell::new(None)),
|
||||
event_buffer: RefCell::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate the application's event handler with the runner
|
||||
/// Associate the application's event handler with the runner.
|
||||
///
|
||||
/// # Safety
|
||||
/// This is ignoring the lifetime of the application handler (which may not
|
||||
/// outlive the EventLoopRunner) and can lead to undefined behaviour if
|
||||
/// the handler is not cleared before the end of real lifetime.
|
||||
///
|
||||
/// All public APIs that take an event handler (`run`, `run_on_demand`,
|
||||
/// `pump_events`) _must_ pair a call to `set_event_handler` with
|
||||
/// a call to `clear_event_handler` before returning to avoid
|
||||
/// undefined behaviour.
|
||||
pub(crate) unsafe fn set_event_handler<F>(&self, f: F)
|
||||
where
|
||||
F: FnMut(Event),
|
||||
{
|
||||
// Erase closure lifetime.
|
||||
// SAFETY: Caller upholds that the lifetime of the closure is upheld.
|
||||
let f =
|
||||
unsafe { mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event)>>(Box::new(f)) };
|
||||
let old_event_handler = self.event_handler.replace(Some(f));
|
||||
assert!(old_event_handler.is_none());
|
||||
}
|
||||
/// The returned type must not be leaked (as that would allow the application to be associated
|
||||
/// with the runner for too long).
|
||||
pub(crate) unsafe fn set_app<'app>(
|
||||
&self,
|
||||
app: &'app mut (dyn ApplicationHandler + 'app),
|
||||
) -> impl Drop + 'app {
|
||||
// Erase app lifetime, to allow storing on the event loop runner.
|
||||
//
|
||||
// SAFETY: Caller upholds that the lifetime of the closure is upheld, by not dropping the
|
||||
// return type which resets it.
|
||||
let f = unsafe {
|
||||
mem::transmute::<
|
||||
&'app mut (dyn ApplicationHandler + 'app),
|
||||
&'static mut (dyn ApplicationHandler + 'static),
|
||||
>(app)
|
||||
};
|
||||
|
||||
pub(crate) fn clear_event_handler(&self) {
|
||||
self.event_handler.set(None);
|
||||
let old_event_handler = self.event_handler.replace(Some(f));
|
||||
|
||||
assert!(old_event_handler.is_none());
|
||||
|
||||
struct Resetter(Rc<EventHandler>);
|
||||
|
||||
impl Drop for Resetter {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
Resetter(self.event_handler.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_runner(&self) {
|
||||
let EventLoopRunner {
|
||||
let Self {
|
||||
thread_id: _,
|
||||
thread_msg_target: _,
|
||||
interrupt_msg_dispatch,
|
||||
runner_state,
|
||||
@@ -188,21 +209,26 @@ impl EventLoopRunner {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor {
|
||||
EventLoopThreadExecutor { thread_id: self.thread_id, target_window: self.thread_msg_target }
|
||||
}
|
||||
}
|
||||
|
||||
/// Event dispatch functions.
|
||||
impl EventLoopRunner {
|
||||
pub(crate) fn prepare_wait(&self) {
|
||||
pub(crate) fn prepare_wait(self: &Rc<Self>) {
|
||||
self.move_state_to(RunnerState::Idle);
|
||||
}
|
||||
|
||||
pub(crate) fn wakeup(&self) {
|
||||
pub(crate) fn wakeup(self: &Rc<Self>) {
|
||||
self.move_state_to(RunnerState::HandlingMainEvents);
|
||||
}
|
||||
|
||||
pub(crate) fn send_event(&self, event: Event) {
|
||||
if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event {
|
||||
self.call_event_handler(event);
|
||||
pub(crate) fn send_event(self: &Rc<Self>, event: Event) {
|
||||
if let Event::Window { event: WindowEvent::RedrawRequested, .. } = event {
|
||||
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
|
||||
// As a rule, to ensure that `pump_events` can't block an external event loop
|
||||
// for too long, we always guarantee that `pump_events` will return control to
|
||||
// the external loop asap after a `RedrawRequested` event is dispatched.
|
||||
@@ -210,31 +236,34 @@ impl EventLoopRunner {
|
||||
} else if self.should_buffer() {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation.
|
||||
// Add the event to a buffer to be processed later.
|
||||
self.event_buffer.borrow_mut().push_back(BufferedEvent::from_event(event))
|
||||
self.event_buffer.borrow_mut().push_back(event.buffer_scale_factor())
|
||||
} else {
|
||||
self.call_event_handler(event);
|
||||
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
|
||||
self.dispatch_buffered_events();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn loop_destroyed(&self) {
|
||||
pub(crate) fn loop_destroyed(self: &Rc<Self>) {
|
||||
self.move_state_to(RunnerState::Destroyed);
|
||||
}
|
||||
|
||||
fn call_event_handler(&self, event: Event) {
|
||||
fn call_event_handler(
|
||||
self: &Rc<Self>,
|
||||
closure: impl FnOnce(&mut dyn ApplicationHandler, &dyn RootActiveEventLoop),
|
||||
) {
|
||||
self.catch_unwind(|| {
|
||||
let mut event_handler = self.event_handler.take().expect(
|
||||
let event_handler = self.event_handler.take().expect(
|
||||
"either event handler is re-entrant (likely), or no event handler is registered \
|
||||
(very unlikely)",
|
||||
);
|
||||
|
||||
event_handler(event);
|
||||
closure(event_handler, ActiveEventLoop::from_ref(self));
|
||||
|
||||
assert!(self.event_handler.replace(Some(event_handler)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
fn dispatch_buffered_events(&self) {
|
||||
fn dispatch_buffered_events(self: &Rc<Self>) {
|
||||
loop {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
@@ -242,7 +271,9 @@ impl EventLoopRunner {
|
||||
// `process_event` will fail.
|
||||
let buffered_event_opt = self.event_buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => e.dispatch_event(|e| self.call_event_handler(e)),
|
||||
Some(e) => {
|
||||
self.call_event_handler(|app, event_loop| e.dispatch_event(app, event_loop))
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
@@ -272,7 +303,7 @@ impl EventLoopRunner {
|
||||
/// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the
|
||||
/// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and
|
||||
/// `new_runner_state == Idle`), the intermediate state transitions will still be executed.
|
||||
fn move_state_to(&self, new_runner_state: RunnerState) {
|
||||
fn move_state_to(self: &Rc<Self>, new_runner_state: RunnerState) {
|
||||
use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized};
|
||||
|
||||
match (self.runner_state.replace(new_runner_state), new_runner_state) {
|
||||
@@ -287,14 +318,14 @@ impl EventLoopRunner {
|
||||
},
|
||||
(Uninitialized, Idle) => {
|
||||
self.call_new_events(true);
|
||||
self.call_event_handler(Event::AboutToWait);
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
},
|
||||
(Uninitialized, Destroyed) => {
|
||||
self.call_new_events(true);
|
||||
self.call_event_handler(Event::AboutToWait);
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
self.call_event_handler(Event::LoopExiting);
|
||||
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
|
||||
},
|
||||
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
|
||||
|
||||
@@ -303,25 +334,25 @@ impl EventLoopRunner {
|
||||
self.call_new_events(false);
|
||||
},
|
||||
(Idle, Destroyed) => {
|
||||
self.call_event_handler(Event::LoopExiting);
|
||||
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
|
||||
},
|
||||
|
||||
(HandlingMainEvents, Idle) => {
|
||||
// This is always the last event we dispatch before waiting for new events
|
||||
self.call_event_handler(Event::AboutToWait);
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
},
|
||||
(HandlingMainEvents, Destroyed) => {
|
||||
self.call_event_handler(Event::AboutToWait);
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
self.call_event_handler(Event::LoopExiting);
|
||||
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
|
||||
},
|
||||
|
||||
(Destroyed, _) => panic!("cannot move state from Destroyed"),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_new_events(&self, init: bool) {
|
||||
fn call_new_events(self: &Rc<Self>, init: bool) {
|
||||
let start_cause = match (init, self.control_flow(), self.exit.get()) {
|
||||
(true, ..) => StartCause::Init,
|
||||
(false, ControlFlow::Poll, None) => StartCause::Poll,
|
||||
@@ -343,45 +374,55 @@ impl EventLoopRunner {
|
||||
}
|
||||
},
|
||||
};
|
||||
self.call_event_handler(Event::NewEvents(start_cause));
|
||||
self.call_event_handler(|app, event_loop| app.new_events(event_loop, start_cause));
|
||||
// NB: For consistency all platforms must call `can_create_surfaces` even though Windows
|
||||
// applications don't themselves have a formal surface destroy/create lifecycle.
|
||||
if init {
|
||||
self.call_event_handler(Event::CreateSurfaces);
|
||||
self.call_event_handler(|app, event_loop| app.can_create_surfaces(event_loop));
|
||||
}
|
||||
self.dispatch_buffered_events();
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferedEvent {
|
||||
pub fn from_event(event: Event) -> BufferedEvent {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
impl Event {
|
||||
/// Mark ScaleFactorChanged as being buffered (which forces us to re-handle when the user set a
|
||||
/// new size).
|
||||
pub fn buffer_scale_factor(self) -> Self {
|
||||
match self {
|
||||
Self::Window {
|
||||
event: WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer },
|
||||
window_id,
|
||||
} => BufferedEvent::ScaleFactorChanged(
|
||||
} => Event::BufferedScaleFactorChanged(
|
||||
window_id.into_raw() as HWND,
|
||||
scale_factor,
|
||||
*surface_size_writer.new_surface_size.upgrade().unwrap().lock().unwrap(),
|
||||
),
|
||||
event => BufferedEvent::Event(event),
|
||||
event => event,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) {
|
||||
pub fn dispatch_event(
|
||||
self,
|
||||
app: &mut dyn ApplicationHandler,
|
||||
event_loop: &dyn RootActiveEventLoop,
|
||||
) {
|
||||
match self {
|
||||
Self::Event(event) => dispatch(event),
|
||||
Self::ScaleFactorChanged(window, scale_factor, new_surface_size) => {
|
||||
Self::Window { window_id, event } => app.window_event(event_loop, window_id, event),
|
||||
Self::Device { device_id, event } => {
|
||||
app.device_event(event_loop, Some(device_id), event)
|
||||
},
|
||||
Self::BufferedScaleFactorChanged(window, scale_factor, new_surface_size) => {
|
||||
let user_new_surface_size = Arc::new(Mutex::new(new_surface_size));
|
||||
dispatch(Event::WindowEvent {
|
||||
window_id: WindowId::from_raw(window as usize),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
app.window_event(
|
||||
event_loop,
|
||||
WindowId::from_raw(window as usize),
|
||||
WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(
|
||||
&user_new_surface_size,
|
||||
)),
|
||||
},
|
||||
});
|
||||
);
|
||||
let surface_size = *user_new_surface_size.lock().unwrap();
|
||||
|
||||
drop(user_new_surface_size);
|
||||
@@ -395,6 +436,7 @@ impl BufferedEvent {
|
||||
window_flags.set_size(window, surface_size);
|
||||
}
|
||||
},
|
||||
Self::WakeUp => app.proxy_wake_up(event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ffi::c_void;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, mem};
|
||||
use std::{fmt, io, mem, ptr};
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
use windows_sys::core::PCWSTR;
|
||||
@@ -40,7 +40,7 @@ impl RgbaIcon {
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
CreateIcon(
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
self.width as i32,
|
||||
self.height as i32,
|
||||
1,
|
||||
@@ -49,7 +49,7 @@ impl RgbaIcon {
|
||||
rgba.as_ptr(),
|
||||
)
|
||||
};
|
||||
if handle != 0 {
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
@@ -68,6 +68,9 @@ struct RaiiIcon {
|
||||
handle: HICON,
|
||||
}
|
||||
|
||||
unsafe impl Send for RaiiIcon {}
|
||||
unsafe impl Sync for RaiiIcon {}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct WinIcon {
|
||||
inner: Arc<RaiiIcon>,
|
||||
@@ -91,7 +94,7 @@ impl WinIcon {
|
||||
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
wide_path.as_ptr(),
|
||||
IMAGE_ICON,
|
||||
width,
|
||||
@@ -99,7 +102,7 @@ impl WinIcon {
|
||||
LR_DEFAULTSIZE | LR_LOADFROMFILE,
|
||||
)
|
||||
};
|
||||
if handle != 0 {
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle as HICON))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
@@ -109,20 +112,35 @@ impl WinIcon {
|
||||
pub fn from_resource(
|
||||
resource_id: u16,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_resource_ptr(resource_id as PCWSTR, size)
|
||||
}
|
||||
|
||||
pub fn from_resource_name(
|
||||
resource_name: &str,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let wide_name = util::encode_wide(resource_name);
|
||||
Self::from_resource_ptr(wide_name.as_ptr(), size)
|
||||
}
|
||||
|
||||
fn from_resource_ptr(
|
||||
resource: PCWSTR,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
util::get_instance_handle(),
|
||||
resource_id as PCWSTR,
|
||||
resource,
|
||||
IMAGE_ICON,
|
||||
width,
|
||||
height,
|
||||
LR_DEFAULTSIZE,
|
||||
)
|
||||
};
|
||||
if handle != 0 {
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle as HICON))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
@@ -136,7 +154,7 @@ impl WinIcon {
|
||||
|
||||
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle());
|
||||
SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle() as isize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,13 +205,13 @@ impl WinCursor {
|
||||
let h = image.height as i32;
|
||||
|
||||
unsafe {
|
||||
let hdc_screen = GetDC(0);
|
||||
if hdc_screen == 0 {
|
||||
let hdc_screen = GetDC(ptr::null_mut());
|
||||
if hdc_screen.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
let hbm_color = CreateCompatibleBitmap(hdc_screen, w, h);
|
||||
ReleaseDC(0, hdc_screen);
|
||||
if hbm_color == 0 {
|
||||
ReleaseDC(ptr::null_mut(), hdc_screen);
|
||||
if hbm_color.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
if SetBitmapBits(hbm_color, bgra.len() as u32, bgra.as_ptr() as *const c_void) == 0 {
|
||||
@@ -204,7 +222,7 @@ impl WinCursor {
|
||||
// Mask created according to https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap#parameters
|
||||
let mask_bits: Vec<u8> = vec![0xff; ((((w + 15) >> 4) << 1) * h) as usize];
|
||||
let hbm_mask = CreateBitmap(w, h, 1, 1, mask_bits.as_ptr() as *const _);
|
||||
if hbm_mask == 0 {
|
||||
if hbm_mask.is_null() {
|
||||
DeleteObject(hbm_color);
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
@@ -220,7 +238,7 @@ impl WinCursor {
|
||||
let handle = CreateIconIndirect(&icon_info as *const _);
|
||||
DeleteObject(hbm_color);
|
||||
DeleteObject(hbm_mask);
|
||||
if handle == 0 {
|
||||
if handle.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
|
||||
@@ -234,6 +252,9 @@ pub struct RaiiCursor {
|
||||
handle: HCURSOR,
|
||||
}
|
||||
|
||||
unsafe impl Send for RaiiCursor {}
|
||||
unsafe impl Sync for RaiiCursor {}
|
||||
|
||||
impl Drop for RaiiCursor {
|
||||
fn drop(&mut self) {
|
||||
unsafe { DestroyCursor(self.handle) };
|
||||
|
||||
@@ -3,12 +3,11 @@ use std::os::windows::prelude::OsStringExt;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use windows_sys::Win32::Foundation::{POINT, RECT};
|
||||
use windows_sys::Win32::Globalization::HIMC;
|
||||
use windows_sys::Win32::UI::Input::Ime::{
|
||||
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
|
||||
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
|
||||
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM, GCS_COMPATTR,
|
||||
GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
|
||||
GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, HIMC, IACE_CHILDREN, IACE_DEFAULT,
|
||||
};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED};
|
||||
|
||||
@@ -138,9 +137,9 @@ impl ImeContext {
|
||||
}
|
||||
|
||||
if allowed {
|
||||
unsafe { ImmAssociateContextEx(hwnd, 0, IACE_DEFAULT) };
|
||||
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_DEFAULT) };
|
||||
} else {
|
||||
unsafe { ImmAssociateContextEx(hwnd, 0, IACE_CHILDREN) };
|
||||
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_CHILDREN) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM};
|
||||
use windows_sys::Win32::System::SystemServices::LANG_KOREAN;
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW,
|
||||
GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW, HKL,
|
||||
MAPVK_VK_TO_VSC_EX, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR,
|
||||
VK_CONTROL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT,
|
||||
VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, VK_NUMLOCK,
|
||||
@@ -20,7 +20,6 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT,
|
||||
VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP,
|
||||
};
|
||||
use windows_sys::Win32::UI::TextServices::HKL;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, WM_KEYLAST,
|
||||
WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
||||
@@ -32,7 +31,7 @@ use crate::platform_impl::platform::event_loop::ProcResult;
|
||||
use crate::platform_impl::platform::keyboard_layout::{
|
||||
Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE,
|
||||
};
|
||||
use crate::platform_impl::platform::{loword, primarylangid, KeyEventExtra};
|
||||
use crate::platform_impl::platform::{loword, primarylangid};
|
||||
|
||||
pub type ExScancode = u16;
|
||||
|
||||
@@ -452,7 +451,7 @@ impl KeyEventBuilder {
|
||||
|
||||
let mut event = event_info.finalize();
|
||||
event.logical_key = logical_key;
|
||||
event.platform_specific.text_with_all_modifiers = text;
|
||||
event.text_with_all_modifiers = text;
|
||||
Some(MessageAsKeyEvent { event, is_synthetic: true })
|
||||
}
|
||||
}
|
||||
@@ -630,10 +629,8 @@ impl PartialKeyEventInfo {
|
||||
location: self.location,
|
||||
state: self.key_state,
|
||||
repeat: self.is_repeat,
|
||||
platform_specific: KeyEventExtra {
|
||||
text_with_all_modifiers: char_with_all_modifiers,
|
||||
key_without_modifiers: self.key_without_modifiers,
|
||||
},
|
||||
text_with_all_modifiers: char_with_all_modifiers,
|
||||
key_without_modifiers: self.key_without_modifiers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,19 @@ use std::sync::Mutex;
|
||||
use smol_str::SmolStr;
|
||||
use windows_sys::Win32::System::SystemServices::{LANG_JAPANESE, LANG_KOREAN};
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, MAPVK_VK_TO_VSC_EX, VIRTUAL_KEY,
|
||||
VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK, VK_BROWSER_FAVORITES,
|
||||
VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH, VK_BROWSER_SEARCH, VK_BROWSER_STOP,
|
||||
VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE,
|
||||
VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11,
|
||||
VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22,
|
||||
VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A,
|
||||
VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT,
|
||||
VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER,
|
||||
VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON,
|
||||
GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, HKL, MAPVK_VK_TO_VSC_EX,
|
||||
VIRTUAL_KEY, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK,
|
||||
VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH,
|
||||
VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_CONVERT,
|
||||
VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE,
|
||||
VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18,
|
||||
VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7,
|
||||
VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A, VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT,
|
||||
VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU,
|
||||
VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON,
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT,
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER,
|
||||
VK_GAMEPAD_VIEW, VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME, VK_ICO_00,
|
||||
@@ -39,7 +40,6 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT,
|
||||
VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM,
|
||||
};
|
||||
use windows_sys::Win32::UI::TextServices::HKL;
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey};
|
||||
use crate::platform_impl::{loword, primarylangid, scancode_to_physicalkey};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use smol_str::SmolStr;
|
||||
use windows_sys::Win32::Foundation::HWND;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
|
||||
|
||||
@@ -6,12 +5,11 @@ pub(crate) use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes
|
||||
pub use self::icon::WinIcon as PlatformIcon;
|
||||
pub(crate) use self::icon::{SelectedCursor, WinCursor as PlatformCustomCursor, WinIcon};
|
||||
pub(crate) use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
|
||||
pub(crate) use self::monitor::MonitorHandle;
|
||||
pub(crate) use self::window::Window;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
|
||||
use crate::event::DeviceId;
|
||||
use crate::icon::Icon;
|
||||
use crate::keyboard::Key;
|
||||
use crate::platform::windows::{BackdropType, Color, CornerPreference};
|
||||
use crate::platform_impl::Fullscreen;
|
||||
|
||||
@@ -61,12 +59,6 @@ fn wrap_device_id(id: u32) -> DeviceId {
|
||||
DeviceId::from_raw(id as i64)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
pub key_without_modifiers: Key,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn get_xbutton_wparam(x: u32) -> u16 {
|
||||
hiword(x)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::hash::Hash;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::{io, mem, ptr};
|
||||
@@ -13,26 +13,20 @@ use windows_sys::Win32::Graphics::Gdi::{
|
||||
|
||||
use super::util::decode_wide;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
|
||||
use crate::monitor::VideoMode;
|
||||
use crate::platform_impl::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
|
||||
use crate::platform_impl::platform::util::has_flag;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: Option<NonZeroU16>,
|
||||
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
pub(crate) mode: VideoMode,
|
||||
// DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen
|
||||
pub(crate) native_video_mode: Box<DEVMODEW>,
|
||||
}
|
||||
|
||||
impl PartialEq for VideoModeHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.size == other.size
|
||||
&& self.bit_depth == other.bit_depth
|
||||
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
|
||||
&& self.monitor == other.monitor
|
||||
self.mode == other.mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,65 +34,39 @@ impl Eq for VideoModeHandle {}
|
||||
|
||||
impl std::hash::Hash for VideoModeHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.size.hash(state);
|
||||
self.bit_depth.hash(state);
|
||||
self.refresh_rate_millihertz.hash(state);
|
||||
self.monitor.hash(state);
|
||||
self.mode.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("VideoModeHandle")
|
||||
.field("size", &self.size)
|
||||
.field("bit_depth", &self.bit_depth)
|
||||
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
|
||||
.field("monitor", &self.monitor)
|
||||
.finish()
|
||||
f.debug_struct("VideoMode").field("mode", &self.mode).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(monitor: MonitorHandle, mode: DEVMODEW) -> Self {
|
||||
fn new(native_video_mode: DEVMODEW) -> Self {
|
||||
const REQUIRED_FIELDS: u32 =
|
||||
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
|
||||
assert!(has_flag(native_video_mode.dmFields, REQUIRED_FIELDS));
|
||||
|
||||
VideoModeHandle {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: NonZeroU16::new(mode.dmBitsPerPel as u16),
|
||||
refresh_rate_millihertz: NonZeroU32::new(mode.dmDisplayFrequency * 1000),
|
||||
monitor,
|
||||
native_video_mode: Box::new(mode),
|
||||
}
|
||||
}
|
||||
let mode = VideoMode {
|
||||
size: (native_video_mode.dmPelsWidth, native_video_mode.dmPelsHeight).into(),
|
||||
bit_depth: NonZeroU16::new(native_video_mode.dmBitsPerPel as u16),
|
||||
refresh_rate_millihertz: NonZeroU32::new(native_video_mode.dmDisplayFrequency * 1000),
|
||||
};
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> MonitorHandle {
|
||||
self.monitor.clone()
|
||||
VideoModeHandle { mode, native_video_mode: Box::new(native_video_mode) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle(HMONITOR);
|
||||
|
||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
// Send and Sync are not implemented for HMONITOR, we have to wrap it and implement them manually.
|
||||
|
||||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
unsafe extern "system" fn monitor_enum_proc(
|
||||
hmonitor: HMONITOR,
|
||||
@@ -115,7 +83,7 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
|
||||
unsafe {
|
||||
EnumDisplayMonitors(
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
ptr::null(),
|
||||
Some(monitor_enum_proc),
|
||||
&mut monitors as *mut _ as LPARAM,
|
||||
@@ -193,7 +161,7 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
||||
pub fn current_video_mode(&self) -> Option<VideoMode> {
|
||||
let monitor_info = get_monitor_info(self.0).ok()?;
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
unsafe {
|
||||
@@ -204,29 +172,26 @@ impl MonitorHandle {
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(VideoModeHandle::new(self.clone(), mode))
|
||||
Some(VideoModeHandle::new(mode).mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
pub(crate) fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
|
||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||
// fields are probably changing, but we aren't looking at those fields
|
||||
// anyway), so we're using a BTreeSet deduplicate
|
||||
let mut modes = BTreeSet::<RootVideoModeHandle>::new();
|
||||
let mod_map = |mode: RootVideoModeHandle| mode.video_mode;
|
||||
let mut modes = HashSet::<VideoModeHandle>::new();
|
||||
|
||||
let monitor_info = match get_monitor_info(self.0) {
|
||||
Ok(monitor_info) => monitor_info,
|
||||
Err(error) => {
|
||||
tracing::warn!("Error from get_monitor_info: {error}");
|
||||
return modes.into_iter().map(mod_map);
|
||||
return modes.into_iter();
|
||||
},
|
||||
};
|
||||
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let mut mode: DEVMODEW = unsafe { mem::zeroed() };
|
||||
@@ -236,13 +201,15 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
// Use Ord impl of RootVideoModeHandle
|
||||
modes.insert(RootVideoModeHandle {
|
||||
video_mode: VideoModeHandle::new(self.clone(), mode),
|
||||
});
|
||||
modes.insert(VideoModeHandle::new(mode));
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
modes.into_iter().map(mod_map)
|
||||
modes.into_iter()
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.video_mode_handles().map(|mode| mode.mode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ pub fn register_all_mice_and_keyboards_for_raw_input(
|
||||
// RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget)
|
||||
let flags = match filter {
|
||||
DeviceEvents::Never => {
|
||||
window_handle = 0;
|
||||
window_handle = ptr::null_mut();
|
||||
RIDEV_REMOVE
|
||||
},
|
||||
DeviceEvents::WhenFocused => RIDEV_DEVNOTIFY,
|
||||
|
||||
@@ -189,7 +189,7 @@ pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const
|
||||
|
||||
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
||||
let module = unsafe { LoadLibraryA(library.as_ptr()) };
|
||||
if module == 0 {
|
||||
if module.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::cell::Cell;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::{io, panic, ptr};
|
||||
@@ -59,7 +60,9 @@ use crate::platform_impl::platform::dpi::{
|
||||
dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi,
|
||||
};
|
||||
use crate::platform_impl::platform::drop_handler::FileDropHandler;
|
||||
use crate::platform_impl::platform::event_loop::{self, ActiveEventLoop, DESTROY_MSG_ID};
|
||||
use crate::platform_impl::platform::event_loop::{
|
||||
self, ActiveEventLoop, Event, EventLoopRunner, DESTROY_MSG_ID,
|
||||
};
|
||||
use crate::platform_impl::platform::icon::{self, IconType};
|
||||
use crate::platform_impl::platform::ime::ImeContext;
|
||||
use crate::platform_impl::platform::keyboard::KeyEventBuilder;
|
||||
@@ -73,10 +76,25 @@ use crate::window::{
|
||||
WindowLevel,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
/// We need to pass the window handle to the event loop thread, which means it needs to be
|
||||
/// Send+Sync.
|
||||
struct SyncWindowHandle(HWND);
|
||||
|
||||
unsafe impl Send for SyncWindowHandle {}
|
||||
unsafe impl Sync for SyncWindowHandle {}
|
||||
|
||||
impl SyncWindowHandle {
|
||||
fn hwnd(&self) -> HWND {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub(crate) struct Window {
|
||||
/// Main handle for the window.
|
||||
window: HWND,
|
||||
window: SyncWindowHandle,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<WindowState>>,
|
||||
@@ -94,7 +112,7 @@ impl Window {
|
||||
// First person to remove the need for cloning here gets a cookie!
|
||||
//
|
||||
// done. you owe me -- ossi
|
||||
unsafe { init(w_attr, event_loop) }
|
||||
unsafe { init(w_attr, &event_loop.0) }
|
||||
}
|
||||
|
||||
fn window_state_lock(&self) -> MutexGuard<'_, WindowState> {
|
||||
@@ -103,7 +121,7 @@ impl Window {
|
||||
|
||||
/// Returns the `hwnd` of this window.
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window
|
||||
self.window.hwnd()
|
||||
}
|
||||
|
||||
pub unsafe fn rwh_06_no_thread_check(
|
||||
@@ -111,7 +129,7 @@ impl Window {
|
||||
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe {
|
||||
// SAFETY: Handle will never be zero.
|
||||
std::num::NonZeroIsize::new_unchecked(self.window)
|
||||
std::num::NonZeroIsize::new_unchecked(self.window.hwnd() as isize)
|
||||
});
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = std::num::NonZeroIsize::new(hinstance);
|
||||
@@ -150,8 +168,7 @@ impl Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow)
|
||||
});
|
||||
});
|
||||
@@ -202,7 +219,7 @@ impl Window {
|
||||
unsafe { ReleaseCapture() };
|
||||
|
||||
unsafe {
|
||||
PostMessageW(window, WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM)
|
||||
PostMessageW(window.hwnd(), WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM)
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -227,7 +244,7 @@ impl Window {
|
||||
|
||||
// get the current system menu
|
||||
let h_menu = GetSystemMenu(self.hwnd(), 0);
|
||||
if h_menu == 0 {
|
||||
if h_menu.is_null() {
|
||||
warn!("The corresponding window doesn't have a system menu");
|
||||
// This situation should not be treated as an error so just return without showing
|
||||
// menu.
|
||||
@@ -334,7 +351,7 @@ impl Window {
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
// Restore fullscreen video mode on exit.
|
||||
if matches!(self.fullscreen(), Some(CoreFullscreen::Exclusive(_))) {
|
||||
if matches!(self.fullscreen(), Some(CoreFullscreen::Exclusive(_, _))) {
|
||||
self.set_fullscreen(None);
|
||||
}
|
||||
|
||||
@@ -373,7 +390,7 @@ impl CoreWindow for Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::TRANSPARENT, transparent)
|
||||
});
|
||||
});
|
||||
@@ -386,21 +403,21 @@ impl CoreWindow for Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::VISIBLE, visible)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> Option<bool> {
|
||||
Some(unsafe { IsWindowVisible(self.window) == 1 })
|
||||
Some(unsafe { IsWindowVisible(self.window.hwnd()) == 1 })
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
// NOTE: mark that we requested a redraw to handle requests during `WM_PAINT` handling.
|
||||
self.window_state.lock().unwrap().redraw_requested = true;
|
||||
unsafe {
|
||||
RedrawWindow(self.hwnd(), ptr::null(), 0, RDW_INTERNALPAINT);
|
||||
RedrawWindow(self.hwnd(), ptr::null(), ptr::null_mut(), RDW_INTERNALPAINT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +451,7 @@ impl CoreWindow for Window {
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -442,14 +459,14 @@ impl CoreWindow for Window {
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
self.hwnd(),
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
0,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE,
|
||||
);
|
||||
InvalidateRgn(self.hwnd(), 0, false.into());
|
||||
InvalidateRgn(self.hwnd(), ptr::null_mut(), false.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,7 +502,7 @@ impl CoreWindow for Window {
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -528,7 +545,7 @@ impl CoreWindow for Window {
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::RESIZABLE, resizable)
|
||||
});
|
||||
});
|
||||
@@ -545,7 +562,7 @@ impl CoreWindow for Window {
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MINIMIZABLE, buttons.contains(WindowButtons::MINIMIZE));
|
||||
f.set(WindowFlags::MAXIMIZABLE, buttons.contains(WindowButtons::MAXIMIZE));
|
||||
f.set(WindowFlags::CLOSABLE, buttons.contains(WindowButtons::CLOSE))
|
||||
@@ -573,7 +590,7 @@ impl CoreWindow for Window {
|
||||
Cursor::Icon(icon) => {
|
||||
self.window_state_lock().mouse.selected_cursor = SelectedCursor::Named(icon);
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let cursor = LoadCursorW(0, util::to_windows_cursor(icon));
|
||||
let cursor = LoadCursorW(ptr::null_mut(), util::to_windows_cursor(icon));
|
||||
SetCursor(cursor);
|
||||
});
|
||||
},
|
||||
@@ -606,7 +623,7 @@ impl CoreWindow for Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.map_err(|err| os_error!(err).into());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -625,7 +642,7 @@ impl CoreWindow for Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible))
|
||||
.set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::HIDDEN, !visible))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -687,7 +704,7 @@ impl CoreWindow for Window {
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
|
||||
});
|
||||
});
|
||||
@@ -710,7 +727,7 @@ impl CoreWindow for Window {
|
||||
WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| {
|
||||
f.set(WindowFlags::MINIMIZED, is_minimized)
|
||||
});
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MINIMIZED, minimized)
|
||||
});
|
||||
});
|
||||
@@ -726,7 +743,7 @@ impl CoreWindow for Window {
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, maximized)
|
||||
});
|
||||
});
|
||||
@@ -756,7 +773,7 @@ impl CoreWindow for Window {
|
||||
// Return if saved Borderless(monitor) is the same as current monitor when requested
|
||||
// fullscreen is Borderless(None)
|
||||
(Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None)))
|
||||
if *monitor == monitor::current_monitor(window) =>
|
||||
if *monitor == monitor::current_monitor(window.hwnd()) =>
|
||||
{
|
||||
return
|
||||
},
|
||||
@@ -771,15 +788,19 @@ impl CoreWindow for Window {
|
||||
// Change video mode if we're transitioning to or from exclusive
|
||||
// fullscreen
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
(_, Some(Fullscreen::Exclusive(video_mode))) => {
|
||||
let monitor = video_mode.monitor();
|
||||
(_, Some(Fullscreen::Exclusive(monitor, video_mode))) => {
|
||||
let monitor_info = monitor::get_monitor_info(monitor.hmonitor()).unwrap();
|
||||
let video_mode =
|
||||
match monitor.video_mode_handles().find(|mode| &mode.mode == video_mode) {
|
||||
Some(monitor) => monitor,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let res = unsafe {
|
||||
ChangeDisplaySettingsExW(
|
||||
monitor_info.szDevice.as_ptr(),
|
||||
&*video_mode.native_video_mode,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
CDS_FULLSCREEN,
|
||||
ptr::null(),
|
||||
)
|
||||
@@ -791,12 +812,12 @@ impl CoreWindow for Window {
|
||||
debug_assert!(res != DISP_CHANGE_FAILED);
|
||||
assert_eq!(res, DISP_CHANGE_SUCCESSFUL);
|
||||
},
|
||||
(Some(Fullscreen::Exclusive(_)), _) => {
|
||||
(Some(Fullscreen::Exclusive(..)), _) => {
|
||||
let res = unsafe {
|
||||
ChangeDisplaySettingsExW(
|
||||
ptr::null(),
|
||||
ptr::null(),
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
CDS_FULLSCREEN,
|
||||
ptr::null(),
|
||||
)
|
||||
@@ -821,14 +842,14 @@ impl CoreWindow for Window {
|
||||
// fine, taking control back from the DWM and ensuring that the `SetWindowPos` call
|
||||
// below goes through.
|
||||
let mut msg = mem::zeroed();
|
||||
PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE);
|
||||
PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, PM_NOREMOVE);
|
||||
}
|
||||
|
||||
// Update window style
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
|
||||
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
|
||||
matches!(fullscreen, Some(Fullscreen::Exclusive(_, _))),
|
||||
);
|
||||
f.set(
|
||||
WindowFlags::MARKER_BORDERLESS_FULLSCREEN,
|
||||
@@ -842,7 +863,7 @@ impl CoreWindow for Window {
|
||||
// will generate WM_SIZE messages of the old window size that can race with what we set
|
||||
// below
|
||||
unsafe {
|
||||
taskbar_mark_fullscreen(window, fullscreen.is_some());
|
||||
taskbar_mark_fullscreen(window.hwnd(), fullscreen.is_some());
|
||||
}
|
||||
|
||||
// Update window bounds
|
||||
@@ -851,16 +872,16 @@ impl CoreWindow for Window {
|
||||
// Save window bounds before entering fullscreen
|
||||
let placement = unsafe {
|
||||
let mut placement = mem::zeroed();
|
||||
GetWindowPlacement(window, &mut placement);
|
||||
GetWindowPlacement(window.hwnd(), &mut placement);
|
||||
placement
|
||||
};
|
||||
|
||||
window_state.lock().unwrap().saved_window = Some(SavedWindow { placement });
|
||||
|
||||
let monitor = match &fullscreen {
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
Fullscreen::Exclusive(monitor, _) => monitor.clone(),
|
||||
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window.hwnd()),
|
||||
};
|
||||
|
||||
let position: (i32, i32) = monitor.position().unwrap_or_default().into();
|
||||
@@ -868,15 +889,15 @@ impl CoreWindow for Window {
|
||||
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
window,
|
||||
0,
|
||||
window.hwnd(),
|
||||
ptr::null_mut(),
|
||||
position.0,
|
||||
position.1,
|
||||
size.0 as i32,
|
||||
size.1 as i32,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER,
|
||||
);
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
InvalidateRgn(window.hwnd(), ptr::null_mut(), false.into());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
@@ -884,8 +905,8 @@ impl CoreWindow for Window {
|
||||
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
|
||||
drop(window_state_lock);
|
||||
unsafe {
|
||||
SetWindowPlacement(window, &placement);
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
SetWindowPlacement(window.hwnd(), &placement);
|
||||
InvalidateRgn(window.hwnd(), ptr::null_mut(), false.into());
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -899,7 +920,7 @@ impl CoreWindow for Window {
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::MARKER_DECORATIONS, decorations)
|
||||
});
|
||||
});
|
||||
@@ -916,7 +937,7 @@ impl CoreWindow for Window {
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
|
||||
f.set(WindowFlags::ALWAYS_ON_TOP, level == WindowLevel::AlwaysOnTop);
|
||||
f.set(WindowFlags::ALWAYS_ON_BOTTOM, level == WindowLevel::AlwaysOnBottom);
|
||||
});
|
||||
@@ -949,7 +970,7 @@ impl CoreWindow for Window {
|
||||
let state = self.window_state.clone();
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let scale_factor = state.lock().unwrap().scale_factor;
|
||||
ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor);
|
||||
ImeContext::current(window.hwnd()).set_ime_cursor_area(spot, size, scale_factor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -958,7 +979,7 @@ impl CoreWindow for Window {
|
||||
let state = self.window_state.clone();
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
state.lock().unwrap().ime_allowed = allowed;
|
||||
ImeContext::set_ime_allowed(window, allowed);
|
||||
ImeContext::set_ime_allowed(window.hwnd(), allowed);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -967,7 +988,7 @@ impl CoreWindow for Window {
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let window = self.window;
|
||||
let active_window_handle = unsafe { GetActiveWindow() };
|
||||
if window == active_window_handle {
|
||||
if window.hwnd() == active_window_handle {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -981,7 +1002,7 @@ impl CoreWindow for Window {
|
||||
|
||||
let flash_info = FLASHWINFO {
|
||||
cbSize: mem::size_of::<FLASHWINFO>() as u32,
|
||||
hwnd: window,
|
||||
hwnd: window.hwnd(),
|
||||
dwFlags: flags,
|
||||
uCount: count,
|
||||
dwTimeout: 0,
|
||||
@@ -991,7 +1012,7 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn set_theme(&self, theme: Option<Theme>) {
|
||||
try_theme(self.window, theme);
|
||||
try_theme(self.window.hwnd(), theme);
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
@@ -1004,9 +1025,9 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
let len = unsafe { GetWindowTextLengthW(self.window) } + 1;
|
||||
let len = unsafe { GetWindowTextLengthW(self.window.hwnd()) } + 1;
|
||||
let mut buf = vec![0; len as usize];
|
||||
unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) };
|
||||
unsafe { GetWindowTextW(self.window.hwnd(), buf.as_mut_ptr(), len) };
|
||||
util::decode_wide(&buf).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
@@ -1016,10 +1037,10 @@ impl CoreWindow for Window {
|
||||
|
||||
let is_visible = window_flags.contains(WindowFlags::VISIBLE);
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
let is_foreground = self.window == unsafe { GetForegroundWindow() };
|
||||
let is_foreground = self.window.hwnd() == unsafe { GetForegroundWindow() };
|
||||
|
||||
if is_visible && !is_minimized && !is_foreground {
|
||||
unsafe { force_window_active(self.window) };
|
||||
unsafe { force_window_active(self.window.hwnd()) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1064,7 +1085,7 @@ impl CoreWindow for Window {
|
||||
|
||||
pub(super) struct InitData<'a> {
|
||||
// inputs
|
||||
pub event_loop: &'a ActiveEventLoop,
|
||||
pub runner: &'a Rc<EventLoopRunner>,
|
||||
pub attributes: WindowAttributes,
|
||||
pub window_flags: WindowFlags,
|
||||
// outputs
|
||||
@@ -1107,7 +1128,11 @@ impl InitData<'_> {
|
||||
|
||||
unsafe { ImeContext::set_ime_allowed(window, false) };
|
||||
|
||||
Window { window, window_state, thread_executor: self.event_loop.create_thread_executor() }
|
||||
Window {
|
||||
window: SyncWindowHandle(window),
|
||||
window_state,
|
||||
thread_executor: self.runner.create_thread_executor(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData {
|
||||
@@ -1125,16 +1150,19 @@ impl InitData<'_> {
|
||||
);
|
||||
}
|
||||
|
||||
let file_drop_runner = self.event_loop.runner_shared.clone();
|
||||
let file_drop_runner = self.runner.clone();
|
||||
let window_id = win.id();
|
||||
let file_drop_handler = FileDropHandler::new(
|
||||
win.window,
|
||||
Box::new(move |event| file_drop_runner.send_event(event)),
|
||||
win.window.hwnd(),
|
||||
Box::new(move |event| {
|
||||
file_drop_runner.send_event(Event::Window { window_id, event })
|
||||
}),
|
||||
);
|
||||
|
||||
let handler_interface_ptr =
|
||||
unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void };
|
||||
|
||||
assert_eq!(unsafe { RegisterDragDrop(win.window, handler_interface_ptr) }, S_OK);
|
||||
assert_eq!(unsafe { RegisterDragDrop(win.window.hwnd(), handler_interface_ptr) }, S_OK);
|
||||
Some(file_drop_handler)
|
||||
} else {
|
||||
None
|
||||
@@ -1142,7 +1170,7 @@ impl InitData<'_> {
|
||||
|
||||
event_loop::WindowData {
|
||||
window_state: win.window_state.clone(),
|
||||
event_loop_runner: self.event_loop.runner_shared.clone(),
|
||||
event_loop_runner: self.runner.clone(),
|
||||
key_event_builder: KeyEventBuilder::default(),
|
||||
_file_drop_handler: file_drop_handler,
|
||||
userdata_removed: Cell::new(false),
|
||||
@@ -1154,7 +1182,7 @@ impl InitData<'_> {
|
||||
// The user data will be registered for the window and can be accessed within the window event
|
||||
// callback.
|
||||
pub unsafe fn on_nccreate(&mut self, window: HWND) -> Option<isize> {
|
||||
let runner = self.event_loop.runner_shared.clone();
|
||||
let runner = self.runner.clone();
|
||||
let result = runner.catch_unwind(|| {
|
||||
let window = unsafe { self.create_window(window) };
|
||||
let window_data = unsafe { self.create_window_data(&window) };
|
||||
@@ -1246,7 +1274,7 @@ impl InitData<'_> {
|
||||
}
|
||||
unsafe fn init(
|
||||
attributes: WindowAttributes,
|
||||
event_loop: &ActiveEventLoop,
|
||||
runner: &Rc<EventLoopRunner>,
|
||||
) -> Result<Window, RequestError> {
|
||||
let title = util::encode_wide(&attributes.title);
|
||||
|
||||
@@ -1300,7 +1328,7 @@ unsafe fn init(
|
||||
let menu = attributes.platform_specific.menu;
|
||||
let fullscreen = attributes.fullscreen.clone();
|
||||
let maximized = attributes.maximized;
|
||||
let mut initdata = InitData { event_loop, attributes, window_flags, window: None };
|
||||
let mut initdata = InitData { runner, attributes, window_flags, window: None };
|
||||
|
||||
let (style, ex_style) = window_flags.to_window_styles();
|
||||
let handle = unsafe {
|
||||
@@ -1313,19 +1341,19 @@ unsafe fn init(
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
parent.unwrap_or(0),
|
||||
menu.unwrap_or(0),
|
||||
parent.unwrap_or(ptr::null_mut()),
|
||||
menu.unwrap_or(ptr::null_mut()),
|
||||
util::get_instance_handle(),
|
||||
&mut initdata as *mut _ as *mut _,
|
||||
)
|
||||
};
|
||||
|
||||
// If the window creation in `InitData` panicked, then should resume panicking here
|
||||
if let Err(panic_error) = event_loop.runner_shared.take_panic_error() {
|
||||
if let Err(panic_error) = runner.take_panic_error() {
|
||||
panic::resume_unwind(panic_error)
|
||||
}
|
||||
|
||||
if handle == 0 {
|
||||
if handle.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
|
||||
@@ -1337,8 +1365,8 @@ unsafe fn init(
|
||||
// This is because if the size is changed in WM_CREATE, the restored size will be stored in that
|
||||
// size.
|
||||
if fullscreen.is_some() {
|
||||
win.set_fullscreen(fullscreen.map(Into::into));
|
||||
unsafe { force_window_active(win.window) };
|
||||
win.set_fullscreen(fullscreen);
|
||||
unsafe { force_window_active(win.window.hwnd()) };
|
||||
} else if maximized {
|
||||
win.set_maximized(true);
|
||||
}
|
||||
@@ -1354,12 +1382,12 @@ unsafe fn register_window_class(class_name: &[u16]) {
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: util::get_instance_handle(),
|
||||
hIcon: 0,
|
||||
hCursor: 0, // must be null in order for cursor state to work properly
|
||||
hbrBackground: 0,
|
||||
hIcon: ptr::null_mut(),
|
||||
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
|
||||
hbrBackground: ptr::null_mut(),
|
||||
lpszMenuName: ptr::null(),
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
hIconSm: 0,
|
||||
hIconSm: ptr::null_mut(),
|
||||
};
|
||||
|
||||
// We ignore errors because registering the same window class twice would trigger
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::io;
|
||||
use std::sync::MutexGuard;
|
||||
use std::{io, ptr};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use windows_sys::Win32::Foundation::{HWND, RECT};
|
||||
@@ -356,7 +356,7 @@ impl WindowFlags {
|
||||
0,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
|
||||
);
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
InvalidateRgn(window, ptr::null_mut(), false.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ impl WindowFlags {
|
||||
}
|
||||
|
||||
// Refresh the window frame
|
||||
SetWindowPos(window, 0, 0, 0, 0, 0, flags);
|
||||
SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
|
||||
SendMessageW(window, event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(), 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -438,7 +438,7 @@ impl WindowFlags {
|
||||
}
|
||||
|
||||
util::win_to_err({
|
||||
let b_menu = GetMenu(hwnd) != 0;
|
||||
let b_menu = !GetMenu(hwnd).is_null();
|
||||
if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) =
|
||||
(*util::GET_DPI_FOR_WINDOW, *util::ADJUST_WINDOW_RECT_EX_FOR_DPI)
|
||||
{
|
||||
@@ -468,14 +468,14 @@ impl WindowFlags {
|
||||
let (width, height): (u32, u32) = self.adjust_size(hwnd, size).into();
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
width as _,
|
||||
height as _,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE,
|
||||
);
|
||||
InvalidateRgn(hwnd, 0, false.into());
|
||||
InvalidateRgn(hwnd, ptr::null_mut(), false.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorSource, MAX_
|
||||
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::RequestError;
|
||||
pub use crate::icon::{BadIcon, Icon};
|
||||
use crate::monitor::{MonitorHandle, VideoModeHandle};
|
||||
use crate::monitor::{MonitorHandle, VideoMode};
|
||||
use crate::platform_impl::PlatformSpecificWindowAttributes;
|
||||
use crate::utils::AsAny;
|
||||
|
||||
@@ -963,7 +963,7 @@ pub trait Window: AsAny + Send + Sync {
|
||||
/// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request.
|
||||
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||
/// - **Android / Orbital:** Unsupported.
|
||||
/// - **Web:** Passing a [`MonitorHandle`] or [`VideoModeHandle`] that was not created with
|
||||
/// - **Web:** Passing a [`MonitorHandle`] or [`VideoMode`] that was not created with
|
||||
#[cfg_attr(
|
||||
any(web_platform, docsrs),
|
||||
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]"
|
||||
@@ -1436,7 +1436,7 @@ pub enum Fullscreen {
|
||||
/// This changes the video mode of the monitor for fullscreen windows and,
|
||||
/// if applicable, captures the monitor for exclusive use by this
|
||||
/// application.
|
||||
Exclusive(VideoModeHandle),
|
||||
Exclusive(MonitorHandle, VideoMode),
|
||||
|
||||
/// Providing `None` to `Borderless` will fullscreen on the current monitor.
|
||||
Borderless(Option<MonitorHandle>),
|
||||
@@ -1544,11 +1544,34 @@ impl Default for ImePurpose {
|
||||
/// [`Window`]: crate::window::Window
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct ActivationToken {
|
||||
pub(crate) _token: String,
|
||||
pub(crate) token: String,
|
||||
}
|
||||
|
||||
impl ActivationToken {
|
||||
pub(crate) fn _new(_token: String) -> Self {
|
||||
Self { _token }
|
||||
/// Make an [`ActivationToken`] from a string.
|
||||
///
|
||||
/// This method should be used to wrap tokens passed by side channels to your application, like
|
||||
/// dbus.
|
||||
///
|
||||
/// The validity of the token is ensured by the windowing system. Using the invalid token will
|
||||
/// only result in the side effect of the operation involving it being ignored (e.g. window
|
||||
/// won't get focused automatically), but won't yield any errors.
|
||||
///
|
||||
/// To obtain a valid token, use
|
||||
#[cfg_attr(
|
||||
any(x11_platform, wayland_platform, docsrs),
|
||||
doc = " [`request_activation_token`](crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token)."
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(any(x11_platform, wayland_platform, docsrs)),
|
||||
doc = " `request_activation_token`."
|
||||
)]
|
||||
pub fn from_raw(token: String) -> Self {
|
||||
Self { token }
|
||||
}
|
||||
|
||||
/// Convert the token to its string representation to later pass via IPC.
|
||||
pub fn into_raw(self) -> String {
|
||||
self.token
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user