Compare commits

..

1 Commits

Author SHA1 Message Date
John Nunley
19477d3f03 doc: Add installation instructions to docs
This commit adds installation instructions to the top-level
documentation for lib.rs. The main goal is to give the dependencies that
X11/Wayland use in a formal place, since it's not written down anywhere
to my knowledge.

Signed-off-by: John Nunley <dev@notgull.net>
2024-12-08 08:30:31 -08:00
97 changed files with 5025 additions and 4420 deletions

View File

@@ -74,19 +74,15 @@ 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' }
# Rustup is broken.
- toolchain: nightly
platform: { name: 'Windows 32bit GNU' }
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
# Android is tested on stable-3
- toolchain: '1.73'
platform: { name: 'Android' }
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
# Redox OS doesn't follow MSRV
- toolchain: '1.73'
platform: { name: 'Redox OS' }
platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
include:
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }

View File

@@ -19,11 +19,6 @@ 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,

View File

@@ -21,7 +21,7 @@ name = "winit"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version = "0.30.9"
version = "0.30.5"
[package.metadata.docs.rs]
features = [
@@ -104,15 +104,14 @@ ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.6.0"
dispatch2 = { version = "0.2.0", default-features = false, features = ["std", "objc2"] }
objc2 = "0.6.0"
block2 = "0.5.1"
core-foundation = "0.9.3"
objc2 = "0.5.2"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
objc2-app-kit = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-foundation",
core-graphics = "0.23.1"
objc2-app-kit = { version = "0.2.2", features = [
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
@@ -129,7 +128,6 @@ objc2-app-kit = { version = "0.3.0", default-features = false, features = [
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPanel",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
@@ -142,37 +140,9 @@ objc2-app-kit = { version = "0.3.0", default-features = false, features = [
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
"std",
objc2-foundation = { version = "0.2.2", features = [
"block2",
"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",
"dispatch",
"NSArray",
"NSAttributedString",
"NSData",
@@ -194,29 +164,20 @@ objc2-foundation = { version = "0.3.0", default-features = false, features = [
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
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",
objc2-foundation = { version = "0.2.2", features = [
"block2",
"objc2-core-foundation",
"dispatch",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSProcessInfo",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-foundation",
objc2-ui-kit = { version = "0.2.2", features = [
"UIApplication",
"UIDevice",
"UIEvent",
@@ -242,7 +203,7 @@ objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
windows-sys = { version = "0.59.0", features = [
windows-sys = { version = "0.52.0", features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.9"
winit = "0.30.5"
```
## [Documentation](https://docs.rs/winit)

File diff suppressed because it is too large Load Diff

View File

@@ -13,21 +13,10 @@ 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, WindowData>,
windows: HashMap<WindowId, Box<dyn Window>>,
}
impl ApplicationHandler for Application {
@@ -37,10 +26,11 @@ 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(), WindowData::new(window, 0xffbbbbbb));
self.windows.insert(window.id(), window);
}
fn window_event(
@@ -66,24 +56,15 @@ 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.window.as_ref(), event_loop, child_index);
let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, WindowData::new(child_window, child_color));
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
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);
}
fill::fill_window(window.as_ref());
}
},
_ => (),
@@ -94,20 +75,12 @@ 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(128.0f32, 96.0))
.with_position(Position::Logical(LogicalPosition::new(x, y)))
.with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.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)) };

View File

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

View File

@@ -9,10 +9,7 @@
#[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 {
@@ -73,7 +70,7 @@ mod platform {
}
}
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
pub fn fill_window(window: &dyn Window) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
@@ -87,21 +84,17 @@ mod platform {
let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xff181818;
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(color);
buffer.fill(DARK_GRAY);
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| {
@@ -119,11 +112,6 @@ 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.

File diff suppressed because it is too large Load Diff

View File

@@ -59,19 +59,23 @@ 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
@@ -161,34 +165,6 @@ 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
@@ -225,8 +201,18 @@ 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, fixed the behaviour of `pump_app_events` to match what the system expects.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.

View File

@@ -1,59 +1,3 @@
## 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

View File

@@ -49,9 +49,70 @@ 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};
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,
}
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -114,42 +175,28 @@ pub enum WindowEvent {
/// The window has been destroyed.
Destroyed,
/// 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>>,
},
/// 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,
/// The window gained or lost focus.
///
@@ -522,7 +569,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).
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).#[derive(Debug, Clone, Copy, PartialEq)]
/// - **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>,
@@ -731,6 +778,12 @@ 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:
@@ -746,7 +799,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`]: Self::key_without_modifiers
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key,
/// Contains the text produced by this keypress.
@@ -767,7 +820,7 @@ pub struct KeyEvent {
/// This is `None` if the current keypress cannot
/// be interpreted as text.
///
/// See also [`text_with_all_modifiers`][Self::text_with_all_modifiers].
/// See also: `text_with_all_modifiers()`
pub text: Option<SmolStr>,
/// Contains the location of this key on the keyboard.
@@ -823,33 +876,13 @@ pub struct KeyEvent {
/// ```
pub repeat: bool,
/// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd>.
/// Platform-specific key event information.
///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
/// all modifiers applied.
///
/// ## 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,
/// On Android, iOS, Redox and Web, this type is a no-op.
pub(crate) platform_specific: platform_impl::KeyEventExtra,
}
/// Describes keyboard modifiers event.
@@ -1160,108 +1193,123 @@ mod tests {
macro_rules! foreach_event {
($closure:expr) => {{
foreach_event!(window: $closure);
foreach_event!(device: $closure);
}};
(window: $closure:expr) => {{
#[allow(unused_mut)]
let mut with_window_event: &mut dyn FnMut(event::WindowEvent) = &mut $closure;
let mut x = $closure;
let fid = event::FingerId::from_raw(0);
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
#[allow(deprecated)]
{
use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
use crate::window::WindowId;
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::*;
// Mainline events.
let wid = WindowId::from_raw(0);
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
x(Suspended);
x(Resumed);
#[allow(unused_mut)]
let mut with_device_event: &mut dyn FnMut(event::DeviceEvent) = &mut $closure;
// Window events.
let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
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_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 });
}
}};
}
#[allow(clippy::clone_on_copy)]
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event| {
foreach_event!(|event: event::Event| {
let event2 = event.clone();
assert_eq!(event, event2);
});
})
}
#[test]
@@ -1279,7 +1327,7 @@ mod tests {
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event| {
foreach_event!(|event: event::Event| {
let _ = format!("{event:?}");
});
let _ = event::StartCause::Init.clone();

View File

@@ -1,5 +1,59 @@
//! 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
@@ -257,10 +311,12 @@
//! [`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

View File

@@ -1,55 +1,86 @@
//! 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;
/// Describes a fullscreen video mode of a monitor.
/// A handle to a fullscreen video mode of a specific monitor.
///
/// 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>,
/// This can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
}
impl VideoMode {
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 {
/// 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.size
self.video_mode.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.bit_depth
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
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() }
}
}
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(())
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(),
)
}
}
@@ -157,13 +188,13 @@ impl MonitorHandle {
/// Returns the currently active video mode of this monitor.
#[inline]
pub fn current_video_mode(&self) -> Option<VideoMode> {
self.inner.current_video_mode()
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
}
/// Returns all fullscreen video modes supported by this monitor.
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner.video_modes()
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
}
}

View File

@@ -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.9",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
//! 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

View File

@@ -107,7 +107,7 @@ use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::monitor::{MonitorHandle, VideoMode};
use crate::monitor::{MonitorHandle, VideoModeHandle};
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 [`VideoMode`] for this monitor.
/// Returns the preferred [`VideoModeHandle`] 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) -> VideoMode;
fn preferred_video_mode(&self) -> VideoModeHandle;
}
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::MainThreadMarker::new_unchecked() };
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
self.inner.preferred_video_mode()
fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
}
}

View File

@@ -21,21 +21,26 @@
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop;
//!
//! define_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! declare_class!(
//! 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 {
//! #[unsafe(method(application:openURLs:))]
//! #[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.
@@ -46,7 +51,7 @@
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! }
//! }
//!
@@ -327,13 +332,6 @@ 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 {
@@ -414,12 +412,6 @@ 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 {
@@ -513,7 +505,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::MainThreadMarker::new_unchecked() };
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
}
}

View File

@@ -42,5 +42,15 @@ 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;

View File

@@ -0,0 +1,35 @@
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()
}
}

View File

@@ -83,18 +83,22 @@ pub trait EventLoopExtPumpEvents {
/// - **Windows**: The implementation will use `PeekMessage` when checking for window messages
/// to avoid blocking your external event loop.
///
/// - **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.
/// - **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.
///
/// 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.
/// 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)
///
/// Furthermore, when pumping events the `NSApplication` is still considered stopped to
/// crates like `rfd` that inspect [`-[NSApplication isRunning]`][isrunning].
/// 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.
///
/// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested
/// [isrunning]: https://developer.apple.com/documentation/appkit/nsapplication/isrunning?language=objc
/// 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.
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,

View File

@@ -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::from_raw)
env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
} else {
env::var(X11_VAR).ok().map(ActivationToken::from_raw)
env::var(X11_VAR).ok().map(ActivationToken::_new)
}
}
}
@@ -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);
}

View File

@@ -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 = *mut c_void;
pub type HWND = isize;
/// Menu Handle type used by Win32 API
pub type HMENU = *mut c_void;
pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = *mut c_void;
pub type HMONITOR = isize;
/// Describes a system-drawn backdrop material of a window.
///
@@ -660,17 +660,6 @@ 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.
///
@@ -682,12 +671,7 @@ 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 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
/// Create an icon from a resource embedded in this executable or library.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
@@ -695,55 +679,6 @@ 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 {
@@ -759,9 +694,4 @@ 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 })
}
}

View File

@@ -1,5 +1,6 @@
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};
@@ -20,7 +21,7 @@ use crate::event_loop::{
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::pump_events::PumpStatus;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
@@ -96,6 +97,9 @@ impl RedrawRequester {
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop {
pub(crate) android_app: AndroidApp,
window_target: ActiveEventLoop,
@@ -475,8 +479,7 @@ impl EventLoop {
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: keycodes::to_logical(key_char, keycode),
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
};
@@ -1018,11 +1021,32 @@ impl MonitorHandle {
unreachable!()
}
pub fn current_video_mode(&self) -> Option<VideoMode> {
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
unreachable!()
}
pub fn video_modes(&self) -> std::iter::Empty<VideoMode> {
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 {
unreachable!()
}
}

View File

@@ -1,101 +1,51 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState};
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);
declare_class!(
pub(super) struct 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() })
};
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
let mtm = MainThreadMarker::from(app);
impl DeclaredClass for WinitApplication {}
// 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);
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] }
}
}
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() };
@@ -137,52 +87,3 @@ 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);
}
}

View File

@@ -1,17 +1,15 @@
use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::Rc;
use std::rc::{Rc, Weak};
use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc;
use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification;
use objc2_foundation::{MainThreadMarker, NSNotification};
use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy;
use super::event_loop::{stop_app_immediately, ActiveEventLoop};
use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
use super::menu;
use super::observer::{EventLoopWaker, RunLoop};
use crate::application::ApplicationHandler;
@@ -28,22 +26,41 @@ pub(super) struct AppState {
run_loop: RunLoop,
event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler,
/// Whether `NSApplicationDidFinishLaunchingNotification` has been sent.
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.
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.
}
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
// 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: MainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
StaticMainThreadBound(OnceCell::new());
impl AppState {
pub(super) fn setup_global(
@@ -52,23 +69,25 @@ impl AppState {
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Rc<Self> {
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 {
let this = Rc::new(AppState {
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![]),
});
@@ -84,17 +103,15 @@ 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
// `NSApplicationDidFinishLaunchingNotification` has been sent. Otherwise the
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
if let Some(activation_policy) = self.activation_policy {
app.setActivationPolicy(activation_policy);
@@ -124,7 +141,23 @@ 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) {
@@ -147,16 +180,57 @@ 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)
}
@@ -184,6 +258,14 @@ 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);
}
}
}
@@ -235,12 +317,21 @@ impl AppState {
}
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>) {
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.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if !self.event_handler.ready() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
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,
@@ -258,14 +349,22 @@ impl AppState {
}
// Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>) {
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.");
// 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 !self.event_handler.ready() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
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| {
@@ -281,12 +380,24 @@ 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(app_timeout);
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, 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))))
}

View File

@@ -4,10 +4,11 @@ use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString,
};
use crate::cursor::{CursorImage, OnlyCursorImageSource};
@@ -66,8 +67,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class();
if unsafe { msg_send![cls, respondsToSelector: sel] } {
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
if cls.responds_to(sel) {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor)
} else {
tracing::warn!("cursor `{sel}` appears to be invalid");
@@ -129,21 +130,25 @@ 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![
msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() {
x = n.as_cgfloat();
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()
}
}
let mut y = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
if let Ok(n) = n.downcast::<NSNumber>() {
y = n.as_cgfloat();
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()
}
}
@@ -183,7 +188,6 @@ 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(),
@@ -204,9 +208,7 @@ 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(),

View File

@@ -1,10 +1,10 @@
use std::ptr::NonNull;
use std::ffi::c_void;
use dispatch2::run_on_main;
use core_foundation::base::CFRelease;
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
use objc2::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_core_foundation::{CFData, CFDataGetBytePtr, CFRetained};
use objc2_foundation::NSPoint;
use objc2_foundation::{run_on_main, NSPoint};
use smol_str::SmolStr;
use super::ffi;
@@ -14,29 +14,37 @@ 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 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 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 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,
@@ -51,6 +59,9 @@ 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));
@@ -112,8 +123,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::Control);
let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
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
@@ -151,8 +162,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
repeat: is_repeat,
state,
text,
text_with_all_modifiers,
key_without_modifiers,
platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
}
}
@@ -162,9 +172,6 @@ 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,
@@ -179,17 +186,14 @@ 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,
@@ -210,27 +214,17 @@ 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)),
})
}
@@ -314,19 +308,26 @@ 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::Shift));
state
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
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::Control));
state.set(
ModifiersState::CONTROL,
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
);
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::Option));
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
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::Command));
state.set(
ModifiersState::SUPER,
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
);
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));

View File

@@ -1,21 +1,29 @@
use std::rc::Rc;
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::sync::Arc;
use std::time::Duration;
use std::time::{Duration, Instant};
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::runtime::ProtocolObject;
use objc2::{available, MainThreadMarker};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSEventMask, NSWindow,
};
use objc2_foundation::{
NSDate, NSDefaultRunLoopMode, NSNotificationCenter, NSObjectProtocol, NSTimeInterval,
NSApplicationWillTerminateNotification, NSWindow,
};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app::override_send_event;
use super::app::WinitApplication;
use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
@@ -25,7 +33,8 @@ use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::macos::ActivationPolicy;
@@ -33,6 +42,36 @@ 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>,
@@ -90,8 +129,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
// Dark appearance was introduced in macOS 10.14
if available!(macos = 10.14) {
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
@@ -138,17 +176,15 @@ 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<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_finish_launching_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -171,6 +207,16 @@ 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),
@@ -185,21 +231,6 @@ 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);
@@ -226,13 +257,14 @@ impl EventLoop {
},
);
setup_control_flow_observers(mtm);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
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,
})
@@ -246,6 +278,10 @@ 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,
@@ -253,21 +289,29 @@ 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() {
// 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`.
debug_assert!(!self.app_state.is_running());
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
}
// 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();
// 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);
}
self.app_state.internal_exit()
})
@@ -283,43 +327,60 @@ impl EventLoop {
) -> PumpStatus {
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
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.
// 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);
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;
// 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) };
// 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);
}
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
@@ -346,3 +407,88 @@ 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);
}
}
}

View File

@@ -4,10 +4,18 @@
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;
use objc2_core_foundation::{cf_type, CFString, CFUUID};
use objc2_core_graphics::CGDirectDisplayID;
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
@@ -15,6 +23,22 @@ 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";
@@ -31,6 +55,9 @@ 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.
//
@@ -40,11 +67,54 @@ pub const IO8BitOverlayPixels: &str = "O8";
// 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) -> *mut CFUUID;
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
#[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(
@@ -54,13 +124,50 @@ 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);
cf_type!(
#[encoding_name = "__TISInputSource"]
unsafe impl TISInputSource {}
);
pub type TISInputSourceRef = *mut TISInputSource;
#[repr(transparent)]
pub struct UCKeyboardLayout(std::ffi::c_void);
@@ -77,15 +184,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
#[link(name = "Carbon", kind = "framework")]
extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
#[allow(non_snake_case)]
pub fn TISGetInputSourceProperty(
inputSource: &TISInputSource,
propertyKey: &CFString,
inputSource: TISInputSourceRef,
propertyKey: CFStringRef,
) -> *mut c_void;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
pub fn LMGetKbdType() -> u8;

View File

@@ -1,8 +1,8 @@
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{sel, MainThreadMarker};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, NSProcessInfo, NSString};
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
struct KeyEquivalent<'a> {
key: &'a NSString,
@@ -48,7 +48,10 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
masks: Some(
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
}),
);

View File

@@ -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};
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;

View File

@@ -1,46 +1,38 @@
#![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 dispatch2::run_on_main;
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 objc2::rc::Retained;
use objc2::MainThreadMarker;
use objc2::runtime::AnyObject;
use objc2_app_kit::NSScreen;
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 objc2_foundation::{ns_string, run_on_main, MainThreadMarker, 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 {
pub(crate) mode: VideoMode,
size: PhysicalSize<u32>,
bit_depth: Option<NonZeroU16>,
refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode,
}
impl PartialEq for VideoModeHandle {
fn eq(&self, other: &Self) -> bool {
self.monitor == other.monitor && self.mode == other.mode
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
}
}
@@ -48,35 +40,56 @@ 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("VideoMode")
.field("mode", &self.mode)
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()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
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,
native_mode: NativeDisplayMode,
mode: NativeDisplayMode,
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
unsafe {
#[allow(deprecated)]
let pixel_encoding =
CGDisplayModeCopyPixelEncoding(Some(&native_mode.0)).unwrap().to_string();
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0))
.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) {
@@ -87,38 +100,48 @@ impl VideoModeHandle {
unimplemented!()
};
let mode = VideoMode {
VideoModeHandle {
size: PhysicalSize::new(
CGDisplayModeGetPixelWidth(Some(&native_mode.0)) as u32,
CGDisplayModeGetPixelHeight(Some(&native_mode.0)) as u32,
ffi::CGDisplayModeGetPixelWidth(mode.0) as u32,
ffi::CGDisplayModeGetPixelHeight(mode.0) as u32,
),
refresh_rate_millihertz,
bit_depth: NonZeroU16::new(bit_depth),
};
VideoModeHandle { mode, monitor: monitor.clone(), native_mode }
monitor: monitor.clone(),
native_mode: 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 {
self.uuid() == other.uuid()
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
}
}
@@ -132,43 +155,35 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.uuid().cmp(&other.uuid())
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.uuid().hash(state);
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> {
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);
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 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(unsafe { CGMainDisplayID() })
MonitorHandle(CGDisplay::main().id)
}
impl fmt::Debug for MonitorHandle {
@@ -190,7 +205,8 @@ 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 screen_num = unsafe { CGDisplayModelNumber(self.0) };
let MonitorHandle(display_id) = *self;
let screen_num = CGDisplay::new(display_id).model_number();
Some(format!("Monitor #{screen_num}"))
}
@@ -204,7 +220,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.0) };
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor()))
}
@@ -220,40 +236,38 @@ impl MonitorHandle {
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
refresh_rate_millihertz(self.0, &current_display_mode)
}
pub fn current_video_mode(&self) -> Option<VideoMode> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz))
}
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> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
unsafe {
let modes = {
let array = CGDisplayCopyAllDisplayModes(self.0, None)
.expect("failed to get list of display modes");
let array_count = CFArrayGetCount(&array);
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 modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(&array, i) as *mut CGDisplayMode;
CFRetained::retain(NonNull::new(mode).unwrap())
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64;
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
@@ -273,11 +287,13 @@ impl MonitorHandle {
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = self.uuid();
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
let other = MonitorHandle::new(other_native_id);
uuid == other.uuid()
let other_uuid = unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
};
uuid == other_uuid
})
}
}
@@ -290,14 +306,15 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
// Retrieve the CGDirectDisplayID associated with this screen
//
// The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for details:
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for `deviceDescription` for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description
.objectForKey(key)
.expect("failed getting screen display id from device description")
.downcast::<NSNumber>()
.expect("NSScreenNumber must be NSNumber");
.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 };
obj.as_u32()
})
@@ -318,7 +335,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 = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height;
let main_screen_height = CGDisplay::main().bounds().size.height;
let y = main_screen_height - frame.size.height - frame.origin.y;
NSPoint::new(frame.origin.x, y)
@@ -326,29 +343,25 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
unsafe {
let refresh_rate = CGDisplayModeGetRefreshRate(Some(&mode.0));
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(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();
#[allow(deprecated)]
if CVDisplayLinkCreateWithCGDisplay(id, NonNull::from(&mut display_link))
!= kCVReturnSuccess
{
if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess {
return None;
}
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
#[allow(deprecated)]
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(&display_link);
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
ffi::CVDisplayLinkRelease(display_link);
// This value is indefinite if an invalid display link was specified
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
if time.flags & ffi::kCVTimeIsIndefinite != 0 {
return None;
}
(time.timeScale as i64)
.checked_div(time.timeValue)
(time.time_scale as i64)
.checked_div(time.time_value)
.map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new)
}

View File

@@ -4,78 +4,132 @@
//! <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 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 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_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-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_info: *mut c_void,
panic_info: *mut c_void,
) {
match activity {
CFRunLoopActivity::AfterWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
},
_ => unreachable!(),
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!(),
}
});
}
}
// 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-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_info: *mut c_void,
panic_info: *mut c_void,
) {
match activity {
CFRunLoopActivity::BeforeWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).cleared();
},
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`");
},
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
});
}
}
#[derive(Debug)]
pub struct RunLoop(CFRetained<CFRunLoop>);
pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
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() }.unwrap())
RunLoop(unsafe { CFRunLoopGetMain() })
}
pub fn wakeup(&self) {
unsafe { CFRunLoopWakeUp(&self.0) }
unsafe { CFRunLoopWakeUp(self.0) }
}
unsafe fn add_observer(
&self,
flags: CFRunLoopActivity,
// The lower the value, the sooner this will run
flags: CFOptionFlags,
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) {
let observer =
unsafe { CFRunLoopObserverCreate(None, flags.0, true, priority, handler, context) }
.unwrap();
unsafe { CFRunLoopAddObserver(&self.0, Some(&observer), kCFRunLoopCommonModes) };
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) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
@@ -112,6 +166,10 @@ 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 || {
@@ -137,33 +195,33 @@ impl RunLoop {
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { CFRunLoopPerformBlock(&self.0, Some(mode), Some(&block)) }
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: ptr::null_mut(),
info: Weak::into_raw(panic_info) as *mut _,
version: 0,
retain: None,
release: None,
copyDescription: None,
};
run_loop.add_observer(
CFRunLoopActivity::AfterWaiting,
kCFRunLoopAfterWaiting,
CFIndex::MIN,
Some(control_flow_begin_handler),
control_flow_begin_handler,
&mut context as *mut _,
);
run_loop.add_observer(
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::MAX,
Some(control_flow_end_handler),
control_flow_end_handler,
&mut context as *mut _,
);
}
@@ -171,7 +229,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
#[derive(Debug)]
pub struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>,
timer: CFRunLoopTimerRef,
/// 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
@@ -186,28 +244,30 @@ pub struct EventLoopWaker {
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe { CFRunLoopTimerInvalidate(&self.timer) };
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _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(
None,
ptr::null_mut(),
f64::MAX,
0.000_000_1,
0,
0,
Some(wakeup_main_loop),
wakeup_main_loop,
ptr::null_mut(),
)
.unwrap();
CFRunLoopAddTimer(&CFRunLoopGetMain().unwrap(), Some(&timer), kCFRunLoopCommonModes);
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
@@ -215,14 +275,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) }
}
}
@@ -240,7 +300,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)
}
}
},

View File

@@ -1,8 +1,5 @@
use objc2_core_graphics::CGError;
use tracing::trace;
use crate::error::OsError;
macro_rules! trace_scope {
($s:literal) => {
let _crate =
@@ -29,12 +26,3 @@ 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:?}")))
}
}

View File

@@ -6,23 +6,24 @@ use std::rc::Rc;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSWindow,
NSTrackingRectTag, NSView,
};
use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, 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,
scancode_to_physicalkey, KeyEventExtra,
};
use super::window::window_id;
use super::window::WinitWindow;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
@@ -137,21 +138,28 @@ pub struct ViewState {
option_as_alt: Cell<OptionAsAlt>,
}
define_class!(
#[unsafe(super(NSView, NSResponder, NSObject))]
#[ivars = ViewState]
#[name = "WinitView"]
declare_class!(
pub(super) struct WinitView;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(isFlipped))]
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)]
fn is_flipped(&self) -> bool {
// `winit` uses the upper-left corner as the origin.
true
}
#[unsafe(method(viewDidMoveToWindow))]
#[method(viewDidMoveToWindow)]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -167,7 +175,7 @@ define_class!(
}
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))]
#[method(viewFrameDidChangeNotification:)]
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -182,45 +190,38 @@ define_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));
}
#[unsafe(method(drawRect:))]
#[method(drawRect:)]
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");
self.ivars().app_state.handle_redraw(window_id(&self.window()));
self.ivars().app_state.handle_redraw(self.window().id());
// This is a direct subclass of NSView, no need to call superclass' drawRect:
}
#[unsafe(method(acceptsFirstResponder))]
#[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`
#[unsafe(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`
#[method_id(touchBar)]
fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar");
None
}
#[unsafe(method(resetCursorRects))]
#[method(resetCursorRects)]
fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects");
let bounds = self.bounds();
@@ -235,13 +236,13 @@ define_class!(
}
unsafe impl NSTextInputClient for WinitView {
#[unsafe(method(hasMarkedText))]
#[method(hasMarkedText)]
fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0
}
#[unsafe(method(markedRange))]
#[method(markedRange)]
fn marked_range(&self) -> NSRange {
trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length();
@@ -253,14 +254,14 @@ define_class!(
}
}
#[unsafe(method(selectedRange))]
#[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)
}
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
#[method(setMarkedText:selectedRange:replacementRange:)]
fn set_marked_text(
&self,
string: &NSObject,
@@ -270,15 +271,23 @@ define_class!(
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
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())
// 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(),
)
} else {
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
panic!("unexpected text {string:?}")
let string: *const NSObject = string;
let string: *const NSString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
)
};
// Update marked text.
@@ -314,7 +323,7 @@ define_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[unsafe(method(unmarkText))]
#[method(unmarkText)]
fn unmark_text(&self) {
trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
@@ -331,13 +340,13 @@ define_class!(
}
}
#[unsafe(method_id(validAttributesForMarkedText))]
#[method_id(validAttributesForMarkedText)]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText");
NSArray::new()
}
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
#[method_id(attributedSubstringForProposedRange:actualRange:)]
fn attributed_substring_for_proposed_range(
&self,
_range: NSRange,
@@ -347,36 +356,42 @@ define_class!(
None
}
#[unsafe(method(characterIndexForPoint:))]
#[method(characterIndexForPoint:)]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:");
0
}
#[unsafe(method(firstRectForCharacterRange:actualRange:))]
#[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))
}
#[unsafe(method(insertText:replacementRange:))]
#[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:");
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()
// 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()
} else {
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
panic!("unexpected text {string:?}")
let string: *const NSObject = string;
let string: *const NSString = string.cast();
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().is_some_and(|c| c.is_control());
@@ -389,16 +404,15 @@ define_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.
#[unsafe(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.
#[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;
}
@@ -412,14 +426,10 @@ define_class!(
}
// Send command action to user if they requested it.
let window_id = window_id(&self.window());
let window_id = self.window().id();
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().to_str().unwrap(),
);
handler.standard_key_binding(event_loop, window_id, command.name());
}
});
@@ -429,9 +439,8 @@ define_class!(
}
}
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(keyDown:))]
unsafe impl WinitView {
#[method(keyDown:)]
fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:");
{
@@ -474,7 +483,7 @@ define_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(),
@@ -490,7 +499,7 @@ define_class!(
}
}
#[unsafe(method(keyUp:))]
#[method(keyUp:)]
fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:");
@@ -498,7 +507,10 @@ define_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),
@@ -507,14 +519,14 @@ define_class!(
}
}
#[unsafe(method(flagsChanged:))]
#[method(flagsChanged:)]
fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:");
self.update_modifiers(event, true);
}
#[unsafe(method(insertTab:))]
#[method(insertTab:)]
fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:");
let window = self.window();
@@ -525,7 +537,7 @@ define_class!(
}
}
#[unsafe(method(insertBackTab:))]
#[method(insertBackTab:)]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:");
let window = self.window();
@@ -538,7 +550,7 @@ define_class!(
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[unsafe(method(cancelOperation:))]
#[method(cancelOperation:)]
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:");
@@ -568,42 +580,42 @@ define_class!(
//
// See https://github.com/rust-windowing/winit/pull/1490 for history.
#[unsafe(method(mouseDown:))]
#[method(mouseDown:)]
fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(mouseUp:))]
#[method(mouseUp:)]
fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[unsafe(method(rightMouseDown:))]
#[method(rightMouseDown:)]
fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(rightMouseUp:))]
#[method(rightMouseUp:)]
fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[unsafe(method(otherMouseDown:))]
#[method(otherMouseDown:)]
fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(otherMouseUp:))]
#[method(otherMouseUp:)]
fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:");
self.mouse_motion(event);
@@ -612,27 +624,27 @@ define_class!(
// No tracing on these because that would be overly verbose
#[unsafe(method(mouseMoved:))]
#[method(mouseMoved:)]
fn mouse_moved(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(mouseDragged:))]
#[method(mouseDragged:)]
fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(rightMouseDragged:))]
#[method(rightMouseDragged:)]
fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(otherMouseDragged:))]
#[method(otherMouseDragged:)]
fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(mouseEntered:))]
#[method(mouseEntered:)]
fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:");
@@ -646,7 +658,7 @@ define_class!(
});
}
#[unsafe(method(mouseExited:))]
#[method(mouseExited:)]
fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:");
@@ -660,7 +672,7 @@ define_class!(
});
}
#[unsafe(method(scrollWheel:))]
#[method(scrollWheel:)]
fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:");
@@ -677,9 +689,9 @@ define_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,
@@ -693,13 +705,17 @@ define_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 });
}
#[unsafe(method(magnifyWithEvent:))]
#[method(magnifyWithEvent:)]
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
@@ -721,16 +737,18 @@ define_class!(
});
}
#[unsafe(method(smartMagnifyWithEvent:))]
#[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,
});
}
#[unsafe(method(rotateWithEvent:))]
#[method(rotateWithEvent:)]
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
@@ -752,7 +770,7 @@ define_class!(
});
}
#[unsafe(method(pressureChangeWithEvent:))]
#[method(pressureChangeWithEvent:)]
fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:");
@@ -766,13 +784,13 @@ define_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
#[unsafe(method(_wantsKeyDownForEvent:))]
#[method(_wantsKeyDownForEvent:)]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:");
true
}
#[unsafe(method(acceptsFirstMouse:))]
#[method(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse
@@ -803,19 +821,26 @@ impl WinitView {
accepts_first_mouse,
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Retained<NSWindow> {
(**self).window().expect("view must be installed in a window")
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 queue_event(&self, event: WindowEvent) {
let window_id = window_id(&self.window());
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
@@ -940,8 +965,10 @@ impl WinitView {
// We'll correct this later.
state: Pressed,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
let location_mask = ModLocationMask::from_location(event.location);

View File

@@ -1,11 +1,10 @@
#![allow(clippy::unnecessary_cast)]
use dispatch2::MainThreadBound;
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{define_class, MainThreadMarker, Message};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject;
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
@@ -17,7 +16,7 @@ use crate::window::{
};
pub(crate) struct Window {
window: MainThreadBound<Retained<NSWindow>>,
window: MainThreadBound<Retained<WinitWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
@@ -65,7 +64,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);
}
@@ -333,21 +332,27 @@ impl CoreWindow for Window {
}
}
define_class!(
#[unsafe(super(NSWindow, NSResponder, NSObject))]
#[name = "WinitWindow"]
declare_class!(
#[derive(Debug)]
pub struct WinitWindow;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitWindow {
#[unsafe(method(canBecomeMainWindow))]
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)]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[unsafe(method(canBecomeKeyWindow))]
#[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
@@ -355,24 +360,8 @@ define_class!(
}
);
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
}
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
}
);
pub(super) fn window_id(window: &NSWindow) -> WindowId {
WindowId::from_raw(window as *const _ as usize)
}

View File

@@ -6,32 +6,25 @@ 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::{
available, define_class, msg_send, sel, ClassType, DefinedClass, MainThreadMarker,
MainThreadOnly, Message,
};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSDraggingInfo, NSFilenamesPboardType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
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, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSNotificationCenter, NSObject,
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString,
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
@@ -39,9 +32,8 @@ 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::{window_id, WinitPanel, WinitWindow};
use super::window::WinitWindow;
use super::{ffi, Fullscreen, MonitorHandle};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
@@ -70,7 +62,6 @@ pub struct PlatformSpecificWindowAttributes {
pub option_as_alt: OptionAsAlt,
pub borderless_game: bool,
pub unified_titlebar: bool,
pub panel: bool,
}
impl Default for PlatformSpecificWindowAttributes {
@@ -90,7 +81,6 @@ impl Default for PlatformSpecificWindowAttributes {
option_as_alt: Default::default(),
borderless_game: false,
unified_titlebar: false,
panel: false,
}
}
}
@@ -100,7 +90,7 @@ pub(crate) struct State {
/// Strong reference to the global application state.
app_state: Rc<AppState>,
window: Retained<NSWindow>,
window: Retained<WinitWindow>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
//
@@ -142,24 +132,30 @@ pub(crate) struct State {
is_borderless_game: Cell<bool>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "WinitWindowDelegate"]
#[ivars = State]
declare_class!(
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 {
#[unsafe(method(windowShouldClose:))]
#[method(windowShouldClose:)]
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
trace_scope!("windowShouldClose:");
self.queue_event(WindowEvent::CloseRequested);
false
}
#[unsafe(method(windowWillClose:))]
#[method(windowWillClose:)]
fn window_will_close(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillClose:");
// `setDelegate:` retains the previous value and then autoreleases it
@@ -171,14 +167,14 @@ define_class!(
self.queue_event(WindowEvent::Destroyed);
}
#[unsafe(method(windowDidResize:))]
#[method(windowDidResize:)]
fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:");
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
self.emit_move_event();
}
#[unsafe(method(windowWillStartLiveResize:))]
#[method(windowWillStartLiveResize:)]
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillStartLiveResize:");
@@ -186,20 +182,20 @@ define_class!(
self.set_resize_increments_inner(increments);
}
#[unsafe(method(windowDidEndLiveResize:))]
#[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.
#[unsafe(method(windowDidMove:))]
#[method(windowDidMove:)]
fn window_did_move(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidMove:");
self.emit_move_event();
}
#[unsafe(method(windowDidChangeBackingProperties:))]
#[method(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
let scale_factor = self.scale_factor();
@@ -215,7 +211,7 @@ define_class!(
});
}
#[unsafe(method(windowDidBecomeKey:))]
#[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
@@ -223,7 +219,7 @@ define_class!(
self.queue_event(WindowEvent::Focused(true));
}
#[unsafe(method(windowDidResignKey:))]
#[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
@@ -239,7 +235,7 @@ define_class!(
}
/// Invoked when before enter fullscreen
#[unsafe(method(windowWillEnterFullScreen:))]
#[method(windowWillEnterFullScreen:)]
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillEnterFullScreen:");
@@ -249,7 +245,7 @@ define_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
@@ -265,14 +261,14 @@ define_class!(
}
/// Invoked when before exit fullscreen
#[unsafe(method(windowWillExitFullScreen:))]
#[method(windowWillExitFullScreen:)]
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:");
self.ivars().in_fullscreen_transition.set(true);
}
#[unsafe(method(window:willUseFullScreenPresentationOptions:))]
#[method(window:willUseFullScreenPresentationOptions:)]
fn window_will_use_fullscreen_presentation_options(
&self,
_: Option<&AnyObject>,
@@ -289,17 +285,17 @@ define_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::FullScreen
| NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
if let Some(Fullscreen::Exclusive(_)) = &*fullscreen {
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
}
options
}
/// Invoked when entered fullscreen
#[unsafe(method(windowDidEnterFullScreen:))]
#[method(windowDidEnterFullScreen:)]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:");
self.ivars().initial_fullscreen.set(false);
@@ -310,7 +306,7 @@ define_class!(
}
/// Invoked when exited fullscreen
#[unsafe(method(windowDidExitFullScreen:))]
#[method(windowDidExitFullScreen:)]
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:");
@@ -337,7 +333,7 @@ define_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.
#[unsafe(method(windowDidFailToEnterFullScreen:))]
#[method(windowDidFailToEnterFullScreen:)]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:");
self.ivars().in_fullscreen_transition.set(false);
@@ -356,14 +352,14 @@ define_class!(
}
// Invoked when the occlusion state of the window changes
#[unsafe(method(windowDidChangeOcclusionState:))]
#[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));
}
#[unsafe(method(windowDidChangeScreen:))]
#[method(windowDidChangeScreen:)]
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
@@ -377,114 +373,67 @@ define_class!(
unsafe impl NSDraggingDestination for WindowDelegate {
/// Invoked when the dragged image enters destination bounds or frame
#[unsafe(method(draggingEntered:))]
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
#[method(draggingEntered:)]
fn dragging_entered(&self, sender: &NSObject) -> bool {
trace_scope!("draggingEntered:");
use std::path::PathBuf;
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();
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 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 });
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::HoveredFile(path));
});
true
}
/// Invoked when the image is released
#[unsafe(method(prepareForDragOperation:))]
#[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
#[unsafe(method(performDragOperation:))]
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
#[method(performDragOperation:)]
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
trace_scope!("performDragOperation:");
use std::path::PathBuf;
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();
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 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 });
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::DroppedFile(path));
});
true
}
/// Invoked when the dragging operation is complete
#[unsafe(method(concludeDragOperation:))]
#[method(concludeDragOperation:)]
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
trace_scope!("concludeDragOperation:");
}
/// Invoked when the dragging operation is cancelled
#[unsafe(method(draggingExited:))]
fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
#[method(draggingExited:)]
fn dragging_exited(&self, _sender: Option<&NSObject>) {
trace_scope!("draggingExited:");
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 });
self.queue_event(WindowEvent::HoveredFileCancelled);
}
}
/// Key-Value Observing
impl WindowDelegate {
#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
// Key-Value Observing
unsafe impl WindowDelegate {
#[method(observeValueForKeyPath:ofObject:change:context:)]
fn observe_value(
&self,
key_path: Option<&NSString>,
@@ -500,15 +449,19 @@ define_class!(
"requested a change dictionary in `addObserver`, but none was provided",
);
let old = change
.objectForKey(unsafe { NSKeyValueChangeOldKey })
.get(unsafe { NSKeyValueChangeOldKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change
.objectForKey(unsafe { NSKeyValueChangeNewKey })
.get(unsafe { NSKeyValueChangeNewKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// The value of `effectiveAppearance` is `NSAppearance`
let old = old.downcast::<NSAppearance>().unwrap();
let new = new.downcast::<NSAppearance>().unwrap();
// 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 };
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
@@ -519,8 +472,8 @@ define_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).
@@ -548,11 +501,11 @@ fn new_window(
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
mtm: MainThreadMarker,
) -> Option<Retained<NSWindow>> {
) -> Option<Retained<WinitWindow>> {
autoreleasepool(|_| {
let screen = match attrs.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Borderless(Some(monitor)))
| Some(Fullscreen::Exclusive(monitor, _)) => {
| Some(Fullscreen::Exclusive(VideoModeHandle { monitor, .. })) => {
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
},
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
@@ -631,33 +584,16 @@ 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: 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: Option<Retained<WinitWindow>> = unsafe {
msg_send_id![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
};
let window = window?;
// It is very important for correct memory management that we
// disable the extra release that would otherwise happen when
@@ -673,22 +609,22 @@ fn new_window(
}
if attrs.content_protected {
window.setSharingType(NSWindowSharingType::None);
window.setSharingType(NSWindowSharingType::NSWindowSharingNone);
}
if attrs.platform_specific.titlebar_transparent {
window.setTitlebarAppearsTransparent(true);
}
if attrs.platform_specific.title_hidden {
window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden);
}
if attrs.platform_specific.titlebar_buttons_hidden {
for titlebar_button in &[
#[allow(deprecated)]
NSWindowFullScreenButton,
NSWindowButton::MiniaturizeButton,
NSWindowButton::CloseButton,
NSWindowButton::ZoomButton,
NSWindowButton::NSWindowMiniaturizeButton,
NSWindowButton::NSWindowCloseButton,
NSWindowButton::NSWindowZoomButton,
] {
if let Some(button) = window.standardWindowButton(*titlebar_button) {
button.setHidden(true);
@@ -708,7 +644,7 @@ fn new_window(
}
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = window.standardWindowButton(NSWindowButton::ZoomButton) {
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) {
button.setEnabled(false);
}
}
@@ -770,7 +706,10 @@ fn new_window(
}
// register for drag and drop operations.
window.registerForDraggedTypes(&NSArray::from_slice(&[unsafe { NSFilenamesPboardType }]));
window
.registerForDraggedTypes(&NSArray::from_id_slice(&[
unsafe { NSFilenamesPboardType }.copy()
]));
Some(window)
})
@@ -798,7 +737,9 @@ 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::Above) };
unsafe {
parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove)
};
},
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
None => (),
@@ -839,7 +780,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![super(delegate), init] };
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
@@ -850,7 +791,8 @@ impl WindowDelegate {
window.addObserver_forKeyPath_options_context(
&delegate,
ns_string!("effectiveAppearance"),
NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old,
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
ptr::null_mut(),
)
};
@@ -894,22 +836,22 @@ impl WindowDelegate {
#[track_caller]
pub(super) fn view(&self) -> Retained<WinitView> {
// The view inside WinitWindow should always be set and be `WinitView`.
self.window().contentView().unwrap().downcast().unwrap()
// SAFETY: The view inside WinitWindow is always `WinitView`
unsafe { Retained::cast(self.window().contentView().unwrap()) }
}
#[track_caller]
pub(super) fn window(&self) -> &NSWindow {
pub(super) fn window(&self) -> &WinitWindow {
&self.ivars().window
}
#[track_caller]
pub(crate) fn id(&self) -> WindowId {
window_id(self.window())
self.window().id()
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = window_id(self.window());
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
@@ -1008,7 +950,7 @@ impl WindowDelegate {
}
pub fn request_redraw(&self) {
self.ivars().app_state.queue_redraw(window_id(self.window()));
self.ivars().app_state.queue_redraw(self.window().id());
}
#[inline]
@@ -1215,7 +1157,8 @@ 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::ZoomButton) {
if let Some(button) = self.window().standardWindowButton(NSWindowButton::NSWindowZoomButton)
{
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
}
}
@@ -1228,7 +1171,7 @@ impl WindowDelegate {
}
if self
.window()
.standardWindowButton(NSWindowButton::ZoomButton)
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
.map(|b| b.isEnabled())
.unwrap_or(true)
{
@@ -1267,9 +1210,8 @@ impl WindowDelegate {
};
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(associate_mouse_cursor) })?;
Ok(())
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
.map_err(|status| os_error!(format!("CGError {status}")).into())
}
#[inline]
@@ -1291,12 +1233,14 @@ 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 = CGPoint {
let point = core_graphics::display::CGPoint {
x: window_position.x + cursor_position.x,
y: window_position.y + cursor_position.y,
};
cgerr(unsafe { CGWarpMouseCursorPosition(point) })?;
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(true) })?;
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}")))?;
Ok(())
}
@@ -1463,7 +1407,7 @@ impl WindowDelegate {
return;
}
},
Fullscreen::Exclusive(monitor, _) => monitor.clone(),
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
}
.ns_screen(mtm)
.unwrap();
@@ -1474,7 +1418,7 @@ impl WindowDelegate {
}
}
if let Some(Fullscreen::Exclusive(ref monitor, ref video_mode)) = fullscreen {
if let Some(Fullscreen::Exclusive(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
@@ -1487,7 +1431,7 @@ impl WindowDelegate {
// parameter, which is not consistent with the docs saying that it
// takes a `NSDictionary`..
let display_id = monitor.native_identifier();
let display_id = video_mode.monitor().native_identifier();
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
@@ -1498,8 +1442,10 @@ 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 cgerr(CGAcquireDisplayFadeReservation(5.0, &mut fade_token)).is_ok() {
CGDisplayFade(
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
== ffi::kCGErrorSuccess
{
ffi::CGDisplayFade(
fade_token,
0.3,
ffi::kCGDisplayBlendNormal,
@@ -1507,27 +1453,25 @@ impl WindowDelegate {
0.0,
0.0,
0.0,
true,
ffi::TRUE,
);
}
cgerr(CGDisplayCapture(display_id)).unwrap();
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
}
let video_mode =
match monitor.video_modes_handles().find(|mode| &mode.mode == video_mode) {
Some(video_mode) => video_mode,
None => return,
};
unsafe {
cgerr(CGDisplaySetDisplayMode(display_id, Some(&video_mode.native_mode.0), None))
.expect("failed to set video mode");
let result = ffi::CGDisplaySetDisplayMode(
display_id,
video_mode.native_mode.0,
std::ptr::null(),
);
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
// After the display has been configured, fade back in
// asynchronously
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
CGDisplayFade(
ffi::CGDisplayFade(
fade_token,
0.6,
ffi::kCGDisplayBlendSolidColor,
@@ -1535,16 +1479,16 @@ impl WindowDelegate {
0.0,
0.0,
0.0,
false,
ffi::FALSE,
);
CGReleaseDisplayFadeReservation(fade_token);
ffi::CGReleaseDisplayFadeReservation(fade_token);
}
}
}
self.ivars().fullscreen.replace(fullscreen.clone());
fn toggle_fullscreen(window: &NSWindow) {
fn toggle_fullscreen(window: &WinitWindow) {
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
@@ -1569,8 +1513,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::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options);
}
@@ -1580,11 +1524,11 @@ impl WindowDelegate {
// State is restored by `window_did_exit_fullscreen`
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Exclusive(ref monitor, _)), None) => {
restore_and_release_display(monitor);
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
restore_and_release_display(&video_mode.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
@@ -1595,23 +1539,24 @@ impl WindowDelegate {
// delegate in `window:willUseFullScreenPresentationOptions:`.
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
let presentation_options = NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options);
let window_level = unsafe { CGShieldingWindowLevel() } as NSWindowLevel + 1;
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
self.window().setLevel(window_level);
},
(Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => {
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::AutoHideDock
| NSApplicationPresentationOptions::AutoHideMenuBar,
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
);
app.setPresentationOptions(presentation_options);
restore_and_release_display(monitor);
restore_and_release_display(&video_mode.monitor());
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
@@ -1719,8 +1664,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::CriticalRequest,
UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest,
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
});
if let Some(ty) = ns_request_type {
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
@@ -1780,7 +1725,7 @@ impl WindowDelegate {
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
if available!(macos = 10.14) {
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
@@ -1795,9 +1740,9 @@ impl WindowDelegate {
#[inline]
pub fn set_content_protected(&self, protected: bool) {
self.window().setSharingType(if protected {
NSWindowSharingType::None
NSWindowSharingType::NSWindowSharingNone
} else {
NSWindowSharingType::ReadOnly
NSWindowSharingType::NSWindowSharingReadOnly
})
}
@@ -1814,8 +1759,8 @@ fn restore_and_release_display(monitor: &MonitorHandle) {
let available_monitors = monitor::available_monitors();
if available_monitors.contains(monitor) {
unsafe {
CGRestorePermanentDisplayConfiguration();
cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap();
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
};
} else {
warn!(
@@ -1860,8 +1805,9 @@ 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::AutoHideDock
| NSApplicationPresentationOptions::AutoHideMenuBar;
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
app.setPresentationOptions(presentation_options);
// Hide the titlebar
@@ -1942,14 +1888,10 @@ 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.objectAtIndex(index)));
group.setSelectedWindow(Some(&windows[index]));
}
}
}
@@ -2022,9 +1964,9 @@ fn dark_appearance_name() -> &'static NSString {
}
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[
unsafe { NSAppearanceNameAqua },
dark_appearance_name(),
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
unsafe { NSAppearanceNameAqua.copy() },
dark_appearance_name().copy(),
]));
if let Some(best_match) = best_match {
if *best_match == *dark_appearance_name() {

View File

@@ -1,126 +0,0 @@
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) };
}
}

View File

@@ -3,7 +3,6 @@
#[cfg(target_os = "macos")]
mod appkit;
mod event_handler;
mod event_loop_proxy;
mod notification_center;
#[cfg(not(target_os = "macos"))]
mod uikit;

View File

@@ -2,10 +2,7 @@ use std::ptr::NonNull;
use block2::RcBlock;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::{
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
};
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
/// Observe the given notification.
///
@@ -15,7 +12,7 @@ pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,
) -> Retained<ProtocolObject<dyn NSObjectProtocol>> {
) -> Retained<NSObject> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() });
});

View File

@@ -3,24 +3,28 @@
use std::cell::{OnceCell, RefCell, RefMut};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::{Arc, Mutex};
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use std::{mem, ptr};
use dispatch2::MainThreadBound;
use objc2::rc::Retained;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopAddTimer,
CFRunLoopGetMain, CFRunLoopTimer, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerSetNextFireDate, CGRect, CGSize,
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView};
use objc2::rc::Retained;
use objc2::sel;
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy;
use super::window::WinitUIWindow;
use super::ActiveEventLoop;
use super::{ActiveEventLoop, EventLoopProxy};
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
@@ -44,10 +48,22 @@ 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: MainThreadBound<OnceCell<EventHandler>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
StaticMainThreadBound(OnceCell::new());
GLOBAL.get(mtm).get_or_init(EventHandler::new)
}
@@ -102,7 +118,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`
@@ -114,21 +130,17 @@ impl AppState {
if guard.is_none() {
#[inline(never)]
#[cold]
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 }));
}));
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(),
waker,
event_loop_proxy,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
queued_events: Vec::new(),
});
}
init_guard(&mut guard, mtm);
init_guard(&mut guard);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
@@ -243,7 +255,6 @@ 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 =>
@@ -398,8 +409,13 @@ 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);
@@ -411,6 +427,10 @@ 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 }));
}
}
}
@@ -420,7 +440,13 @@ 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 let Ok(window) = window.downcast::<WinitUIWindow>() {
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
};
events.push(EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::Occluded(occluded),
@@ -484,7 +510,13 @@ pub(crate) fn terminated(application: &UIApplication) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if let Ok(window) = window.downcast::<WinitUIWindow>() {
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
};
events.push(EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::Destroyed,
@@ -498,10 +530,6 @@ 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) {
@@ -541,46 +569,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
}
struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>,
timer: CFRunLoopTimerRef,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(&self.timer);
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
fn new(rl: CFRetained<CFRunLoop>) -> EventLoopWaker {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _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(
None,
ptr::null_mut(),
f64::MAX,
0.000_000_1,
0,
0,
Some(wakeup_main_loop),
wakeup_main_loop,
ptr::null_mut(),
)
.unwrap();
CFRunLoopAddTimer(&rl, Some(&timer), kCFRunLoopCommonModes);
);
CFRunLoopAddTimer(rl, 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) {
@@ -593,8 +621,94 @@ 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()
}

View File

@@ -1,15 +1,18 @@
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 objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{msg_send, ClassType, MainThreadMarker};
use objc2_core_foundation::{
kCFRunLoopDefaultMode, CFIndex, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopGetMain,
CFRunLoopObserver, CFRunLoopObserverCreate,
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_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
@@ -26,7 +29,8 @@ use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform_impl::Window;
@@ -124,13 +128,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<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>>,
_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>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -185,9 +189,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
// The `object` in `UIApplicationWillEnterForegroundNotification` is documented to
// be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap();
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, false);
},
);
@@ -199,9 +203,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
// The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be
// `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap();
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, true);
},
);
@@ -212,9 +216,9 @@ impl EventLoop {
move |notification| {
let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object");
// The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented
// to be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap();
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// (somewhat) documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
app_state::terminated(&app);
},
);
@@ -240,7 +244,7 @@ impl EventLoop {
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send![UIApplication::class(), sharedApplication] };
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
application.is_none(),
"\
@@ -273,19 +277,78 @@ 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-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
@@ -301,68 +364,65 @@ 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-unwind" fn control_flow_main_end_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
let main_loop = CFRunLoopGetMain().unwrap();
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
None,
CFRunLoopActivity::AfterWaiting.0,
true,
CFIndex::MIN,
Some(control_flow_begin_handler),
ptr::null_mut(),
)
.unwrap();
CFRunLoopAddObserver(&main_loop, Some(&begin_observer), kCFRunLoopDefaultMode);
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::MIN,
control_flow_begin_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
0, // see comment on `control_flow_main_end_handler`
Some(control_flow_main_end_handler),
ptr::null_mut(),
)
.unwrap();
CFRunLoopAddObserver(&main_loop, Some(&main_end_observer), kCFRunLoopDefaultMode);
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
CFIndex::MAX,
Some(control_flow_end_handler),
ptr::null_mut(),
)
.unwrap();
CFRunLoopAddObserver(&main_loop, Some(&end_observer), kCFRunLoopDefaultMode);
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::MAX,
control_flow_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
}
}

View File

@@ -10,9 +10,9 @@ mod window;
use std::fmt;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
@@ -20,6 +20,9 @@ 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 {}

View File

@@ -1,29 +1,30 @@
#![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque;
use std::num::NonZeroU32;
use std::collections::{BTreeSet, VecDeque};
use std::num::{NonZeroU16, NonZeroU32};
use std::{fmt, hash, ptr};
use dispatch2::{run_on_main, MainThreadBound};
use objc2::mutability::IsRetainable;
use objc2::rc::Retained;
use objc2::{available, MainThreadMarker, Message};
use objc2_foundation::NSInteger;
use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode};
use crate::dpi::PhysicalPosition;
use crate::monitor::VideoMode;
use super::app_state;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
impl<T: Message> Clone for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + 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: Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + 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() };
@@ -31,7 +32,7 @@ impl<T: Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
}
}
impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> {
impl<T: IsRetainable + 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() };
@@ -39,12 +40,14 @@ impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> {
}
}
impl<T: Message> Eq for MainThreadBoundDelegateImpls<T> {}
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle {
pub(crate) mode: VideoMode,
pub(crate) size: (u32, u32),
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle,
}
impl VideoModeHandle {
@@ -55,18 +58,30 @@ impl VideoModeHandle {
) -> VideoModeHandle {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
let mode = VideoMode {
size: (size.width as u32, size.height as u32).into(),
bit_depth: None,
refresh_rate_millihertz,
};
VideoModeHandle {
mode,
size: (size.width as u32, size.height as u32),
refresh_rate_millihertz,
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)
}
@@ -149,7 +164,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())
}
})
@@ -164,53 +179,52 @@ impl MonitorHandle {
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
pub fn current_video_mode(&self) -> Option<VideoMode> {
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
Some(run_on_main(|mtm| {
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).currentMode().unwrap(),
mtm,
)
.mode
}))
}
pub fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm);
// Use Ord impl of RootVideoModeHandle
ui_screen
let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| VideoModeHandle::new(ui_screen.clone(), mode, mtm))
.collect::<Vec<_>>()
.into_iter()
})
}
.map(|mode| RootVideoModeHandle {
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm),
})
.collect();
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes_handles().map(|handle| handle.mode)
modes.into_iter().map(|mode| mode.video_mode)
})
}
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoMode {
pub fn preferred_video_mode(&self) -> VideoModeHandle {
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 = {
if available!(ios = 10.3, tvos = 10.2) {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
uiscreen.maximumFramesPerSecond()
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
@@ -223,9 +237,7 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
tracing::warn!(
"`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps"
);
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
}
};
@@ -254,7 +266,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))

View File

@@ -3,9 +3,8 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
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::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2_ui_kit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
@@ -22,6 +21,7 @@ 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,26 +39,36 @@ pub struct WinitViewState {
fingers: Cell<u8>,
}
define_class!(
#[unsafe(super(UIView, UIResponder, NSObject))]
#[name = "WinitUIView"]
#[ivars = WinitViewState]
declare_class!(
pub(crate) struct WinitView;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(drawRect:))]
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:)]
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] };
}
#[unsafe(method(layoutSubviews))]
#[method(layoutSubviews)]
fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
@@ -72,13 +82,16 @@ define_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),
},
);
}
#[unsafe(method(setContentScaleFactor:))]
#[method(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap();
let _: () =
@@ -111,46 +124,49 @@ define_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)),
},
)),
);
}
#[unsafe(method(safeAreaInsetsDidChange))]
#[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();
}
#[unsafe(method(touchesBegan:withEvent:))]
#[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[unsafe(method(touchesMoved:withEvent:))]
#[method(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[unsafe(method(touchesEnded:withEvent:))]
#[method(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[unsafe(method(touchesCancelled:withEvent:))]
#[method(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[unsafe(method(pinchGesture:))]
#[method(pinchGesture:)]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap();
@@ -158,40 +174,46 @@ define_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);
}
#[unsafe(method(doubleTapGesture:))]
#[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();
@@ -199,7 +221,7 @@ define_class!(
}
}
#[unsafe(method(rotationGesture:))]
#[method(rotationGesture:)]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap();
@@ -208,24 +230,23 @@ define_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:?}"),
};
@@ -243,7 +264,7 @@ define_class!(
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[unsafe(method(panGesture:))]
#[method(panGesture:)]
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
let window = self.window().unwrap();
@@ -254,7 +275,7 @@ define_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);
@@ -262,26 +283,25 @@ define_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 {
@@ -295,7 +315,7 @@ define_class!(
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[unsafe(method(canBecomeFirstResponder))]
#[method(canBecomeFirstResponder)]
fn can_become_first_responder(&self) -> bool {
true
}
@@ -304,30 +324,27 @@ define_class!(
unsafe impl NSObjectProtocol for WinitView {}
unsafe impl UIGestureRecognizerDelegate for WinitView {
#[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))]
fn should_recognize_simultaneously(
&self,
_gesture_recognizer: &UIGestureRecognizer,
_other_gesture_recognizer: &UIGestureRecognizer,
) -> bool {
#[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 {
#[unsafe(method(hasText))]
#[method(hasText)]
fn has_text(&self) -> bool {
true
}
#[unsafe(method(insertText:))]
#[method(insertText:)]
fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text)
}
#[unsafe(method(deleteBackward))]
#[method(deleteBackward)]
fn delete_backward(&self) {
self.handle_delete_backward()
}
@@ -353,7 +370,7 @@ impl WinitView {
primary_finger: Cell::new(None),
fingers: Cell::new(0),
});
let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
@@ -365,8 +382,8 @@ impl WinitView {
}
fn window(&self) -> Option<Retained<WinitUIWindow>> {
// `WinitView`s should always be installed in a `WinitUIWindow`
(**self).window().map(|window| window.downcast().unwrap())
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
(**self).window().map(|window| unsafe { Retained::cast(window) })
}
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
@@ -461,12 +478,13 @@ 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 available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
} else if os_supports_force {
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.
@@ -483,7 +501,7 @@ impl WinitView {
} else {
None
};
let touch_id = Retained::as_ptr(&touch) as usize;
let touch_id = touch as *const UITouch as usize;
let phase = touch.phase();
let position = {
let scale_factor = self.contentScaleFactor();
@@ -643,12 +661,7 @@ impl WinitView {
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
text_with_all_modifiers: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
key_without_modifiers: Key::Character(text.clone()),
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
@@ -671,11 +684,10 @@ 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,
},

View File

@@ -1,13 +1,14 @@
use std::cell::Cell;
use objc2::rc::Retained;
use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2_foundation::NSObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, 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;
@@ -19,42 +20,51 @@ pub struct ViewControllerState {
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
}
define_class!(
#[unsafe(super(UIViewController, UIResponder, NSObject))]
#[name = "WinitUIViewController"]
#[ivars = ViewControllerState]
declare_class!(
pub(crate) struct WinitViewController;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitViewController {
#[unsafe(method(shouldAutorotate))]
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)]
fn should_autorotate(&self) -> bool {
true
}
#[unsafe(method(prefersStatusBarHidden))]
#[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
self.ivars().prefers_status_bar_hidden.get()
}
#[unsafe(method(preferredStatusBarStyle))]
#[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.ivars().preferred_status_bar_style.get()
}
#[unsafe(method(prefersHomeIndicatorAutoHidden))]
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.ivars().prefers_home_indicator_auto_hidden.get()
}
#[unsafe(method(supportedInterfaceOrientations))]
#[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.ivars().supported_orientations.get()
}
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
#[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()
}
}
);
@@ -77,13 +87,11 @@ impl WinitViewController {
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.ivars().prefers_home_indicator_auto_hidden.set(val);
if available!(ios = 11.0, visionos = 1.0) {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
tracing::warn!(
"`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \
Ignoring"
);
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
@@ -93,13 +101,11 @@ impl WinitViewController {
UIRectEdge(val.bits().into())
};
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
if available!(ios = 11.0, visionos = 1.0) {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
tracing::warn!(
"`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \
visionOS. Ignoring"
);
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
@@ -140,7 +146,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![super(this), init] };
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden(
window_attributes.platform_specific.prefers_status_bar_hidden,

View File

@@ -2,11 +2,11 @@
use std::collections::VecDeque;
use dispatch2::MainThreadBound;
use objc2::rc::Retained;
use objc2::{available, class, define_class, msg_send, MainThreadMarker};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
use objc2_foundation::{NSObject, NSObjectProtocol};
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_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow,
@@ -32,31 +32,43 @@ use crate::window::{
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
define_class!(
#[unsafe(super(UIWindow, UIResponder, NSObject))]
#[name = "WinitUIWindow"]
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitUIWindow {
#[unsafe(method(becomeKeyWindow))]
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)]
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] };
}
#[unsafe(method(resignKeyWindow))]
#[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] };
}
}
@@ -74,18 +86,15 @@ 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![mtm.alloc(), initWithFrame: frame] };
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref monitor, ref video_mode)) => {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen(mtm);
if let Some(video_mode) =
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
{
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
}
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen);
},
Some(Fullscreen::Borderless(Some(ref monitor))) => {
@@ -196,7 +205,8 @@ impl Inner {
}
pub fn safe_area(&self) -> PhysicalInsets<u32> {
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
// Only available on iOS 11.0
let insets = if app_state::os_capabilities().safe_area {
self.view.safeAreaInsets()
} else {
// Assume the status bar frame is the only thing that obscures the view
@@ -302,13 +312,9 @@ impl Inner {
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor {
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)));
}
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen(mtm);
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
uiscreen.clone()
},
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
@@ -483,7 +489,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 monitor, _)) => monitor.ui_screen(mtm),
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(None)) | None => &main_screen,
};

View File

@@ -16,6 +16,7 @@ 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;
@@ -197,16 +198,9 @@ impl KeyContext<'_> {
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
text_with_all_modifiers,
key_without_modifiers,
}
let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers };
KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific }
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {

View File

@@ -4,23 +4,26 @@
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::monitor::VideoMode;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
@@ -162,16 +165,52 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<VideoMode> {
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
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)]
@@ -290,9 +329,9 @@ impl EventLoop {
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread(),
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)]
Backend::X => EventLoop::new_x11_any_thread(),
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
}
}

View File

@@ -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::{DeviceEvent, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
@@ -38,12 +38,6 @@ 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
@@ -389,9 +383,10 @@ impl EventLoop {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.active_event_loop, window_id, event)
},
Event::DeviceEvent { event } => {
app.device_event(&self.active_event_loop, None, event)
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
@@ -404,9 +399,10 @@ impl EventLoop {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.active_event_loop, window_id, event)
},
Event::DeviceEvent { event } => {
app.device_event(&self.active_event_loop, None, event)
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}

View File

@@ -2,8 +2,7 @@
use std::vec::Drain;
use super::Event;
use crate::event::{DeviceEvent, WindowEvent};
use crate::event::{DeviceEvent, Event, WindowEvent};
use crate::window::WindowId;
/// An event loop's sink to deliver events from the Wayland event callbacks
@@ -27,7 +26,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 });
self.window_events.push(Event::DeviceEvent { event, device_id: None });
}
/// Add new window event to a queue.

View File

@@ -1,7 +1,7 @@
//! Winit's Wayland backend.
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use output::MonitorHandle;
pub use output::{MonitorHandle, VideoModeHandle};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
pub use window::Window;

View File

@@ -1,11 +1,11 @@
use std::num::NonZeroU32;
use std::num::{NonZeroU16, NonZeroU32};
use sctk::output::{Mode, OutputData};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Proxy;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::monitor::VideoMode;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle;
#[derive(Clone, Debug)]
pub struct MonitorHandle {
@@ -54,19 +54,27 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<VideoMode> {
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
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(wayland_mode_to_core_mode)
mode.map(|mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode))
})
})
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
let output_data = self.proxy.data::<OutputData>().unwrap();
let modes = output_data.with_output_info(|info| info.modes.clone());
modes.into_iter().map(wayland_mode_to_core_mode)
let monitor = self.clone();
modes.into_iter().map(move |mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode))
})
}
}
@@ -96,11 +104,38 @@ impl std::hash::Hash for MonitorHandle {
}
}
/// 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),
#[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()
}
}

View File

@@ -27,10 +27,7 @@ use sctk::seat::pointer::{
use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent};
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, WindowId};

View File

@@ -119,15 +119,11 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
None => return,
};
// 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,
);
}
// Clear preedit at the start of `Done`.
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() {

View File

@@ -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::from_raw(token),
token: ActivationToken::_new(token),
},
*window_id,
);

View File

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

View File

@@ -4,7 +4,6 @@ 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};
@@ -46,25 +45,13 @@ 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,
position: PhysicalPosition::default(),
result: None,
dragging: false,
})
Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None })
}
pub fn reset(&mut self) {
@@ -72,7 +59,6 @@ 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

View File

@@ -122,15 +122,19 @@ 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,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
is_allowed,
)
};
if result.is_err() {

View File

@@ -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::{InputMethod, Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::input_method::{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) allowed: bool,
pub(crate) style: Style,
// 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: &InputMethod,
im: ffi::XIM,
style: Style,
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,24 +219,20 @@ 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.im, style, window)
},
Style::None(style) => unsafe {
ImeContext::create_none_ic(xconn, im.im, style, window)
ImeContext::create_nothing_ic(xconn, im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
@@ -245,7 +241,7 @@ impl ImeContext {
let mut context = ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
allowed,
style,
_client_data: unsafe { Box::from_raw(client_data) },
};
@@ -352,7 +348,7 @@ impl ImeContext {
}
pub fn is_allowed(&self) -> bool {
self.allowed
!matches!(self.style, Style::None(_))
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks

View File

@@ -177,7 +177,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
)
.map_err(GetXimServersError::GetPropertyError)?
.into_iter()
.map(|atom| atom as _)
.map(ffi::Atom::from)
.collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());

View File

@@ -10,12 +10,13 @@ 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;
use self::input_method::{PotentialInputMethods, Style};
use super::{ffi, util, XConnection, XError};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -112,26 +113,39 @@ impl Ime {
pub fn create_context(
&mut self,
window: ffi::Window,
with_ime: bool,
with_preedit: 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.im,
style,
window,
None,
self.inner.event_sender.clone(),
with_ime,
)?
};
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
// 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
};
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
Some(context)

View File

@@ -24,7 +24,7 @@ use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::event::{DeviceId, StartCause, WindowEvent};
use crate::event::{DeviceId, Event, 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::from_raw(token),
token: crate::window::ActivationToken::_new(token),
};
app.window_event(&self.event_processor.target, window_id, event);
},
@@ -574,7 +574,22 @@ 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, app);
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."),
}
}
});
}
}

View File

@@ -1,12 +1,12 @@
use std::num::NonZeroU32;
use std::num::{NonZeroU16, 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;
use crate::monitor::VideoMode;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl::VideoModeHandle as PlatformVideoModeHandle;
// Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
@@ -21,14 +21,32 @@ impl XConnection {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) current: bool,
pub(crate) mode: VideoMode,
pub(crate) size: (u32, u32),
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) native_mode: randr::Mode,
pub(crate) monitor: Option<MonitorHandle>,
}
impl From<VideoModeHandle> for VideoMode {
fn from(handle: VideoModeHandle) -> Self {
handle.mode
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()
}
}
@@ -47,7 +65,7 @@ pub struct MonitorHandle {
/// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect,
/// Supported video modes on this monitor
pub(crate) video_modes: Vec<VideoModeHandle>,
video_modes: Vec<VideoModeHandle>,
}
impl PartialEq for MonitorHandle {
@@ -141,13 +159,17 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<VideoMode> {
self.video_modes.iter().find(|mode| mode.current).cloned().map(Into::into)
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
self.video_modes.iter().find(|mode| mode.current).cloned().map(PlatformVideoModeHandle::X)
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes.clone().into_iter().map(Into::into)
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)
})
}
}

View File

@@ -95,7 +95,7 @@ impl FrameExtentsHeuristic {
impl XConnection {
// This is adequate for inner_position
pub fn translate_coords_root(
pub fn translate_coords(
&self,
window: xproto::Window,
root: xproto::Window,
@@ -103,19 +103,6 @@ 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,
@@ -202,7 +189,7 @@ impl XConnection {
// that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = {
let coords = self
.translate_coords_root(window, root)
.translate_coords(window, root)
.expect("Failed to translate window coordinates");
(coords.dst_y, coords.child)
};

View File

@@ -7,7 +7,6 @@ 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`.
@@ -86,11 +85,9 @@ impl XConnection {
.map(|mode| {
VideoModeHandle {
current: mode.id == current_mode,
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),
},
size: (mode.width.into(), mode.height.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

View File

@@ -20,11 +20,10 @@ 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::{SurfaceSizeWriter, WindowEvent};
use crate::event::{Event, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform::x11::WindowType;
use crate::platform_impl::x11::atoms::*;
@@ -33,6 +32,7 @@ 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,17 +1035,20 @@ 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(ref monitor, _)))
| (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref monitor, _))) => {
let id = monitor.native_identifier();
(&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();
shared_state_lock.desktop_video_mode = Some((
id,
self.xconn.get_crtc_mode(id).expect("Failed to get desktop video mode"),
monitor.id,
self.xconn.get_crtc_mode(monitor.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)
@@ -1069,8 +1072,8 @@ impl UnownedWindow {
},
Some(fullscreen) => {
let (video_mode, monitor) = match fullscreen {
Fullscreen::Exclusive(PlatformMonitorHandle::X(monitor), video_mode) => {
(Some(video_mode), monitor.clone())
Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => {
(Some(video_mode), video_mode.monitor.clone().unwrap())
},
Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => {
(None, monitor)
@@ -1087,15 +1090,7 @@ impl UnownedWindow {
return Ok(None);
}
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
}
})
}) {
if let Some(video_mode) = video_mode {
// 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
@@ -1122,7 +1117,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, native_mode)
.set_crtc_config(monitor.id, video_mode.native_mode)
.expect("failed to set video mode");
}
@@ -1212,8 +1207,7 @@ impl UnownedWindow {
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
app: &mut dyn ApplicationHandler,
event_loop: &ActiveEventLoop,
mut callback: impl FnMut(Event),
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
@@ -1233,9 +1227,12 @@ impl UnownedWindow {
let old_surface_size = PhysicalSize::new(width, height);
let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
app.window_event(event_loop, self.id(), WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
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)),
},
});
let new_surface_size = *surface_size.lock().unwrap();
@@ -1519,7 +1516,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_root(self.xwindow, self.root)
.translate_coords(self.xwindow, self.root)
.map(|coords| (coords.dst_x.into(), coords.dst_y.into()))
.unwrap()
}
@@ -1830,11 +1827,6 @@ 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(());
@@ -1846,7 +1838,6 @@ 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
@@ -1854,33 +1845,34 @@ 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(()),
@@ -1900,7 +1892,11 @@ impl UnownedWindow {
}
.map_err(|err| RequestError::Os(os_error!(err)))
},
CursorGrabMode::Locked => return Ok(()),
CursorGrabMode::Locked => {
return Err(
NotSupportedError::new("locked cursor is not implemented on X11").into()
);
},
};
if result.is_ok() {

View File

@@ -1,4 +1,4 @@
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)]
@@ -30,19 +30,17 @@ use self::web as platform;
use self::windows as platform;
/// Helper for converting between platform-specific and generic
/// [`VideoMode`]/[`MonitorHandle`]
/// [`VideoModeHandle`]/[`MonitorHandle`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Fullscreen {
Exclusive(MonitorHandle, VideoMode),
Exclusive(VideoModeHandle),
Borderless(Option<MonitorHandle>),
}
impl From<RootFullscreen> for Fullscreen {
fn from(f: RootFullscreen) -> Self {
match f {
RootFullscreen::Exclusive(handle, video_mode) => {
Self::Exclusive(handle.inner, video_mode)
},
RootFullscreen::Exclusive(mode) => Self::Exclusive(mode.video_mode),
RootFullscreen::Borderless(Some(handle)) => Self::Borderless(Some(handle.inner)),
RootFullscreen::Borderless(None) => Self::Borderless(None),
}
@@ -52,8 +50,8 @@ impl From<RootFullscreen> for Fullscreen {
impl From<Fullscreen> for RootFullscreen {
fn from(f: Fullscreen) -> Self {
match f {
Fullscreen::Exclusive(inner, video_mode) => {
Self::Exclusive(RootMonitorHandle { inner }, video_mode)
Fullscreen::Exclusive(video_mode) => {
Self::Exclusive(RootVideoModeHandle { video_mode })
},
Fullscreen::Borderless(Some(inner)) => {
Self::Borderless(Some(RootMonitorHandle { inner }))

View File

@@ -12,7 +12,8 @@ use orbclient::{
use smol_str::SmolStr;
use super::{
MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowProperties,
KeyEventExtra, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket,
WindowProperties,
};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
@@ -371,8 +372,10 @@ impl EventLoop {
state: element_state(pressed),
repeat: false,
text,
key_without_modifiers,
text_with_all_modifiers,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
},
is_synthetic: false,
};

View File

@@ -1,13 +1,16 @@
#![cfg(target_os = "redox")]
use std::num::{NonZeroU16, NonZeroU32};
use std::{fmt, str};
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::window::Window;
use crate::dpi::PhysicalPosition;
use crate::monitor::VideoMode;
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;
mod window;
pub(crate) use crate::cursor::{
@@ -148,12 +151,43 @@ impl MonitorHandle {
1.0 // TODO
}
pub fn current_video_mode(&self) -> Option<VideoMode> {
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
// (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 video_modes(&self) -> impl Iterator<Item = VideoMode> {
std::iter::empty()
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
// TODO
None
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub key_without_modifiers: Key,
pub text_with_all_modifiers: Option<SmolStr>,
}

View File

@@ -1,6 +1,7 @@
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};
@@ -23,20 +24,20 @@ impl EventLoop {
Ok(EventLoop { elw: ActiveEventLoop::new() })
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
let app = Box::new(app);
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));
// 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 app = unsafe {
std::mem::transmute::<
Box<dyn ApplicationHandler + '_>,
Box<dyn ApplicationHandler + 'static>,
>(app)
let handler = unsafe {
std::mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event) + 'static>>(handler)
};
self.elw.run(app, false);
self.elw.run(handler, 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 '!'
@@ -47,8 +48,9 @@ impl EventLoop {
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app), true);
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 window_target(&self) -> &dyn RootActiveEventLoop {
@@ -83,3 +85,18 @@ 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),
}
}

View File

@@ -13,12 +13,11 @@ 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, DeviceId, ElementState, RawKeyEvent, StartCause, WindowEvent};
use crate::event::{DeviceEvent, ElementState, Event, RawKeyEvent, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform_impl::platform::backend::{EventListenerHandle, SafeAreaHandle};
@@ -28,6 +27,8 @@ 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())
@@ -46,7 +47,7 @@ struct Execution {
runner: RefCell<RunnerEnum>,
suspended: Cell<bool>,
event_loop_recreation: Cell<bool>,
events: RefCell<VecDeque<Event>>,
events: RefCell<VecDeque<EventWrapper>>,
id: Cell<usize>,
window: web_sys::Window,
navigator: Navigator,
@@ -92,13 +93,12 @@ impl RunnerEnum {
struct Runner {
state: State,
app: Box<dyn ApplicationHandler>,
event_loop: ActiveEventLoop,
event_handler: Box<EventHandler>,
}
impl Runner {
pub fn new(app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) -> Self {
Runner { state: State::Init, app, event_loop }
pub fn new(event_handler: Box<EventHandler>) -> Self {
Runner { state: State::Init, event_handler }
}
/// Returns the corresponding `StartCause` for the current `state`, or `None`
@@ -115,33 +115,19 @@ impl Runner {
})
}
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 } => {
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 } => {
if let Some(canvas) = canvas.upgrade() {
canvas.handle_scale_change(
runner,
|window_id, event| {
self.app.window_event(&self.event_loop, window_id, event);
},
|event| (self.event_handler)(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),
}
}
}
@@ -230,13 +216,13 @@ impl Shared {
self.0.destroy_pending.borrow_mut().push_back(id);
}
pub(crate) fn start(&self, app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) {
pub(crate) fn start(&self, event_handler: Box<EventHandler>) {
let mut runner = self.0.runner.borrow_mut();
assert!(matches!(*runner, RunnerEnum::Pending));
if self.0.monitor.is_initializing() {
*runner = RunnerEnum::Initializing(Runner::new(app, event_loop));
*runner = RunnerEnum::Initializing(Runner::new(event_handler));
} else {
*runner = RunnerEnum::Running(Runner::new(app, event_loop));
*runner = RunnerEnum::Running(Runner::new(event_handler));
drop(runner);
@@ -459,7 +445,7 @@ impl Shared {
pub fn request_redraw(&self, id: WindowId) {
self.0.redraw_pending.borrow_mut().insert(id);
self.send_events([]);
self.send_events::<EventWrapper>(iter::empty());
}
fn init(&self) {
@@ -487,7 +473,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(&self, event: Event) {
pub(crate) fn send_event<E: Into<EventWrapper>>(&self, event: E) {
self.send_events(iter::once(event));
}
@@ -528,7 +514,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(&self, events: impl IntoIterator<Item = Event>) {
pub(crate) fn send_events<E: Into<EventWrapper>>(&self, events: impl IntoIterator<Item = E>) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
@@ -553,7 +539,7 @@ impl Shared {
}
if !process_immediately {
// Queue these events to look at later
self.0.events.borrow_mut().extend(events);
self.0.events.borrow_mut().extend(events.into_iter().map(Into::into));
return;
}
// At this point, we know this is a fresh set of events
@@ -571,7 +557,8 @@ 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(start_event).chain(events);
let events =
iter::once(EventWrapper::from(start_event)).chain(events.into_iter().map(Into::into));
self.run_until_cleared(events);
}
@@ -592,9 +579,9 @@ impl Shared {
// cleared
//
// This will also process any events that have been queued or that are queued during processing
fn run_until_cleared(&self, events: impl Iterator<Item = Event>) {
fn run_until_cleared<E: Into<EventWrapper>>(&self, events: impl Iterator<Item = E>) {
for event in events {
self.handle_event(event);
self.handle_event(event.into());
}
self.process_destroy_pending_windows();
@@ -628,7 +615,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: Event) {
fn handle_event(&self, event: impl Into<EventWrapper>) {
if self.is_closed() {
self.exit();
}
@@ -638,7 +625,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),
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event.into()),
// 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.
@@ -665,7 +652,13 @@ 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));
events.extend(
self.0
.event_loop_proxy
.take()
.then_some(Event::UserWakeUp)
.map(EventWrapper::from),
);
events.pop_front()
};
@@ -852,16 +845,13 @@ impl WeakShared {
}
}
#[allow(clippy::enum_variant_names)]
pub(crate) enum Event {
NewEvents(StartCause),
WindowEvent { window_id: WindowId, event: WindowEvent },
pub(crate) enum EventWrapper {
Event(Event),
ScaleChange { canvas: Weak<backend::Canvas>, size: PhysicalSize<u32>, scale: f64 },
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
Suspended,
CreateSurfaces,
Resumed,
AboutToWait,
LoopExiting,
UserWakeUp,
}
impl From<Event> for EventWrapper {
fn from(value: Event) -> Self {
Self::Event(value)
}
}

View File

@@ -6,13 +6,12 @@ use std::sync::Arc;
use web_sys::Element;
use super::super::lock;
use super::super::monitor::MonitorPermissionFuture;
use super::runner::Event;
use super::super::{lock, KeyEventExtra};
use super::runner::EventWrapper;
use super::{backend, runner};
use crate::application::ApplicationHandler;
use crate::error::{NotSupportedError, RequestError};
use crate::event::{ElementState, KeyEvent, TouchPhase, WindowEvent};
use crate::event::{ElementState, Event, KeyEvent, TouchPhase, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
@@ -55,9 +54,13 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
pub(crate) fn run(
&self,
event_handler: Box<runner::EventHandler>,
event_loop_recreation: bool,
) {
self.runner.event_loop_recreation(event_loop_recreation);
self.runner.start(app, self.clone());
self.runner.start(event_handler);
}
pub fn generate_id(&self) -> WindowId {
@@ -140,13 +143,12 @@ impl ActiveEventLoop {
device_id: None,
event: KeyEvent {
physical_key,
logical_key: logical_key.clone(),
text: text.clone(),
logical_key,
text,
location,
state: ElementState::Pressed,
repeat,
text_with_all_modifiers: text,
key_without_modifiers: logical_key,
platform_specific: KeyEventExtra,
},
is_synthetic: false,
},
@@ -175,13 +177,12 @@ impl ActiveEventLoop {
device_id: None,
event: KeyEvent {
physical_key,
logical_key: logical_key.clone(),
text: text.clone(),
logical_key,
text,
location,
state: ElementState::Released,
repeat,
text_with_all_modifiers: text,
key_without_modifiers: logical_key,
platform_specific: KeyEventExtra,
},
is_synthetic: false,
},
@@ -395,7 +396,7 @@ impl ActiveEventLoop {
let canvas = canvas_clone.clone();
move |size, scale| {
runner.send_event(Event::ScaleChange {
runner.send_event(EventWrapper::ScaleChange {
canvas: Rc::downgrade(&canvas),
size,
scale,

View File

@@ -2,6 +2,9 @@ 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 {

View File

@@ -40,8 +40,10 @@ 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};

View File

@@ -3,8 +3,9 @@ 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;
use std::num::{NonZeroU16, NonZeroU32};
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::rc::{Rc, Weak};
@@ -28,7 +29,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, VideoMode};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::web::{
MonitorPermissionError, Orientation, OrientationData, OrientationLock, OrientationLockError,
};
@@ -58,16 +59,12 @@ impl MonitorHandle {
self.inner.queue(|inner| inner.name())
}
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 current_video_mode(&self) -> Option<VideoModeHandle> {
Some(VideoModeHandle(self.clone()))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.current_video_mode().into_iter()
pub fn video_modes(&self) -> Once<VideoModeHandle> {
iter::once(VideoModeHandle(self.clone()))
}
pub fn orientation(&self) -> OrientationData {
@@ -255,6 +252,35 @@ 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>,

View File

@@ -1,6 +1,7 @@
{
"devDependencies": {
"@eslint/js": "^9",
"@types/eslint__js": "^8",
"eslint": "^9",
"typescript": "^5",
"typescript-eslint": "^8"

View File

@@ -12,7 +12,6 @@ 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;
@@ -24,7 +23,7 @@ use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::RequestError;
use crate::event::{
ButtonSource, DeviceId, ElementState, MouseScrollDelta, PointerKind, PointerSource,
SurfaceSizeWriter, WindowEvent,
SurfaceSizeWriter,
};
use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey};
use crate::platform_impl::Fullscreen;
@@ -488,7 +487,7 @@ impl Canvas {
pub(crate) fn handle_scale_change(
&self,
runner: &super::super::event_loop::runner::Shared,
event_handler: impl FnOnce(WindowId, WindowEvent),
event_handler: impl FnOnce(crate::event::Event),
current_size: PhysicalSize<u32>,
scale: f64,
) {
@@ -496,9 +495,12 @@ impl Canvas {
self.set_current_size(current_size);
let new_size = {
let new_size = Arc::new(Mutex::new(current_size));
event_handler(self.id, WindowEvent::ScaleFactorChanged {
scale_factor: scale,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_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)),
},
});
let new_size = *new_size.lock().unwrap();
@@ -521,7 +523,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(runner::Event::WindowEvent {
runner.send_event(crate::event::Event::WindowEvent {
window_id: self.id,
event: crate::event::WindowEvent::SurfaceResized(new_size),
})

View File

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

View File

@@ -133,7 +133,7 @@ fn should_apps_use_dark_mode() -> bool {
let module = LoadLibraryA("uxtheme.dll\0".as_ptr());
if module.is_null() {
if module == 0 {
return None;
}

View File

@@ -3,15 +3,11 @@
use std::ffi::c_void;
use windows_sys::core::{GUID, HRESULT};
use windows_sys::core::{IUnknown, GUID, HRESULT};
use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL};
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;
use windows_sys::Win32::System::Com::{
IAdviseSink, IDataObject, IEnumFORMATETC, IEnumSTATDATA, FORMATETC, STGMEDIUM,
};
#[repr(C)]
pub struct IUnknownVtbl {
@@ -76,13 +72,13 @@ pub struct IDropTargetVtbl {
This: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: u32,
pt: POINTL,
pt: *const POINTL,
pdwEffect: *mut u32,
) -> HRESULT,
pub DragOver: unsafe extern "system" fn(
This: *mut IDropTarget,
grfKeyState: u32,
pt: POINTL,
pt: *const POINTL,
pdwEffect: *mut u32,
) -> HRESULT,
pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT,
@@ -90,7 +86,7 @@ pub struct IDropTargetVtbl {
This: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: u32,
pt: POINTL,
pt: *const POINTL,
pdwEffect: *mut u32,
) -> HRESULT,
}

View File

@@ -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.is_null() {
if hdc == 0 {
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.is_null() {
if monitor == 0 {
return BASE_DPI;
}

View File

@@ -5,28 +5,27 @@ use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use tracing::debug;
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::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::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE};
use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP};
use crate::dpi::PhysicalPosition;
use crate::event::WindowEvent;
use crate::event::Event;
use crate::platform_impl::platform::definitions::{
IDataObject, IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknown, IUnknownVtbl,
IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl,
};
use crate::window::WindowId;
#[repr(C)]
pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
send_event: Box<dyn Fn(WindowEvent)>,
send_event: Box<dyn Fn(Event)>,
cursor_effect: u32,
valid: bool, /* If the currently hovered item is not valid there must not be any
* `DragLeft` emitted */
hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any
* `HoveredFileCancelled` emitted */
}
pub struct FileDropHandler {
@@ -35,14 +34,14 @@ pub struct FileDropHandler {
#[allow(non_snake_case)]
impl FileDropHandler {
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(WindowEvent)>) -> FileDropHandler {
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(Event)>) -> 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,
valid: false,
hovered_is_valid: false,
});
FileDropHandler { data: Box::into_raw(data) }
}
@@ -78,23 +77,22 @@ impl FileDropHandler {
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: u32,
pt: POINTL,
_pt: *const POINTL,
pdwEffect: *mut u32,
) -> HRESULT {
use crate::event::WindowEvent::HoveredFile;
let drop_handler = unsafe { Self::from_interface(this) };
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 });
}
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();
drop_handler.cursor_effect =
if drop_handler.valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
unsafe {
*pdwEffect = drop_handler.cursor_effect;
}
@@ -105,18 +103,10 @@ impl FileDropHandler {
pub unsafe extern "system" fn DragOver(
this: *mut IDropTarget,
_grfKeyState: u32,
pt: POINTL,
_pt: *const 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;
}
@@ -125,9 +115,13 @@ 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.valid {
(drop_handler.send_event)(WindowEvent::DragLeft { position: None });
if drop_handler.hovered_is_valid {
drop_handler.send_event(Event::WindowEvent {
window_id: WindowId::from_raw(drop_handler.window as usize),
event: HoveredFileCancelled,
});
}
S_OK
@@ -137,27 +131,21 @@ impl FileDropHandler {
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: u32,
pt: POINTL,
pdwEffect: *mut u32,
_pt: *const POINTL,
_pdwEffect: *mut u32,
) -> HRESULT {
use crate::event::WindowEvent::DroppedFile;
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);
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;
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) };
}
S_OK
@@ -167,9 +155,9 @@ impl FileDropHandler {
unsafe { &mut *(this as *mut _) }
}
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, mut callback: F) -> Option<HDROP>
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<HDROP>
where
F: FnMut(PathBuf),
F: Fn(PathBuf),
{
let drop_format = FORMATETC {
cfFormat: CF_HDROP,
@@ -219,6 +207,12 @@ 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

View File

@@ -1,27 +1,22 @@
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::{ActiveEventLoop, ControlFlow, EventLoopThreadExecutor};
use crate::application::ApplicationHandler;
use super::ControlFlow;
use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, DeviceId, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
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<&'static mut (dyn ApplicationHandler + 'static)>>;
type EventHandler = Cell<Option<Box<dyn FnMut(Event)>>>;
pub(crate) struct EventLoopRunner {
pub(super) thread_id: u32,
// The event loop's win32 handles
pub(super) thread_msg_target: HWND,
@@ -34,8 +29,8 @@ pub(crate) struct EventLoopRunner {
exit: Cell<Option<i32>>,
runner_state: Cell<RunnerState>,
last_events_cleared: Cell<Instant>,
event_handler: Rc<EventHandler>,
event_buffer: RefCell<VecDeque<Event>>,
event_handler: EventHandler,
event_buffer: RefCell<VecDeque<BufferedEvent>>,
panic_error: Cell<Option<PanicError>>,
}
@@ -56,20 +51,14 @@ pub(crate) enum RunnerState {
Destroyed,
}
#[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,
enum BufferedEvent {
Event(Event),
ScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
}
impl EventLoopRunner {
pub(crate) fn new(thread_id: u32, thread_msg_target: HWND) -> Self {
Self {
thread_id,
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner {
EventLoopRunner {
thread_msg_target,
interrupt_msg_dispatch: Cell::new(false),
runner_state: Cell::new(RunnerState::Uninitialized),
@@ -77,50 +66,40 @@ impl EventLoopRunner {
exit: Cell::new(None),
panic_error: Cell::new(None),
last_events_cleared: Cell::new(Instant::now()),
event_handler: Rc::new(Cell::new(None)),
event_handler: 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.
///
/// 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)
};
/// 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());
}
struct Resetter(Rc<EventHandler>);
impl Drop for Resetter {
fn drop(&mut self) {
self.0.set(None);
}
}
Resetter(self.event_handler.clone())
pub(crate) fn clear_event_handler(&self) {
self.event_handler.set(None);
}
pub(crate) fn reset_runner(&self) {
let Self {
thread_id: _,
let EventLoopRunner {
thread_msg_target: _,
interrupt_msg_dispatch,
runner_state,
@@ -209,26 +188,21 @@ 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: &Rc<Self>) {
pub(crate) fn prepare_wait(&self) {
self.move_state_to(RunnerState::Idle);
}
pub(crate) fn wakeup(self: &Rc<Self>) {
pub(crate) fn wakeup(&self) {
self.move_state_to(RunnerState::HandlingMainEvents);
}
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));
pub(crate) fn send_event(&self, event: Event) {
if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event {
self.call_event_handler(event);
// 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.
@@ -236,34 +210,31 @@ 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(event.buffer_scale_factor())
self.event_buffer.borrow_mut().push_back(BufferedEvent::from_event(event))
} else {
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
self.call_event_handler(event);
self.dispatch_buffered_events();
}
}
pub(crate) fn loop_destroyed(self: &Rc<Self>) {
pub(crate) fn loop_destroyed(&self) {
self.move_state_to(RunnerState::Destroyed);
}
fn call_event_handler(
self: &Rc<Self>,
closure: impl FnOnce(&mut dyn ApplicationHandler, &dyn RootActiveEventLoop),
) {
fn call_event_handler(&self, event: Event) {
self.catch_unwind(|| {
let event_handler = self.event_handler.take().expect(
let mut event_handler = self.event_handler.take().expect(
"either event handler is re-entrant (likely), or no event handler is registered \
(very unlikely)",
);
closure(event_handler, ActiveEventLoop::from_ref(self));
event_handler(event);
assert!(self.event_handler.replace(Some(event_handler)).is_none());
});
}
fn dispatch_buffered_events(self: &Rc<Self>) {
fn dispatch_buffered_events(&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
@@ -271,9 +242,7 @@ impl EventLoopRunner {
// `process_event` will fail.
let buffered_event_opt = self.event_buffer.borrow_mut().pop_front();
match buffered_event_opt {
Some(e) => {
self.call_event_handler(|app, event_loop| e.dispatch_event(app, event_loop))
},
Some(e) => e.dispatch_event(|e| self.call_event_handler(e)),
None => break,
}
}
@@ -303,7 +272,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: &Rc<Self>, new_runner_state: RunnerState) {
fn move_state_to(&self, new_runner_state: RunnerState) {
use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized};
match (self.runner_state.replace(new_runner_state), new_runner_state) {
@@ -318,14 +287,14 @@ impl EventLoopRunner {
},
(Uninitialized, Idle) => {
self.call_new_events(true);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
},
(Uninitialized, Destroyed) => {
self.call_new_events(true);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
self.call_event_handler(Event::LoopExiting);
},
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
@@ -334,25 +303,25 @@ impl EventLoopRunner {
self.call_new_events(false);
},
(Idle, Destroyed) => {
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
self.call_event_handler(Event::LoopExiting);
},
(HandlingMainEvents, Idle) => {
// This is always the last event we dispatch before waiting for new events
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
},
(HandlingMainEvents, Destroyed) => {
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
self.call_event_handler(Event::LoopExiting);
},
(Destroyed, _) => panic!("cannot move state from Destroyed"),
}
}
fn call_new_events(self: &Rc<Self>, init: bool) {
fn call_new_events(&self, init: bool) {
let start_cause = match (init, self.control_flow(), self.exit.get()) {
(true, ..) => StartCause::Init,
(false, ControlFlow::Poll, None) => StartCause::Poll,
@@ -374,55 +343,45 @@ impl EventLoopRunner {
}
},
};
self.call_event_handler(|app, event_loop| app.new_events(event_loop, start_cause));
self.call_event_handler(Event::NewEvents(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(|app, event_loop| app.can_create_surfaces(event_loop));
self.call_event_handler(Event::CreateSurfaces);
}
self.dispatch_buffered_events();
}
}
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 {
impl BufferedEvent {
pub fn from_event(event: Event) -> BufferedEvent {
match event {
Event::WindowEvent {
event: WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer },
window_id,
} => Event::BufferedScaleFactorChanged(
} => BufferedEvent::ScaleFactorChanged(
window_id.into_raw() as HWND,
scale_factor,
*surface_size_writer.new_surface_size.upgrade().unwrap().lock().unwrap(),
),
event => event,
event => BufferedEvent::Event(event),
}
}
pub fn dispatch_event(
self,
app: &mut dyn ApplicationHandler,
event_loop: &dyn RootActiveEventLoop,
) {
pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) {
match self {
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) => {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window, scale_factor, new_surface_size) => {
let user_new_surface_size = Arc::new(Mutex::new(new_surface_size));
app.window_event(
event_loop,
WindowId::from_raw(window as usize),
WindowEvent::ScaleFactorChanged {
dispatch(Event::WindowEvent {
window_id: WindowId::from_raw(window as usize),
event: 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);
@@ -436,7 +395,6 @@ impl Event {
window_flags.set_size(window, surface_size);
}
},
Self::WakeUp => app.proxy_wake_up(event_loop),
}
}
}

View File

@@ -1,7 +1,7 @@
use std::ffi::c_void;
use std::path::Path;
use std::sync::Arc;
use std::{fmt, io, mem, ptr};
use std::{fmt, io, mem};
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(
ptr::null_mut(),
0,
self.width as i32,
self.height as i32,
1,
@@ -49,7 +49,7 @@ impl RgbaIcon {
rgba.as_ptr(),
)
};
if !handle.is_null() {
if handle != 0 {
Ok(WinIcon::from_handle(handle))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
@@ -68,9 +68,6 @@ 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>,
@@ -94,7 +91,7 @@ impl WinIcon {
let handle = unsafe {
LoadImageW(
ptr::null_mut(),
0,
wide_path.as_ptr(),
IMAGE_ICON,
width,
@@ -102,7 +99,7 @@ impl WinIcon {
LR_DEFAULTSIZE | LR_LOADFROMFILE,
)
};
if !handle.is_null() {
if handle != 0 {
Ok(WinIcon::from_handle(handle as HICON))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
@@ -112,35 +109,20 @@ 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,
resource_id as PCWSTR,
IMAGE_ICON,
width,
height,
LR_DEFAULTSIZE,
)
};
if !handle.is_null() {
if handle != 0 {
Ok(WinIcon::from_handle(handle as HICON))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
@@ -154,7 +136,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() as isize);
SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle());
}
}
@@ -205,13 +187,13 @@ impl WinCursor {
let h = image.height as i32;
unsafe {
let hdc_screen = GetDC(ptr::null_mut());
if hdc_screen.is_null() {
let hdc_screen = GetDC(0);
if hdc_screen == 0 {
return Err(os_error!(io::Error::last_os_error()).into());
}
let hbm_color = CreateCompatibleBitmap(hdc_screen, w, h);
ReleaseDC(ptr::null_mut(), hdc_screen);
if hbm_color.is_null() {
ReleaseDC(0, hdc_screen);
if hbm_color == 0 {
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 {
@@ -222,7 +204,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.is_null() {
if hbm_mask == 0 {
DeleteObject(hbm_color);
return Err(os_error!(io::Error::last_os_error()).into());
}
@@ -238,7 +220,7 @@ impl WinCursor {
let handle = CreateIconIndirect(&icon_info as *const _);
DeleteObject(hbm_color);
DeleteObject(hbm_mask);
if handle.is_null() {
if handle == 0 {
return Err(os_error!(io::Error::last_os_error()).into());
}
@@ -252,9 +234,6 @@ 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) };

View File

@@ -3,11 +3,12 @@ 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, HIMC, IACE_CHILDREN, IACE_DEFAULT,
GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
};
use windows_sys::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED};
@@ -137,9 +138,9 @@ impl ImeContext {
}
if allowed {
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_DEFAULT) };
unsafe { ImmAssociateContextEx(hwnd, 0, IACE_DEFAULT) };
} else {
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_CHILDREN) };
unsafe { ImmAssociateContextEx(hwnd, 0, IACE_CHILDREN) };
}
}

View File

@@ -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, HKL,
GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW,
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,6 +20,7 @@ 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,
@@ -31,7 +32,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};
use crate::platform_impl::platform::{loword, primarylangid, KeyEventExtra};
pub type ExScancode = u16;
@@ -451,7 +452,7 @@ impl KeyEventBuilder {
let mut event = event_info.finalize();
event.logical_key = logical_key;
event.text_with_all_modifiers = text;
event.platform_specific.text_with_all_modifiers = text;
Some(MessageAsKeyEvent { event, is_synthetic: true })
}
}
@@ -629,8 +630,10 @@ impl PartialKeyEventInfo {
location: self.location,
state: self.key_state,
repeat: self.is_repeat,
text_with_all_modifiers: char_with_all_modifiers,
key_without_modifiers: self.key_without_modifiers,
platform_specific: KeyEventExtra {
text_with_all_modifiers: char_with_all_modifiers,
key_without_modifiers: self.key_without_modifiers,
},
}
}
}

View File

@@ -7,19 +7,18 @@ 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, 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,
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,
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,
@@ -40,6 +39,7 @@ 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};

View File

@@ -1,3 +1,4 @@
use smol_str::SmolStr;
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
@@ -5,11 +6,12 @@ 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;
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
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;
@@ -59,6 +61,12 @@ 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)

View File

@@ -1,4 +1,4 @@
use std::collections::{HashSet, VecDeque};
use std::collections::{BTreeSet, VecDeque};
use std::hash::Hash;
use std::num::{NonZeroU16, NonZeroU32};
use std::{io, mem, ptr};
@@ -13,20 +13,26 @@ use windows_sys::Win32::Graphics::Gdi::{
use super::util::decode_wide;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoMode;
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
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) mode: VideoMode,
pub(crate) size: (u32, u32),
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) monitor: MonitorHandle,
// 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.mode == other.mode
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
}
}
@@ -34,39 +40,65 @@ impl Eq for VideoModeHandle {}
impl std::hash::Hash for VideoModeHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.mode.hash(state);
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("VideoMode").field("mode", &self.mode).finish()
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()
}
}
impl VideoModeHandle {
fn new(native_video_mode: DEVMODEW) -> Self {
fn new(monitor: MonitorHandle, mode: DEVMODEW) -> Self {
const REQUIRED_FIELDS: u32 =
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
assert!(has_flag(native_video_mode.dmFields, REQUIRED_FIELDS));
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
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),
};
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),
}
}
VideoModeHandle { mode, native_video_mode: Box::new(native_video_mode) }
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()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle(HMONITOR);
// Send and Sync are not implemented for HMONITOR, we have to wrap it and implement them manually.
// 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
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR,
@@ -83,7 +115,7 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
unsafe {
EnumDisplayMonitors(
ptr::null_mut(),
0,
ptr::null(),
Some(monitor_enum_proc),
&mut monitors as *mut _ as LPARAM,
@@ -161,7 +193,7 @@ impl MonitorHandle {
}
#[inline]
pub fn current_video_mode(&self) -> Option<VideoMode> {
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
let monitor_info = get_monitor_info(self.0).ok()?;
let device_name = monitor_info.szDevice.as_ptr();
unsafe {
@@ -172,26 +204,29 @@ impl MonitorHandle {
{
None
} else {
Some(VideoModeHandle::new(mode).mode)
Some(VideoModeHandle::new(self.clone(), mode))
}
}
}
pub(crate) fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
#[inline]
pub fn video_modes(&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 = HashSet::<VideoModeHandle>::new();
let mut modes = BTreeSet::<RootVideoModeHandle>::new();
let mod_map = |mode: RootVideoModeHandle| mode.video_mode;
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();
return modes.into_iter().map(mod_map);
},
};
let device_name = monitor_info.szDevice.as_ptr();
let mut i = 0;
loop {
let mut mode: DEVMODEW = unsafe { mem::zeroed() };
@@ -201,15 +236,13 @@ impl MonitorHandle {
}
// Use Ord impl of RootVideoModeHandle
modes.insert(VideoModeHandle::new(mode));
modes.insert(RootVideoModeHandle {
video_mode: VideoModeHandle::new(self.clone(), mode),
});
i += 1;
}
modes.into_iter()
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_mode_handles().map(|mode| mode.mode)
modes.into_iter().map(mod_map)
}
}

View File

@@ -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 = ptr::null_mut();
window_handle = 0;
RIDEV_REMOVE
},
DeviceEvents::WhenFocused => RIDEV_DEVNOTIFY,

View File

@@ -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.is_null() {
if module == 0 {
return None;
}

View File

@@ -3,7 +3,6 @@
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};
@@ -60,9 +59,7 @@ 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, Event, EventLoopRunner, DESTROY_MSG_ID,
};
use crate::platform_impl::platform::event_loop::{self, ActiveEventLoop, 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;
@@ -76,25 +73,10 @@ 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: SyncWindowHandle,
window: HWND,
/// The current window state.
window_state: Arc<Mutex<WindowState>>,
@@ -112,7 +94,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.0) }
unsafe { init(w_attr, event_loop) }
}
fn window_state_lock(&self) -> MutexGuard<'_, WindowState> {
@@ -121,7 +103,7 @@ impl Window {
/// Returns the `hwnd` of this window.
pub fn hwnd(&self) -> HWND {
self.window.hwnd()
self.window
}
pub unsafe fn rwh_06_no_thread_check(
@@ -129,7 +111,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.hwnd() as isize)
std::num::NonZeroIsize::new_unchecked(self.window)
});
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
window_handle.hinstance = std::num::NonZeroIsize::new(hinstance);
@@ -168,7 +150,8 @@ impl 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.hwnd(), |f| {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow)
});
});
@@ -219,7 +202,7 @@ impl Window {
unsafe { ReleaseCapture() };
unsafe {
PostMessageW(window.hwnd(), WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM)
PostMessageW(window, WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM)
};
});
}
@@ -244,7 +227,7 @@ impl Window {
// get the current system menu
let h_menu = GetSystemMenu(self.hwnd(), 0);
if h_menu.is_null() {
if h_menu == 0 {
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.
@@ -351,7 +334,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);
}
@@ -390,7 +373,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::TRANSPARENT, transparent)
});
});
@@ -403,21 +386,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::VISIBLE, visible)
});
});
}
fn is_visible(&self) -> Option<bool> {
Some(unsafe { IsWindowVisible(self.window.hwnd()) == 1 })
Some(unsafe { IsWindowVisible(self.window) == 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(), ptr::null_mut(), RDW_INTERNALPAINT);
RedrawWindow(self.hwnd(), ptr::null(), 0, RDW_INTERNALPAINT);
}
}
@@ -451,7 +434,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
@@ -459,14 +442,14 @@ impl CoreWindow for Window {
unsafe {
SetWindowPos(
self.hwnd(),
ptr::null_mut(),
0,
x,
y,
0,
0,
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE,
);
InvalidateRgn(self.hwnd(), ptr::null_mut(), false.into());
InvalidateRgn(self.hwnd(), 0, false.into());
}
}
@@ -502,7 +485,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
@@ -545,7 +528,7 @@ impl CoreWindow for Window {
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::RESIZABLE, resizable)
});
});
@@ -562,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |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))
@@ -590,7 +573,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(ptr::null_mut(), util::to_windows_cursor(icon));
let cursor = LoadCursorW(0, util::to_windows_cursor(icon));
SetCursor(cursor);
});
},
@@ -623,7 +606,7 @@ impl CoreWindow for Window {
.lock()
.unwrap()
.mouse
.set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::GRABBED, confine))
.set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine))
.map_err(|err| os_error!(err).into());
let _ = tx.send(result);
});
@@ -642,7 +625,7 @@ impl CoreWindow for Window {
.lock()
.unwrap()
.mouse
.set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::HIDDEN, !visible))
.set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible))
.map_err(|e| e.to_string());
let _ = tx.send(result);
});
@@ -704,7 +687,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
});
});
@@ -727,7 +710,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.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MINIMIZED, minimized)
});
});
@@ -743,7 +726,7 @@ impl CoreWindow for Window {
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MAXIMIZED, maximized)
});
});
@@ -773,7 +756,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.hwnd()) =>
if *monitor == monitor::current_monitor(window) =>
{
return
},
@@ -788,19 +771,15 @@ impl CoreWindow for Window {
// Change video mode if we're transitioning to or from exclusive
// fullscreen
match (&old_fullscreen, &fullscreen) {
(_, Some(Fullscreen::Exclusive(monitor, video_mode))) => {
(_, Some(Fullscreen::Exclusive(video_mode))) => {
let monitor = video_mode.monitor();
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,
ptr::null_mut(),
0,
CDS_FULLSCREEN,
ptr::null(),
)
@@ -812,12 +791,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(),
ptr::null_mut(),
0,
CDS_FULLSCREEN,
ptr::null(),
)
@@ -842,14 +821,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, ptr::null_mut(), 0, 0, PM_NOREMOVE);
PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE);
}
// Update window style
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
matches!(fullscreen, Some(Fullscreen::Exclusive(_, _))),
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
);
f.set(
WindowFlags::MARKER_BORDERLESS_FULLSCREEN,
@@ -863,7 +842,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.hwnd(), fullscreen.is_some());
taskbar_mark_fullscreen(window, fullscreen.is_some());
}
// Update window bounds
@@ -872,16 +851,16 @@ impl CoreWindow for Window {
// Save window bounds before entering fullscreen
let placement = unsafe {
let mut placement = mem::zeroed();
GetWindowPlacement(window.hwnd(), &mut placement);
GetWindowPlacement(window, &mut placement);
placement
};
window_state.lock().unwrap().saved_window = Some(SavedWindow { placement });
let monitor = match &fullscreen {
Fullscreen::Exclusive(monitor, _) => monitor.clone(),
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
Fullscreen::Borderless(None) => monitor::current_monitor(window.hwnd()),
Fullscreen::Borderless(None) => monitor::current_monitor(window),
};
let position: (i32, i32) = monitor.position().unwrap_or_default().into();
@@ -889,15 +868,15 @@ impl CoreWindow for Window {
unsafe {
SetWindowPos(
window.hwnd(),
ptr::null_mut(),
window,
0,
position.0,
position.1,
size.0 as i32,
size.1 as i32,
SWP_ASYNCWINDOWPOS | SWP_NOZORDER,
);
InvalidateRgn(window.hwnd(), ptr::null_mut(), false.into());
InvalidateRgn(window, 0, false.into());
}
},
None => {
@@ -905,8 +884,8 @@ impl CoreWindow for Window {
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
drop(window_state_lock);
unsafe {
SetWindowPlacement(window.hwnd(), &placement);
InvalidateRgn(window.hwnd(), ptr::null_mut(), false.into());
SetWindowPlacement(window, &placement);
InvalidateRgn(window, 0, false.into());
}
}
},
@@ -920,7 +899,7 @@ impl CoreWindow for Window {
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::MARKER_DECORATIONS, decorations)
});
});
@@ -937,7 +916,7 @@ impl CoreWindow for Window {
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| {
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
f.set(WindowFlags::ALWAYS_ON_TOP, level == WindowLevel::AlwaysOnTop);
f.set(WindowFlags::ALWAYS_ON_BOTTOM, level == WindowLevel::AlwaysOnBottom);
});
@@ -970,7 +949,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.hwnd()).set_ime_cursor_area(spot, size, scale_factor);
ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor);
});
}
@@ -979,7 +958,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.hwnd(), allowed);
ImeContext::set_ime_allowed(window, allowed);
})
}
@@ -988,7 +967,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.hwnd() == active_window_handle {
if window == active_window_handle {
return;
}
@@ -1002,7 +981,7 @@ impl CoreWindow for Window {
let flash_info = FLASHWINFO {
cbSize: mem::size_of::<FLASHWINFO>() as u32,
hwnd: window.hwnd(),
hwnd: window,
dwFlags: flags,
uCount: count,
dwTimeout: 0,
@@ -1012,7 +991,7 @@ impl CoreWindow for Window {
}
fn set_theme(&self, theme: Option<Theme>) {
try_theme(self.window.hwnd(), theme);
try_theme(self.window, theme);
}
fn theme(&self) -> Option<Theme> {
@@ -1025,9 +1004,9 @@ impl CoreWindow for Window {
}
fn title(&self) -> String {
let len = unsafe { GetWindowTextLengthW(self.window.hwnd()) } + 1;
let len = unsafe { GetWindowTextLengthW(self.window) } + 1;
let mut buf = vec![0; len as usize];
unsafe { GetWindowTextW(self.window.hwnd(), buf.as_mut_ptr(), len) };
unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) };
util::decode_wide(&buf).to_string_lossy().to_string()
}
@@ -1037,10 +1016,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.hwnd() == unsafe { GetForegroundWindow() };
let is_foreground = self.window == unsafe { GetForegroundWindow() };
if is_visible && !is_minimized && !is_foreground {
unsafe { force_window_active(self.window.hwnd()) };
unsafe { force_window_active(self.window) };
}
}
@@ -1085,7 +1064,7 @@ impl CoreWindow for Window {
pub(super) struct InitData<'a> {
// inputs
pub runner: &'a Rc<EventLoopRunner>,
pub event_loop: &'a ActiveEventLoop,
pub attributes: WindowAttributes,
pub window_flags: WindowFlags,
// outputs
@@ -1128,11 +1107,7 @@ impl InitData<'_> {
unsafe { ImeContext::set_ime_allowed(window, false) };
Window {
window: SyncWindowHandle(window),
window_state,
thread_executor: self.runner.create_thread_executor(),
}
Window { window, window_state, thread_executor: self.event_loop.create_thread_executor() }
}
unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData {
@@ -1150,19 +1125,16 @@ impl InitData<'_> {
);
}
let file_drop_runner = self.runner.clone();
let window_id = win.id();
let file_drop_runner = self.event_loop.runner_shared.clone();
let file_drop_handler = FileDropHandler::new(
win.window.hwnd(),
Box::new(move |event| {
file_drop_runner.send_event(Event::Window { window_id, event })
}),
win.window,
Box::new(move |event| file_drop_runner.send_event(event)),
);
let handler_interface_ptr =
unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void };
assert_eq!(unsafe { RegisterDragDrop(win.window.hwnd(), handler_interface_ptr) }, S_OK);
assert_eq!(unsafe { RegisterDragDrop(win.window, handler_interface_ptr) }, S_OK);
Some(file_drop_handler)
} else {
None
@@ -1170,7 +1142,7 @@ impl InitData<'_> {
event_loop::WindowData {
window_state: win.window_state.clone(),
event_loop_runner: self.runner.clone(),
event_loop_runner: self.event_loop.runner_shared.clone(),
key_event_builder: KeyEventBuilder::default(),
_file_drop_handler: file_drop_handler,
userdata_removed: Cell::new(false),
@@ -1182,7 +1154,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.runner.clone();
let runner = self.event_loop.runner_shared.clone();
let result = runner.catch_unwind(|| {
let window = unsafe { self.create_window(window) };
let window_data = unsafe { self.create_window_data(&window) };
@@ -1274,7 +1246,7 @@ impl InitData<'_> {
}
unsafe fn init(
attributes: WindowAttributes,
runner: &Rc<EventLoopRunner>,
event_loop: &ActiveEventLoop,
) -> Result<Window, RequestError> {
let title = util::encode_wide(&attributes.title);
@@ -1328,7 +1300,7 @@ unsafe fn init(
let menu = attributes.platform_specific.menu;
let fullscreen = attributes.fullscreen.clone();
let maximized = attributes.maximized;
let mut initdata = InitData { runner, attributes, window_flags, window: None };
let mut initdata = InitData { event_loop, attributes, window_flags, window: None };
let (style, ex_style) = window_flags.to_window_styles();
let handle = unsafe {
@@ -1341,19 +1313,19 @@ unsafe fn init(
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
parent.unwrap_or(ptr::null_mut()),
menu.unwrap_or(ptr::null_mut()),
parent.unwrap_or(0),
menu.unwrap_or(0),
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) = runner.take_panic_error() {
if let Err(panic_error) = event_loop.runner_shared.take_panic_error() {
panic::resume_unwind(panic_error)
}
if handle.is_null() {
if handle == 0 {
return Err(os_error!(io::Error::last_os_error()).into());
}
@@ -1365,8 +1337,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);
unsafe { force_window_active(win.window.hwnd()) };
win.set_fullscreen(fullscreen.map(Into::into));
unsafe { force_window_active(win.window) };
} else if maximized {
win.set_maximized(true);
}
@@ -1382,12 +1354,12 @@ unsafe fn register_window_class(class_name: &[u16]) {
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: util::get_instance_handle(),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
hbrBackground: ptr::null_mut(),
hIcon: 0,
hCursor: 0, // must be null in order for cursor state to work properly
hbrBackground: 0,
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
hIconSm: ptr::null_mut(),
hIconSm: 0,
};
// We ignore errors because registering the same window class twice would trigger

View File

@@ -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, ptr::null_mut(), false.into());
InvalidateRgn(window, 0, false.into());
}
}
@@ -420,7 +420,7 @@ impl WindowFlags {
}
// Refresh the window frame
SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
SetWindowPos(window, 0, 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).is_null();
let b_menu = GetMenu(hwnd) != 0;
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,
ptr::null_mut(),
0,
0,
0,
width as _,
height as _,
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE,
);
InvalidateRgn(hwnd, ptr::null_mut(), false.into());
InvalidateRgn(hwnd, 0, false.into());
}
}
}

View File

@@ -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, VideoMode};
use crate::monitor::{MonitorHandle, VideoModeHandle};
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 [`VideoMode`] that was not created with
/// - **Web:** Passing a [`MonitorHandle`] or [`VideoModeHandle`] 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(MonitorHandle, VideoMode),
Exclusive(VideoModeHandle),
/// Providing `None` to `Borderless` will fullscreen on the current monitor.
Borderless(Option<MonitorHandle>),
@@ -1544,34 +1544,11 @@ 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 {
/// 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
pub(crate) fn _new(_token: String) -> Self {
Self { _token }
}
}