mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
22 Commits
notgull/im
...
rwh-send-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e66eba38f5 | ||
|
|
e9a25a4c91 | ||
|
|
674657efb6 | ||
|
|
7d5bee767c | ||
|
|
745cfaab2c | ||
|
|
a8f49dc8ef | ||
|
|
e5310ade08 | ||
|
|
37946e0a3a | ||
|
|
86b737f5e7 | ||
|
|
e37585e5bc | ||
|
|
4aeeb24745 | ||
|
|
8cd3aaa8a2 | ||
|
|
2c15de7cf9 | ||
|
|
0a7ea61834 | ||
|
|
4ee11018c2 | ||
|
|
4f669ebbd2 | ||
|
|
7761b2b16c | ||
|
|
ae41e3265f | ||
|
|
8702a09333 | ||
|
|
8b5c84f404 | ||
|
|
a676d0018b | ||
|
|
04ca85a909 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain: [stable, nightly, '1.65.0']
|
||||
toolchain: [stable, nightly, '1.70.0']
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
@@ -43,10 +43,10 @@ jobs:
|
||||
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||
exclude:
|
||||
# Android is tested on stable-3
|
||||
- toolchain: '1.65.0'
|
||||
- toolchain: '1.70.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
include:
|
||||
- toolchain: '1.69.0'
|
||||
- toolchain: '1.70.0'
|
||||
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||
|
||||
env:
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
- name: Build tests
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS
|
||||
|
||||
- name: Run tests
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test $OPTIONS
|
||||
|
||||
- name: Lint with clippy
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
- name: Build tests with serde enabled
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test --no-run $OPTIONS --features serde
|
||||
|
||||
- name: Run tests with serde enabled
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.toolchain != '1.65.0'
|
||||
matrix.toolchain != '1.70.0'
|
||||
run: cargo $CMD test $OPTIONS --features serde
|
||||
|
||||
# See restore step above
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -17,6 +17,15 @@ Unreleased` header.
|
||||
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
||||
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
||||
- On macOS, add services menu.
|
||||
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
|
||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
|
||||
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
|
||||
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
|
||||
|
||||
# 0.29.5
|
||||
|
||||
- On macOS, remove spurious error logging when handling `Fn`.
|
||||
- On X11, fix an issue where floating point data from the server is
|
||||
misinterpreted during a drag and drop operation.
|
||||
@@ -25,8 +34,8 @@ Unreleased` header.
|
||||
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||
- On X11, fix `Xft.dpi` detection from Xresources.
|
||||
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
|
||||
- On Web, remove queuing fullscreen request in absence of transient activation.
|
||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
||||
- On Wayland, fix resize being sent on focus change.
|
||||
- On Windows, fix `set_ime_cursor_area`.
|
||||
|
||||
# 0.29.4
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
|
||||
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
|
||||
38
Cargo.toml
38
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.4"
|
||||
version = "0.29.5"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -10,7 +10,7 @@ readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.65.0"
|
||||
rust-version = "1.70.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
@@ -43,7 +43,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", "xim"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
@@ -86,13 +86,13 @@ ndk-sys = "0.5.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.4.1"
|
||||
objc2 = "0.5.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.23.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.icrate]
|
||||
version = "0.0.4"
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
@@ -105,10 +105,31 @@ features = [
|
||||
"Foundation_NSProcessInfo",
|
||||
"Foundation_NSThread",
|
||||
"Foundation_NSNumber",
|
||||
"AppKit",
|
||||
"AppKit_NSAppearance",
|
||||
"AppKit_NSApplication",
|
||||
"AppKit_NSBitmapImageRep",
|
||||
"AppKit_NSButton",
|
||||
"AppKit_NSColor",
|
||||
"AppKit_NSControl",
|
||||
"AppKit_NSCursor",
|
||||
"AppKit_NSEvent",
|
||||
"AppKit_NSGraphicsContext",
|
||||
"AppKit_NSImage",
|
||||
"AppKit_NSImageRep",
|
||||
"AppKit_NSMenu",
|
||||
"AppKit_NSMenuItem",
|
||||
"AppKit_NSPasteboard",
|
||||
"AppKit_NSResponder",
|
||||
"AppKit_NSScreen",
|
||||
"AppKit_NSTextInputContext",
|
||||
"AppKit_NSView",
|
||||
"AppKit_NSWindow",
|
||||
"AppKit_NSWindowTabGroup",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.icrate]
|
||||
version = "0.0.4"
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
"dispatch",
|
||||
"Foundation",
|
||||
@@ -167,7 +188,6 @@ wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = tr
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xim = { version = "0.3.0", features = ["x11rb-client", "client"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
@@ -192,6 +212,7 @@ features = [
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'HtmlImageElement',
|
||||
'ImageBitmap',
|
||||
'ImageBitmapOptions',
|
||||
'ImageBitmapRenderingContext',
|
||||
@@ -232,6 +253,3 @@ web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||
members = [
|
||||
"run-wasm",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
xim = { git = "https://github.com/forkgull/xim-rs", branch = "x11rb-13" }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.4"
|
||||
winit = "0.29.5"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -42,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
|
||||
the MSRV will be accompanied by a minor version bump.
|
||||
|
||||
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||
@@ -156,7 +156,7 @@ For more details, refer to these `android-activity` [example applications](https
|
||||
|
||||
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.29.4", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.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 logging as above).
|
||||
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
dpi::{LogicalPosition, LogicalSize, Position},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
raw_window_handle::HasRawWindowHandle,
|
||||
raw_window_handle::HasWindowHandle,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
@@ -26,14 +26,13 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event_loop: &EventLoopWindowTarget<()>,
|
||||
windows: &mut HashMap<WindowId, Window>,
|
||||
) {
|
||||
let parent = parent.raw_window_handle().unwrap();
|
||||
let parent = parent.window_handle().unwrap();
|
||||
let mut builder = WindowBuilder::new()
|
||||
.with_title("child window")
|
||||
.with_inner_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.
|
||||
builder = unsafe { builder.with_parent_window(Some(parent)) };
|
||||
builder = builder.with_parent_window(Some(parent));
|
||||
let child_window = builder.build(event_loop).unwrap();
|
||||
|
||||
let id = child_window.id();
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
keyboard::Key,
|
||||
window::{CustomCursor, WindowBuilder},
|
||||
};
|
||||
|
||||
fn decode_cursor(bytes: &[u8]) -> CustomCursor {
|
||||
fn decode_cursor<T>(bytes: &[u8], window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
|
||||
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
||||
let samples = img.into_flat_samples();
|
||||
let (_, w, h) = samples.extents();
|
||||
let (w, h) = (w as u16, h as u16);
|
||||
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
|
||||
let builder = CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap();
|
||||
|
||||
builder.build(window_target)
|
||||
}
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
@@ -43,8 +45,8 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
let mut cursor_visible = true;
|
||||
|
||||
let custom_cursors = [
|
||||
decode_cursor(include_bytes!("data/cross.png")),
|
||||
decode_cursor(include_bytes!("data/cross2.png")),
|
||||
decode_cursor(include_bytes!("data/cross.png"), &event_loop),
|
||||
decode_cursor(include_bytes!("data/cross2.png"), &event_loop),
|
||||
];
|
||||
|
||||
event_loop.run(move |event, _elwt| match event {
|
||||
|
||||
122
src/cursor.rs
122
src/cursor.rs
@@ -1,7 +1,10 @@
|
||||
use core::fmt;
|
||||
use std::{error::Error, sync::Arc};
|
||||
use std::hash::Hasher;
|
||||
use std::sync::Arc;
|
||||
use std::{error::Error, hash::Hash};
|
||||
|
||||
use crate::platform_impl::PlatformCustomCursor;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::{self, PlatformCustomCursor, PlatformCustomCursorBuilder};
|
||||
|
||||
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
|
||||
pub const MAX_CURSOR_SIZE: u16 = 2048;
|
||||
@@ -16,23 +19,37 @@ const PIXEL_SIZE: usize = 4;
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use winit::window::CustomCursor;
|
||||
/// ```no_run
|
||||
/// use winit::{
|
||||
/// event::{Event, WindowEvent},
|
||||
/// event_loop::{ControlFlow, EventLoop},
|
||||
/// window::{CustomCursor, Window},
|
||||
/// };
|
||||
///
|
||||
/// let mut event_loop = EventLoop::new().unwrap();
|
||||
///
|
||||
/// let w = 10;
|
||||
/// let h = 10;
|
||||
/// let rgba = vec![255; (w * h * 4) as usize];
|
||||
/// let custom_cursor = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
||||
///
|
||||
/// #[cfg(not(target_family = "wasm"))]
|
||||
/// let builder = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
||||
///
|
||||
/// #[cfg(target_family = "wasm")]
|
||||
/// let custom_cursor_url = {
|
||||
/// let builder = {
|
||||
/// use winit::platform::web::CustomCursorExtWebSys;
|
||||
/// CustomCursor::from_url("http://localhost:3000/cursor.png", 0, 0).unwrap()
|
||||
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
|
||||
/// };
|
||||
///
|
||||
/// let custom_cursor = builder.build(&event_loop);
|
||||
///
|
||||
/// let window = Window::new(&event_loop).unwrap();
|
||||
/// window.set_custom_cursor(&custom_cursor);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CustomCursor {
|
||||
pub(crate) inner: Arc<PlatformCustomCursor>,
|
||||
/// Platforms should make sure this is cheap to clone.
|
||||
pub(crate) inner: PlatformCustomCursor,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
@@ -48,20 +65,35 @@ impl CustomCursor {
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
Ok(Self {
|
||||
inner: PlatformCustomCursor::from_rgba(
|
||||
) -> Result<CustomCursorBuilder, BadImage> {
|
||||
Ok(CustomCursorBuilder {
|
||||
inner: PlatformCustomCursorBuilder::from_rgba(
|
||||
rgba.into(),
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
)?
|
||||
.into(),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a [`CustomCursor`].
|
||||
///
|
||||
/// See [`CustomCursor`] for more details.
|
||||
#[derive(Debug)]
|
||||
pub struct CustomCursorBuilder {
|
||||
pub(crate) inner: PlatformCustomCursorBuilder,
|
||||
}
|
||||
|
||||
impl CustomCursorBuilder {
|
||||
pub fn build<T>(self, window_target: &EventLoopWindowTarget<T>) -> CustomCursor {
|
||||
CustomCursor {
|
||||
inner: PlatformCustomCursor::build(self.inner, &window_target.p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BadImage {
|
||||
@@ -120,9 +152,54 @@ impl fmt::Display for BadImage {
|
||||
|
||||
impl Error for BadImage {}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursor` if they need to only work with images.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CursorImage {
|
||||
/// Platforms export this directly as `PlatformCustomCursorBuilder` if they need to only work with images.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OnlyCursorImageBuilder(pub(crate) CursorImage);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OnlyCursorImageBuilder {
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
|
||||
|
||||
impl Hash for OnlyCursorImage {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OnlyCursorImage {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OnlyCursorImage {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OnlyCursorImage {
|
||||
fn build<T>(
|
||||
builder: OnlyCursorImageBuilder,
|
||||
_: &platform_impl::EventLoopWindowTarget<T>,
|
||||
) -> Self {
|
||||
Self(Arc::new(builder.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct CursorImage {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u16,
|
||||
pub(crate) height: u16,
|
||||
@@ -130,9 +207,8 @@ pub struct CursorImage {
|
||||
pub(crate) hotspot_y: u16,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl CursorImage {
|
||||
pub fn from_rgba(
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
@@ -180,12 +256,12 @@ impl CursorImage {
|
||||
}
|
||||
|
||||
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct NoCustomCursor;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NoCustomCursor {
|
||||
pub fn from_rgba(
|
||||
pub(crate) fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
@@ -195,4 +271,8 @@ impl NoCustomCursor {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn build<T>(self, _: &platform_impl::EventLoopWindowTarget<T>) -> NoCustomCursor {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::Id;
|
||||
|
||||
use crate::{
|
||||
@@ -365,7 +366,9 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
}
|
||||
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
|
||||
// SAFETY: We only use the marker to get a pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
self.inner.ns_screen(mtm).map(|s| Id::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,19 +155,19 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// - **Windows**: The implementation will use `PeekMessage` when checking for
|
||||
/// window messages to avoid blocking your external event loop.
|
||||
///
|
||||
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
|
||||
/// - **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.
|
||||
///
|
||||
/// 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 `NSApp` which aren't accessible to application
|
||||
/// developers)
|
||||
/// implementation details for `NSApplication` which aren't accessible to
|
||||
/// application developers)
|
||||
///
|
||||
/// It's likely this will be less efficient than polling on other OSs and
|
||||
/// it also means the `NSApp` is stopped while outside of the Winit
|
||||
/// 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 `NSApp` is global state.
|
||||
/// because the `NSApplication` is global state.
|
||||
///
|
||||
/// If you render outside of Winit you are likely to see window resizing artifacts
|
||||
/// since MacOS expects applications to render synchronously during any `drawRect`
|
||||
|
||||
@@ -27,11 +27,12 @@
|
||||
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::cursor::CustomCursorBuilder;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::PlatformCustomCursor;
|
||||
use crate::platform_impl::PlatformCustomCursorBuilder;
|
||||
use crate::window::CustomCursor;
|
||||
use crate::window::{Window, WindowBuilder};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
@@ -209,18 +210,17 @@ pub trait CustomCursorExtWebSys {
|
||||
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
|
||||
///
|
||||
/// [PNG]: https://en.wikipedia.org/wiki/PNG
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self;
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder;
|
||||
}
|
||||
|
||||
impl CustomCursorExtWebSys for CustomCursor {
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self {
|
||||
Self {
|
||||
inner: PlatformCustomCursor::Url {
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorBuilder {
|
||||
CustomCursorBuilder {
|
||||
inner: PlatformCustomCursorBuilder::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
}
|
||||
.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ use android_activity::{
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
event::{self, Force, InnerSizeWriter, StartCause},
|
||||
@@ -752,6 +751,18 @@ impl DeviceId {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable.
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Window {
|
||||
app: AndroidApp,
|
||||
redraw_requester: RedrawRequester,
|
||||
@@ -907,7 +918,7 @@ impl Window {
|
||||
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub fn set_custom_cursor(&self, _: CustomCursor) {}
|
||||
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
|
||||
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
@@ -1035,6 +1046,7 @@ impl Display for OsError {
|
||||
}
|
||||
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -73,11 +73,12 @@ pub(crate) use self::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
window::{OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
use self::uikit::UIScreen;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{
|
||||
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
|
||||
use objc2::mutability::IsRetainable;
|
||||
use objc2::rc::Id;
|
||||
use objc2::Message;
|
||||
|
||||
use super::uikit::{UIScreen, UIScreenMode};
|
||||
use crate::{
|
||||
@@ -20,16 +21,15 @@ use crate::{
|
||||
#[derive(Debug)]
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
|
||||
|
||||
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
|
||||
)
|
||||
Self(MainThreadMarker::run_on_main(|mtm| {
|
||||
MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> 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() };
|
||||
@@ -37,7 +37,7 @@ impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> 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() };
|
||||
@@ -45,7 +45,7 @@ impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct VideoMode {
|
||||
@@ -100,11 +100,9 @@ pub struct MonitorHandle {
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ui_screen: self
|
||||
.ui_screen
|
||||
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
|
||||
}
|
||||
MainThreadMarker::run_on_main(|mtm| Self {
|
||||
ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,16 +166,16 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<String> {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
let main = UIScreen::main(mtm);
|
||||
if *ui_screen == main {
|
||||
if *self.ui_screen(mtm) == main {
|
||||
Some("Primary".to_string())
|
||||
} else if *ui_screen == main.mirroredScreen() {
|
||||
} else if *self.ui_screen(mtm) == main.mirroredScreen() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == &**ui_screen)
|
||||
.position(|rhs| rhs == &**self.ui_screen(mtm))
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
})
|
||||
@@ -186,31 +184,32 @@ impl MonitorHandle {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
let bounds = self
|
||||
.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
|
||||
.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
let bounds = self
|
||||
.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
|
||||
.get_on_main(|ui_screen| ui_screen.nativeBounds());
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.ui_screen
|
||||
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
|
||||
.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
Some(
|
||||
self.ui_screen
|
||||
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
|
||||
.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
let ui_screen = self.ui_screen(mtm);
|
||||
// Use Ord impl of RootVideoMode
|
||||
|
||||
let modes: BTreeSet<_> = ui_screen
|
||||
@@ -230,8 +229,12 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
self.ui_screen.get_on_main(|ui_screen, mtm| {
|
||||
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
VideoMode::new(
|
||||
self.ui_screen(mtm).clone(),
|
||||
self.ui_screen(mtm).preferredMode().unwrap(),
|
||||
mtm,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::Cell;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyClass;
|
||||
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
use objc2::{
|
||||
declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::app_state::{self, EventWrapper};
|
||||
use super::uikit::{
|
||||
@@ -37,6 +37,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitUIView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(drawRect:)]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
@@ -274,11 +276,7 @@ pub struct ViewControllerState {
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
pub(crate) struct WinitViewController {
|
||||
state: IvarDrop<Box<ViewControllerState>, "_state">,
|
||||
}
|
||||
|
||||
mod view_controller_ivars;
|
||||
pub(crate) struct WinitViewController;
|
||||
|
||||
unsafe impl ClassType for WinitViewController {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
@@ -287,28 +285,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitUIViewController";
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
#[method(init)]
|
||||
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = msg_send![super(this), init];
|
||||
this.map(|this| {
|
||||
// These are set in WinitViewController::new, it's just to set them
|
||||
// to _something_.
|
||||
Ivar::write(
|
||||
&mut this.state,
|
||||
Box::new(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(
|
||||
UIRectEdge::NONE,
|
||||
),
|
||||
}),
|
||||
);
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitViewController {
|
||||
type Ivars = ViewControllerState;
|
||||
}
|
||||
|
||||
unsafe impl WinitViewController {
|
||||
@@ -319,27 +297,27 @@ declare_class!(
|
||||
|
||||
#[method(prefersStatusBarHidden)]
|
||||
fn prefers_status_bar_hidden(&self) -> bool {
|
||||
self.state.prefers_status_bar_hidden.get()
|
||||
self.ivars().prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[method(preferredStatusBarStyle)]
|
||||
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
|
||||
self.state.preferred_status_bar_style.get()
|
||||
self.ivars().preferred_status_bar_style.get()
|
||||
}
|
||||
|
||||
#[method(prefersHomeIndicatorAutoHidden)]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
self.state.prefers_home_indicator_auto_hidden.get()
|
||||
self.ivars().prefers_home_indicator_auto_hidden.get()
|
||||
}
|
||||
|
||||
#[method(supportedInterfaceOrientations)]
|
||||
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
|
||||
self.state.supported_orientations.get()
|
||||
self.ivars().supported_orientations.get()
|
||||
}
|
||||
|
||||
#[method(preferredScreenEdgesDeferringSystemGestures)]
|
||||
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
|
||||
self.state
|
||||
self.ivars()
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.get()
|
||||
}
|
||||
@@ -348,17 +326,17 @@ declare_class!(
|
||||
|
||||
impl WinitViewController {
|
||||
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
|
||||
self.state.prefers_status_bar_hidden.set(val);
|
||||
self.ivars().prefers_status_bar_hidden.set(val);
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
|
||||
self.state.preferred_status_bar_style.set(val);
|
||||
self.ivars().preferred_status_bar_style.set(val);
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
|
||||
self.state.prefers_home_indicator_auto_hidden.set(val);
|
||||
self.ivars().prefers_home_indicator_auto_hidden.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
@@ -368,7 +346,7 @@ impl WinitViewController {
|
||||
}
|
||||
|
||||
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
|
||||
self.state
|
||||
self.ivars()
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.set(val);
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
@@ -401,7 +379,7 @@ impl WinitViewController {
|
||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
||||
}
|
||||
};
|
||||
self.state.supported_orientations.set(mask);
|
||||
self.ivars().supported_orientations.set(mask);
|
||||
UIViewController::attemptRotationToDeviceOrientation();
|
||||
}
|
||||
|
||||
@@ -411,7 +389,15 @@ impl WinitViewController {
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: &UIView,
|
||||
) -> Id<Self> {
|
||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
|
||||
// These are set properly below, we just to set them to something in the meantime.
|
||||
let this = Self::alloc().set_ivars(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
|
||||
|
||||
@@ -446,6 +432,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitUIWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitUIWindow {}
|
||||
|
||||
unsafe impl WinitUIWindow {
|
||||
#[method(becomeKeyWindow)]
|
||||
fn become_key_window(&self) {
|
||||
@@ -518,6 +506,8 @@ declare_class!(
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitApplicationDelegate {}
|
||||
|
||||
// UIApplicationDelegate protocol
|
||||
unsafe impl WinitApplicationDelegate {
|
||||
#[method(application:didFinishLaunchingWithOptions:)]
|
||||
|
||||
@@ -11,14 +11,13 @@ use super::app_state::EventWrapper;
|
||||
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
|
||||
use super::view::{WinitUIWindow, WinitView, WinitViewController};
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, WindowEvent},
|
||||
icon::Icon,
|
||||
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformCustomCursor,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -26,6 +25,19 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable (would work similar to macOS).
|
||||
warn!("parent windows are unsupported on iOS");
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Inner {
|
||||
window: Id<WinitUIWindow>,
|
||||
view_controller: Id<WinitViewController>,
|
||||
@@ -178,7 +190,7 @@ impl Inner {
|
||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_custom_cursor(&self, _: CustomCursor) {
|
||||
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {
|
||||
debug!("`Window::set_custom_cursor` ignored on iOS")
|
||||
}
|
||||
|
||||
@@ -360,14 +372,14 @@ impl Inner {
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let mut window_handle = rwh_06::UiKitWindowHandle::new({
|
||||
let ui_view = Id::as_ptr(&self.view) as _;
|
||||
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
|
||||
});
|
||||
window_handle.ui_view_controller =
|
||||
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
|
||||
Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
|
||||
rwh_06::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
@@ -521,7 +533,19 @@ impl Window {
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
|
||||
self.inner.get_on_main(|inner, _mtm| f(inner))
|
||||
self.inner.get_on_main(|inner| f(inner))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_window_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
if let Some(mtm) = MainThreadMarker::new() {
|
||||
Ok(self.inner.get(mtm).raw_window_handle_rwh_06())
|
||||
} else {
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform::x11::XlibErrorHook;
|
||||
use crate::{
|
||||
@@ -41,7 +40,8 @@ pub use x11::XNotSupported;
|
||||
#[cfg(x11_platform)]
|
||||
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
|
||||
|
||||
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::OnlyCursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
@@ -141,6 +141,43 @@ impl fmt::Display for OsError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum OwnedWindowHandle {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11rb::protocol::xproto::Window),
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland,
|
||||
}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// TODO: Do we need to do something extra to extend the lifetime of
|
||||
// the window lives beyond the passed-in handle?
|
||||
match handle.as_raw() {
|
||||
#[cfg(x11_platform)]
|
||||
rwh_06::RawWindowHandle::Xlib(handle) => {
|
||||
Self::X(handle.window as x11rb::protocol::xproto::Window)
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
rwh_06::RawWindowHandle::Xcb(handle) => Self::X(handle.window.get()),
|
||||
#[cfg(wayland_platform)]
|
||||
rwh_06::RawWindowHandle::Wayland(_handle) => {
|
||||
// Wayland does not currently support parent windows, but it
|
||||
// could support owned handles.
|
||||
Self::Wayland
|
||||
}
|
||||
#[cfg(not(x11_platform))]
|
||||
handle => panic!("invalid window handle {handle:?} on Wayland"),
|
||||
#[cfg(not(wayland_platform))]
|
||||
handle => panic!("invalid window handle {handle:?} on X11"),
|
||||
#[cfg(all(x11_platform, wayland_platform))]
|
||||
handle => panic!("invalid window handle {handle:?} on X11 or Wayland"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Window {
|
||||
#[cfg(x11_platform)]
|
||||
X(x11::Window),
|
||||
@@ -427,7 +464,7 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
|
||||
}
|
||||
|
||||
|
||||
@@ -307,7 +307,11 @@ impl WindowHandler for WinitState {
|
||||
&mut self.events_sink,
|
||||
);
|
||||
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
// NOTE: Only update when the value is `Some` to not override consequent configures with
|
||||
// the same sizes.
|
||||
if new_size.is_some() {
|
||||
self.window_compositor_updates[pos].size = new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ pub struct CustomCursor {
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
pub fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
|
||||
pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
|
||||
let (buffer, canvas) = pool
|
||||
.create_buffer(
|
||||
image.width as i32,
|
||||
|
||||
@@ -15,14 +15,13 @@ use sctk::shell::xdg::window::Window as SctkWindow;
|
||||
use sctk::shell::xdg::window::WindowDecorations;
|
||||
use sctk::shell::WaylandSurface;
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::event_loop::AsyncRequestSerial;
|
||||
use crate::platform_impl::{
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
|
||||
PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
};
|
||||
use crate::window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -508,8 +507,11 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
self.window_state.lock().unwrap().set_custom_cursor(cursor);
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_custom_cursor(&cursor.0);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -28,7 +28,7 @@ use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::event::WindowEvent;
|
||||
@@ -262,7 +262,7 @@ impl WindowState {
|
||||
shm: &Shm,
|
||||
subcompositor: &Option<Arc<SubcompositorState>>,
|
||||
event_sink: &mut EventSink,
|
||||
) -> LogicalSize<u32> {
|
||||
) -> Option<LogicalSize<u32>> {
|
||||
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
|
||||
// should be delivered before the first configure, thus apply it to
|
||||
// properly scale the physical sizes provided by the users.
|
||||
@@ -325,14 +325,9 @@ impl WindowState {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
(
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into(),
|
||||
false,
|
||||
)
|
||||
let width = width.map(|w| w.get()).unwrap_or(1);
|
||||
let height = height.map(|h| h.get()).unwrap_or(1);
|
||||
((width, height).into(), false)
|
||||
}
|
||||
(_, _) if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
@@ -358,13 +353,31 @@ impl WindowState {
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
// XXX Set the configure before doing a resize.
|
||||
let new_state = configure.state;
|
||||
let old_state = self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.state);
|
||||
|
||||
let state_change_requires_resize = old_state
|
||||
.map(|old_state| {
|
||||
!old_state
|
||||
.symmetric_difference(new_state)
|
||||
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
|
||||
.is_empty()
|
||||
})
|
||||
// NOTE: `None` is present for the initial configure, thus we must always resize.
|
||||
.unwrap_or(true);
|
||||
|
||||
// NOTE: Set the configure before doing a resize, since we query it during it.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
|
||||
new_size
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
self.resize(new_size);
|
||||
Some(new_size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
@@ -713,10 +726,10 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the custom cursor icon.
|
||||
pub fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
|
||||
pub(crate) fn set_custom_cursor(&mut self, cursor: &CursorImage) {
|
||||
let cursor = {
|
||||
let mut pool = self.custom_cursor_pool.lock().unwrap();
|
||||
CustomCursor::new(&mut pool, &cursor.inner)
|
||||
CustomCursor::new(&mut pool, cursor)
|
||||
};
|
||||
|
||||
if self.cursor_visible {
|
||||
@@ -970,7 +983,7 @@ impl WindowState {
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
@@ -1001,7 +1014,7 @@ impl WindowState {
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// XXX when fractional scaling is not used update the buffer scale.
|
||||
// NOTE: When fractional scaling is not used update the buffer scale.
|
||||
if self.fractional_scale.is_none() {
|
||||
let _ = self.window.set_buffer_scale(self.scale_factor as _);
|
||||
}
|
||||
@@ -1149,7 +1162,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
|
||||
match theme {
|
||||
|
||||
@@ -40,6 +40,7 @@ atom_manager! {
|
||||
WM_DELETE_WINDOW,
|
||||
WM_PROTOCOLS,
|
||||
WM_STATE,
|
||||
XIM_SERVERS,
|
||||
|
||||
// Assorted ICCCM Atoms
|
||||
_NET_WM_ICON,
|
||||
|
||||
@@ -44,7 +44,7 @@ impl From<io::Error> for DndDataParseError {
|
||||
pub(crate) struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
// Populated by XdndEnter event handler
|
||||
pub version: Option<u32>,
|
||||
pub version: Option<c_long>,
|
||||
pub type_list: Option<Vec<xproto::Atom>>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub source_window: Option<xproto::Window>,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,862 +0,0 @@
|
||||
//! IME handler, using the xim-rs crate.
|
||||
|
||||
use super::{X11Error, X11rbConnection, XConnection};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::xproto::Window;
|
||||
use x11rb::protocol::Event;
|
||||
|
||||
use xim::x11rb::{HasConnection, X11rbClient};
|
||||
use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl HasConnection for XConnection {
|
||||
type Connection = X11rbConnection;
|
||||
|
||||
fn conn(&self) -> &Self::Connection {
|
||||
self.xcb_connection()
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of the IME events that can occur.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, Option<usize>),
|
||||
Commit(String),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Invalid states that an IME client can enter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InvalidImeState {
|
||||
/// The IME has no style information.
|
||||
NoStyle,
|
||||
|
||||
/// No windows in the pending window queue.
|
||||
NoWindows,
|
||||
|
||||
/// Invalid input context.
|
||||
InvalidIc(u16),
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidImeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InvalidImeState::NoStyle => write!(f, "IME has no style information"),
|
||||
InvalidImeState::NoWindows => {
|
||||
write!(f, "IME has no windows in the pending window queue")
|
||||
}
|
||||
InvalidImeState::InvalidIc(ic) => write!(f, "IME has invalid input context {}", ic),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME spot position for given `window_id`.
|
||||
Position(Window, i16, i16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(Window, bool),
|
||||
}
|
||||
|
||||
/// The IME data for winit.
|
||||
pub(super) struct ImeData {
|
||||
/// The XIM client manager.
|
||||
client: X11rbClient<Arc<XConnection>>,
|
||||
|
||||
/// Relevant IME data.
|
||||
handler: ImeHandler,
|
||||
}
|
||||
|
||||
/// Inner IME handler.
|
||||
struct ImeHandler {
|
||||
/// Handle to the event queue.
|
||||
event_queue: Rc<RefCell<VecDeque<Event>>>,
|
||||
|
||||
/// Whether IME is currently disconnected.
|
||||
disconnected: bool,
|
||||
|
||||
/// IME events waiting to be read.
|
||||
ime_events: VecDeque<(Window, ImeEvent)>,
|
||||
|
||||
/// Windows waiting to be assigned an input context.
|
||||
pending_windows: VecDeque<WindowData>,
|
||||
|
||||
/// Currently registered input styles.
|
||||
styles: Option<(Style, Style)>,
|
||||
|
||||
/// The input method for the display, if there is one.
|
||||
input_method: Option<u16>,
|
||||
|
||||
/// Hash map between input contexts and their associated data.
|
||||
input_contexts: HashMap<u16, IcData>,
|
||||
|
||||
/// Map between window IDs and their associated input contexts.
|
||||
window_contexts: HashMap<Window, u16>,
|
||||
}
|
||||
|
||||
/// Data relevant for each input context.
|
||||
struct IcData {
|
||||
/// Data associated with the window.
|
||||
window: WindowData,
|
||||
|
||||
/// Newly set point for the context.
|
||||
new_spot: Option<Point>,
|
||||
|
||||
/// The current preedit string.
|
||||
///
|
||||
/// We use a `Vec<char>` here instead of a string because the IME indices operate on chars,
|
||||
/// not bytes.
|
||||
text: Vec<char>,
|
||||
|
||||
/// The current cursor position in the preedit string.
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
/// Windows waiting for IME events.
|
||||
struct WindowData {
|
||||
/// The window ID.
|
||||
id: Window,
|
||||
|
||||
/// The style of the window.
|
||||
style: Style,
|
||||
|
||||
/// Current "spot" for the context.
|
||||
spot: Point,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Style {
|
||||
Preedit,
|
||||
Nothing,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ImeData {
|
||||
/// Creates the IME data for the display.
|
||||
pub(super) fn new(
|
||||
conn: &Arc<XConnection>,
|
||||
screen: usize,
|
||||
event_queue: &Rc<RefCell<VecDeque<Event>>>,
|
||||
) -> Result<Self, X11Error> {
|
||||
// IM servers to try, in order:
|
||||
// - None, which defaults to the environment variable `XMODIFIERS` in xim's impl.
|
||||
// - "local", which is the default for most IMEs.
|
||||
// - empty string, which may work in some cases.
|
||||
let input_methods = [None, Some("local"), Some("")];
|
||||
let mut last_error = X11Error::Ime(ClientError::NoXimServer);
|
||||
|
||||
for im in input_methods {
|
||||
// Try to initialize a client here.
|
||||
match X11rbClient::init(conn.clone(), screen, im) {
|
||||
Ok(client) => {
|
||||
return Ok(Self {
|
||||
client,
|
||||
handler: ImeHandler {
|
||||
event_queue: event_queue.clone(),
|
||||
disconnected: true,
|
||||
ime_events: VecDeque::new(),
|
||||
pending_windows: VecDeque::new(),
|
||||
styles: None,
|
||||
input_method: None,
|
||||
input_contexts: HashMap::new(),
|
||||
window_contexts: HashMap::new(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
log::warn!("Failed to create XIM client for {:?}: {err}", ImData(im));
|
||||
last_error = X11Error::Ime(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error)
|
||||
}
|
||||
|
||||
/// Filter an event.
|
||||
pub(super) fn filter_event(&mut self, event: &Event) -> Result<bool, X11Error> {
|
||||
self.client
|
||||
.filter_event(event, &mut self.handler)
|
||||
.map_err(X11Error::Ime)
|
||||
}
|
||||
|
||||
/// Connection to the X server.
|
||||
fn conn(&self) -> &X11rbConnection {
|
||||
self.client.conn()
|
||||
}
|
||||
|
||||
/// Get an IME event.
|
||||
pub(super) fn next_ime_event(&mut self) -> Option<(Window, ImeEvent)> {
|
||||
self.handler.ime_events.pop_front()
|
||||
}
|
||||
|
||||
/// Create a new IME context for the provided window.
|
||||
pub(super) fn create_context(
|
||||
&mut self,
|
||||
window: Window,
|
||||
with_preedit: bool,
|
||||
spot: Option<Point>,
|
||||
) -> Result<bool, X11Error> {
|
||||
// If we aren't connected, nothing can be done.
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
let method = match self.handler.input_method {
|
||||
Some(im) => im,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// Get the current style.
|
||||
let style = match (self.handler.styles, with_preedit) {
|
||||
(None, _) => return Err(X11Error::InvalidImeState(InvalidImeState::NoStyle)),
|
||||
(Some((preedit_style, _)), true) => preedit_style,
|
||||
(Some((_, none_style)), false) => none_style,
|
||||
};
|
||||
|
||||
// Setup IC attributes.
|
||||
let ic_attributes = {
|
||||
let mut ic_attributes = self
|
||||
.client
|
||||
.build_ic_attributes()
|
||||
.push(AttributeName::ClientWindow, window);
|
||||
|
||||
let ic_style = match style {
|
||||
Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING,
|
||||
Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING,
|
||||
Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE,
|
||||
};
|
||||
|
||||
if let Some(spot) = spot.clone() {
|
||||
ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot);
|
||||
}
|
||||
|
||||
ic_attributes
|
||||
.push(AttributeName::InputStyle, ic_style)
|
||||
.build()
|
||||
};
|
||||
|
||||
// Create the IC.
|
||||
self.client.create_ic(method, ic_attributes)?;
|
||||
|
||||
// Add to the waiting window list.
|
||||
self.handler.pending_windows.push_back(WindowData {
|
||||
id: window,
|
||||
style,
|
||||
spot: spot.unwrap_or(Point { x: 0, y: 0 }),
|
||||
});
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Remove an IME context for a window.
|
||||
pub(super) fn remove_context(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
let method = match self.handler.input_method {
|
||||
Some(im) => im,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// Remove the pending window if it's still pending.
|
||||
let mut removed = false;
|
||||
self.handler.pending_windows.retain(|pending| {
|
||||
if pending.id == window {
|
||||
removed = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if removed {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Remove the IC if it's already created.
|
||||
if let Some(ic) = self.handler.window_contexts.remove(&window) {
|
||||
self.handler.input_contexts.remove(&ic);
|
||||
|
||||
// Destroy the IC.
|
||||
self.client.destroy_ic(method, ic)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Focus an IME context.
|
||||
pub(super) fn focus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
self.client.set_focus(method, ic)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Unfocus an IME context.
|
||||
pub(super) fn unfocus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
self.client.unset_focus(method, ic)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Set the spot for an IME context.
|
||||
pub(super) fn set_spot(&mut self, window: Window, x: i16, y: i16) -> Result<(), X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
// If the IC is not available, or if the spot is the same, then we don't need to update.
|
||||
let ic_data = match self.handler.input_contexts.get_mut(&ic) {
|
||||
Some(ic_data) => ic_data,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let new_point = Point { x, y };
|
||||
if !matches!(ic_data.window.style, Style::None) || ic_data.window.spot == new_point {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let new_attrs = self
|
||||
.client
|
||||
.build_ic_attributes()
|
||||
.push(AttributeName::SpotLocation, new_point.clone())
|
||||
.build();
|
||||
self.client.set_ic_values(method, ic, new_attrs)?;
|
||||
|
||||
// Indicate that we have a new spot.
|
||||
debug_assert!(ic_data.new_spot.is_none());
|
||||
ic_data.new_spot = Some(new_point);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_ime_allowed(
|
||||
&mut self,
|
||||
window: Window,
|
||||
allowed: bool,
|
||||
) -> Result<(), X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the client info.
|
||||
let _ = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
let mut spot = None;
|
||||
|
||||
// See if we need to update the allowed state.
|
||||
if let Some(ic_data) = self.handler.input_contexts.get(&ic) {
|
||||
spot = Some(ic_data.window.spot.clone());
|
||||
if matches!(ic_data.window.style, Style::None) != allowed {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete and re-install the IC.
|
||||
self.remove_context(window)?;
|
||||
self.create_context(window, allowed, spot)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for the input method to be set.
|
||||
fn wait_for_method(&mut self) -> Result<u16, X11Error> {
|
||||
loop {
|
||||
if let Some(im) = self.handler.input_method {
|
||||
return Ok(im);
|
||||
}
|
||||
|
||||
// Wait and hope the input method is set.
|
||||
self.block_for_ime()?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for an input context to be set.
|
||||
fn wait_for_context(&mut self, window: Window) -> Result<Option<u16>, X11Error> {
|
||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
||||
return Ok(Some(*cid));
|
||||
}
|
||||
|
||||
// If the window isn't in our pending windows queue, there's no way for it to get an IC.
|
||||
if !self
|
||||
.handler
|
||||
.pending_windows
|
||||
.iter()
|
||||
.any(|WindowData { id, .. }| *id == window)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
loop {
|
||||
self.block_for_ime()?;
|
||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
||||
return Ok(Some(*cid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until we've acted on an IME event.
|
||||
fn block_for_ime(&mut self) -> Result<(), X11Error> {
|
||||
let mut last_event = self.conn().poll_for_event()?;
|
||||
|
||||
loop {
|
||||
if let Some(last_event) = last_event.as_ref() {
|
||||
if self.filter_event(last_event)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// This scope keeps track of the event queue handle.
|
||||
{
|
||||
// Check the event queue for events.
|
||||
let event_queue = self.handler.event_queue.clone();
|
||||
let mut event_queue = event_queue.borrow_mut();
|
||||
|
||||
// Check the event queue for events we can use.
|
||||
let mut found_event = false;
|
||||
let mut last_err = None;
|
||||
event_queue.retain(|event| match self.filter_event(event) {
|
||||
Ok(false) => {
|
||||
found_event = true;
|
||||
true
|
||||
}
|
||||
Ok(true) => false,
|
||||
Err(err) => {
|
||||
last_err = Some(err);
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
// Push our own event to the queue.
|
||||
if let Some(last_event) = last_event.take() {
|
||||
event_queue.push_back(last_event);
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
if let Some(err) = last_err {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// If we found an event, then we're done.
|
||||
if found_event {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Waiting for IME event");
|
||||
last_event = Some(self.conn().wait_for_event()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: xim::Client> ClientHandler<C> for ImeHandler {
|
||||
fn handle_connect(&mut self, client: &mut C) -> Result<(), ClientError> {
|
||||
// We have been connected, now request a new input method for our current locale.
|
||||
self.disconnected = false;
|
||||
client.open(&locale())
|
||||
}
|
||||
|
||||
fn handle_disconnect(&mut self) {
|
||||
// We are now disconnected.
|
||||
self.disconnected = true;
|
||||
}
|
||||
|
||||
fn handle_open(&mut self, client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
||||
// We now have an input method.
|
||||
debug_assert!(self.input_method.is_none());
|
||||
self.input_method = Some(input_method_id);
|
||||
|
||||
// Ask for the IM's attributes.
|
||||
client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle])
|
||||
}
|
||||
|
||||
fn handle_close(&mut self, _client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
||||
// No more input method.
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
self.input_method = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_im_values(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
mut attributes: xim::AHashMap<xim::AttributeName, Vec<u8>>,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the input styles.
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
let styles = {
|
||||
let style = attributes
|
||||
.remove(&AttributeName::QueryInputStyle)
|
||||
.expect("No query input style");
|
||||
let mut result = vec![0u32; style.len() / 4];
|
||||
|
||||
bytemuck::cast_slice_mut::<u32, u8>(&mut result).copy_from_slice(&style);
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
{
|
||||
// The styles that we're looking for.
|
||||
let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING;
|
||||
let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING;
|
||||
let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE;
|
||||
|
||||
for style in styles {
|
||||
let style = InputStyle::from_bits_truncate(style);
|
||||
|
||||
if style == lu_preedit_style {
|
||||
preedit_style = Some(Style::Preedit);
|
||||
} else if style == lu_nothing_style {
|
||||
preedit_style = Some(Style::Nothing);
|
||||
} else if style == lu_none_style {
|
||||
none_style = Some(Style::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (preedit_style, none_style) = match (preedit_style, none_style) {
|
||||
(None, None) => {
|
||||
log::error!("No supported input styles found");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
(Some(style), None) | (None, Some(style)) => (style, style),
|
||||
|
||||
(Some(preedit_style), Some(none_style)) => (preedit_style, none_style),
|
||||
};
|
||||
|
||||
self.styles = Some((preedit_style, none_style));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_create_ic(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the window that wanted the IC context.
|
||||
let window = self
|
||||
.pending_windows
|
||||
.pop_front()
|
||||
.ok_or_else(|| invalid_state(InvalidImeState::NoWindows))?;
|
||||
|
||||
// Create the IC data.
|
||||
let ic_data = IcData {
|
||||
window,
|
||||
new_spot: None,
|
||||
text: Vec::new(),
|
||||
cursor: 0,
|
||||
};
|
||||
|
||||
// Store the context.
|
||||
let (window, style) = (ic_data.window.id, ic_data.window.style);
|
||||
self.input_contexts.insert(input_context_id, ic_data);
|
||||
self.window_contexts.insert(window, input_context_id);
|
||||
|
||||
// Indicate our status.
|
||||
let event = if matches!(style, Style::Nothing) {
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
self.ime_events.push_back((window, event));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_destroy_ic(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
// This is already handled by the higher-level function.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_ic_values(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the IC data.
|
||||
let ic_data = self
|
||||
.input_contexts
|
||||
.get_mut(&input_context_id)
|
||||
.ok_or_else(|| invalid_state(InvalidImeState::InvalidIc(input_context_id)))?;
|
||||
|
||||
// Move up the new spot
|
||||
if let Some(spot) = ic_data.new_spot.take() {
|
||||
ic_data.window.spot = spot;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_start(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Start a pre-edit.
|
||||
ic_data.text.clear();
|
||||
ic_data.cursor = 0;
|
||||
|
||||
// Indicate the start.
|
||||
self.ime_events
|
||||
.push_back((ic_data.window.id, ImeEvent::Start));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_draw(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
caret: i32,
|
||||
chg_first: i32,
|
||||
chg_len: i32,
|
||||
_status: xim::PreeditDrawStatus,
|
||||
preedit_string: &str,
|
||||
_feedbacks: Vec<xim::Feedback>,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Set the cursor.
|
||||
ic_data.cursor = caret as usize;
|
||||
|
||||
// Figure out the range of text to change.
|
||||
let change_range = chg_first as usize..(chg_first + chg_len) as usize;
|
||||
|
||||
// If the range doesn't fit our current text, warn and return.
|
||||
if change_range.start > ic_data.text.len() || change_range.end > ic_data.text.len() {
|
||||
warn!(
|
||||
"Preedit draw range {}..{} doesn't fit text of length {}",
|
||||
change_range.start,
|
||||
change_range.end,
|
||||
ic_data.text.len()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Update the text in the changed range.
|
||||
{
|
||||
let text = &mut ic_data.text;
|
||||
let mut old_text_tail = text.split_off(change_range.end);
|
||||
|
||||
text.truncate(change_range.start);
|
||||
text.extend(preedit_string.chars());
|
||||
text.append(&mut old_text_tail);
|
||||
}
|
||||
|
||||
// Send the event.
|
||||
let cursor_byte_pos = calc_byte_position(&ic_data.text, ic_data.cursor);
|
||||
let event = ImeEvent::Update(ic_data.text.iter().collect(), Some(cursor_byte_pos));
|
||||
|
||||
self.ime_events.push_back((ic_data.window.id, event));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_caret(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
position: &mut i32,
|
||||
direction: xim::CaretDirection,
|
||||
_style: xim::CaretStyle,
|
||||
) -> Result<(), ClientError> {
|
||||
// We only care about absolute position.
|
||||
if matches!(direction, xim::CaretDirection::AbsolutePosition) {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
ic_data.cursor = *position as usize;
|
||||
|
||||
// Send the event
|
||||
let event =
|
||||
ImeEvent::Update(ic_data.text.iter().collect(), Some(*position as usize));
|
||||
self.ime_events.push_back((ic_data.window.id, event));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_done(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the client data.
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// We're done with a preedit.
|
||||
ic_data.text.clear();
|
||||
ic_data.cursor = 0;
|
||||
|
||||
// Send a message to the window.
|
||||
let window = ic_data.window.id;
|
||||
self.ime_events.push_back((window, ImeEvent::End));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_commit(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
text: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the client data.
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Send a message to the window.
|
||||
let window = ic_data.window.id;
|
||||
self.ime_events
|
||||
.push_back((window, ImeEvent::Commit(text.to_owned())));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_query_extension(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_extensions: &[xim::Extension],
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_forward_event(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
_flag: xim::ForwardEventFlag,
|
||||
_xev: C::XEvent,
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_event_mask(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
_forward_event_mask: u32,
|
||||
_synchronous_event_mask: u32,
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn invalid_state(state: InvalidImeState) -> ClientError {
|
||||
ClientError::Other(Box::new(X11Error::InvalidImeState(state)))
|
||||
}
|
||||
|
||||
struct ImData(Option<&'static str>);
|
||||
|
||||
impl fmt::Debug for ImData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
Some(name) => write!(f, "\"{}\"", name),
|
||||
None => write!(f, "default input method"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current locale.
|
||||
fn locale() -> String {
|
||||
use std::ffi::CStr;
|
||||
|
||||
const EN_US: &str = "en_US.UTF-8";
|
||||
|
||||
// Get the pointer to the current locale.
|
||||
let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) };
|
||||
|
||||
// If locale_ptr is null, just default to en_US.UTF-8.
|
||||
if locale_ptr.is_null() {
|
||||
return EN_US.to_owned();
|
||||
}
|
||||
|
||||
// Convert the pointer to a CStr.
|
||||
let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) };
|
||||
|
||||
// Convert the CStr to a String to prevent the result from getting clobbered.
|
||||
locale_cstr.to_str().unwrap_or(EN_US).to_owned()
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter().take(pos).map(|c| c.len_utf8()).sum()
|
||||
}
|
||||
215
src/platform_impl/linux/x11/ime/callbacks.rs
Normal file
215
src/platform_impl/linux/x11/ime/callbacks.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{
|
||||
context::{ImeContext, ImeContextCreationError},
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::PotentialInputMethods,
|
||||
};
|
||||
|
||||
pub(crate) unsafe fn xim_set_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
xim: ffi::XIM,
|
||||
field: *const c_char,
|
||||
callback: *mut ffi::XIMCallback,
|
||||
) -> Result<(), XError> {
|
||||
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
|
||||
// access that isn't type-checked.
|
||||
unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
// Set a callback for when an input method matching the current locale modifiers becomes
|
||||
// available. Note that this has nothing to do with what input methods are open or able to be
|
||||
// opened, and simply uses the modifiers that are set when the callback is set.
|
||||
// * This is called per locale modifier, not per input method opened with that locale modifier.
|
||||
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
|
||||
// input contexts would always silently fail to use the input method.
|
||||
pub(crate) unsafe fn set_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XRegisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn unset_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnregisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_destroy_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
inner: &ImeInner,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
xim_set_callback(
|
||||
xconn,
|
||||
im,
|
||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||
&inner.destroy_callback as *const _ as *mut _,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
// Boxed to prevent large error type
|
||||
MethodOpenFailed(Box<PotentialInputMethods>),
|
||||
ContextCreationFailed(ImeContextCreationError),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
}
|
||||
|
||||
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
|
||||
// includes replacing all existing input contexts and free'ing resources as necessary. This only
|
||||
// modifies existing state if all operations succeed.
|
||||
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
|
||||
let (new_im, is_fallback) = {
|
||||
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
|
||||
let is_fallback = new_im.is_fallback();
|
||||
(
|
||||
new_im.ok().ok_or_else(|| {
|
||||
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
|
||||
(*inner).potential_input_methods.clone()
|
||||
}))
|
||||
})?,
|
||||
is_fallback,
|
||||
)
|
||||
};
|
||||
|
||||
// It's important to always set a destroy callback, since there's otherwise potential for us
|
||||
// to try to use or free a resource that's already been destroyed on the server.
|
||||
{
|
||||
let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result
|
||||
}
|
||||
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
|
||||
|
||||
let mut new_contexts = HashMap::new();
|
||||
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
|
||||
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
||||
|
||||
// Check if the IME was allowed on that context.
|
||||
let is_allowed = old_context
|
||||
.as_ref()
|
||||
.map(|old_context| old_context.is_allowed())
|
||||
.unwrap_or_default();
|
||||
|
||||
// We can't use the style from the old context here, since it may change on reload, so
|
||||
// pick style from the new XIM based on the old state.
|
||||
let style = if is_allowed {
|
||||
new_im.preedit_style
|
||||
} else {
|
||||
new_im.none_style
|
||||
};
|
||||
|
||||
let new_context = {
|
||||
let result = unsafe {
|
||||
ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
style,
|
||||
*window,
|
||||
spot,
|
||||
(*inner).event_sender.clone(),
|
||||
)
|
||||
};
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result.map_err(ReplaceImError::ContextCreationFailed)?
|
||||
};
|
||||
new_contexts.insert(*window, Some(new_context));
|
||||
}
|
||||
|
||||
// If we've made it this far, everything succeeded.
|
||||
unsafe {
|
||||
let _ = (*inner).destroy_all_contexts_if_necessary();
|
||||
let _ = (*inner).close_im_if_necessary();
|
||||
(*inner).im = Some(new_im);
|
||||
(*inner).contexts = new_contexts;
|
||||
(*inner).is_destroyed = false;
|
||||
(*inner).is_fallback = is_fallback;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xim_instantiate_callback(
|
||||
_display: *mut ffi::Display,
|
||||
client_data: ffi::XPointer,
|
||||
// This field is unsupplied.
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
},
|
||||
Err(err) => unsafe {
|
||||
if (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to reopen input method: {err:?}");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is triggered when the input method is closed on the server end. When this
|
||||
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
|
||||
// free'd (attempting to do so causes our connection to freeze).
|
||||
pub unsafe extern "C" fn xim_destroy_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
// This field is unsupplied.
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
unsafe { (*inner).is_destroyed = true };
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
if unsafe { !(*inner).is_fallback } {
|
||||
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
|
||||
// Attempt to open fallback input method.
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe { (*inner).is_fallback = true },
|
||||
Err(err) => {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to open fallback input method: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
385
src/platform_impl/linux/x11/ime/context.rs
Normal file
385
src/platform_impl/linux/x11/ime/context.rs
Normal file
@@ -0,0 +1,385 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_short;
|
||||
use std::sync::Arc;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ImeContextCreationError {
|
||||
/// Got the error from Xlib.
|
||||
XError(XError),
|
||||
|
||||
/// Got null pointer from Xlib but without exact reason.
|
||||
Null,
|
||||
}
|
||||
|
||||
/// The callback used by XIM preedit functions.
|
||||
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
|
||||
|
||||
/// Wrapper for creating XIM callbacks.
|
||||
#[inline]
|
||||
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
|
||||
XIMCallback {
|
||||
client_data,
|
||||
callback: Some(callback),
|
||||
}
|
||||
}
|
||||
|
||||
/// The server started preedit.
|
||||
extern "C" fn preedit_start_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) -> i32 {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
client_data.text.clear();
|
||||
client_data.cursor_pos = 0;
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::Start))
|
||||
.expect("failed to send preedit start event");
|
||||
-1
|
||||
}
|
||||
|
||||
/// Done callback is used when the preedit should be hidden.
|
||||
extern "C" fn preedit_done_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
// Drop text buffer and reset cursor position on done.
|
||||
client_data.text = Vec::new();
|
||||
client_data.cursor_pos = 0;
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::End))
|
||||
.expect("failed to send preedit end event");
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter()
|
||||
.take(pos)
|
||||
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
||||
}
|
||||
|
||||
/// Preedit text information to be drawn inline by the client.
|
||||
extern "C" fn preedit_draw_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
|
||||
client_data.cursor_pos = call_data.caret as usize;
|
||||
|
||||
let chg_range =
|
||||
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
|
||||
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
|
||||
warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL indicate text deletion
|
||||
let mut new_chars = if call_data.text.is_null() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let xim_text = unsafe { &mut *(call_data.text) };
|
||||
if xim_text.encoding_is_wchar > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { xim_text.string.multi_byte };
|
||||
|
||||
if new_text.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { CStr::from_ptr(new_text) };
|
||||
|
||||
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
|
||||
.chars()
|
||||
.collect()
|
||||
};
|
||||
let mut old_text_tail = client_data.text.split_off(chg_range.end);
|
||||
client_data.text.truncate(chg_range.start);
|
||||
client_data.text.append(&mut new_chars);
|
||||
client_data.text.append(&mut old_text_tail);
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
|
||||
/// Handling of cursor movements in preedit text.
|
||||
extern "C" fn preedit_caret_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
|
||||
|
||||
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
|
||||
client_data.cursor_pos = call_data.position as usize;
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to simplify callback creation and latter passing into Xlib XIM.
|
||||
struct PreeditCallbacks {
|
||||
start_callback: ffi::XIMCallback,
|
||||
done_callback: ffi::XIMCallback,
|
||||
draw_callback: ffi::XIMCallback,
|
||||
caret_callback: ffi::XIMCallback,
|
||||
}
|
||||
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
|
||||
|
||||
PreeditCallbacks {
|
||||
start_callback,
|
||||
done_callback,
|
||||
caret_callback,
|
||||
draw_callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeContextClientData {
|
||||
window: ffi::Window,
|
||||
event_sender: ImeEventSender,
|
||||
text: Vec<char>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
// XXX: this struct doesn't destroy its XIC resource when dropped.
|
||||
// This is intentional, as it doesn't have enough information to know whether or not the context
|
||||
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
||||
// through `ImeInner`.
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) style: Style,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
|
||||
// there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
pub(crate) unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: Style,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
event_sender,
|
||||
text: Vec::new(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => unsafe {
|
||||
ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
)
|
||||
},
|
||||
Style::Nothing(style) => unsafe {
|
||||
ImeContext::create_nothing_ic(xconn, im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
xconn
|
||||
.check_errors()
|
||||
.map_err(ImeContextCreationError::XError)?;
|
||||
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
style,
|
||||
_client_data: unsafe { Box::from_raw(client_data) },
|
||||
};
|
||||
|
||||
// Set the spot location, if it's present.
|
||||
if let Some(ic_spot) = ic_spot {
|
||||
context.set_spot(xconn, ic_spot.x, ic_spot.y)
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
unsafe fn create_none_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_preedit_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Option<ffi::XIC> {
|
||||
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
||||
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.start_callback) as *const _,
|
||||
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.done_callback) as *const _,
|
||||
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.caret_callback) as *const _,
|
||||
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.draw_callback) as *const _,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
})
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_nothing_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XSetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnsetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
!matches!(self.style, Style::None(_))
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
|
||||
// window and couldn't be changed.
|
||||
//
|
||||
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
|
||||
pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
||||
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_spot = ffi::XPoint { x, y };
|
||||
|
||||
unsafe {
|
||||
let preedit_attr = util::memory::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&self.ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
(xconn.xlib.XSetICValues)(
|
||||
self.ic,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/platform_impl/linux/x11/ime/inner.rs
Normal file
75
src/platform_impl/linux/x11/ime/inner.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::{collections::HashMap, mem, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{
|
||||
context::ImeContext,
|
||||
input_method::{InputMethod, PotentialInputMethods},
|
||||
};
|
||||
use crate::platform_impl::platform::x11::ime::ImeEventSender;
|
||||
|
||||
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XCloseIM)(im) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XDestroyIC)(ic) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) struct ImeInner {
|
||||
pub xconn: Arc<XConnection>,
|
||||
pub im: Option<InputMethod>,
|
||||
pub potential_input_methods: PotentialInputMethods,
|
||||
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||
// WARNING: this is initially zeroed!
|
||||
pub destroy_callback: ffi::XIMCallback,
|
||||
pub event_sender: ImeEventSender,
|
||||
// Indicates whether or not the the input method was destroyed on the server end
|
||||
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
|
||||
pub is_destroyed: bool,
|
||||
pub is_fallback: bool,
|
||||
}
|
||||
|
||||
impl ImeInner {
|
||||
pub(crate) fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: None,
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
event_sender,
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed && self.im.is_some() {
|
||||
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
|
||||
for context in self.contexts.values().flatten() {
|
||||
unsafe { self.destroy_ic_if_necessary(context.ic)? };
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
}
|
||||
370
src/platform_impl/linux/x11/ime/input_method.rs
Normal file
370
src/platform_impl/linux/x11/ime/input_method.rs
Normal file
@@ -0,0 +1,370 @@
|
||||
use std::{
|
||||
env,
|
||||
ffi::{CStr, CString, IntoStringError},
|
||||
fmt,
|
||||
os::raw::{c_char, c_ulong, c_ushort},
|
||||
ptr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{super::atoms::*, ffi, util, XConnection, XError};
|
||||
use once_cell::sync::Lazy;
|
||||
use x11rb::protocol::xproto;
|
||||
|
||||
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
|
||||
|
||||
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
|
||||
let _lock = GLOBAL_LOCK.lock();
|
||||
|
||||
// XSetLocaleModifiers returns...
|
||||
// * The current locale modifiers if it's given a NULL pointer.
|
||||
// * The new locale modifiers if we succeeded in setting them.
|
||||
// * NULL if the locale modifiers string is malformed or if the
|
||||
// current locale is not supported by Xlib.
|
||||
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
|
||||
|
||||
let im = unsafe {
|
||||
(xconn.xlib.XOpenIM)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if im.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(im)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputMethod {
|
||||
pub im: ffi::XIM,
|
||||
pub preedit_style: Style,
|
||||
pub none_style: Style,
|
||||
_name: String,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
|
||||
let mut styles: *mut XIMStyles = std::ptr::null_mut();
|
||||
|
||||
// Query the styles supported by the XIM.
|
||||
unsafe {
|
||||
if !(xconn.xlib.XGetIMValues)(
|
||||
im,
|
||||
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
|
||||
(&mut styles) as *mut _,
|
||||
std::ptr::null_mut::<()>(),
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
|
||||
.iter()
|
||||
.for_each(|style| match *style {
|
||||
XIM_PREEDIT_STYLE => {
|
||||
preedit_style = Some(Style::Preedit(*style));
|
||||
}
|
||||
XIM_NOTHING_STYLE if preedit_style.is_none() => {
|
||||
preedit_style = Some(Style::Nothing(*style))
|
||||
}
|
||||
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
(xconn.xlib.XFree)(styles.cast());
|
||||
};
|
||||
|
||||
if preedit_style.is_none() && none_style.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
|
||||
let none_style = none_style.unwrap_or(preedit_style);
|
||||
|
||||
Some(InputMethod {
|
||||
im,
|
||||
_name: name,
|
||||
preedit_style,
|
||||
none_style,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
|
||||
|
||||
/// Style of the IME context.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
/// Preedit callbacks.
|
||||
Preedit(XIMStyle),
|
||||
|
||||
/// Nothing.
|
||||
Nothing(XIMStyle),
|
||||
|
||||
/// No IME.
|
||||
None(XIMStyle),
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style::None(XIM_NONE_STYLE)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct XIMStyles {
|
||||
count_styles: c_ushort,
|
||||
supported_styles: *const XIMStyle,
|
||||
}
|
||||
|
||||
pub(crate) type XIMStyle = c_ulong;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMethodResult {
|
||||
/// Input method used locale modifier from `XMODIFIERS` environment variable.
|
||||
XModifiers(InputMethod),
|
||||
/// Input method used internal fallback locale modifier.
|
||||
Fallback(InputMethod),
|
||||
/// Input method could not be opened using any locale modifier tried.
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl InputMethodResult {
|
||||
pub fn is_fallback(&self) -> bool {
|
||||
matches!(self, InputMethodResult::Fallback(_))
|
||||
}
|
||||
|
||||
pub fn ok(self) -> Option<InputMethod> {
|
||||
use self::InputMethodResult::*;
|
||||
match self {
|
||||
XModifiers(im) | Fallback(im) => Some(im),
|
||||
Failure => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum GetXimServersError {
|
||||
XError(XError),
|
||||
GetPropertyError(util::GetPropertyError),
|
||||
InvalidUtf8(IntoStringError),
|
||||
}
|
||||
|
||||
impl From<util::GetPropertyError> for GetXimServersError {
|
||||
fn from(error: util::GetPropertyError) -> Self {
|
||||
GetXimServersError::GetPropertyError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
|
||||
// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named
|
||||
// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
|
||||
// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
|
||||
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
|
||||
// XMODIFIERS to `@server=ibus`?!?"
|
||||
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
|
||||
let atoms = xconn.atoms();
|
||||
let servers_atom = atoms[XIM_SERVERS];
|
||||
|
||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
||||
|
||||
let mut atoms: Vec<ffi::Atom> = xconn
|
||||
.get_property::<xproto::Atom>(
|
||||
root as xproto::Window,
|
||||
servers_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::ATOM),
|
||||
)
|
||||
.map_err(GetXimServersError::GetPropertyError)?
|
||||
.into_iter()
|
||||
.map(ffi::Atom::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||
unsafe {
|
||||
(xconn.xlib.XGetAtomNames)(
|
||||
xconn.display,
|
||||
atoms.as_mut_ptr(),
|
||||
atoms.len() as _,
|
||||
names.as_mut_ptr() as _,
|
||||
)
|
||||
};
|
||||
unsafe { names.set_len(atoms.len()) };
|
||||
|
||||
let mut formatted_names = Vec::with_capacity(names.len());
|
||||
for name in names {
|
||||
let string = unsafe { CStr::from_ptr(name) }
|
||||
.to_owned()
|
||||
.into_string()
|
||||
.map_err(GetXimServersError::InvalidUtf8)?;
|
||||
unsafe { (xconn.xlib.XFree)(name as _) };
|
||||
formatted_names.push(string.replace("@server=", "@im="));
|
||||
}
|
||||
xconn.check_errors().map_err(GetXimServersError::XError)?;
|
||||
Ok(formatted_names)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InputMethodName {
|
||||
c_string: CString,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl InputMethodName {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
let c_string = CString::new(string.clone())
|
||||
.expect("String used to construct CString contained null byte");
|
||||
InputMethodName { c_string, string }
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
let c_string =
|
||||
CString::new(string).expect("String used to construct CString contained null byte");
|
||||
InputMethodName {
|
||||
c_string,
|
||||
string: string.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for InputMethodName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.string.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PotentialInputMethod {
|
||||
name: InputMethodName,
|
||||
successful: Option<bool>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethod {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_string(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_str(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.successful = None;
|
||||
}
|
||||
|
||||
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
|
||||
let im = unsafe { open_im(xconn, &self.name.c_string) };
|
||||
self.successful = Some(im.is_some());
|
||||
im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
|
||||
// came from, and if it succeeded.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PotentialInputMethods {
|
||||
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
|
||||
// need to know.
|
||||
xmodifiers: Option<PotentialInputMethod>,
|
||||
// We have some standard options at our disposal that should ostensibly always work. For users
|
||||
// who only need compose sequences, this ensures that the program launches without a hitch
|
||||
// For users who need more sophisticated IME features, this is more or less a silent failure.
|
||||
// Logging features should be added in the future to allow both audiences to be effectively
|
||||
// served.
|
||||
fallbacks: [PotentialInputMethod; 2],
|
||||
// For diagnostic purposes, we include the list of XIM servers that the server reports as
|
||||
// being available.
|
||||
_xim_servers: Result<Vec<String>, GetXimServersError>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethods {
|
||||
pub fn new(xconn: &Arc<XConnection>) -> Self {
|
||||
let xmodifiers = env::var("XMODIFIERS")
|
||||
.ok()
|
||||
.map(PotentialInputMethod::from_string);
|
||||
PotentialInputMethods {
|
||||
// Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
|
||||
// XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
|
||||
// running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
|
||||
// defined in the profile (or parent environment) then that parent XMODIFIERS is used.
|
||||
// If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
|
||||
// XSetLocaleModifiers uses the default local input method. Note that defining
|
||||
// XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
|
||||
// that case, we get `None` and end up skipping ahead to the next method.
|
||||
xmodifiers,
|
||||
fallbacks: [
|
||||
// This is a standard input method that supports compose sequences, which should
|
||||
// always be available. `@im=none` appears to mean the same thing.
|
||||
PotentialInputMethod::from_str("@im=local"),
|
||||
// This explicitly specifies to use the implementation-dependent default, though
|
||||
// that seems to be equivalent to just using the local input method.
|
||||
PotentialInputMethod::from_str("@im="),
|
||||
],
|
||||
// The XIM_SERVERS property can have surprising values. For instance, when I exited
|
||||
// ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
|
||||
// that the fcitx input method could only be successfully opened using "@im=ibus".
|
||||
// Presumably due to this quirk, it's actually possible to alternate between ibus and
|
||||
// fcitx in a running application.
|
||||
_xim_servers: unsafe { get_xim_servers(xconn) },
|
||||
}
|
||||
}
|
||||
|
||||
// This resets the `successful` field of every potential input method, ensuring we have
|
||||
// accurate information when this struct is re-used by the destruction/instantiation callbacks.
|
||||
fn reset(&mut self) {
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
input_method.reset();
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
input_method.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_im(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
callback: Option<&dyn Fn()>,
|
||||
) -> InputMethodResult {
|
||||
use self::InputMethodResult::*;
|
||||
|
||||
self.reset();
|
||||
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return XModifiers(im);
|
||||
} else if let Some(ref callback) = callback {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return Fallback(im);
|
||||
}
|
||||
}
|
||||
|
||||
Failure
|
||||
}
|
||||
}
|
||||
249
src/platform_impl/linux/x11/ime/mod.rs
Normal file
249
src/platform_impl/linux/x11/ime/mod.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
// Important: all XIM calls need to happen from the same thread!
|
||||
|
||||
mod callbacks;
|
||||
mod context;
|
||||
mod inner;
|
||||
mod input_method;
|
||||
|
||||
use std::sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
pub use self::context::ImeContextCreationError;
|
||||
use self::{
|
||||
callbacks::*,
|
||||
context::ImeContext,
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::{PotentialInputMethods, Style},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, usize),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub type ImeReceiver = Receiver<ImeRequest>;
|
||||
pub type ImeSender = Sender<ImeRequest>;
|
||||
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
|
||||
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME spot position for given `window_id`.
|
||||
Position(ffi::Window, i16, i16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(ffi::Window, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ImeCreationError {
|
||||
// Boxed to prevent large error type
|
||||
OpenFailure(Box<PotentialInputMethods>),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
}
|
||||
|
||||
pub(crate) struct Ime {
|
||||
xconn: Arc<XConnection>,
|
||||
// The actual meat of this struct is boxed away, since it needs to have a fixed location in
|
||||
// memory so we can pass a pointer to it around.
|
||||
inner: Box<ImeInner>,
|
||||
}
|
||||
|
||||
impl Ime {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeCreationError> {
|
||||
let potential_input_methods = PotentialInputMethods::new(&xconn);
|
||||
|
||||
let (mut inner, client_data) = {
|
||||
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
|
||||
let inner_ptr = Box::into_raw(inner);
|
||||
let client_data = inner_ptr as _;
|
||||
let destroy_callback = ffi::XIMCallback {
|
||||
client_data,
|
||||
callback: Some(xim_destroy_callback),
|
||||
};
|
||||
inner = unsafe { Box::from_raw(inner_ptr) };
|
||||
inner.destroy_callback = destroy_callback;
|
||||
(inner, client_data)
|
||||
};
|
||||
|
||||
let xconn = Arc::clone(&inner.xconn);
|
||||
|
||||
let input_method = inner.potential_input_methods.open_im(
|
||||
&xconn,
|
||||
Some(&|| {
|
||||
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
|
||||
}),
|
||||
);
|
||||
|
||||
let is_fallback = input_method.is_fallback();
|
||||
if let Some(input_method) = input_method.ok() {
|
||||
inner.is_fallback = is_fallback;
|
||||
unsafe {
|
||||
let result = set_destroy_callback(&xconn, input_method.im, &inner)
|
||||
.map_err(ImeCreationError::SetDestroyCallbackFailed);
|
||||
if result.is_err() {
|
||||
let _ = close_im(&xconn, input_method.im);
|
||||
}
|
||||
result?;
|
||||
}
|
||||
inner.im = Some(input_method);
|
||||
Ok(Ime { xconn, inner })
|
||||
} else {
|
||||
Err(ImeCreationError::OpenFailure(Box::new(
|
||||
inner.potential_input_methods,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_destroyed(&self) -> bool {
|
||||
self.inner.is_destroyed
|
||||
}
|
||||
|
||||
// This pattern is used for various methods here:
|
||||
// Ok(_) indicates that nothing went wrong internally
|
||||
// Ok(true) indicates that the action was actually performed
|
||||
// Ok(false) indicates that the action is not presently applicable
|
||||
pub fn create_context(
|
||||
&mut self,
|
||||
window: ffi::Window,
|
||||
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,
|
||||
style,
|
||||
window,
|
||||
None,
|
||||
self.inner.event_sender.clone(),
|
||||
)?
|
||||
};
|
||||
|
||||
// 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)
|
||||
};
|
||||
|
||||
self.inner.contexts.insert(window, context);
|
||||
Ok(!self.is_destroyed())
|
||||
}
|
||||
|
||||
pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
|
||||
if self.is_destroyed() {
|
||||
return None;
|
||||
}
|
||||
if let Some(Some(context)) = self.inner.contexts.get(&window) {
|
||||
Some(context.ic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if let Some(Some(context)) = self.inner.contexts.remove(&window) {
|
||||
unsafe {
|
||||
self.inner.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.focus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.unfocus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.set_spot(&self.xconn, x as _, y as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
if allowed == context.is_allowed() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove context for that window.
|
||||
let _ = self.remove_context(window);
|
||||
|
||||
// Create new context supporting IME input.
|
||||
let _ = self.create_context(window, allowed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ime {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.inner.destroy_all_contexts_if_necessary();
|
||||
let _ = self.inner.close_im_if_necessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,13 @@ use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CStr,
|
||||
fmt,
|
||||
mem::MaybeUninit,
|
||||
ops::Deref,
|
||||
os::{
|
||||
raw::*,
|
||||
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
|
||||
},
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice, str,
|
||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
||||
@@ -40,14 +42,19 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use libc::{self, setlocale, LC_CTYPE};
|
||||
|
||||
use atoms::*;
|
||||
|
||||
use x11rb::protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
xproto::{self, ConnectionExt as _},
|
||||
};
|
||||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::{
|
||||
connection::RequestConnection,
|
||||
protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
xproto::{self, ConnectionExt as _},
|
||||
},
|
||||
};
|
||||
use x11rb::{
|
||||
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
|
||||
xcb_ffi::ReplyOrIdError,
|
||||
@@ -56,6 +63,7 @@ use x11rb::{
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
event_processor::EventProcessor,
|
||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
|
||||
};
|
||||
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
|
||||
use crate::{
|
||||
@@ -139,18 +147,15 @@ pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
wm_delete_window: xproto::Atom,
|
||||
net_wm_ping: xproto::Atom,
|
||||
ime_sender: mpsc::Sender<ime::ImeRequest>,
|
||||
ime_sender: ImeSender,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<Option<i32>>,
|
||||
root: xproto::Window,
|
||||
ime: RefCell<Ime>,
|
||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<ActivationToken>,
|
||||
device_events: Cell<DeviceEvents>,
|
||||
|
||||
/// State of IME.
|
||||
ime: Option<RefCell<ime::ImeData>>,
|
||||
|
||||
_marker: ::std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -196,26 +201,57 @@ impl<T: 'static> EventLoop<T> {
|
||||
let wm_delete_window = atoms[WM_DELETE_WINDOW];
|
||||
let net_wm_ping = atoms[_NET_WM_PING];
|
||||
|
||||
// Create an event queue.
|
||||
let event_queue = Rc::new(RefCell::new(std::collections::VecDeque::with_capacity(4)));
|
||||
|
||||
let dnd = Dnd::new(Arc::clone(&xconn))
|
||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
||||
|
||||
let (ime_sender, ime_receiver) = mpsc::channel();
|
||||
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
|
||||
// Input methods will open successfully without setting the locale, but it won't be
|
||||
// possible to actually commit pre-edit sequences.
|
||||
unsafe {
|
||||
// Remember default locale to restore it if target locale is unsupported
|
||||
// by Xlib
|
||||
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
||||
|
||||
let ime = match ime::ImeData::new(&xconn, xconn.default_screen_index(), &event_queue) {
|
||||
Ok(ime) => Some(ime),
|
||||
Err(e) => {
|
||||
log::error!("Failed to open IME: {e}");
|
||||
None
|
||||
// Check if set locale is supported by Xlib.
|
||||
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
|
||||
// will fail.
|
||||
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
|
||||
if !locale_supported {
|
||||
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
warn!(
|
||||
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
|
||||
CStr::from_ptr(unsupported_locale).to_string_lossy(),
|
||||
CStr::from_ptr(default_locale).to_string_lossy()
|
||||
);
|
||||
// Restore default locale
|
||||
setlocale(LC_CTYPE, default_locale);
|
||||
}
|
||||
};
|
||||
}
|
||||
let ime = RefCell::new({
|
||||
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
|
||||
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
||||
panic!("Failed to open input method: {state:#?}");
|
||||
}
|
||||
result.expect("Failed to set input method destruction callback")
|
||||
});
|
||||
|
||||
xconn
|
||||
let randr_event_offset = xconn
|
||||
.select_xrandr_input(root)
|
||||
.expect("Failed to query XRandR extension");
|
||||
|
||||
let xi2ext = xconn
|
||||
.xcb_connection()
|
||||
.extension_information(xinput::X11_EXTENSION_NAME)
|
||||
.expect("Failed to query XInput extension")
|
||||
.expect("X server missing XInput extension");
|
||||
let xkbext = xconn
|
||||
.xcb_connection()
|
||||
.extension_information(xkb::X11_EXTENSION_NAME)
|
||||
.expect("Failed to query XKB extension")
|
||||
.expect("X server missing XKB extension");
|
||||
|
||||
// Check for XInput2 support.
|
||||
xconn
|
||||
.xcb_connection()
|
||||
@@ -267,12 +303,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
root,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(None),
|
||||
windows: Default::default(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
ime: ime.map(RefCell::new),
|
||||
ime_sender,
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
@@ -297,11 +333,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
|
||||
let event_processor = EventProcessor {
|
||||
event_queue,
|
||||
target: target.clone(),
|
||||
dnd,
|
||||
devices: Default::default(),
|
||||
ime_requests: ime_receiver,
|
||||
randr_event_offset,
|
||||
ime_receiver,
|
||||
ime_event_receiver,
|
||||
xi2ext,
|
||||
xkbext,
|
||||
kb_state,
|
||||
num_touch: 0,
|
||||
held_key_press: None,
|
||||
@@ -584,10 +623,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
F: FnMut(Event<T>, &RootELW<T>),
|
||||
{
|
||||
let target = &self.target;
|
||||
let mut xev = MaybeUninit::uninit();
|
||||
let wt = get_xtarget(&self.target);
|
||||
|
||||
while let Some(event) = self.event_processor.poll_one_event() {
|
||||
self.event_processor.process_event(event, |event| {
|
||||
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, |event| {
|
||||
if let Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(wid),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
@@ -839,11 +880,8 @@ pub enum X11Error {
|
||||
/// The XID range has been exhausted.
|
||||
XidsExhausted(IdsExhausted),
|
||||
|
||||
/// An IME client error occurred.
|
||||
Ime(xim::ClientError),
|
||||
|
||||
/// The IME client has entered an invalid state.
|
||||
InvalidImeState(ime::InvalidImeState),
|
||||
/// Got `null` from an Xlib function without a reason.
|
||||
UnexpectedNull(&'static str),
|
||||
|
||||
/// Got an invalid activation token.
|
||||
InvalidActivationToken(Vec<u8>),
|
||||
@@ -862,9 +900,8 @@ impl fmt::Display for X11Error {
|
||||
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
|
||||
X11Error::Ime(e) => write!(f, "An IME error occurred: {}", e),
|
||||
X11Error::InvalidImeState(e) => write!(f, "Invalid IME state: {}", e),
|
||||
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
|
||||
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
|
||||
X11Error::InvalidActivationToken(s) => write!(
|
||||
f,
|
||||
"Invalid activation token: {}",
|
||||
@@ -927,6 +964,15 @@ impl From<ReplyError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ime::ImeContextCreationError> for X11Error {
|
||||
fn from(value: ime::ImeContextCreationError) -> Self {
|
||||
match value {
|
||||
ime::ImeContextCreationError::XError(e) => e.into(),
|
||||
ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReplyOrIdError> for X11Error {
|
||||
fn from(value: ReplyOrIdError) -> Self {
|
||||
match value {
|
||||
@@ -937,18 +983,6 @@ impl From<ReplyOrIdError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xim::ClientError> for X11Error {
|
||||
fn from(e: xim::ClientError) -> Self {
|
||||
match e {
|
||||
xim::ClientError::Other(other) => match other.downcast::<X11Error>() {
|
||||
Ok(x11) => *x11,
|
||||
Err(other) => X11Error::Ime(xim::ClientError::Other(other)),
|
||||
},
|
||||
e => X11Error::Ime(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The underlying x11rb connection that we are using.
|
||||
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
|
||||
|
||||
@@ -967,6 +1001,34 @@ impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to
|
||||
/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed
|
||||
struct GenericEventCookie<'a> {
|
||||
xconn: &'a XConnection,
|
||||
cookie: ffi::XGenericEventCookie,
|
||||
}
|
||||
|
||||
impl<'a> GenericEventCookie<'a> {
|
||||
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
|
||||
unsafe {
|
||||
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
||||
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
|
||||
Some(GenericEventCookie { xconn, cookie })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for GenericEventCookie<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
|
||||
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
|
||||
}
|
||||
@@ -1068,15 +1130,8 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the raw X11 representation for a 32-bit fixed point to a double.
|
||||
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
||||
#[inline]
|
||||
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
||||
(fp as f64) / ((1 << 16) as f64)
|
||||
}
|
||||
|
||||
/// Conver the raw X11 representation for a 64-bit fixed point number to a double.
|
||||
#[inline]
|
||||
fn xinput_fp3232_to_float(fp: xinput::Fp3232) -> f64 {
|
||||
let xinput::Fp3232 { integral, frac } = fp;
|
||||
integral as f64 + (frac as f64 / (1u64 << 32) as f64)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ impl XConnection {
|
||||
.expect("Failed to set cursor");
|
||||
}
|
||||
|
||||
pub fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
|
||||
pub(crate) fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
|
||||
self.update_cursor(window, cursor.inner.cursor)
|
||||
.expect("Failed to set cursor");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::{slice, str};
|
||||
use x11rb::protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
@@ -8,6 +9,11 @@ use super::*;
|
||||
pub const VIRTUAL_CORE_POINTER: u16 = 2;
|
||||
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
|
||||
|
||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
||||
// To test if `lookup_utf8` works correctly, set this to 1.
|
||||
const TEXT_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
impl XConnection {
|
||||
pub fn select_xinput_events(
|
||||
&self,
|
||||
@@ -54,4 +60,52 @@ impl XConnection {
|
||||
.reply()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn lookup_utf8_inner(
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: *mut u8,
|
||||
size: usize,
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = unsafe {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer as *mut c_char,
|
||||
size as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
};
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
// If the buffer overflows, we'll make a new one on the heap.
|
||||
let mut vec;
|
||||
|
||||
let (_, status, count) =
|
||||
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
||||
|
||||
let bytes = if status == ffi::XBufferOverflow {
|
||||
vec = Vec::with_capacity(count as usize);
|
||||
let (_, _, new_count) =
|
||||
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
||||
debug_assert_eq!(count, new_count);
|
||||
|
||||
unsafe { vec.set_len(count as usize) };
|
||||
&vec[..count as usize]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
||||
};
|
||||
|
||||
str::from_utf8(bytes).unwrap_or("").to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,18 @@ pub(crate) struct XSmartPointer<'a, T> {
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer { xconn, ptr })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
};
|
||||
|
||||
@@ -33,6 +34,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is impoartant, so pay attention!
|
||||
// Xlib has an output buffer, and tries to hide the async nature of X from you.
|
||||
|
||||
@@ -4,11 +4,9 @@ use std::{
|
||||
mem::replace,
|
||||
os::raw::*,
|
||||
path::Path,
|
||||
sync::{mpsc, Arc, Mutex, MutexGuard},
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
@@ -32,8 +30,9 @@ use crate::{
|
||||
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
|
||||
X11Error,
|
||||
},
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
OwnedWindowHandle as PlatformOwnedWindowHandle,
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
|
||||
PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
|
||||
@@ -43,9 +42,9 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
ffi,
|
||||
ime::ImeRequest,
|
||||
util::{self, CustomCursor, SelectedCursor},
|
||||
CookieResultExt, EventLoopWindowTarget, VoidCookie, WindowId, XConnection,
|
||||
CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
|
||||
XConnection,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -135,7 +134,7 @@ pub(crate) struct UnownedWindow {
|
||||
cursor_grabbed_mode: Mutex<CursorGrabMode>,
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
cursor_visible: Mutex<bool>,
|
||||
ime_sender: Mutex<mpsc::Sender<ImeRequest>>,
|
||||
ime_sender: Mutex<ImeSender>,
|
||||
pub shared_state: Mutex<SharedState>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<super::ActivationToken>,
|
||||
@@ -159,15 +158,12 @@ impl UnownedWindow {
|
||||
) -> Result<UnownedWindow, RootOsError> {
|
||||
let xconn = &event_loop.xconn;
|
||||
let atoms = xconn.atoms();
|
||||
#[cfg(feature = "rwh_06")]
|
||||
let root = match window_attrs.parent_window.0 {
|
||||
Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
|
||||
Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
|
||||
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
|
||||
let root = match window_attrs.parent_window {
|
||||
Some(PlatformOwnedWindowHandle::X(handle)) => handle,
|
||||
#[cfg(wayland_platform)]
|
||||
Some(handle) => panic!("invalid window handle {handle:?} on X11"),
|
||||
None => event_loop.root,
|
||||
};
|
||||
#[cfg(not(feature = "rwh_06"))]
|
||||
let root = event_loop.root;
|
||||
|
||||
let mut monitors = leap!(xconn.available_monitors());
|
||||
let guessed_monitor = if monitors.is_empty() {
|
||||
@@ -547,10 +543,11 @@ impl UnownedWindow {
|
||||
.ignore_error();
|
||||
|
||||
{
|
||||
if let Some(ime) = event_loop.ime.as_ref() {
|
||||
let result = ime.borrow_mut().create_context(window.xwindow, false, None);
|
||||
leap!(result);
|
||||
}
|
||||
let result = event_loop
|
||||
.ime
|
||||
.borrow_mut()
|
||||
.create_context(window.xwindow as ffi::Window, false);
|
||||
leap!(result);
|
||||
}
|
||||
|
||||
// These properties must be set after mapping
|
||||
@@ -1551,8 +1548,8 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: RootCustomCursor) {
|
||||
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner) };
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.0) };
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
if *self.cursor_visible.lock().unwrap() {
|
||||
@@ -1791,11 +1788,11 @@ impl UnownedWindow {
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) {
|
||||
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
|
||||
let _ = self
|
||||
.ime_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(ImeRequest::Position(self.xwindow, x, y));
|
||||
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
|
||||
self.xwindow as ffi::Window,
|
||||
x,
|
||||
y,
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1804,7 +1801,7 @@ impl UnownedWindow {
|
||||
.ime_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(ImeRequest::Allow(self.xwindow, allowed));
|
||||
.send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -91,14 +91,6 @@ impl XConnection {
|
||||
conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
|
||||
};
|
||||
|
||||
// Make sure Xlib knows XCB is handling events.
|
||||
unsafe {
|
||||
(xlib_xcb.XSetEventQueueOwner)(
|
||||
display,
|
||||
x11_dl::xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the default screen.
|
||||
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
|
||||
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSEvent, NSEventModifierFlagCommand, NSEventTypeKeyUp, NSEventTypeLeftMouseDown,
|
||||
NSEventTypeLeftMouseDragged, NSEventTypeLeftMouseUp, NSEventTypeMouseMoved,
|
||||
NSEventTypeOtherMouseDown, NSEventTypeOtherMouseDragged, NSEventTypeOtherMouseUp,
|
||||
NSEventTypeRightMouseDown, NSEventTypeRightMouseDragged, NSEventTypeRightMouseUp, NSResponder,
|
||||
};
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType};
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
||||
|
||||
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||
use super::event::flags_contains;
|
||||
use super::{app_state::AppState, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(super) struct WinitApplication;
|
||||
|
||||
unsafe impl ClassType for WinitApplication {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplication";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitApplication {}
|
||||
|
||||
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)
|
||||
@@ -27,13 +34,13 @@ declare_class!(
|
||||
// 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 = event.type_();
|
||||
let modifier_flags = event.modifierFlags();
|
||||
if event_type == NSEventType::NSKeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask)
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventTypeKeyUp
|
||||
&& flags_contains(modifier_flags, NSEventModifierFlagCommand)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
unsafe { key_window.sendEvent(event) };
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
} else {
|
||||
maybe_dispatch_device_event(event);
|
||||
@@ -44,14 +51,15 @@ declare_class!(
|
||||
);
|
||||
|
||||
fn maybe_dispatch_device_event(event: &NSEvent) {
|
||||
let event_type = event.type_();
|
||||
let event_type = unsafe { event.r#type() };
|
||||
#[allow(non_upper_case_globals)]
|
||||
match event_type {
|
||||
NSEventType::NSMouseMoved
|
||||
| NSEventType::NSLeftMouseDragged
|
||||
| NSEventType::NSOtherMouseDragged
|
||||
| NSEventType::NSRightMouseDragged => {
|
||||
let delta_x = event.deltaX() as f64;
|
||||
let delta_y = event.deltaY() as f64;
|
||||
NSEventTypeMouseMoved
|
||||
| NSEventTypeLeftMouseDragged
|
||||
| NSEventTypeOtherMouseDragged
|
||||
| NSEventTypeRightMouseDragged => {
|
||||
let delta_x = unsafe { event.deltaX() } as f64;
|
||||
let delta_y = unsafe { event.deltaY() } as f64;
|
||||
|
||||
if delta_x != 0.0 {
|
||||
queue_device_event(DeviceEvent::Motion {
|
||||
@@ -73,17 +81,15 @@ fn maybe_dispatch_device_event(event: &NSEvent) {
|
||||
});
|
||||
}
|
||||
}
|
||||
NSEventType::NSLeftMouseDown
|
||||
| NSEventType::NSRightMouseDown
|
||||
| NSEventType::NSOtherMouseDown => {
|
||||
NSEventTypeLeftMouseDown | NSEventTypeRightMouseDown | NSEventTypeOtherMouseDown => {
|
||||
queue_device_event(DeviceEvent::Button {
|
||||
button: event.buttonNumber() as u32,
|
||||
button: unsafe { event.buttonNumber() } as u32,
|
||||
state: ElementState::Pressed,
|
||||
});
|
||||
}
|
||||
NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => {
|
||||
NSEventTypeLeftMouseUp | NSEventTypeRightMouseUp | NSEventTypeOtherMouseUp => {
|
||||
queue_device_event(DeviceEvent::Button {
|
||||
button: event.buttonNumber() as u32,
|
||||
button: unsafe { event.buttonNumber() } as u32,
|
||||
state: ElementState::Released,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,54 +1,41 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::declare::{IvarBool, IvarEncode};
|
||||
use icrate::AppKit::{NSApplicationActivationPolicy, NSApplicationDelegate};
|
||||
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType};
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
|
||||
use super::app_state::AppState;
|
||||
use super::appkit::NSApplicationActivationPolicy;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct State {
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ApplicationDelegate {
|
||||
activation_policy: IvarEncode<NSApplicationActivationPolicy, "_activation_policy">,
|
||||
default_menu: IvarBool<"_default_menu">,
|
||||
activate_ignoring_other_apps: IvarBool<"_activate_ignoring_other_apps">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub(super) struct ApplicationDelegate;
|
||||
|
||||
unsafe impl ClassType for ApplicationDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
unsafe impl ApplicationDelegate {
|
||||
#[method(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)]
|
||||
unsafe fn init(
|
||||
this: *mut Self,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
|
||||
this.map(|this| {
|
||||
*this.activation_policy = activation_policy;
|
||||
*this.default_menu = default_menu;
|
||||
*this.activate_ignoring_other_apps = activate_ignoring_other_apps;
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for ApplicationDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for ApplicationDelegate {}
|
||||
|
||||
unsafe impl NSApplicationDelegate for ApplicationDelegate {
|
||||
#[method(applicationDidFinishLaunching:)]
|
||||
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
AppState::launched(
|
||||
*self.activation_policy,
|
||||
*self.default_menu,
|
||||
*self.activate_ignoring_other_apps,
|
||||
self.ivars().activation_policy,
|
||||
self.ivars().default_menu,
|
||||
self.ivars().activate_ignoring_other_apps,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,17 +50,16 @@ declare_class!(
|
||||
|
||||
impl ApplicationDelegate {
|
||||
pub(super) fn new(
|
||||
mtm: MainThreadMarker,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithActivationPolicy: activation_policy,
|
||||
defaultMenu: default_menu,
|
||||
activateIgnoringOtherApps: activate_ignoring_other_apps,
|
||||
]
|
||||
}
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
activation_policy,
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
});
|
||||
unsafe { msg_send_id![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ use std::{
|
||||
};
|
||||
|
||||
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
|
||||
use icrate::Foundation::{is_main_thread, NSSize};
|
||||
use icrate::AppKit::{NSApplication, NSApplicationActivationPolicy};
|
||||
use icrate::Foundation::{is_main_thread, MainThreadMarker, NSSize};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
|
||||
use super::{
|
||||
event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow,
|
||||
event::dummy_event, event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never,
|
||||
window::WinitWindow,
|
||||
};
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
@@ -57,14 +58,14 @@ impl<T> EventLoopHandler<T> {
|
||||
where
|
||||
F: FnOnce(&mut EventLoopHandler<T>, RefMut<'_, dyn FnMut(Event<T>, &RootWindowTarget<T>)>),
|
||||
{
|
||||
// The `NSApp` and our `HANDLER` are global state and so it's possible that
|
||||
// we could get a delegate callback after the application has exit an
|
||||
// `NSApplication` and our `HANDLER` are global state and so it's possible
|
||||
// that we could get a delegate callback after the application has exit an
|
||||
// `EventLoop`. If the loop has been exit then our weak `self.callback`
|
||||
// will fail to upgrade.
|
||||
//
|
||||
// We don't want to panic or output any verbose logging if we fail to
|
||||
// upgrade the weak reference since it might be valid that the application
|
||||
// re-starts the `NSApp` after exiting a Winit `EventLoop`
|
||||
// re-starts the `NSApplication` after exiting a Winit `EventLoop`
|
||||
if let Some(callback) = self.callback.upgrade() {
|
||||
let callback = callback.borrow_mut();
|
||||
(f)(self, callback);
|
||||
@@ -144,9 +145,9 @@ impl Handler {
|
||||
|
||||
/// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called
|
||||
///
|
||||
/// NB: This is global / `NSApp` state and since the app will only be launched
|
||||
/// once but an `EventLoop` may be run more than once then only the first
|
||||
/// `EventLoop` will observe the `NSApp` before it is launched.
|
||||
/// NB: This is global / `NSApplication` state and since the app will only
|
||||
/// be launched once but an `EventLoop` may be run more than once then only
|
||||
/// the first `EventLoop` will observe the application before it is launched.
|
||||
fn is_launched(&self) -> bool {
|
||||
self.launched.load(Ordering::Acquire)
|
||||
}
|
||||
@@ -158,8 +159,8 @@ impl Handler {
|
||||
|
||||
/// `true` if an `EventLoop` is currently running
|
||||
///
|
||||
/// NB: This is global / `NSApp` state and may persist beyond the lifetime of
|
||||
/// a running `EventLoop`.
|
||||
/// NB: This is global / `NSApplication` state and may persist beyond the
|
||||
/// lifetime of a running `EventLoop`.
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
@@ -167,7 +168,7 @@ impl Handler {
|
||||
self.running.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set when an `EventLoop` starts running, after the `NSApp` is launched
|
||||
/// Set when an `EventLoop` starts running, after the `NSApplication` is launched
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
@@ -180,8 +181,8 @@ impl Handler {
|
||||
/// Since an `EventLoop` may be run more than once we need make sure to reset the
|
||||
/// `control_flow` state back to `Poll` each time the loop exits.
|
||||
///
|
||||
/// Note: that if the `NSApp` has been launched then that state is preserved, and we won't
|
||||
/// need to re-launch the app if subsequent EventLoops are run.
|
||||
/// 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.
|
||||
///
|
||||
/// # Caveat
|
||||
/// This is only intended to be called from the main thread
|
||||
@@ -392,7 +393,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
// If `pump_events` is called to progress the event loop then we bootstrap the event
|
||||
// loop via `[NSApp run]` but will use `CFRunLoopRunInMode` for subsequent calls to
|
||||
// loop via `-[NSAppplication run]` but will use `CFRunLoopRunInMode` for subsequent calls to
|
||||
// `pump_events`
|
||||
pub fn request_stop_on_launch() {
|
||||
HANDLER.request_stop_app_on_launch();
|
||||
@@ -459,13 +460,15 @@ impl AppState {
|
||||
create_default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) {
|
||||
let app = NSApp();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
// 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.
|
||||
app.setActivationPolicy(activation_policy);
|
||||
|
||||
window_activation_hack(&app);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(activate_ignoring_other_apps);
|
||||
|
||||
HANDLER.set_launched();
|
||||
@@ -473,21 +476,22 @@ impl AppState {
|
||||
if create_default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
menu::initialize();
|
||||
menu::initialize(&app);
|
||||
}
|
||||
|
||||
Self::start_running();
|
||||
|
||||
// If the `NSApp` is being launched via `EventLoop::pump_events()` then we'll
|
||||
// If the application is being launched via `EventLoop::pump_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 HANDLER.should_stop_app_on_launch() {
|
||||
// Note: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`[NSApp run]` effectively
|
||||
// ignored the attempt to stop the RunLoop and re-started it.). So we
|
||||
// return from `pump_events` by stopping the `NSApp`
|
||||
// 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.
|
||||
Self::stop();
|
||||
}
|
||||
}
|
||||
@@ -589,11 +593,12 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn stop() {
|
||||
let app = NSApp();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
autoreleasepool(|_| {
|
||||
app.stop(None);
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
||||
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
use icrate::Foundation::{NSArray, NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSAppearance;
|
||||
|
||||
unsafe impl ClassType for NSAppearance {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
type NSAppearanceName = NSString;
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSAppearance {
|
||||
#[method_id(appearanceNamed:)]
|
||||
pub fn appearanceNamed(name: &NSAppearanceName) -> Id<Self>;
|
||||
|
||||
#[method_id(bestMatchFromAppearancesWithNames:)]
|
||||
pub fn bestMatchFromAppearancesWithNames(
|
||||
&self,
|
||||
appearances: &NSArray<NSAppearanceName>,
|
||||
) -> Id<NSAppearanceName>;
|
||||
}
|
||||
);
|
||||
@@ -1,143 +0,0 @@
|
||||
use icrate::Foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2::{Encode, Encoding};
|
||||
|
||||
use super::{NSAppearance, NSEvent, NSMenu, NSResponder, NSWindow};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSApplication;
|
||||
|
||||
unsafe impl ClassType for NSApplication {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
pub(crate) fn NSApp() -> Id<NSApplication> {
|
||||
// TODO: Only allow access from main thread
|
||||
NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() })
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSApplication {
|
||||
/// This can only be called on the main thread since it may initialize
|
||||
/// the application and since it's parameters may be changed by the main
|
||||
/// thread at any time (hence it is only safe to access on the main thread).
|
||||
pub fn shared(_mtm: MainThreadMarker) -> Id<Self> {
|
||||
let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] };
|
||||
// SAFETY: `sharedApplication` always initializes the app if it isn't already
|
||||
unsafe { app.unwrap_unchecked() }
|
||||
}
|
||||
|
||||
#[method_id(currentEvent)]
|
||||
pub fn currentEvent(&self) -> Option<Id<NSEvent>>;
|
||||
|
||||
#[method(postEvent:atStart:)]
|
||||
pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool);
|
||||
|
||||
#[method(presentationOptions)]
|
||||
pub fn presentationOptions(&self) -> NSApplicationPresentationOptions;
|
||||
|
||||
#[method_id(windows)]
|
||||
pub fn windows(&self) -> Id<NSArray<NSWindow>>;
|
||||
|
||||
#[method_id(keyWindow)]
|
||||
pub fn keyWindow(&self) -> Option<Id<NSWindow>>;
|
||||
|
||||
// TODO: NSApplicationDelegate
|
||||
#[method(setDelegate:)]
|
||||
pub fn setDelegate(&self, delegate: &AnyObject);
|
||||
|
||||
#[method(setPresentationOptions:)]
|
||||
pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions);
|
||||
|
||||
#[method(hide:)]
|
||||
pub fn hide(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(orderFrontCharacterPalette:)]
|
||||
#[allow(dead_code)]
|
||||
pub fn orderFrontCharacterPalette(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(hideOtherApplications:)]
|
||||
pub fn hideOtherApplications(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(stop:)]
|
||||
pub fn stop(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(activateIgnoringOtherApps:)]
|
||||
pub fn activateIgnoringOtherApps(&self, ignore: bool);
|
||||
|
||||
#[method(requestUserAttention:)]
|
||||
pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger;
|
||||
|
||||
#[method(setActivationPolicy:)]
|
||||
pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool;
|
||||
|
||||
#[method(setMainMenu:)]
|
||||
pub fn setMainMenu(&self, menu: &NSMenu);
|
||||
|
||||
#[method(setServicesMenu:)]
|
||||
pub fn setServicesMenu(&self, menu: &NSMenu);
|
||||
|
||||
#[method_id(effectiveAppearance)]
|
||||
pub fn effectiveAppearance(&self) -> Id<NSAppearance>;
|
||||
|
||||
#[method(setAppearance:)]
|
||||
pub fn setAppearance(&self, appearance: Option<&NSAppearance>);
|
||||
|
||||
#[method(run)]
|
||||
pub unsafe fn run(&self);
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSApplicationActivationPolicy {
|
||||
NSApplicationActivationPolicyRegular = 0,
|
||||
NSApplicationActivationPolicyAccessory = 1,
|
||||
NSApplicationActivationPolicyProhibited = 2,
|
||||
NSApplicationActivationPolicyERROR = -1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSApplicationActivationPolicy {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NSApplicationPresentationOptions: NSUInteger {
|
||||
const NSApplicationPresentationDefault = 0;
|
||||
const NSApplicationPresentationAutoHideDock = 1 << 0;
|
||||
const NSApplicationPresentationHideDock = 1 << 1;
|
||||
const NSApplicationPresentationAutoHideMenuBar = 1 << 2;
|
||||
const NSApplicationPresentationHideMenuBar = 1 << 3;
|
||||
const NSApplicationPresentationDisableAppleMenu = 1 << 4;
|
||||
const NSApplicationPresentationDisableProcessSwitching = 1 << 5;
|
||||
const NSApplicationPresentationDisableForceQuit = 1 << 6;
|
||||
const NSApplicationPresentationDisableSessionTermination = 1 << 7;
|
||||
const NSApplicationPresentationDisableHideApplication = 1 << 8;
|
||||
const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9;
|
||||
const NSApplicationPresentationFullScreen = 1 << 10;
|
||||
const NSApplicationPresentationAutoHideToolbar = 1 << 11;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSApplicationPresentationOptions {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSRequestUserAttentionType {
|
||||
NSCriticalRequest = 0,
|
||||
NSInformationalRequest = 10,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSRequestUserAttentionType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use std::ffi::c_uchar;
|
||||
|
||||
use icrate::Foundation::{NSInteger, NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Bool;
|
||||
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NSImageRep;
|
||||
|
||||
unsafe impl ClassType for NSImageRep {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern "C" {
|
||||
static NSDeviceRGBColorSpace: &'static NSString;
|
||||
}
|
||||
|
||||
extern_class!(
|
||||
// <https://developer.apple.com/documentation/appkit/nsbitmapimagerep?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSBitmapImageRep;
|
||||
|
||||
unsafe impl ClassType for NSBitmapImageRep {
|
||||
type Super = NSImageRep;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSBitmapImageRep {
|
||||
pub fn init_rgba(width: NSInteger, height: NSInteger) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![Self::alloc(),
|
||||
initWithBitmapDataPlanes: std::ptr::null_mut::<*mut c_uchar>(),
|
||||
pixelsWide: width,
|
||||
pixelsHigh: height,
|
||||
bitsPerSample: 8 as NSInteger,
|
||||
samplesPerPixel: 4 as NSInteger,
|
||||
hasAlpha: Bool::new(true),
|
||||
isPlanar: Bool::new(false),
|
||||
colorSpaceName: NSDeviceRGBColorSpace,
|
||||
bytesPerRow: width * 4,
|
||||
bitsPerPixel: 32 as NSInteger,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitmap_data(&self) -> *mut u8 {
|
||||
unsafe { msg_send![self, bitmapData] }
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,15 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
|
||||
use super::{NSControl, NSResponder, NSView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSButton;
|
||||
|
||||
unsafe impl ClassType for NSButton {
|
||||
#[inherits(NSView, NSResponder, NSObject)]
|
||||
type Super = NSControl;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// An object that stores color data and sometimes opacity (alpha value).
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nscolor?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSColor;
|
||||
|
||||
unsafe impl ClassType for NSColor {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Documentation clearly states:
|
||||
// > Color objects are immutable and thread-safe
|
||||
unsafe impl Send for NSColor {}
|
||||
unsafe impl Sync for NSColor {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSColor {
|
||||
#[method_id(clearColor)]
|
||||
pub fn clear() -> Id<Self>;
|
||||
}
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::{NSResponder, NSView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSControl;
|
||||
|
||||
unsafe impl ClassType for NSControl {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSControl {
|
||||
#[method(setEnabled:)]
|
||||
pub fn setEnabled(&self, enabled: bool);
|
||||
|
||||
#[method(isEnabled)]
|
||||
pub fn isEnabled(&self) -> bool;
|
||||
}
|
||||
);
|
||||
@@ -1,259 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{
|
||||
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString,
|
||||
};
|
||||
use objc2::rc::{DefaultId, Id};
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::{NSBitmapImageRep, NSImage};
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
extern_class!(
|
||||
/// <https://developer.apple.com/documentation/appkit/nscursor?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSCursor;
|
||||
|
||||
unsafe impl ClassType for NSCursor {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: NSCursor is immutable, stated here:
|
||||
// https://developer.apple.com/documentation/appkit/nscursor/1527062-image?language=objc
|
||||
unsafe impl Send for NSCursor {}
|
||||
unsafe impl Sync for NSCursor {}
|
||||
|
||||
macro_rules! def_cursor {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
pub fn $name:ident();
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
pub fn $name() -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), $name] }
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
macro_rules! def_undocumented_cursor {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
pub fn $name:ident();
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
pub fn $name() -> Id<Self> {
|
||||
unsafe { Self::from_selector(sel!($name)).unwrap_or_else(|| Default::default()) }
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
/// Documented cursors
|
||||
unsafe impl NSCursor {
|
||||
def_cursor!(
|
||||
pub fn arrowCursor();
|
||||
pub fn pointingHandCursor();
|
||||
pub fn openHandCursor();
|
||||
pub fn closedHandCursor();
|
||||
pub fn IBeamCursor();
|
||||
pub fn IBeamCursorForVerticalLayout();
|
||||
pub fn dragCopyCursor();
|
||||
pub fn dragLinkCursor();
|
||||
pub fn operationNotAllowedCursor();
|
||||
pub fn contextualMenuCursor();
|
||||
pub fn crosshairCursor();
|
||||
pub fn resizeRightCursor();
|
||||
pub fn resizeUpCursor();
|
||||
pub fn resizeLeftCursor();
|
||||
pub fn resizeDownCursor();
|
||||
pub fn resizeLeftRightCursor();
|
||||
pub fn resizeUpDownCursor();
|
||||
);
|
||||
|
||||
// Creating cursors should be thread-safe, though using them for anything probably isn't.
|
||||
pub fn new(image: &NSImage, hotSpot: NSPoint) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithImage: image, hotSpot: hotSpot] }
|
||||
}
|
||||
|
||||
pub fn invisible() -> Id<Self> {
|
||||
// 16x16 GIF data for invisible cursor
|
||||
// You can reproduce this via ImageMagick.
|
||||
// $ convert -size 16x16 xc:none cursor.gif
|
||||
static CURSOR_BYTES: &[u8] = &[
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9,
|
||||
0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
|
||||
];
|
||||
|
||||
static CURSOR: Lazy<Id<NSCursor>> = Lazy::new(|| {
|
||||
// TODO: Consider using `dataWithBytesNoCopy:`
|
||||
let data = NSData::with_bytes(CURSOR_BYTES);
|
||||
let image = NSImage::new_with_data(&data);
|
||||
NSCursor::new(&image, NSPoint::new(0.0, 0.0))
|
||||
});
|
||||
|
||||
CURSOR.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Undocumented cursors
|
||||
unsafe impl NSCursor {
|
||||
#[method(respondsToSelector:)]
|
||||
fn class_responds_to(sel: Sel) -> bool;
|
||||
|
||||
#[method_id(performSelector:)]
|
||||
unsafe fn from_selector_unchecked(sel: Sel) -> Id<Self>;
|
||||
|
||||
unsafe fn from_selector(sel: Sel) -> Option<Id<Self>> {
|
||||
if Self::class_responds_to(sel) {
|
||||
Some(unsafe { Self::from_selector_unchecked(sel) })
|
||||
} else {
|
||||
warn!("Cursor `{:?}` appears to be invalid", sel);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def_undocumented_cursor!(
|
||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
||||
pub fn _helpCursor();
|
||||
pub fn _zoomInCursor();
|
||||
pub fn _zoomOutCursor();
|
||||
pub fn _windowResizeNorthEastCursor();
|
||||
pub fn _windowResizeNorthWestCursor();
|
||||
pub fn _windowResizeSouthEastCursor();
|
||||
pub fn _windowResizeSouthWestCursor();
|
||||
pub fn _windowResizeNorthEastSouthWestCursor();
|
||||
pub fn _windowResizeNorthWestSouthEastCursor();
|
||||
|
||||
// While these two are available, the former just loads a white arrow,
|
||||
// and the latter loads an ugly deflated beachball!
|
||||
// pub fn _moveCursor();
|
||||
// pub fn _waitCursor();
|
||||
|
||||
// An even more undocumented cursor...
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
||||
pub fn busyButClickableCursor();
|
||||
);
|
||||
}
|
||||
|
||||
/// Webkit cursors
|
||||
unsafe impl NSCursor {
|
||||
// Note that loading `busybutclickable` with this code won't animate
|
||||
// the frames; instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(name: &NSString) -> Id<Self> {
|
||||
// Snatch a cursor from WebKit; They fit the style of the native
|
||||
// cursors, and will seem completely standard to macOS users.
|
||||
//
|
||||
// https://stackoverflow.com/a/21786835/5435443
|
||||
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors");
|
||||
let cursor_path = root.stringByAppendingPathComponent(name);
|
||||
|
||||
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
|
||||
let image = NSImage::new_by_referencing_file(&pdf_path);
|
||||
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
<NSDictionary<NSObject, NSObject>>::class(),
|
||||
dictionaryWithContentsOfFile: &*info_path,
|
||||
]
|
||||
};
|
||||
let mut x = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
x = unsafe { &*ptr }.as_cgfloat()
|
||||
}
|
||||
}
|
||||
let mut y = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
y = unsafe { &*ptr }.as_cgfloat()
|
||||
}
|
||||
}
|
||||
|
||||
let hotspot = NSPoint::new(x, y);
|
||||
Self::new(&image, hotspot)
|
||||
}
|
||||
|
||||
pub fn moveCursor() -> Id<Self> {
|
||||
unsafe { Self::load_webkit_cursor(ns_string!("move")) }
|
||||
}
|
||||
|
||||
pub fn cellCursor() -> Id<Self> {
|
||||
unsafe { Self::load_webkit_cursor(ns_string!("cell")) }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl NSCursor {
|
||||
pub fn from_icon(icon: CursorIcon) -> Id<Self> {
|
||||
match icon {
|
||||
CursorIcon::Default => Default::default(),
|
||||
CursorIcon::Pointer => Self::pointingHandCursor(),
|
||||
CursorIcon::Grab => Self::openHandCursor(),
|
||||
CursorIcon::Grabbing => Self::closedHandCursor(),
|
||||
CursorIcon::Text => Self::IBeamCursor(),
|
||||
CursorIcon::VerticalText => Self::IBeamCursorForVerticalLayout(),
|
||||
CursorIcon::Copy => Self::dragCopyCursor(),
|
||||
CursorIcon::Alias => Self::dragLinkCursor(),
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => Self::operationNotAllowedCursor(),
|
||||
CursorIcon::ContextMenu => Self::contextualMenuCursor(),
|
||||
CursorIcon::Crosshair => Self::crosshairCursor(),
|
||||
CursorIcon::EResize => Self::resizeRightCursor(),
|
||||
CursorIcon::NResize => Self::resizeUpCursor(),
|
||||
CursorIcon::WResize => Self::resizeLeftCursor(),
|
||||
CursorIcon::SResize => Self::resizeDownCursor(),
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => Self::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => Self::resizeUpDownCursor(),
|
||||
CursorIcon::Help => Self::_helpCursor(),
|
||||
CursorIcon::ZoomIn => Self::_zoomInCursor(),
|
||||
CursorIcon::ZoomOut => Self::_zoomOutCursor(),
|
||||
CursorIcon::NeResize => Self::_windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => Self::_windowResizeNorthWestCursor(),
|
||||
CursorIcon::SeResize => Self::_windowResizeSouthEastCursor(),
|
||||
CursorIcon::SwResize => Self::_windowResizeSouthWestCursor(),
|
||||
CursorIcon::NeswResize => Self::_windowResizeNorthEastSouthWestCursor(),
|
||||
CursorIcon::NwseResize => Self::_windowResizeNorthWestSouthEastCursor(),
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
CursorIcon::Wait | CursorIcon::Progress => Self::busyButClickableCursor(),
|
||||
CursorIcon::Move | CursorIcon::AllScroll => Self::moveCursor(),
|
||||
CursorIcon::Cell => Self::cellCursor(),
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_image(cursor: &CursorImage) -> Id<Self> {
|
||||
let width = cursor.width;
|
||||
let height = cursor.height;
|
||||
|
||||
let bitmap = NSBitmapImageRep::init_rgba(width as isize, height as isize);
|
||||
let bitmap_data =
|
||||
unsafe { std::slice::from_raw_parts_mut(bitmap.bitmap_data(), cursor.rgba.len()) };
|
||||
bitmap_data.copy_from_slice(&cursor.rgba);
|
||||
|
||||
let image = NSImage::init_with_size(NSSize::new(width.into(), height.into()));
|
||||
image.add_representation(&bitmap);
|
||||
|
||||
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
|
||||
|
||||
NSCursor::new(&image, hotspot)
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSCursor {
|
||||
fn default_id() -> Id<Self> {
|
||||
Self::arrowCursor()
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
use std::os::raw::c_ushort;
|
||||
|
||||
use icrate::Foundation::{
|
||||
CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger,
|
||||
};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSEvent;
|
||||
|
||||
unsafe impl ClassType for NSEvent {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// > Safely handled only on the same thread, whether that be the main thread
|
||||
// > or a secondary thread; otherwise you run the risk of having events get
|
||||
// > out of sequence.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123383>
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSEvent {
|
||||
#[method_id(
|
||||
otherEventWithType:
|
||||
location:
|
||||
modifierFlags:
|
||||
timestamp:
|
||||
windowNumber:
|
||||
context:
|
||||
subtype:
|
||||
data1:
|
||||
data2:
|
||||
)]
|
||||
unsafe fn otherEventWithType(
|
||||
type_: NSEventType,
|
||||
location: NSPoint,
|
||||
flags: NSEventModifierFlags,
|
||||
time: NSTimeInterval,
|
||||
window_num: NSInteger,
|
||||
context: Option<&NSObject>, // NSGraphicsContext
|
||||
subtype: NSEventSubtype,
|
||||
data1: NSInteger,
|
||||
data2: NSInteger,
|
||||
) -> Id<Self>;
|
||||
|
||||
pub fn dummy() -> Id<Self> {
|
||||
unsafe {
|
||||
Self::otherEventWithType(
|
||||
NSEventType::NSApplicationDefined,
|
||||
NSPoint::new(0.0, 0.0),
|
||||
NSEventModifierFlags::empty(),
|
||||
0.0,
|
||||
0,
|
||||
None,
|
||||
NSEventSubtype::NSWindowExposedEventType,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[method_id(
|
||||
keyEventWithType:
|
||||
location:
|
||||
modifierFlags:
|
||||
timestamp:
|
||||
windowNumber:
|
||||
context:
|
||||
characters:
|
||||
charactersIgnoringModifiers:
|
||||
isARepeat:
|
||||
keyCode:
|
||||
)]
|
||||
pub fn keyEventWithType(
|
||||
type_: NSEventType,
|
||||
location: NSPoint,
|
||||
modifier_flags: NSEventModifierFlags,
|
||||
timestamp: NSTimeInterval,
|
||||
window_num: NSInteger,
|
||||
context: Option<&NSObject>,
|
||||
characters: &NSString,
|
||||
characters_ignoring_modifiers: &NSString,
|
||||
is_a_repeat: bool,
|
||||
scancode: c_ushort,
|
||||
) -> Id<Self>;
|
||||
|
||||
#[method(locationInWindow)]
|
||||
pub fn locationInWindow(&self) -> NSPoint;
|
||||
|
||||
// TODO: MainThreadMarker
|
||||
#[method(pressedMouseButtons)]
|
||||
pub fn pressedMouseButtons() -> NSUInteger;
|
||||
|
||||
#[method(modifierFlags)]
|
||||
pub fn modifierFlags(&self) -> NSEventModifierFlags;
|
||||
|
||||
#[method(type)]
|
||||
pub fn type_(&self) -> NSEventType;
|
||||
|
||||
#[method(keyCode)]
|
||||
pub fn key_code(&self) -> c_ushort;
|
||||
|
||||
#[method(magnification)]
|
||||
pub fn magnification(&self) -> CGFloat;
|
||||
|
||||
#[method(phase)]
|
||||
pub fn phase(&self) -> NSEventPhase;
|
||||
|
||||
#[method(momentumPhase)]
|
||||
pub fn momentumPhase(&self) -> NSEventPhase;
|
||||
|
||||
#[method(deltaX)]
|
||||
pub fn deltaX(&self) -> CGFloat;
|
||||
|
||||
#[method(deltaY)]
|
||||
pub fn deltaY(&self) -> CGFloat;
|
||||
|
||||
#[method(buttonNumber)]
|
||||
pub fn buttonNumber(&self) -> NSInteger;
|
||||
|
||||
#[method(scrollingDeltaX)]
|
||||
pub fn scrollingDeltaX(&self) -> CGFloat;
|
||||
|
||||
#[method(scrollingDeltaY)]
|
||||
pub fn scrollingDeltaY(&self) -> CGFloat;
|
||||
|
||||
#[method(hasPreciseScrollingDeltas)]
|
||||
pub fn hasPreciseScrollingDeltas(&self) -> bool;
|
||||
|
||||
#[method(rotation)]
|
||||
pub fn rotation(&self) -> f32;
|
||||
|
||||
#[method(pressure)]
|
||||
pub fn pressure(&self) -> f32;
|
||||
|
||||
#[method(stage)]
|
||||
pub fn stage(&self) -> NSInteger;
|
||||
|
||||
#[method(isARepeat)]
|
||||
pub fn is_a_repeat(&self) -> bool;
|
||||
|
||||
#[method(windowNumber)]
|
||||
pub fn window_number(&self) -> NSInteger;
|
||||
|
||||
#[method(timestamp)]
|
||||
pub fn timestamp(&self) -> NSTimeInterval;
|
||||
|
||||
#[method_id(characters)]
|
||||
pub fn characters(&self) -> Option<Id<NSString>>;
|
||||
|
||||
#[method_id(charactersIgnoringModifiers)]
|
||||
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString>>;
|
||||
|
||||
pub fn lshift_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELSHIFTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn rshift_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERSHIFTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn lctrl_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELCTLKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn rctrl_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERCTLKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn lalt_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELALTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn ralt_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERALTKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn lcmd_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICELCMDKEYMASK != 0
|
||||
}
|
||||
|
||||
pub fn rcmd_pressed(&self) -> bool {
|
||||
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||
raw_modifiers & NX_DEVICERCMDKEYMASK != 0
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSEvent {}
|
||||
|
||||
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
|
||||
const NX_DEVICELCTLKEYMASK: u32 = 0x00000001;
|
||||
const NX_DEVICELSHIFTKEYMASK: u32 = 0x00000002;
|
||||
const NX_DEVICERSHIFTKEYMASK: u32 = 0x00000004;
|
||||
const NX_DEVICELCMDKEYMASK: u32 = 0x00000008;
|
||||
const NX_DEVICERCMDKEYMASK: u32 = 0x00000010;
|
||||
const NX_DEVICELALTKEYMASK: u32 = 0x00000020;
|
||||
const NX_DEVICERALTKEYMASK: u32 = 0x00000040;
|
||||
const NX_DEVICERCTLKEYMASK: u32 = 0x00002000;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct NSEventModifierFlags: NSUInteger {
|
||||
const NSAlphaShiftKeyMask = 1 << 16;
|
||||
const NSShiftKeyMask = 1 << 17;
|
||||
const NSControlKeyMask = 1 << 18;
|
||||
const NSAlternateKeyMask = 1 << 19;
|
||||
const NSCommandKeyMask = 1 << 20;
|
||||
const NSNumericPadKeyMask = 1 << 21;
|
||||
const NSHelpKeyMask = 1 << 22;
|
||||
const NSFunctionKeyMask = 1 << 23;
|
||||
const NSDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventModifierFlags {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct NSEventPhase: NSUInteger {
|
||||
const NSEventPhaseNone = 0;
|
||||
const NSEventPhaseBegan = 0x1 << 0;
|
||||
const NSEventPhaseStationary = 0x1 << 1;
|
||||
const NSEventPhaseChanged = 0x1 << 2;
|
||||
const NSEventPhaseEnded = 0x1 << 3;
|
||||
const NSEventPhaseCancelled = 0x1 << 4;
|
||||
const NSEventPhaseMayBegin = 0x1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventPhase {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(i16)] // short
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSEventSubtype {
|
||||
// TODO: Not sure what these values are
|
||||
// NSMouseEventSubtype = NX_SUBTYPE_DEFAULT,
|
||||
// NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT,
|
||||
// NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY
|
||||
// NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH,
|
||||
NSWindowExposedEventType = 0,
|
||||
NSApplicationActivatedEventType = 1,
|
||||
NSApplicationDeactivatedEventType = 2,
|
||||
NSWindowMovedEventType = 4,
|
||||
NSScreenChangedEventType = 8,
|
||||
NSAWTEventType = 16,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventSubtype {
|
||||
const ENCODING: Encoding = i16::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
pub enum NSEventType {
|
||||
NSLeftMouseDown = 1,
|
||||
NSLeftMouseUp = 2,
|
||||
NSRightMouseDown = 3,
|
||||
NSRightMouseUp = 4,
|
||||
NSMouseMoved = 5,
|
||||
NSLeftMouseDragged = 6,
|
||||
NSRightMouseDragged = 7,
|
||||
NSMouseEntered = 8,
|
||||
NSMouseExited = 9,
|
||||
NSKeyDown = 10,
|
||||
NSKeyUp = 11,
|
||||
NSFlagsChanged = 12,
|
||||
NSAppKitDefined = 13,
|
||||
NSSystemDefined = 14,
|
||||
NSApplicationDefined = 15,
|
||||
NSPeriodic = 16,
|
||||
NSCursorUpdate = 17,
|
||||
NSScrollWheel = 22,
|
||||
NSTabletPoint = 23,
|
||||
NSTabletProximity = 24,
|
||||
NSOtherMouseDown = 25,
|
||||
NSOtherMouseUp = 26,
|
||||
NSOtherMouseDragged = 27,
|
||||
NSEventTypeGesture = 29,
|
||||
NSEventTypeMagnify = 30,
|
||||
NSEventTypeSwipe = 31,
|
||||
NSEventTypeRotate = 18,
|
||||
NSEventTypeBeginGesture = 19,
|
||||
NSEventTypeEndGesture = 20,
|
||||
NSEventTypePressure = 34,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use icrate::Foundation::{NSData, NSObject, NSSize, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::NSBitmapImageRep;
|
||||
|
||||
extern_class!(
|
||||
// TODO: Can this be mutable?
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSImage;
|
||||
|
||||
unsafe impl ClassType for NSImage {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented Thread-Unsafe, but:
|
||||
// > One thread can create an NSImage object, draw to the image buffer,
|
||||
// > and pass it off to the main thread for drawing. The underlying image
|
||||
// > cache is shared among all threads.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728>
|
||||
//
|
||||
// So really only unsafe to mutate on several threads.
|
||||
unsafe impl Send for NSImage {}
|
||||
unsafe impl Sync for NSImage {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSImage {
|
||||
pub fn new_by_referencing_file(path: &NSString) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initByReferencingFile: path] }
|
||||
}
|
||||
|
||||
pub fn new_with_data(data: &NSData) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithData: data] }
|
||||
}
|
||||
|
||||
pub fn init_with_size(size: NSSize) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithSize: size] }
|
||||
}
|
||||
|
||||
pub fn add_representation(&self, representation: &NSBitmapImageRep) {
|
||||
unsafe { msg_send![self, addRepresentation: representation] }
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
use icrate::Foundation::NSObject;
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::NSMenuItem;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSMenu;
|
||||
|
||||
unsafe impl ClassType for NSMenu {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSMenu {
|
||||
#[method_id(new)]
|
||||
pub fn new() -> Id<Self>;
|
||||
|
||||
#[method(addItem:)]
|
||||
pub fn addItem(&self, item: &NSMenuItem);
|
||||
}
|
||||
);
|
||||
@@ -1,47 +0,0 @@
|
||||
use icrate::Foundation::{NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::{NSEventModifierFlags, NSMenu};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSMenuItem;
|
||||
|
||||
unsafe impl ClassType for NSMenuItem {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSMenuItem {
|
||||
#[method_id(new)]
|
||||
pub fn new() -> Id<Self>;
|
||||
|
||||
pub fn newWithTitle(
|
||||
title: &NSString,
|
||||
action: Option<Sel>,
|
||||
key_equivalent: &NSString,
|
||||
) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithTitle: title,
|
||||
action: action,
|
||||
keyEquivalent: key_equivalent,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[method_id(separatorItem)]
|
||||
pub fn separatorItem() -> Id<Self>;
|
||||
|
||||
#[method(setKeyEquivalentModifierMask:)]
|
||||
pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags);
|
||||
|
||||
#[method(setSubmenu:)]
|
||||
pub fn setSubmenu(&self, submenu: &NSMenu);
|
||||
}
|
||||
);
|
||||
@@ -1,68 +0,0 @@
|
||||
//! Safe bindings for the AppKit framework.
|
||||
//!
|
||||
//! These are split out from the rest of `winit` to make safety easier to review.
|
||||
//! In the future, these should probably live in another crate like `cacao`.
|
||||
//!
|
||||
//! TODO: Main thread safety.
|
||||
// Objective-C methods have different conventions, and it's much easier to
|
||||
// understand if we just use the same names
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
#![allow(clippy::enum_variant_names)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
mod appearance;
|
||||
mod application;
|
||||
mod bitmap_image_rep;
|
||||
mod button;
|
||||
mod color;
|
||||
mod control;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod image;
|
||||
mod menu;
|
||||
mod menu_item;
|
||||
mod pasteboard;
|
||||
mod responder;
|
||||
mod screen;
|
||||
mod tab_group;
|
||||
mod text_input_client;
|
||||
mod text_input_context;
|
||||
mod version;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
pub(crate) use self::appearance::NSAppearance;
|
||||
pub(crate) use self::application::{
|
||||
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
|
||||
NSRequestUserAttentionType,
|
||||
};
|
||||
pub(crate) use self::bitmap_image_rep::NSBitmapImageRep;
|
||||
pub(crate) use self::button::NSButton;
|
||||
pub(crate) use self::color::NSColor;
|
||||
pub(crate) use self::control::NSControl;
|
||||
pub(crate) use self::cursor::NSCursor;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::event::{
|
||||
NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType,
|
||||
};
|
||||
pub(crate) use self::image::NSImage;
|
||||
pub(crate) use self::menu::NSMenu;
|
||||
pub(crate) use self::menu_item::NSMenuItem;
|
||||
pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType};
|
||||
pub(crate) use self::responder::NSResponder;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
|
||||
pub(crate) use self::tab_group::NSWindowTabGroup;
|
||||
pub(crate) use self::text_input_client::NSTextInputClient;
|
||||
pub(crate) use self::text_input_context::NSTextInputContext;
|
||||
pub(crate) use self::version::NSAppKitVersion;
|
||||
pub(crate) use self::view::{NSTrackingRectTag, NSView};
|
||||
pub(crate) use self::window::{
|
||||
NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState,
|
||||
NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode,
|
||||
NSWindowTitleVisibility,
|
||||
};
|
||||
|
||||
#[link(name = "AppKit", kind = "framework")]
|
||||
extern "C" {}
|
||||
@@ -1,26 +0,0 @@
|
||||
use icrate::Foundation::{NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSPasteboard;
|
||||
|
||||
unsafe impl ClassType for NSPasteboard {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSPasteboard {
|
||||
#[method_id(propertyListForType:)]
|
||||
pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id<NSObject>;
|
||||
}
|
||||
);
|
||||
|
||||
pub type NSPasteboardType = NSString;
|
||||
|
||||
extern "C" {
|
||||
pub static NSFilenamesPboardType: &'static NSPasteboardType;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use icrate::Foundation::{NSArray, NSObject};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::NSEvent;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NSResponder;
|
||||
|
||||
unsafe impl ClassType for NSResponder {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Thread-Unsafe".
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSResponder {
|
||||
#[method(interpretKeyEvents:)]
|
||||
pub(crate) unsafe fn interpretKeyEvents(&self, events: &NSArray<NSEvent>);
|
||||
}
|
||||
);
|
||||
@@ -1,65 +0,0 @@
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSScreen;
|
||||
|
||||
unsafe impl ClassType for NSScreen {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Main thread marker!
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSScreen {
|
||||
/// The application object must have been created.
|
||||
#[method_id(mainScreen)]
|
||||
pub fn main() -> Option<Id<Self>>;
|
||||
|
||||
/// The application object must have been created.
|
||||
#[method_id(screens)]
|
||||
pub fn screens() -> Id<NSArray<Self>>;
|
||||
|
||||
#[method(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(visibleFrame)]
|
||||
pub fn visibleFrame(&self) -> NSRect;
|
||||
|
||||
#[method_id(deviceDescription)]
|
||||
pub fn deviceDescription(&self) -> Id<NSDictionary<NSDeviceDescriptionKey, AnyObject>>;
|
||||
|
||||
pub fn display_id(&self) -> u32 {
|
||||
let key = ns_string!("NSScreenNumber");
|
||||
|
||||
objc2::rc::autoreleasepool(|_| {
|
||||
let device_description = self.deviceDescription();
|
||||
|
||||
// Retrieve the CGDirectDisplayID associated with this screen
|
||||
//
|
||||
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for `deviceDescription` for details:
|
||||
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
|
||||
let obj = device_description
|
||||
.get(key)
|
||||
.expect("failed getting screen display id from device description");
|
||||
let obj: *const AnyObject = obj;
|
||||
let obj: *const NSNumber = obj.cast();
|
||||
let obj: &NSNumber = unsafe { &*obj };
|
||||
|
||||
obj.as_u32()
|
||||
})
|
||||
}
|
||||
|
||||
#[method(backingScaleFactor)]
|
||||
pub fn backingScaleFactor(&self) -> CGFloat;
|
||||
}
|
||||
);
|
||||
|
||||
pub type NSDeviceDescriptionKey = NSString;
|
||||
@@ -1,31 +0,0 @@
|
||||
use icrate::Foundation::{NSArray, NSObject};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::NSWindow;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSWindowTabGroup;
|
||||
|
||||
unsafe impl ClassType for NSWindowTabGroup {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSWindowTabGroup {
|
||||
#[method(selectNextTab)]
|
||||
pub fn selectNextTab(&self);
|
||||
|
||||
#[method(selectPreviousTab)]
|
||||
pub fn selectPreviousTab(&self);
|
||||
|
||||
#[method_id(windows)]
|
||||
pub fn tabbedWindows(&self) -> Option<Id<NSArray<NSWindow>>>;
|
||||
|
||||
#[method(setSelectedWindow:)]
|
||||
pub fn setSelectedWindow(&self, window: &NSWindow);
|
||||
}
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
use objc2::{extern_protocol, ProtocolType};
|
||||
|
||||
extern_protocol!(
|
||||
pub(crate) unsafe trait NSTextInputClient {
|
||||
// TODO: Methods
|
||||
}
|
||||
|
||||
unsafe impl ProtocolType for dyn NSTextInputClient {}
|
||||
);
|
||||
@@ -1,29 +0,0 @@
|
||||
use icrate::Foundation::{NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
type NSTextInputSourceIdentifier = NSString;
|
||||
|
||||
extern_class!(
|
||||
/// Main-Thread-Only!
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSTextInputContext;
|
||||
|
||||
unsafe impl ClassType for NSTextInputContext {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSTextInputContext {
|
||||
#[method(invalidateCharacterCoordinates)]
|
||||
pub fn invalidateCharacterCoordinates(&self);
|
||||
|
||||
#[method(discardMarkedText)]
|
||||
pub fn discardMarkedText(&self);
|
||||
|
||||
#[method_id(selectedKeyboardInputSource)]
|
||||
pub fn selectedKeyboardInputSource(&self) -> Option<Id<NSTextInputSourceIdentifier>>;
|
||||
}
|
||||
);
|
||||
@@ -1,62 +0,0 @@
|
||||
#[repr(transparent)]
|
||||
#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
|
||||
pub struct NSAppKitVersion(f64);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
impl NSAppKitVersion {
|
||||
pub fn current() -> Self {
|
||||
extern "C" {
|
||||
static NSAppKitVersionNumber: NSAppKitVersion;
|
||||
}
|
||||
|
||||
unsafe { NSAppKitVersionNumber }
|
||||
}
|
||||
|
||||
pub fn floor(self) -> Self {
|
||||
Self(self.0.floor())
|
||||
}
|
||||
|
||||
pub const NSAppKitVersionNumber10_0: Self = Self(577.0);
|
||||
pub const NSAppKitVersionNumber10_1: Self = Self(620.0);
|
||||
pub const NSAppKitVersionNumber10_2: Self = Self(663.0);
|
||||
pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6);
|
||||
pub const NSAppKitVersionNumber10_3: Self = Self(743.0);
|
||||
pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14);
|
||||
pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2);
|
||||
pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24);
|
||||
pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33);
|
||||
pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36);
|
||||
pub const NSAppKitVersionNumber10_4: Self = Self(824.0);
|
||||
pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1);
|
||||
pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23);
|
||||
pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33);
|
||||
pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41);
|
||||
pub const NSAppKitVersionNumber10_5: Self = Self(949.0);
|
||||
pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27);
|
||||
pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33);
|
||||
pub const NSAppKitVersionNumber10_6: Self = Self(1038.0);
|
||||
pub const NSAppKitVersionNumber10_7: Self = Self(1138.0);
|
||||
pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23);
|
||||
pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32);
|
||||
pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47);
|
||||
pub const NSAppKitVersionNumber10_8: Self = Self(1187.0);
|
||||
pub const NSAppKitVersionNumber10_9: Self = Self(1265.0);
|
||||
pub const NSAppKitVersionNumber10_10: Self = Self(1343.0);
|
||||
pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0);
|
||||
pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0);
|
||||
pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0);
|
||||
pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0);
|
||||
pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0);
|
||||
pub const NSAppKitVersionNumber10_11: Self = Self(1404.0);
|
||||
pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13);
|
||||
pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34);
|
||||
pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34);
|
||||
pub const NSAppKitVersionNumber10_12: Self = Self(1504.0);
|
||||
pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60);
|
||||
pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76);
|
||||
pub const NSAppKitVersionNumber10_13: Self = Self(1561.0);
|
||||
pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1);
|
||||
pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2);
|
||||
pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroIsize;
|
||||
use std::ptr;
|
||||
|
||||
use icrate::Foundation::{NSObject, NSPoint, NSRect};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::{NSCursor, NSResponder, NSTextInputContext, NSWindow};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSView;
|
||||
|
||||
unsafe impl ClassType for NSView {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Main Thread Only".
|
||||
// > generally thread safe, although operations on views such as creating,
|
||||
// > resizing, and moving should happen on the main thread.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
//
|
||||
// > If you want to use a thread to draw to a view, bracket all drawing code
|
||||
// > between the lockFocusIfCanDraw and unlockFocus methods of NSView.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB>
|
||||
|
||||
extern_methods!(
|
||||
/// Getter methods
|
||||
unsafe impl NSView {
|
||||
#[method(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(bounds)]
|
||||
pub fn bounds(&self) -> NSRect;
|
||||
|
||||
#[method_id(inputContext)]
|
||||
pub fn inputContext(
|
||||
&self,
|
||||
// _mtm: MainThreadMarker,
|
||||
) -> Option<Id<NSTextInputContext>>;
|
||||
|
||||
#[method(hasMarkedText)]
|
||||
pub fn hasMarkedText(&self) -> bool;
|
||||
|
||||
#[method(convertPoint:fromView:)]
|
||||
pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint;
|
||||
|
||||
#[method_id(window)]
|
||||
pub fn window(&self) -> Option<Id<NSWindow>>;
|
||||
}
|
||||
|
||||
unsafe impl NSView {
|
||||
#[method(setWantsBestResolutionOpenGLSurface:)]
|
||||
pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool);
|
||||
|
||||
#[method(setWantsLayer:)]
|
||||
pub fn setWantsLayer(&self, wants_layer: bool);
|
||||
|
||||
#[method(setPostsFrameChangedNotifications:)]
|
||||
pub fn setPostsFrameChangedNotifications(&self, value: bool);
|
||||
|
||||
#[method(removeTrackingRect:)]
|
||||
pub fn removeTrackingRect(&self, tag: NSTrackingRectTag);
|
||||
|
||||
#[method(addTrackingRect:owner:userData:assumeInside:)]
|
||||
unsafe fn inner_addTrackingRect(
|
||||
&self,
|
||||
rect: NSRect,
|
||||
owner: &AnyObject,
|
||||
user_data: *mut c_void,
|
||||
assume_inside: bool,
|
||||
) -> Option<NSTrackingRectTag>;
|
||||
|
||||
pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag {
|
||||
// SAFETY: The user data is NULL, so it is valid
|
||||
unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) }
|
||||
.expect("failed creating tracking rect")
|
||||
}
|
||||
|
||||
#[method(addCursorRect:cursor:)]
|
||||
// NSCursor safe to take by shared reference since it is already immutable
|
||||
pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor);
|
||||
|
||||
#[method(setHidden:)]
|
||||
pub fn setHidden(&self, hidden: bool);
|
||||
}
|
||||
);
|
||||
|
||||
/// <https://developer.apple.com/documentation/appkit/nstrackingrecttag?language=objc>
|
||||
pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero!
|
||||
@@ -1,440 +0,0 @@
|
||||
use icrate::Foundation::{
|
||||
CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
|
||||
use super::{
|
||||
NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView, NSWindowTabGroup,
|
||||
};
|
||||
|
||||
extern_class!(
|
||||
/// Main-Thread-Only!
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NSWindow;
|
||||
|
||||
unsafe impl ClassType for NSWindow {
|
||||
#[inherits(NSObject)]
|
||||
type Super = NSResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Main Thread Only", but:
|
||||
// > Thread safe in that you can create and manage them on a secondary thread.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123364>
|
||||
//
|
||||
// So could in theory be `Send`, and perhaps also `Sync` - but we would like
|
||||
// interior mutability on windows, since that's just much easier, and in that
|
||||
// case, they can't be!
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSWindow {
|
||||
#[method(frame)]
|
||||
pub(crate) fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(windowNumber)]
|
||||
pub(crate) fn windowNumber(&self) -> NSInteger;
|
||||
|
||||
#[method(backingScaleFactor)]
|
||||
pub(crate) fn backingScaleFactor(&self) -> CGFloat;
|
||||
|
||||
#[method_id(contentView)]
|
||||
pub(crate) fn contentView(&self) -> Id<NSView>;
|
||||
|
||||
#[method(setContentView:)]
|
||||
pub(crate) fn setContentView(&self, view: &NSView);
|
||||
|
||||
#[method(setInitialFirstResponder:)]
|
||||
pub(crate) fn setInitialFirstResponder(&self, view: &NSView);
|
||||
|
||||
#[method(makeFirstResponder:)]
|
||||
#[must_use]
|
||||
pub(crate) fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool;
|
||||
|
||||
#[method(contentRectForFrameRect:)]
|
||||
pub(crate) fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect;
|
||||
|
||||
#[method_id(screen)]
|
||||
pub(crate) fn screen(&self) -> Option<Id<NSScreen>>;
|
||||
|
||||
#[method(setContentSize:)]
|
||||
pub(crate) fn setContentSize(&self, contentSize: NSSize);
|
||||
|
||||
#[method(setFrameTopLeftPoint:)]
|
||||
pub(crate) fn setFrameTopLeftPoint(&self, point: NSPoint);
|
||||
|
||||
#[method(setMinSize:)]
|
||||
pub(crate) fn setMinSize(&self, minSize: NSSize);
|
||||
|
||||
#[method(setMaxSize:)]
|
||||
pub(crate) fn setMaxSize(&self, maxSize: NSSize);
|
||||
|
||||
#[method(setResizeIncrements:)]
|
||||
pub(crate) fn setResizeIncrements(&self, increments: NSSize);
|
||||
|
||||
#[method(contentResizeIncrements)]
|
||||
pub(crate) fn contentResizeIncrements(&self) -> NSSize;
|
||||
|
||||
#[method(setContentResizeIncrements:)]
|
||||
pub(crate) fn setContentResizeIncrements(&self, increments: NSSize);
|
||||
|
||||
#[method(setFrame:display:)]
|
||||
pub(crate) fn setFrame_display(&self, frameRect: NSRect, flag: bool);
|
||||
|
||||
#[method(setMovable:)]
|
||||
pub(crate) fn setMovable(&self, movable: bool);
|
||||
|
||||
#[method(setSharingType:)]
|
||||
pub(crate) fn setSharingType(&self, sharingType: NSWindowSharingType);
|
||||
|
||||
#[method(setTabbingMode:)]
|
||||
pub(crate) fn setTabbingMode(&self, tabbingMode: NSWindowTabbingMode);
|
||||
|
||||
#[method(setOpaque:)]
|
||||
pub(crate) fn setOpaque(&self, opaque: bool);
|
||||
|
||||
#[method(hasShadow)]
|
||||
pub(crate) fn hasShadow(&self) -> bool;
|
||||
|
||||
#[method(setHasShadow:)]
|
||||
pub(crate) fn setHasShadow(&self, has_shadow: bool);
|
||||
|
||||
#[method(setIgnoresMouseEvents:)]
|
||||
pub(crate) fn setIgnoresMouseEvents(&self, ignores: bool);
|
||||
|
||||
#[method(setBackgroundColor:)]
|
||||
pub(crate) fn setBackgroundColor(&self, color: &NSColor);
|
||||
|
||||
#[method(styleMask)]
|
||||
pub(crate) fn styleMask(&self) -> NSWindowStyleMask;
|
||||
|
||||
#[method(setStyleMask:)]
|
||||
pub(crate) fn setStyleMask(&self, mask: NSWindowStyleMask);
|
||||
|
||||
#[method(registerForDraggedTypes:)]
|
||||
pub(crate) fn registerForDraggedTypes(&self, types: &NSArray<NSPasteboardType>);
|
||||
|
||||
#[method(makeKeyAndOrderFront:)]
|
||||
pub(crate) fn makeKeyAndOrderFront(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(orderFront:)]
|
||||
pub(crate) fn orderFront(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(miniaturize:)]
|
||||
pub(crate) fn miniaturize(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(deminiaturize:)]
|
||||
pub(crate) fn deminiaturize(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(toggleFullScreen:)]
|
||||
pub(crate) fn toggleFullScreen(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(orderOut:)]
|
||||
pub(crate) fn orderOut(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(zoom:)]
|
||||
pub(crate) fn zoom(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(selectNextKeyView:)]
|
||||
pub(crate) fn selectNextKeyView(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method(selectPreviousKeyView:)]
|
||||
pub(crate) fn selectPreviousKeyView(&self, sender: Option<&AnyObject>);
|
||||
|
||||
#[method_id(firstResponder)]
|
||||
pub(crate) fn firstResponder(&self) -> Option<Id<NSResponder>>;
|
||||
|
||||
#[method_id(standardWindowButton:)]
|
||||
pub(crate) fn standardWindowButton(&self, kind: NSWindowButton) -> Option<Id<NSButton>>;
|
||||
|
||||
#[method(setTitle:)]
|
||||
pub(crate) fn setTitle(&self, title: &NSString);
|
||||
|
||||
#[method_id(title)]
|
||||
pub(crate) fn title_(&self) -> Id<NSString>;
|
||||
|
||||
#[method(setReleasedWhenClosed:)]
|
||||
pub(crate) fn setReleasedWhenClosed(&self, val: bool);
|
||||
|
||||
#[method(setAcceptsMouseMovedEvents:)]
|
||||
pub(crate) fn setAcceptsMouseMovedEvents(&self, val: bool);
|
||||
|
||||
#[method(setTitlebarAppearsTransparent:)]
|
||||
pub(crate) fn setTitlebarAppearsTransparent(&self, val: bool);
|
||||
|
||||
#[method(setTitleVisibility:)]
|
||||
pub(crate) fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility);
|
||||
|
||||
#[method(setMovableByWindowBackground:)]
|
||||
pub(crate) fn setMovableByWindowBackground(&self, val: bool);
|
||||
|
||||
#[method(setLevel:)]
|
||||
pub(crate) fn setLevel(&self, level: NSWindowLevel);
|
||||
|
||||
#[method(setAllowsAutomaticWindowTabbing:)]
|
||||
pub(crate) fn setAllowsAutomaticWindowTabbing(val: bool);
|
||||
|
||||
#[method(setTabbingIdentifier:)]
|
||||
pub(crate) fn setTabbingIdentifier(&self, identifier: &NSString);
|
||||
|
||||
#[method(setDocumentEdited:)]
|
||||
pub(crate) fn setDocumentEdited(&self, val: bool);
|
||||
|
||||
#[method(occlusionState)]
|
||||
pub(crate) fn occlusionState(&self) -> NSWindowOcclusionState;
|
||||
|
||||
#[method(center)]
|
||||
pub(crate) fn center(&self);
|
||||
|
||||
#[method(isResizable)]
|
||||
pub(crate) fn isResizable(&self) -> bool;
|
||||
|
||||
#[method(isMiniaturizable)]
|
||||
pub(crate) fn isMiniaturizable(&self) -> bool;
|
||||
|
||||
#[method(hasCloseBox)]
|
||||
pub(crate) fn hasCloseBox(&self) -> bool;
|
||||
|
||||
#[method(isMiniaturized)]
|
||||
pub(crate) fn isMiniaturized(&self) -> bool;
|
||||
|
||||
#[method(isVisible)]
|
||||
pub(crate) fn isVisible(&self) -> bool;
|
||||
|
||||
#[method(isKeyWindow)]
|
||||
pub(crate) fn isKeyWindow(&self) -> bool;
|
||||
|
||||
#[method(isZoomed)]
|
||||
pub(crate) fn isZoomed(&self) -> bool;
|
||||
|
||||
#[method(allowsAutomaticWindowTabbing)]
|
||||
pub(crate) fn allowsAutomaticWindowTabbing() -> bool;
|
||||
|
||||
#[method(selectNextTab)]
|
||||
pub(crate) fn selectNextTab(&self);
|
||||
|
||||
#[method_id(tabbingIdentifier)]
|
||||
pub(crate) fn tabbingIdentifier(&self) -> Id<NSString>;
|
||||
|
||||
#[method_id(tabGroup)]
|
||||
pub(crate) fn tabGroup(&self) -> Option<Id<NSWindowTabGroup>>;
|
||||
|
||||
#[method(isDocumentEdited)]
|
||||
pub(crate) fn isDocumentEdited(&self) -> bool;
|
||||
|
||||
#[method(close)]
|
||||
pub(crate) fn close(&self);
|
||||
|
||||
#[method(performWindowDragWithEvent:)]
|
||||
// TODO: Can this actually accept NULL?
|
||||
pub(crate) fn performWindowDragWithEvent(&self, event: Option<&NSEvent>);
|
||||
|
||||
#[method(invalidateCursorRectsForView:)]
|
||||
pub(crate) fn invalidateCursorRectsForView(&self, view: &NSView);
|
||||
|
||||
#[method(setDelegate:)]
|
||||
pub(crate) fn setDelegate(&self, delegate: Option<&NSObject>);
|
||||
|
||||
#[method(sendEvent:)]
|
||||
pub(crate) unsafe fn sendEvent(&self, event: &NSEvent);
|
||||
|
||||
#[method(addChildWindow:ordered:)]
|
||||
pub(crate) unsafe fn addChildWindow(&self, child: &NSWindow, ordered: NSWindowOrderingMode);
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowTitleVisibility {
|
||||
#[doc(alias = "NSWindowTitleVisible")]
|
||||
Visible = 0,
|
||||
#[doc(alias = "NSWindowTitleHidden")]
|
||||
Hidden = 1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowTitleVisibility {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowButton {
|
||||
#[doc(alias = "NSWindowCloseButton")]
|
||||
Close = 0,
|
||||
#[doc(alias = "NSWindowMiniaturizeButton")]
|
||||
Miniaturize = 1,
|
||||
#[doc(alias = "NSWindowZoomButton")]
|
||||
Zoom = 2,
|
||||
#[doc(alias = "NSWindowToolbarButton")]
|
||||
Toolbar = 3,
|
||||
#[doc(alias = "NSWindowDocumentIconButton")]
|
||||
DocumentIcon = 4,
|
||||
#[doc(alias = "NSWindowDocumentVersionsButton")]
|
||||
DocumentVersions = 6,
|
||||
#[doc(alias = "NSWindowFullScreenButton")]
|
||||
#[deprecated = "Deprecated since macOS 10.12"]
|
||||
FullScreen = 7,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowButton {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
// CGWindowLevel.h
|
||||
//
|
||||
// Note: There are two different things at play in this header:
|
||||
// `CGWindowLevel` and `CGWindowLevelKey`.
|
||||
//
|
||||
// It seems like there was a push towards using "key" values instead of the
|
||||
// raw window level values, and then you were supposed to use
|
||||
// `CGWindowLevelForKey` to get the actual level.
|
||||
//
|
||||
// But the values that `NSWindowLevel` has are compiled in, and as such has
|
||||
// to remain ABI compatible, so they're safe for us to hardcode as well.
|
||||
#[allow(dead_code)]
|
||||
mod window_level {
|
||||
const kCGNumReservedWindowLevels: i32 = 16;
|
||||
const kCGNumReservedBaseWindowLevels: i32 = 5;
|
||||
|
||||
pub const kCGBaseWindowLevel: i32 = i32::MIN;
|
||||
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
|
||||
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
|
||||
|
||||
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
|
||||
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
|
||||
pub const kCGBackstopMenuLevel: i32 = -20;
|
||||
pub const kCGNormalWindowLevel: i32 = 0;
|
||||
pub const kCGFloatingWindowLevel: i32 = 3;
|
||||
pub const kCGTornOffMenuWindowLevel: i32 = 3;
|
||||
pub const kCGModalPanelWindowLevel: i32 = 8;
|
||||
pub const kCGUtilityWindowLevel: i32 = 19;
|
||||
pub const kCGDockWindowLevel: i32 = 20;
|
||||
pub const kCGMainMenuWindowLevel: i32 = 24;
|
||||
pub const kCGStatusWindowLevel: i32 = 25;
|
||||
pub const kCGPopUpMenuWindowLevel: i32 = 101;
|
||||
pub const kCGOverlayWindowLevel: i32 = 102;
|
||||
pub const kCGHelpWindowLevel: i32 = 200;
|
||||
pub const kCGDraggingWindowLevel: i32 = 500;
|
||||
pub const kCGScreenSaverWindowLevel: i32 = 1000;
|
||||
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
|
||||
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
|
||||
}
|
||||
use window_level::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct NSWindowLevel(pub NSInteger);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NSWindowLevel {
|
||||
#[doc(alias = "BelowNormalWindowLevel")]
|
||||
pub const BELOW_NORMAL: Self = Self((kCGNormalWindowLevel - 1) as _);
|
||||
#[doc(alias = "NSNormalWindowLevel")]
|
||||
pub const Normal: Self = Self(kCGNormalWindowLevel as _);
|
||||
#[doc(alias = "NSFloatingWindowLevel")]
|
||||
pub const Floating: Self = Self(kCGFloatingWindowLevel as _);
|
||||
#[doc(alias = "NSTornOffMenuWindowLevel")]
|
||||
pub const TornOffMenu: Self = Self(kCGTornOffMenuWindowLevel as _);
|
||||
#[doc(alias = "NSModalPanelWindowLevel")]
|
||||
pub const ModalPanel: Self = Self(kCGModalPanelWindowLevel as _);
|
||||
#[doc(alias = "NSMainMenuWindowLevel")]
|
||||
pub const MainMenu: Self = Self(kCGMainMenuWindowLevel as _);
|
||||
#[doc(alias = "NSStatusWindowLevel")]
|
||||
pub const Status: Self = Self(kCGStatusWindowLevel as _);
|
||||
#[doc(alias = "NSPopUpMenuWindowLevel")]
|
||||
pub const PopUpMenu: Self = Self(kCGPopUpMenuWindowLevel as _);
|
||||
#[doc(alias = "NSScreenSaverWindowLevel")]
|
||||
pub const ScreenSaver: Self = Self(kCGScreenSaverWindowLevel as _);
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowLevel {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NSWindowOcclusionState: NSUInteger {
|
||||
const NSWindowOcclusionStateVisible = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowOcclusionState {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NSWindowStyleMask: NSUInteger {
|
||||
const NSBorderlessWindowMask = 0;
|
||||
const NSTitledWindowMask = 1 << 0;
|
||||
const NSClosableWindowMask = 1 << 1;
|
||||
const NSMiniaturizableWindowMask = 1 << 2;
|
||||
const NSResizableWindowMask = 1 << 3;
|
||||
const NSTexturedBackgroundWindowMask = 1 << 8;
|
||||
const NSUnifiedTitleAndToolbarWindowMask = 1 << 12;
|
||||
const NSFullScreenWindowMask = 1 << 14;
|
||||
const NSFullSizeContentViewWindowMask = 1 << 15;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowStyleMask {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSBackingStoreType {
|
||||
NSBackingStoreRetained = 0,
|
||||
NSBackingStoreNonretained = 1,
|
||||
NSBackingStoreBuffered = 2,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSBackingStoreType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowSharingType {
|
||||
NSWindowSharingNone = 0,
|
||||
NSWindowSharingReadOnly = 1,
|
||||
NSWindowSharingReadWrite = 2,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowSharingType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowOrderingMode {
|
||||
NSWindowAbove = 1,
|
||||
NSWindowBelow = -1,
|
||||
NSWindowOut = 0,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowOrderingMode {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowTabbingMode {
|
||||
NSWindowTabbingModeAutomatic = 0,
|
||||
NSWindowTabbingModeDisallowed = 2,
|
||||
NSWindowTabbingModePreferred = 1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowTabbingMode {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
228
src/platform_impl/macos/cursor.rs
Normal file
228
src/platform_impl/macos/cursor.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use icrate::AppKit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
|
||||
use icrate::Foundation::{
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
|
||||
NSString,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ffi::c_uchar;
|
||||
use std::slice;
|
||||
|
||||
use super::EventLoopWindowTarget;
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::cursor::OnlyCursorImageBuilder;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct CustomCursor(pub(crate) Id<NSCursor>);
|
||||
|
||||
// SAFETY: NSCursor is immutable and thread-safe
|
||||
// TODO(madsmtm): Put this logic in icrate itself
|
||||
unsafe impl Send for CustomCursor {}
|
||||
unsafe impl Sync for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn build<T>(
|
||||
cursor: OnlyCursorImageBuilder,
|
||||
_: &EventLoopWindowTarget<T>,
|
||||
) -> CustomCursor {
|
||||
Self(cursor_from_image(&cursor.0))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> {
|
||||
let width = cursor.width;
|
||||
let height = cursor.height;
|
||||
|
||||
let bitmap = unsafe {
|
||||
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
|
||||
NSBitmapImageRep::alloc(),
|
||||
std::ptr::null_mut::<*mut c_uchar>(),
|
||||
width as isize,
|
||||
height as isize,
|
||||
8,
|
||||
4,
|
||||
true,
|
||||
false,
|
||||
NSDeviceRGBColorSpace,
|
||||
width as isize * 4,
|
||||
32,
|
||||
).unwrap()
|
||||
};
|
||||
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
|
||||
bitmap_data.copy_from_slice(&cursor.rgba);
|
||||
|
||||
let image = unsafe {
|
||||
NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()))
|
||||
};
|
||||
unsafe { image.addRepresentation(&bitmap) };
|
||||
|
||||
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
|
||||
|
||||
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
|
||||
}
|
||||
|
||||
pub(crate) fn default_cursor() -> Id<NSCursor> {
|
||||
NSCursor::arrowCursor()
|
||||
}
|
||||
|
||||
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Id<NSCursor>> {
|
||||
let cls = NSCursor::class();
|
||||
if cls.responds_to(sel) {
|
||||
let cursor: Id<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
Some(cursor)
|
||||
} else {
|
||||
warn!("cursor `{sel}` appears to be invalid");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! def_undocumented_cursor {
|
||||
{$(
|
||||
$(#[$($m:meta)*])*
|
||||
fn $name:ident();
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
#[allow(non_snake_case)]
|
||||
fn $name() -> Id<NSCursor> {
|
||||
unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) }
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
def_undocumented_cursor!(
|
||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
||||
fn _helpCursor();
|
||||
fn _zoomInCursor();
|
||||
fn _zoomOutCursor();
|
||||
fn _windowResizeNorthEastCursor();
|
||||
fn _windowResizeNorthWestCursor();
|
||||
fn _windowResizeSouthEastCursor();
|
||||
fn _windowResizeSouthWestCursor();
|
||||
fn _windowResizeNorthEastSouthWestCursor();
|
||||
fn _windowResizeNorthWestSouthEastCursor();
|
||||
|
||||
// While these two are available, the former just loads a white arrow,
|
||||
// and the latter loads an ugly deflated beachball!
|
||||
// pub fn _moveCursor();
|
||||
// pub fn _waitCursor();
|
||||
|
||||
// An even more undocumented cursor...
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
||||
fn busyButClickableCursor();
|
||||
);
|
||||
|
||||
// Note that loading `busybutclickable` with this code won't animate
|
||||
// the frames; instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
|
||||
// Snatch a cursor from WebKit; They fit the style of the native
|
||||
// cursors, and will seem completely standard to macOS users.
|
||||
//
|
||||
// https://stackoverflow.com/a/21786835/5435443
|
||||
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors");
|
||||
let cursor_path = root.stringByAppendingPathComponent(name);
|
||||
|
||||
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
|
||||
let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap();
|
||||
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
<NSDictionary<NSObject, NSObject>>::class(),
|
||||
dictionaryWithContentsOfFile: &*info_path,
|
||||
]
|
||||
};
|
||||
let mut x = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
x = unsafe { &*ptr }.as_cgfloat()
|
||||
}
|
||||
}
|
||||
let mut y = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
y = unsafe { &*ptr }.as_cgfloat()
|
||||
}
|
||||
}
|
||||
|
||||
let hotspot = NSPoint::new(x, y);
|
||||
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
|
||||
}
|
||||
|
||||
fn webkit_move() -> Id<NSCursor> {
|
||||
unsafe { load_webkit_cursor(ns_string!("move")) }
|
||||
}
|
||||
|
||||
fn webkit_cell() -> Id<NSCursor> {
|
||||
unsafe { load_webkit_cursor(ns_string!("cell")) }
|
||||
}
|
||||
|
||||
pub(crate) fn invisible_cursor() -> Id<NSCursor> {
|
||||
// 16x16 GIF data for invisible cursor
|
||||
// You can reproduce this via ImageMagick.
|
||||
// $ convert -size 16x16 xc:none cursor.gif
|
||||
static CURSOR_BYTES: &[u8] = &[
|
||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F,
|
||||
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
|
||||
];
|
||||
|
||||
static CURSOR: Lazy<CustomCursor> = Lazy::new(|| {
|
||||
// TODO: Consider using `dataWithBytesNoCopy:`
|
||||
let data = NSData::with_bytes(CURSOR_BYTES);
|
||||
let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap();
|
||||
let hotspot = NSPoint::new(0.0, 0.0);
|
||||
CustomCursor(NSCursor::initWithImage_hotSpot(
|
||||
NSCursor::alloc(),
|
||||
&image,
|
||||
hotspot,
|
||||
))
|
||||
});
|
||||
|
||||
CURSOR.0.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Id<NSCursor> {
|
||||
match icon {
|
||||
CursorIcon::Default => default_cursor(),
|
||||
CursorIcon::Pointer => NSCursor::pointingHandCursor(),
|
||||
CursorIcon::Grab => NSCursor::openHandCursor(),
|
||||
CursorIcon::Grabbing => NSCursor::closedHandCursor(),
|
||||
CursorIcon::Text => NSCursor::IBeamCursor(),
|
||||
CursorIcon::VerticalText => NSCursor::IBeamCursorForVerticalLayout(),
|
||||
CursorIcon::Copy => NSCursor::dragCopyCursor(),
|
||||
CursorIcon::Alias => NSCursor::dragLinkCursor(),
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
|
||||
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
|
||||
CursorIcon::Crosshair => NSCursor::crosshairCursor(),
|
||||
CursorIcon::EResize => NSCursor::resizeRightCursor(),
|
||||
CursorIcon::NResize => NSCursor::resizeUpCursor(),
|
||||
CursorIcon::WResize => NSCursor::resizeLeftCursor(),
|
||||
CursorIcon::SResize => NSCursor::resizeDownCursor(),
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
|
||||
CursorIcon::Help => _helpCursor(),
|
||||
CursorIcon::ZoomIn => _zoomInCursor(),
|
||||
CursorIcon::ZoomOut => _zoomOutCursor(),
|
||||
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
|
||||
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
|
||||
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
|
||||
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
|
||||
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),
|
||||
CursorIcon::Move | CursorIcon::AllScroll => webkit_move(),
|
||||
CursorIcon::Cell => webkit_cell(),
|
||||
_ => default_cursor(),
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,15 @@ use core_foundation::{
|
||||
base::CFRelease,
|
||||
data::{CFDataGetBytePtr, CFDataRef},
|
||||
};
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use icrate::AppKit::{
|
||||
NSEvent, NSEventModifierFlagCommand, NSEventModifierFlagControl, NSEventModifierFlagOption,
|
||||
NSEventModifierFlagShift, NSEventModifierFlags, NSEventSubtypeWindowExposed,
|
||||
NSEventTypeApplicationDefined,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSPoint};
|
||||
use objc2::rc::Id;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use super::appkit::{NSEvent, NSEventModifierFlags};
|
||||
use crate::{
|
||||
event::{ElementState, KeyEvent, Modifiers},
|
||||
keyboard::{
|
||||
@@ -98,8 +103,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
}
|
||||
|
||||
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
|
||||
let string = ns_event
|
||||
.charactersIgnoringModifiers()
|
||||
let string = unsafe { ns_event.charactersIgnoringModifiers() }
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
if string.is_empty() {
|
||||
@@ -122,15 +126,14 @@ pub(crate) fn create_key_event(
|
||||
use ElementState::{Pressed, Released};
|
||||
let state = if is_press { Pressed } else { Released };
|
||||
|
||||
let scancode = ns_event.key_code();
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let mut physical_key =
|
||||
key_override.unwrap_or_else(|| PhysicalKey::from_scancode(scancode as u32));
|
||||
|
||||
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
|
||||
None
|
||||
} else {
|
||||
let characters = ns_event
|
||||
.characters()
|
||||
let characters = unsafe { ns_event.characters() }
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
if characters.is_empty() {
|
||||
@@ -148,8 +151,8 @@ pub(crate) fn create_key_event(
|
||||
let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) {
|
||||
let key_without_modifiers = get_modifierless_char(scancode);
|
||||
|
||||
let modifiers = NSEvent::modifierFlags(ns_event);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let modifiers = unsafe { ns_event.modifierFlags() };
|
||||
let has_ctrl = flags_contains(modifiers, NSEventModifierFlagControl);
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl here, not checking for alt because we DO want to
|
||||
@@ -311,42 +314,84 @@ pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey {
|
||||
}
|
||||
}
|
||||
|
||||
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
|
||||
const NX_DEVICELCTLKEYMASK: NSEventModifierFlags = 0x00000001;
|
||||
const NX_DEVICELSHIFTKEYMASK: NSEventModifierFlags = 0x00000002;
|
||||
const NX_DEVICERSHIFTKEYMASK: NSEventModifierFlags = 0x00000004;
|
||||
const NX_DEVICELCMDKEYMASK: NSEventModifierFlags = 0x00000008;
|
||||
const NX_DEVICERCMDKEYMASK: NSEventModifierFlags = 0x00000010;
|
||||
const NX_DEVICELALTKEYMASK: NSEventModifierFlags = 0x00000020;
|
||||
const NX_DEVICERALTKEYMASK: NSEventModifierFlags = 0x00000040;
|
||||
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = 0x00002000;
|
||||
|
||||
pub(super) fn flags_contains(flags: NSEventModifierFlags, value: NSEventModifierFlags) -> bool {
|
||||
flags & value == value
|
||||
}
|
||||
|
||||
pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
|
||||
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICELALTKEYMASK)
|
||||
}
|
||||
|
||||
pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
|
||||
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICERALTKEYMASK)
|
||||
}
|
||||
|
||||
pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
let flags = event.modifierFlags();
|
||||
let flags = unsafe { event.modifierFlags() };
|
||||
let mut state = ModifiersState::empty();
|
||||
let mut pressed_mods = ModifiersKeys::empty();
|
||||
|
||||
state.set(
|
||||
ModifiersState::SHIFT,
|
||||
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagShift),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LSHIFT,
|
||||
flags_contains(flags, NX_DEVICELSHIFTKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RSHIFT,
|
||||
flags_contains(flags, NX_DEVICERSHIFTKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, event.lshift_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, event.rshift_pressed());
|
||||
|
||||
state.set(
|
||||
ModifiersState::CONTROL,
|
||||
flags.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagControl),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LCONTROL,
|
||||
flags_contains(flags, NX_DEVICELCTLKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RCONTROL,
|
||||
flags_contains(flags, NX_DEVICERCTLKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, event.lctrl_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RCONTROL, event.rctrl_pressed());
|
||||
|
||||
state.set(
|
||||
ModifiersState::ALT,
|
||||
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagOption),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LALT,
|
||||
flags_contains(flags, NX_DEVICELALTKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RALT,
|
||||
flags_contains(flags, NX_DEVICERALTKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LALT, event.lalt_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RALT, event.ralt_pressed());
|
||||
|
||||
state.set(
|
||||
ModifiersState::SUPER,
|
||||
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
flags_contains(flags, NSEventModifierFlagCommand),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::LSUPER,
|
||||
flags_contains(flags, NX_DEVICELCMDKEYMASK),
|
||||
);
|
||||
pressed_mods.set(
|
||||
ModifiersKeys::RSUPER,
|
||||
flags_contains(flags, NX_DEVICERCMDKEYMASK),
|
||||
);
|
||||
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, event.lcmd_pressed());
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, event.rcmd_pressed());
|
||||
|
||||
Modifiers {
|
||||
state,
|
||||
@@ -354,6 +399,22 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dummy_event() -> Option<Id<NSEvent>> {
|
||||
unsafe {
|
||||
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
|
||||
NSEventTypeApplicationDefined,
|
||||
NSPoint::new(0.0, 0.0),
|
||||
0, // Empty NSEventModifierFlags
|
||||
0.0,
|
||||
0,
|
||||
None,
|
||||
NSEventSubtypeWindowExposed,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
let code = match self {
|
||||
|
||||
@@ -17,12 +17,18 @@ use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::NSObjectProtocol;
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSApplicationActivationPolicyAccessory, NSApplicationActivationPolicyProhibited,
|
||||
NSApplicationActivationPolicyRegular, NSWindow,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSObjectProtocol};
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use objc2::{
|
||||
rc::{autoreleasepool, Id},
|
||||
runtime::ProtocolObject,
|
||||
};
|
||||
|
||||
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow};
|
||||
use super::event::dummy_event;
|
||||
use crate::{
|
||||
error::EventLoopError,
|
||||
event::Event,
|
||||
@@ -123,19 +129,19 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub(crate) fn hide_application(&self) {
|
||||
NSApplication::shared(self.mtm).hide(None)
|
||||
NSApplication::sharedApplication(self.mtm).hide(None)
|
||||
}
|
||||
|
||||
pub(crate) fn hide_other_applications(&self) {
|
||||
NSApplication::shared(self.mtm).hideOtherApplications(None)
|
||||
NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
|
||||
}
|
||||
|
||||
pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
|
||||
NSWindow::setAllowsAutomaticWindowTabbing(enabled)
|
||||
NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
|
||||
}
|
||||
|
||||
pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
|
||||
NSWindow::allowsAutomaticWindowTabbing()
|
||||
NSWindow::allowsAutomaticWindowTabbing(self.mtm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,20 +202,20 @@ impl<T> EventLoop<T> {
|
||||
panic!("`winit` requires control over the principal class. You must create the event loop before other parts of your application initialize NSApplication");
|
||||
}
|
||||
|
||||
use NSApplicationActivationPolicy::*;
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
||||
};
|
||||
let delegate = ApplicationDelegate::new(
|
||||
mtm,
|
||||
activation_policy,
|
||||
attributes.default_menu,
|
||||
attributes.activate_ignoring_other_apps,
|
||||
);
|
||||
|
||||
autoreleasepool(|_| {
|
||||
app.setDelegate(&delegate);
|
||||
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
});
|
||||
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
@@ -307,7 +313,7 @@ impl<T> EventLoop<T> {
|
||||
|
||||
// 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 `NSApp` and saving the
|
||||
// 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() {
|
||||
@@ -355,8 +361,6 @@ impl<T> EventLoop<T> {
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
autoreleasepool(|_| {
|
||||
let app = NSApp();
|
||||
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
||||
@@ -377,25 +381,24 @@ impl<T> EventLoop<T> {
|
||||
// catch panics to make sure we can't unwind without clearing the set callback
|
||||
// (which would leave the global `AppState` in an undefined, unsafe state)
|
||||
let catch_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
// As a special case, if the `NSApp` hasn't been launched yet then we at least run
|
||||
// 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 !AppState::is_launched() {
|
||||
debug_assert!(!AppState::is_running());
|
||||
|
||||
AppState::request_stop_on_launch();
|
||||
unsafe {
|
||||
app.run();
|
||||
self.app.run();
|
||||
}
|
||||
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application has launched
|
||||
} else if !AppState::is_running() {
|
||||
// Even though the NSApp may have been launched, it's possible we aren't 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.
|
||||
AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
|
||||
} else {
|
||||
// Only run the NSApp for as long as the given `Duration` allows so we
|
||||
// don't block the external loop.
|
||||
// Only run for as long as the given `Duration` allows so we don't block the external loop.
|
||||
match timeout {
|
||||
Some(Duration::ZERO) => {
|
||||
AppState::set_wait_timeout(None);
|
||||
@@ -415,13 +418,13 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
AppState::set_stop_app_on_redraw_requested(true);
|
||||
unsafe {
|
||||
app.run();
|
||||
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 `NSApp` and saving the
|
||||
// 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() {
|
||||
@@ -459,6 +462,7 @@ impl<T> EventLoop<T> {
|
||||
/// 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> {
|
||||
@@ -473,11 +477,11 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
let panic_info = panic_info.upgrade().unwrap();
|
||||
panic_info.set_panic(e);
|
||||
}
|
||||
let app = NSApp();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
app.stop(None);
|
||||
// Posting a dummy event to get `stop` to take effect immediately.
|
||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
||||
app.postEvent_atStart(&dummy_event().unwrap(), true);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,3 +210,45 @@ extern "C" {
|
||||
unicodeString: *mut UniChar,
|
||||
) -> OSStatus;
|
||||
}
|
||||
|
||||
// CGWindowLevel.h
|
||||
//
|
||||
// Note: There are two different things at play in this header:
|
||||
// `CGWindowLevel` and `CGWindowLevelKey`.
|
||||
//
|
||||
// It seems like there was a push towards using "key" values instead of the
|
||||
// raw window level values, and then you were supposed to use
|
||||
// `CGWindowLevelForKey` to get the actual level.
|
||||
//
|
||||
// But the values that `NSWindowLevel` has are compiled in, and as such has
|
||||
// to remain ABI compatible, so they're safe for us to hardcode as well.
|
||||
#[allow(dead_code, non_upper_case_globals)]
|
||||
mod window_level {
|
||||
const kCGNumReservedWindowLevels: i32 = 16;
|
||||
const kCGNumReservedBaseWindowLevels: i32 = 5;
|
||||
|
||||
pub const kCGBaseWindowLevel: i32 = i32::MIN;
|
||||
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
|
||||
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
|
||||
|
||||
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
|
||||
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
|
||||
pub const kCGBackstopMenuLevel: i32 = -20;
|
||||
pub const kCGNormalWindowLevel: i32 = 0;
|
||||
pub const kCGFloatingWindowLevel: i32 = 3;
|
||||
pub const kCGTornOffMenuWindowLevel: i32 = 3;
|
||||
pub const kCGModalPanelWindowLevel: i32 = 8;
|
||||
pub const kCGUtilityWindowLevel: i32 = 19;
|
||||
pub const kCGDockWindowLevel: i32 = 20;
|
||||
pub const kCGMainMenuWindowLevel: i32 = 24;
|
||||
pub const kCGStatusWindowLevel: i32 = 25;
|
||||
pub const kCGPopUpMenuWindowLevel: i32 = 101;
|
||||
pub const kCGOverlayWindowLevel: i32 = 102;
|
||||
pub const kCGHelpWindowLevel: i32 = 200;
|
||||
pub const kCGDraggingWindowLevel: i32 = 500;
|
||||
pub const kCGScreenSaverWindowLevel: i32 = 1000;
|
||||
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
|
||||
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
|
||||
}
|
||||
|
||||
pub use window_level::*;
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{NSProcessInfo, NSString};
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSEventModifierFlagCommand, NSEventModifierFlagOption, NSEventModifierFlags,
|
||||
NSMenu, NSMenuItem,
|
||||
};
|
||||
use icrate::Foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::sel;
|
||||
|
||||
use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
|
||||
struct KeyEquivalent<'a> {
|
||||
key: &'a NSString,
|
||||
masks: Option<NSEventModifierFlags>,
|
||||
}
|
||||
|
||||
pub fn initialize() {
|
||||
let menubar = NSMenu::new();
|
||||
let app_menu_item = NSMenuItem::new();
|
||||
pub fn initialize(app: &NSApplication) {
|
||||
let mtm = MainThreadMarker::from(app);
|
||||
let menubar = NSMenu::new(mtm);
|
||||
let app_menu_item = NSMenuItem::new(mtm);
|
||||
menubar.addItem(&app_menu_item);
|
||||
|
||||
let app_menu = NSMenu::new();
|
||||
let app_menu = NSMenu::new(mtm);
|
||||
let process_name = NSProcessInfo::processInfo().processName();
|
||||
|
||||
// About menu item
|
||||
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
|
||||
let about_item = menu_item(
|
||||
mtm,
|
||||
&about_item_title,
|
||||
Some(sel!(orderFrontStandardAboutPanel:)),
|
||||
None,
|
||||
);
|
||||
|
||||
// Services menu item
|
||||
let services_menu = NSMenu::new();
|
||||
let services_item = menu_item(ns_string!("Services"), None, None);
|
||||
services_item.setSubmenu(&services_menu);
|
||||
let services_menu = NSMenu::new(mtm);
|
||||
let services_item = menu_item(mtm, ns_string!("Services"), None, None);
|
||||
services_item.setSubmenu(Some(&services_menu));
|
||||
|
||||
// Seperator menu item
|
||||
let sep_first = NSMenuItem::separatorItem();
|
||||
let sep_first = NSMenuItem::separatorItem(mtm);
|
||||
|
||||
// Hide application menu item
|
||||
let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name);
|
||||
let hide_item = menu_item(
|
||||
mtm,
|
||||
&hide_item_title,
|
||||
Some(sel!(hide:)),
|
||||
Some(KeyEquivalent {
|
||||
@@ -49,30 +53,31 @@ pub fn initialize() {
|
||||
// Hide other applications menu item
|
||||
let hide_others_item_title = ns_string!("Hide Others");
|
||||
let hide_others_item = menu_item(
|
||||
mtm,
|
||||
hide_others_item_title,
|
||||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask,
|
||||
),
|
||||
masks: Some(NSEventModifierFlagOption | NSEventModifierFlagCommand),
|
||||
}),
|
||||
);
|
||||
|
||||
// Show applications menu item
|
||||
let show_all_item_title = ns_string!("Show All");
|
||||
let show_all_item = menu_item(
|
||||
mtm,
|
||||
show_all_item_title,
|
||||
Some(sel!(unhideAllApplications:)),
|
||||
None,
|
||||
);
|
||||
|
||||
// Seperator menu item
|
||||
let sep = NSMenuItem::separatorItem();
|
||||
let sep = NSMenuItem::separatorItem(mtm);
|
||||
|
||||
// Quit application menu item
|
||||
let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name);
|
||||
let quit_item = menu_item(
|
||||
mtm,
|
||||
&quit_item_title,
|
||||
Some(sel!(terminate:)),
|
||||
Some(KeyEquivalent {
|
||||
@@ -89,14 +94,14 @@ pub fn initialize() {
|
||||
app_menu.addItem(&show_all_item);
|
||||
app_menu.addItem(&sep);
|
||||
app_menu.addItem(&quit_item);
|
||||
app_menu_item.setSubmenu(&app_menu);
|
||||
app_menu_item.setSubmenu(Some(&app_menu));
|
||||
|
||||
let app = NSApp();
|
||||
app.setServicesMenu(&services_menu);
|
||||
app.setMainMenu(&menubar);
|
||||
unsafe { app.setServicesMenu(Some(&services_menu)) };
|
||||
app.setMainMenu(Some(&menubar));
|
||||
}
|
||||
|
||||
fn menu_item(
|
||||
mtm: MainThreadMarker,
|
||||
title: &NSString,
|
||||
selector: Option<Sel>,
|
||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
||||
@@ -105,7 +110,9 @@ fn menu_item(
|
||||
Some(ke) => (ke.key, ke.masks),
|
||||
None => (ns_string!(""), None),
|
||||
};
|
||||
let item = NSMenuItem::newWithTitle(title, selector, key);
|
||||
let item = unsafe {
|
||||
NSMenuItem::initWithTitle_action_keyEquivalent(mtm.alloc(), title, selector, key)
|
||||
};
|
||||
if let Some(masks) = masks {
|
||||
item.setKeyEquivalentModifierMask(masks)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ mod util;
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod appkit;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
@@ -27,8 +27,9 @@ pub(crate) use self::{
|
||||
};
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
|
||||
pub(crate) use self::window::Window;
|
||||
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
|
||||
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use self::window::{OwnedWindowHandle, Window};
|
||||
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ use core_foundation::{
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
use icrate::AppKit::NSScreen;
|
||||
use icrate::Foundation::{ns_string, MainThreadMarker, NSNumber};
|
||||
use objc2::{rc::Id, runtime::AnyObject};
|
||||
|
||||
use super::appkit::NSScreen;
|
||||
use super::ffi;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
|
||||
@@ -210,10 +211,12 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
match self.ns_screen() {
|
||||
Some(screen) => screen.backingScaleFactor() as f64,
|
||||
None => 1.0, // default to 1.0 when we can't find the screen
|
||||
}
|
||||
MainThreadMarker::run_on_main(|mtm| {
|
||||
match self.ns_screen(mtm) {
|
||||
Some(screen) => screen.backingScaleFactor() as f64,
|
||||
None => 1.0, // default to 1.0 when we can't find the screen
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
@@ -303,10 +306,10 @@ impl MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self) -> Option<Id<NSScreen>> {
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Id<NSScreen>> {
|
||||
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
NSScreen::screens().into_iter().find(|screen| {
|
||||
let other_native_id = screen.display_id();
|
||||
NSScreen::screens(mtm).into_iter().find(|screen| {
|
||||
let other_native_id = get_display_id(screen);
|
||||
let other_uuid = unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
|
||||
};
|
||||
@@ -314,3 +317,25 @@ impl MonitorHandle {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
|
||||
let key = ns_string!("NSScreenNumber");
|
||||
|
||||
objc2::rc::autoreleasepool(|_| {
|
||||
let device_description = screen.deviceDescription();
|
||||
|
||||
// Retrieve the CGDirectDisplayID associated with this screen
|
||||
//
|
||||
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for `deviceDescription` for details:
|
||||
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
|
||||
let obj = device_description
|
||||
.get(key)
|
||||
.expect("failed getting screen display id from device description");
|
||||
let obj: *const AnyObject = obj;
|
||||
let obj: *const NSNumber = obj.cast();
|
||||
let obj: &NSNumber = unsafe { &*obj };
|
||||
|
||||
obj.as_u32()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use core_foundation::runloop::{
|
||||
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use icrate::Foundation::MainThreadMarker;
|
||||
|
||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
||||
where
|
||||
@@ -36,7 +37,8 @@ where
|
||||
// However we want to keep that weak reference around after the function.
|
||||
std::mem::forget(info_from_raw);
|
||||
|
||||
stop_app_on_panic(Weak::clone(&panic_info), move || {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
|
||||
let _ = &panic_info;
|
||||
f(panic_info.0)
|
||||
});
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::boxed::Box;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ptr::NonNull;
|
||||
use std::ptr;
|
||||
|
||||
use icrate::Foundation::{
|
||||
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
|
||||
NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
use icrate::AppKit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhaseBegan, NSEventPhaseCancelled,
|
||||
NSEventPhaseChanged, NSEventPhaseEnded, NSEventPhaseMayBegin, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView,
|
||||
};
|
||||
use icrate::Foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize,
|
||||
NSString, NSUInteger,
|
||||
};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::{Id, WeakId};
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::{
|
||||
appkit::{
|
||||
NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag,
|
||||
NSView,
|
||||
},
|
||||
event::{code_to_key, code_to_location},
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::cursor::{default_cursor, invisible_cursor};
|
||||
use super::event::{code_to_key, code_to_location};
|
||||
use super::event::{lalt_pressed, ralt_pressed};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
event::{
|
||||
@@ -49,7 +51,7 @@ impl Default for CursorState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
visible: true,
|
||||
cursor: Default::default(),
|
||||
cursor: default_cursor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,93 +142,50 @@ pub struct ViewState {
|
||||
|
||||
marked_text: RefCell<Id<NSMutableAttributedString>>,
|
||||
accepts_first_mouse: bool,
|
||||
|
||||
// Weak reference because the window keeps a strong reference to the view
|
||||
_ns_window: WeakId<WinitWindow>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
pub(super) struct WinitView {
|
||||
// Weak reference because the window keeps a strong reference to the view
|
||||
_ns_window: IvarDrop<Box<WeakId<WinitWindow>>, "__ns_window">,
|
||||
state: IvarDrop<Box<ViewState>, "_state">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub(super) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitView";
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(initWithId:acceptsFirstMouse:)]
|
||||
unsafe fn init_with_id(
|
||||
this: *mut Self,
|
||||
window: &WinitWindow,
|
||||
accepts_first_mouse: bool,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
|
||||
this.map(|this| {
|
||||
let state = ViewState {
|
||||
accepts_first_mouse,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ivar::write(
|
||||
&mut this._ns_window,
|
||||
Box::new(WeakId::new(&window.retain())),
|
||||
);
|
||||
Ivar::write(&mut this.state, Box::new(state));
|
||||
|
||||
this.setPostsFrameChangedNotifications(true);
|
||||
|
||||
let notification_center: &AnyObject =
|
||||
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
|
||||
// About frame change
|
||||
let frame_did_change_notification_name =
|
||||
NSString::from_str("NSViewFrameDidChangeNotification");
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
notification_center,
|
||||
addObserver: &*this,
|
||||
selector: sel!(frameDidChange:),
|
||||
name: &*frame_did_change_notification_name,
|
||||
object: &*this,
|
||||
];
|
||||
}
|
||||
|
||||
*this.state.input_source.borrow_mut() = this.current_input_source();
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = ViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(viewDidMoveToWindow)]
|
||||
fn view_did_move_to_window(&self) {
|
||||
trace_scope!("viewDidMoveToWindow");
|
||||
if let Some(tracking_rect) = self.state.tracking_rect.take() {
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
|
||||
let rect = self.frame();
|
||||
let tracking_rect = self.add_tracking_rect(rect, false);
|
||||
self.state.tracking_rect.set(Some(tracking_rect));
|
||||
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
|
||||
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
}
|
||||
|
||||
#[method(frameDidChange:)]
|
||||
fn frame_did_change(&self, _event: &NSEvent) {
|
||||
trace_scope!("frameDidChange:");
|
||||
if let Some(tracking_rect) = self.state.tracking_rect.take() {
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
|
||||
let rect = self.frame();
|
||||
let tracking_rect = self.add_tracking_rect(rect, false);
|
||||
self.state.tracking_rect.set(Some(tracking_rect));
|
||||
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
|
||||
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
|
||||
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.
|
||||
@@ -241,7 +200,7 @@ declare_class!(
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
|
||||
if let Some(window) = self._ns_window.load() {
|
||||
if let Some(window) = self.ivars()._ns_window.load() {
|
||||
AppState::handle_redraw(WindowId(window.id()));
|
||||
}
|
||||
|
||||
@@ -270,12 +229,12 @@ declare_class!(
|
||||
fn reset_cursor_rects(&self) {
|
||||
trace_scope!("resetCursorRects");
|
||||
let bounds = self.bounds();
|
||||
let cursor_state = self.state.cursor_state.borrow();
|
||||
let cursor_state = self.ivars().cursor_state.borrow();
|
||||
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
|
||||
if cursor_state.visible {
|
||||
self.addCursorRect(bounds, &cursor_state.cursor);
|
||||
self.addCursorRect_cursor(bounds, &cursor_state.cursor);
|
||||
} else {
|
||||
self.addCursorRect(bounds, &NSCursor::invisible());
|
||||
self.addCursorRect_cursor(bounds, &invisible_cursor());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,13 +243,13 @@ declare_class!(
|
||||
#[method(hasMarkedText)]
|
||||
fn has_marked_text(&self) -> bool {
|
||||
trace_scope!("hasMarkedText");
|
||||
self.state.marked_text.borrow().length() > 0
|
||||
self.ivars().marked_text.borrow().length() > 0
|
||||
}
|
||||
|
||||
#[method(markedRange)]
|
||||
fn marked_range(&self) -> NSRange {
|
||||
trace_scope!("markedRange");
|
||||
let length = self.state.marked_text.borrow().length();
|
||||
let length = self.ivars().marked_text.borrow().length();
|
||||
if length > 0 {
|
||||
NSRange::new(0, length)
|
||||
} else {
|
||||
@@ -333,19 +292,19 @@ declare_class!(
|
||||
};
|
||||
|
||||
// Update marked text.
|
||||
*self.state.marked_text.borrow_mut() = marked_text;
|
||||
*self.ivars().marked_text.borrow_mut() = marked_text;
|
||||
|
||||
// Notify IME is active if application still doesn't know it.
|
||||
if self.state.ime_state.get() == ImeState::Disabled {
|
||||
*self.state.input_source.borrow_mut() = self.current_input_source();
|
||||
if self.ivars().ime_state.get() == ImeState::Disabled {
|
||||
*self.ivars().input_source.borrow_mut() = self.current_input_source();
|
||||
self.queue_event(WindowEvent::Ime(Ime::Enabled));
|
||||
}
|
||||
|
||||
if self.hasMarkedText() {
|
||||
self.state.ime_state.set(ImeState::Preedit);
|
||||
if unsafe { self.hasMarkedText() } {
|
||||
self.ivars().ime_state.set(ImeState::Preedit);
|
||||
} else {
|
||||
// In case the preedit was cleared, set IME into the Ground state.
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
|
||||
// Empty string basically means that there's no preedit, so indicate that by sending
|
||||
@@ -363,15 +322,15 @@ declare_class!(
|
||||
#[method(unmarkText)]
|
||||
fn unmark_text(&self) {
|
||||
trace_scope!("unmarkText");
|
||||
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
|
||||
let input_context = self.inputContext().expect("input context");
|
||||
input_context.discardMarkedText();
|
||||
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
|
||||
if self.is_ime_enabled() {
|
||||
// Leave the Preedit self.state
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
// Leave the Preedit self.ivars()
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
} else {
|
||||
warn!("Expected to have IME enabled when receiving unmarkText");
|
||||
}
|
||||
@@ -410,9 +369,9 @@ declare_class!(
|
||||
let content_rect = window.contentRectForFrameRect(window.frame());
|
||||
let base_x = content_rect.origin.x as f64;
|
||||
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
|
||||
let x = base_x + self.state.ime_position.get().x;
|
||||
let y = base_y - self.state.ime_position.get().y;
|
||||
let LogicalSize { width, height } = self.state.ime_size.get();
|
||||
let x = base_x + self.ivars().ime_position.get().x;
|
||||
let y = base_y - self.ivars().ime_position.get().y;
|
||||
let LogicalSize { width, height } = self.ivars().ime_size.get();
|
||||
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
|
||||
}
|
||||
|
||||
@@ -434,10 +393,10 @@ declare_class!(
|
||||
let is_control = string.chars().next().map_or(false, |c| c.is_control());
|
||||
|
||||
// Commit only if we have marked text.
|
||||
if self.hasMarkedText() && self.is_ime_enabled() && !is_control {
|
||||
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
|
||||
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
|
||||
self.state.ime_state.set(ImeState::Commited);
|
||||
self.ivars().ime_state.set(ImeState::Commited);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,15 +408,15 @@ declare_class!(
|
||||
// We shouldn't forward any character from just commited 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.state.ime_state.get() == ImeState::Commited {
|
||||
if self.ivars().ime_state.get() == ImeState::Commited {
|
||||
return;
|
||||
}
|
||||
|
||||
self.state.forward_key_to_app.set(true);
|
||||
self.ivars().forward_key_to_app.set(true);
|
||||
|
||||
if self.hasMarkedText() && self.state.ime_state.get() == ImeState::Preedit {
|
||||
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit {
|
||||
// Leave preedit so that we also report the key-up for this key.
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,19 +426,19 @@ declare_class!(
|
||||
fn key_down(&self, event: &NSEvent) {
|
||||
trace_scope!("keyDown:");
|
||||
{
|
||||
let mut prev_input_source = self.state.input_source.borrow_mut();
|
||||
let mut prev_input_source = self.ivars().input_source.borrow_mut();
|
||||
let current_input_source = self.current_input_source();
|
||||
if *prev_input_source != current_input_source && self.is_ime_enabled() {
|
||||
*prev_input_source = current_input_source;
|
||||
drop(prev_input_source);
|
||||
self.state.ime_state.set(ImeState::Disabled);
|
||||
self.ivars().ime_state.set(ImeState::Disabled);
|
||||
self.queue_event(WindowEvent::Ime(Ime::Disabled));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the characters from the event.
|
||||
let old_ime_state = self.state.ime_state.get();
|
||||
self.state.forward_key_to_app.set(false);
|
||||
let old_ime_state = self.ivars().ime_state.get();
|
||||
self.ivars().forward_key_to_app.set(false);
|
||||
let event = replace_event(event, self.window().option_as_alt());
|
||||
|
||||
// The `interpretKeyEvents` function might call
|
||||
@@ -488,32 +447,32 @@ declare_class!(
|
||||
// we must send the `KeyboardInput` event during IME if it triggered
|
||||
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
|
||||
// is not handled by IME and should be handled by the application)
|
||||
if self.state.ime_allowed.get() {
|
||||
if self.ivars().ime_allowed.get() {
|
||||
let events_for_nsview = NSArray::from_slice(&[&*event]);
|
||||
unsafe { self.interpretKeyEvents(&events_for_nsview) };
|
||||
|
||||
// If the text was commited we must treat the next keyboard event as IME related.
|
||||
if self.state.ime_state.get() == ImeState::Commited {
|
||||
if self.ivars().ime_state.get() == ImeState::Commited {
|
||||
// Remove any marked text, so normal input can continue.
|
||||
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
}
|
||||
}
|
||||
|
||||
self.update_modifiers(&event, false);
|
||||
|
||||
let had_ime_input = match self.state.ime_state.get() {
|
||||
let had_ime_input = match self.ivars().ime_state.get() {
|
||||
ImeState::Commited => {
|
||||
// Allow normal input after the commit.
|
||||
self.state.ime_state.set(ImeState::Ground);
|
||||
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.state.ime_state.get(),
|
||||
_ => old_ime_state != self.ivars().ime_state.get(),
|
||||
};
|
||||
|
||||
if !had_ime_input || self.state.forward_key_to_app.get() {
|
||||
let key_event = create_key_event(&event, true, event.is_a_repeat(), None);
|
||||
if !had_ime_input || self.ivars().forward_key_to_app.get() {
|
||||
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: key_event,
|
||||
@@ -531,7 +490,7 @@ declare_class!(
|
||||
|
||||
// We want to send keyboard input when we are currently in the ground state.
|
||||
if matches!(
|
||||
self.state.ime_state.get(),
|
||||
self.ivars().ime_state.get(),
|
||||
ImeState::Ground | ImeState::Disabled
|
||||
) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
@@ -575,14 +534,15 @@ declare_class!(
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
|
||||
#[method(cancelOperation:)]
|
||||
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
trace_scope!("cancelOperation:");
|
||||
|
||||
let event = NSApp()
|
||||
let event = NSApplication::sharedApplication(mtm)
|
||||
.currentEvent()
|
||||
.expect("could not find current event");
|
||||
|
||||
self.update_modifiers(&event, false);
|
||||
let event = create_key_event(&event, true, event.is_a_repeat(), None);
|
||||
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
@@ -679,8 +639,8 @@ declare_class!(
|
||||
self.mouse_motion(event);
|
||||
|
||||
let delta = {
|
||||
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
|
||||
if event.hasPreciseScrollingDeltas() {
|
||||
let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) };
|
||||
if unsafe { event.hasPreciseScrollingDeltas() } {
|
||||
let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor());
|
||||
MouseScrollDelta::PixelDelta(delta)
|
||||
} else {
|
||||
@@ -692,18 +652,19 @@ declare_class!(
|
||||
// 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.
|
||||
let phase = match event.momentumPhase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.momentumPhase() } {
|
||||
NSEventPhaseMayBegin | NSEventPhaseBegan => {
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
|
||||
NSEventPhaseEnded | NSEventPhaseCancelled => {
|
||||
TouchPhase::Ended
|
||||
}
|
||||
_ => match event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
_ => match unsafe { event.phase() } {
|
||||
NSEventPhaseMayBegin | NSEventPhaseBegan => {
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
|
||||
NSEventPhaseEnded | NSEventPhaseCancelled => {
|
||||
TouchPhase::Ended
|
||||
}
|
||||
_ => TouchPhase::Moved,
|
||||
@@ -724,17 +685,18 @@ declare_class!(
|
||||
fn magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("magnifyWithEvent:");
|
||||
|
||||
let phase = match event.phase() {
|
||||
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.phase() } {
|
||||
NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadMagnify {
|
||||
device_id: DEVICE_ID,
|
||||
delta: event.magnification(),
|
||||
delta: unsafe { event.magnification() },
|
||||
phase,
|
||||
});
|
||||
}
|
||||
@@ -752,17 +714,18 @@ declare_class!(
|
||||
fn rotate_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("rotateWithEvent:");
|
||||
|
||||
let phase = match event.phase() {
|
||||
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.phase() } {
|
||||
NSEventPhaseBegan => TouchPhase::Started,
|
||||
NSEventPhaseChanged => TouchPhase::Moved,
|
||||
NSEventPhaseCancelled => TouchPhase::Cancelled,
|
||||
NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadRotate {
|
||||
device_id: DEVICE_ID,
|
||||
delta: event.rotation(),
|
||||
delta: unsafe { event.rotation() },
|
||||
phase,
|
||||
});
|
||||
}
|
||||
@@ -775,8 +738,8 @@ declare_class!(
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadPressure {
|
||||
device_id: DEVICE_ID,
|
||||
pressure: event.pressure(),
|
||||
stage: event.stage() as i64,
|
||||
pressure: unsafe { event.pressure() },
|
||||
stage: unsafe { event.stage() } as i64,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -792,20 +755,41 @@ declare_class!(
|
||||
#[method(acceptsFirstMouse:)]
|
||||
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("acceptsFirstMouse:");
|
||||
self.state.accepts_first_mouse
|
||||
self.ivars().accepts_first_mouse
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitView {
|
||||
pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id<Self> {
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let this = mtm.alloc().set_ivars(ViewState {
|
||||
accepts_first_mouse,
|
||||
_ns_window: WeakId::new(&window.retain()),
|
||||
..Default::default()
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.setPostsFrameChangedNotifications(true);
|
||||
let notification_center: &AnyObject =
|
||||
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
|
||||
// About frame change
|
||||
let frame_did_change_notification_name =
|
||||
NSString::from_str("NSViewFrameDidChangeNotification");
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithId: window,
|
||||
acceptsFirstMouse: accepts_first_mouse,
|
||||
]
|
||||
let _: () = msg_send![
|
||||
notification_center,
|
||||
addObserver: &*this,
|
||||
selector: sel!(frameDidChange:),
|
||||
name: &*frame_did_change_notification_name,
|
||||
object: &*this,
|
||||
];
|
||||
}
|
||||
|
||||
*this.ivars().input_source.borrow_mut() = this.current_input_source();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Id<WinitWindow> {
|
||||
@@ -814,7 +798,10 @@ impl WinitView {
|
||||
// (which is incompatible with `frameDidChange:`)
|
||||
//
|
||||
// unsafe { msg_send_id![self, window] }
|
||||
self._ns_window.load().expect("view to have a window")
|
||||
self.ivars()
|
||||
._ns_window
|
||||
.load()
|
||||
.expect("view to have a window")
|
||||
}
|
||||
|
||||
fn window_id(&self) -> WindowId {
|
||||
@@ -842,7 +829,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn is_ime_enabled(&self) -> bool {
|
||||
!matches!(self.state.ime_state.get(), ImeState::Disabled)
|
||||
!matches!(self.ivars().ime_state.get(), ImeState::Disabled)
|
||||
}
|
||||
|
||||
fn current_input_source(&self) -> String {
|
||||
@@ -854,7 +841,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) {
|
||||
let mut cursor_state = self.state.cursor_state.borrow_mut();
|
||||
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
|
||||
cursor_state.cursor = icon;
|
||||
}
|
||||
|
||||
@@ -862,7 +849,7 @@ impl WinitView {
|
||||
///
|
||||
/// Returns whether the state changed.
|
||||
pub(super) fn set_cursor_visible(&self, visible: bool) -> bool {
|
||||
let mut cursor_state = self.state.cursor_state.borrow_mut();
|
||||
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
|
||||
if visible != cursor_state.visible {
|
||||
cursor_state.visible = visible;
|
||||
true
|
||||
@@ -872,19 +859,19 @@ impl WinitView {
|
||||
}
|
||||
|
||||
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
|
||||
if self.state.ime_allowed.get() == ime_allowed {
|
||||
if self.ivars().ime_allowed.get() == ime_allowed {
|
||||
return;
|
||||
}
|
||||
self.state.ime_allowed.set(ime_allowed);
|
||||
if self.state.ime_allowed.get() {
|
||||
self.ivars().ime_allowed.set(ime_allowed);
|
||||
if self.ivars().ime_allowed.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear markedText
|
||||
*self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
|
||||
if self.state.ime_state.get() != ImeState::Disabled {
|
||||
self.state.ime_state.set(ImeState::Disabled);
|
||||
if self.ivars().ime_state.get() != ImeState::Disabled {
|
||||
self.ivars().ime_state.set(ImeState::Disabled);
|
||||
self.queue_event(WindowEvent::Ime(Ime::Disabled));
|
||||
}
|
||||
}
|
||||
@@ -894,17 +881,17 @@ impl WinitView {
|
||||
position: LogicalPosition<f64>,
|
||||
size: LogicalSize<f64>,
|
||||
) {
|
||||
self.state.ime_position.set(position);
|
||||
self.state.ime_size.set(size);
|
||||
self.ivars().ime_position.set(position);
|
||||
self.ivars().ime_size.set(size);
|
||||
let input_context = self.inputContext().expect("input context");
|
||||
input_context.invalidateCharacterCoordinates();
|
||||
}
|
||||
|
||||
/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
|
||||
pub(super) fn reset_modifiers(&self) {
|
||||
if !self.state.modifiers.get().state().is_empty() {
|
||||
self.state.modifiers.set(Modifiers::default());
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
|
||||
if !self.ivars().modifiers.get().state().is_empty() {
|
||||
self.ivars().modifiers.set(Modifiers::default());
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,8 +900,8 @@ impl WinitView {
|
||||
use ElementState::{Pressed, Released};
|
||||
|
||||
let current_modifiers = event_mods(ns_event);
|
||||
let prev_modifiers = self.state.modifiers.get();
|
||||
self.state.modifiers.set(current_modifiers);
|
||||
let prev_modifiers = self.ivars().modifiers.get();
|
||||
self.ivars().modifiers.set(current_modifiers);
|
||||
|
||||
// This function was called form the flagsChanged event, which is triggered
|
||||
// when the user presses/releases a modifier even if the same kind of modifier
|
||||
@@ -925,8 +912,8 @@ impl WinitView {
|
||||
// later will work though, since the flags are attached to the event and contain valid
|
||||
// information.
|
||||
'send_event: {
|
||||
if is_flags_changed_event && ns_event.key_code() != 0 {
|
||||
let scancode = ns_event.key_code();
|
||||
if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 {
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let physical_key = PhysicalKey::from_scancode(scancode as u32);
|
||||
|
||||
// We'll correct the `is_press` later.
|
||||
@@ -943,7 +930,7 @@ impl WinitView {
|
||||
event.location = code_to_location(physical_key);
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
|
||||
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
|
||||
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
|
||||
let phys_mod = phys_mod_state
|
||||
.entry(key)
|
||||
.or_insert(ModLocationMask::empty());
|
||||
@@ -1021,7 +1008,7 @@ impl WinitView {
|
||||
return;
|
||||
}
|
||||
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
|
||||
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
|
||||
}
|
||||
|
||||
fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
|
||||
@@ -1037,7 +1024,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn mouse_motion(&self, event: &NSEvent) {
|
||||
let window_point = event.locationInWindow();
|
||||
let window_point = unsafe { event.locationInWindow() };
|
||||
let view_point = self.convertPoint_fromView(window_point, None);
|
||||
let view_rect = self.frame();
|
||||
|
||||
@@ -1046,7 +1033,7 @@ impl WinitView {
|
||||
|| view_point.x > view_rect.size.width
|
||||
|| view_point.y > view_rect.size.height
|
||||
{
|
||||
let mouse_buttons_down = NSEvent::pressedMouseButtons();
|
||||
let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() };
|
||||
if mouse_buttons_down == 0 {
|
||||
// Point is outside of the client area (view) and no buttons are pressed
|
||||
return;
|
||||
@@ -1073,7 +1060,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
||||
// For the other events, it's always set to 0.
|
||||
// MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons,
|
||||
// but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications.
|
||||
match event.buttonNumber() {
|
||||
match unsafe { event.buttonNumber() } {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Right,
|
||||
2 => MouseButton::Middle,
|
||||
@@ -1089,30 +1076,35 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
||||
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> {
|
||||
let ev_mods = event_mods(event).state;
|
||||
let ignore_alt_characters = match option_as_alt {
|
||||
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
|
||||
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
|
||||
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
|
||||
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
|
||||
OptionAsAlt::Both if ev_mods.alt_key() => true,
|
||||
_ => false,
|
||||
} && !ev_mods.control_key()
|
||||
&& !ev_mods.super_key();
|
||||
|
||||
if ignore_alt_characters {
|
||||
let ns_chars = event
|
||||
.charactersIgnoringModifiers()
|
||||
.expect("expected characters to be non-null");
|
||||
let ns_chars = unsafe {
|
||||
event
|
||||
.charactersIgnoringModifiers()
|
||||
.expect("expected characters to be non-null")
|
||||
};
|
||||
|
||||
NSEvent::keyEventWithType(
|
||||
event.type_(),
|
||||
event.locationInWindow(),
|
||||
event.modifierFlags(),
|
||||
event.timestamp(),
|
||||
event.window_number(),
|
||||
None,
|
||||
&ns_chars,
|
||||
&ns_chars,
|
||||
event.is_a_repeat(),
|
||||
event.key_code(),
|
||||
)
|
||||
unsafe {
|
||||
NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
|
||||
event.r#type(),
|
||||
event.locationInWindow(),
|
||||
event.modifierFlags(),
|
||||
event.timestamp(),
|
||||
event.windowNumber(),
|
||||
None,
|
||||
&ns_chars,
|
||||
&ns_chars,
|
||||
event.isARepeat(),
|
||||
event.keyCode(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
} else {
|
||||
event.copy()
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::f64;
|
||||
use std::ops;
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::{
|
||||
dpi::{
|
||||
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
|
||||
@@ -18,14 +15,13 @@ use crate::{
|
||||
platform::macos::{OptionAsAlt, WindowExtMacOS},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
appkit::NSWindowOrderingMode,
|
||||
event_loop::EventLoopWindowTarget,
|
||||
ffi,
|
||||
monitor::{self, MonitorHandle, VideoMode},
|
||||
util,
|
||||
view::WinitView,
|
||||
window_delegate::WinitWindowDelegate,
|
||||
Fullscreen, OsError,
|
||||
Fullscreen, OsError, PlatformCustomCursor,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -33,22 +29,30 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use core_graphics::display::{CGDisplay, CGPoint};
|
||||
use icrate::AppKit::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
|
||||
NSApplicationPresentationAutoHideDock, NSApplicationPresentationAutoHideMenuBar,
|
||||
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
|
||||
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSBackingStoreBuffered,
|
||||
NSColor, NSCriticalRequest, NSFilenamesPboardType, NSInformationalRequest, NSResponder,
|
||||
NSScreen, NSView, NSWindow, NSWindowAbove, NSWindowCloseButton, NSWindowFullScreenButton,
|
||||
NSWindowLevel, NSWindowMiniaturizeButton, NSWindowSharingNone, NSWindowSharingReadOnly,
|
||||
NSWindowStyleMask, NSWindowStyleMaskBorderless, NSWindowStyleMaskClosable,
|
||||
NSWindowStyleMaskFullSizeContentView, NSWindowStyleMaskMiniaturizable,
|
||||
NSWindowStyleMaskResizable, NSWindowStyleMaskTitled, NSWindowTabbingModePreferred,
|
||||
NSWindowTitleHidden, NSWindowZoomButton,
|
||||
};
|
||||
use icrate::Foundation::{
|
||||
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint,
|
||||
NSRect, NSSize, NSString,
|
||||
CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSObject, NSPoint, NSRect,
|
||||
NSSize, NSString,
|
||||
};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
|
||||
use super::appkit::{
|
||||
NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
NSColor, NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen,
|
||||
NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
};
|
||||
use super::ffi::CGSMainConnectionID;
|
||||
use super::ffi::CGSSetWindowBackgroundBlurRadius;
|
||||
use super::cursor::cursor_from_icon;
|
||||
use super::ffi::kCGFloatingWindowLevel;
|
||||
use super::ffi::{kCGNormalWindowLevel, CGSMainConnectionID, CGSSetWindowBackgroundBlurRadius};
|
||||
use super::monitor::get_display_id;
|
||||
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Id<WinitWindow>>,
|
||||
@@ -59,7 +63,7 @@ pub(crate) struct Window {
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.window
|
||||
.get_on_main(|window, _| autoreleasepool(|_| window.close()))
|
||||
.get_on_main(|window| autoreleasepool(|_| window.close()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +75,8 @@ impl Window {
|
||||
) -> Result<Self, RootOsError> {
|
||||
let mtm = MainThreadMarker::new()
|
||||
.expect("windows can only be created on the main thread on macOS");
|
||||
let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
|
||||
let (window, _delegate) =
|
||||
autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs, mtm))?;
|
||||
Ok(Window {
|
||||
window: MainThreadBound::new(window, mtm),
|
||||
_delegate: MainThreadBound::new(_delegate, mtm),
|
||||
@@ -87,7 +92,19 @@ impl Window {
|
||||
&self,
|
||||
f: impl FnOnce(&WinitWindow) -> R + Send,
|
||||
) -> R {
|
||||
self.window.get_on_main(|window, _mtm| f(window))
|
||||
self.window.get_on_main(|window| f(window))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub(crate) fn raw_window_handle_rwh_06(
|
||||
&self,
|
||||
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
if let Some(mtm) = MainThreadMarker::new() {
|
||||
Ok(self.window.get(mtm).raw_window_handle_rwh_06())
|
||||
} else {
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +129,54 @@ impl From<u64> for WindowId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OwnedWindowHandle {
|
||||
ns_view: MainThreadBound<Id<NSView>>,
|
||||
}
|
||||
|
||||
impl Clone for OwnedWindowHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ns_view: MainThreadMarker::run_on_main(|mtm| {
|
||||
MainThreadBound::new(self.ns_view.get(mtm).clone(), mtm)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
let mtm =
|
||||
MainThreadMarker::new().expect("can only have handles on macOS on the main thread");
|
||||
let ns_view = match handle.as_raw() {
|
||||
rwh_06::RawWindowHandle::AppKit(handle) => {
|
||||
// SAFETY: Taking `WindowHandle<'_>` ensures that the pointer is valid.
|
||||
// Unwrap is fine, since the pointer comes from `NonNull`.
|
||||
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap()
|
||||
}
|
||||
handle => panic!("invalid window handle {handle:?} on macOS"),
|
||||
};
|
||||
Self {
|
||||
ns_view: MainThreadBound::new(ns_view, mtm),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn raw_window_handle(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
if let Some(mtm) = MainThreadMarker::new() {
|
||||
let window_handle = rwh_06::AppKitWindowHandle::new({
|
||||
let ptr = Id::as_ptr(self.ns_view.get(mtm)) as *mut _;
|
||||
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
|
||||
});
|
||||
let handle = rwh_06::RawWindowHandle::AppKit(window_handle);
|
||||
Ok(handle)
|
||||
} else {
|
||||
Err(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub movable_by_window_background: bool,
|
||||
@@ -148,52 +213,17 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub struct WinitWindow {
|
||||
// TODO: Fix unnecessary boxing here
|
||||
shared_state: IvarDrop<Box<Mutex<SharedState>>, "_shared_state">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub struct WinitWindow;
|
||||
|
||||
unsafe impl ClassType for WinitWindow {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSWindow;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindow";
|
||||
}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
#[method(initWithContentRect:styleMask:state:)]
|
||||
unsafe fn init(
|
||||
this: *mut Self,
|
||||
frame: NSRect,
|
||||
mask: NSWindowStyleMask,
|
||||
state: *mut c_void,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe {
|
||||
msg_send![
|
||||
super(this),
|
||||
initWithContentRect: frame,
|
||||
styleMask: mask,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
||||
this.map(|this| {
|
||||
// SAFETY: The pointer originally came from `Box::into_raw`.
|
||||
Ivar::write(&mut this.shared_state, unsafe {
|
||||
Box::from_raw(state as *mut Mutex<SharedState>)
|
||||
});
|
||||
|
||||
// It is imperative to correct memory management that we
|
||||
// disable the extra release that would otherwise happen when
|
||||
// calling `clone` on the window.
|
||||
this.setReleasedWhenClosed(false);
|
||||
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitWindow {
|
||||
type Ivars = Mutex<SharedState>;
|
||||
}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
@@ -294,6 +324,7 @@ impl WinitWindow {
|
||||
fn new(
|
||||
attrs: WindowAttributes,
|
||||
pl_attrs: PlatformSpecificWindowBuilderAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Result<(Id<Self>, Id<WinitWindowDelegate>), RootOsError> {
|
||||
trace_scope!("WinitWindow::new");
|
||||
|
||||
@@ -301,15 +332,15 @@ impl WinitWindow {
|
||||
let screen = match attrs.fullscreen.0.clone().map(Into::into) {
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
| Some(Fullscreen::Exclusive(VideoMode { monitor, .. })) => {
|
||||
monitor.ns_screen().or_else(NSScreen::main)
|
||||
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
|
||||
}
|
||||
Some(Fullscreen::Borderless(None)) => NSScreen::main(),
|
||||
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
|
||||
None => None,
|
||||
};
|
||||
let frame = match &screen {
|
||||
Some(screen) => screen.frame(),
|
||||
None => {
|
||||
let scale_factor = NSScreen::main()
|
||||
let scale_factor = NSScreen::mainScreen(mtm)
|
||||
.map(|screen| screen.backingScaleFactor() as f64)
|
||||
.unwrap_or(1.0);
|
||||
let (width, height) = match attrs.inner_size {
|
||||
@@ -339,31 +370,31 @@ impl WinitWindow {
|
||||
// if decorations is set to false, ignore pl_attrs
|
||||
//
|
||||
// if the titlebar is hidden, ignore other pl_attrs
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
NSWindowStyleMaskBorderless
|
||||
| NSWindowStyleMaskResizable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
} else {
|
||||
// default case, resizable window with titlebar and titlebar buttons
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSTitledWindowMask
|
||||
NSWindowStyleMaskClosable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
| NSWindowStyleMaskResizable
|
||||
| NSWindowStyleMaskTitled
|
||||
};
|
||||
|
||||
if !attrs.resizable {
|
||||
masks &= !NSWindowStyleMask::NSResizableWindowMask;
|
||||
masks &= !NSWindowStyleMaskResizable;
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) {
|
||||
masks &= !NSWindowStyleMask::NSMiniaturizableWindowMask;
|
||||
masks &= !NSWindowStyleMaskMiniaturizable;
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) {
|
||||
masks &= !NSWindowStyleMask::NSClosableWindowMask;
|
||||
masks &= !NSWindowStyleMaskClosable;
|
||||
}
|
||||
|
||||
if pl_attrs.fullsize_content_view {
|
||||
masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
|
||||
masks |= NSWindowStyleMaskFullSizeContentView;
|
||||
}
|
||||
|
||||
let state = SharedState {
|
||||
@@ -373,18 +404,23 @@ impl WinitWindow {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Pass the state through FFI to the method declared on the class
|
||||
let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast();
|
||||
let this = mtm.alloc().set_ivars(Mutex::new(state));
|
||||
let this: Option<Id<Self>> = unsafe {
|
||||
msg_send_id![
|
||||
WinitWindow::alloc(),
|
||||
super(this),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
state: state_ptr,
|
||||
backing: NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
let this = this?;
|
||||
|
||||
// It is very important for correct memory management that we
|
||||
// disable the extra release that would otherwise happen when
|
||||
// calling `close` on the window.
|
||||
unsafe { this.setReleasedWhenClosed(false) };
|
||||
|
||||
let resize_increments = match attrs
|
||||
.resize_increments
|
||||
.map(|i| i.to_logical::<f64>(this.scale_factor()))
|
||||
@@ -402,26 +438,26 @@ impl WinitWindow {
|
||||
|
||||
if let Some(identifier) = pl_attrs.tabbing_identifier {
|
||||
this.setTabbingIdentifier(&NSString::from_str(&identifier));
|
||||
this.setTabbingMode(NSWindowTabbingMode::NSWindowTabbingModePreferred);
|
||||
this.setTabbingMode(NSWindowTabbingModePreferred);
|
||||
}
|
||||
|
||||
if attrs.content_protected {
|
||||
this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
||||
this.setSharingType(NSWindowSharingNone);
|
||||
}
|
||||
|
||||
if pl_attrs.titlebar_transparent {
|
||||
this.setTitlebarAppearsTransparent(true);
|
||||
}
|
||||
if pl_attrs.title_hidden {
|
||||
this.setTitleVisibility(NSWindowTitleVisibility::Hidden);
|
||||
this.setTitleVisibility(NSWindowTitleHidden);
|
||||
}
|
||||
if pl_attrs.titlebar_buttons_hidden {
|
||||
for titlebar_button in &[
|
||||
#[allow(deprecated)]
|
||||
NSWindowButton::FullScreen,
|
||||
NSWindowButton::Miniaturize,
|
||||
NSWindowButton::Close,
|
||||
NSWindowButton::Zoom,
|
||||
NSWindowFullScreenButton,
|
||||
NSWindowMiniaturizeButton,
|
||||
NSWindowCloseButton,
|
||||
NSWindowZoomButton,
|
||||
] {
|
||||
if let Some(button) = this.standardWindowButton(*titlebar_button) {
|
||||
button.setHidden(true);
|
||||
@@ -433,7 +469,7 @@ impl WinitWindow {
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
|
||||
if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) {
|
||||
if let Some(button) = this.standardWindowButton(NSWindowZoomButton) {
|
||||
button.setEnabled(false);
|
||||
}
|
||||
}
|
||||
@@ -451,25 +487,16 @@ impl WinitWindow {
|
||||
})
|
||||
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?;
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
match attrs.parent_window.0 {
|
||||
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
|
||||
// SAFETY: Caller ensures the pointer is valid or NULL
|
||||
// Unwrap is fine, since the pointer comes from `NonNull`.
|
||||
let parent_view: Id<NSView> =
|
||||
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
|
||||
let parent = parent_view.window().ok_or_else(|| {
|
||||
os_error!(OsError::CreationError(
|
||||
"parent view should be installed in a window"
|
||||
))
|
||||
})?;
|
||||
if let Some(parent_window) = attrs.parent_window {
|
||||
let parent = parent_window.ns_view.get(mtm).window().ok_or_else(|| {
|
||||
os_error!(OsError::CreationError(
|
||||
"parent view should be installed in a window"
|
||||
))
|
||||
})?;
|
||||
|
||||
// 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(&this, NSWindowOrderingMode::NSWindowAbove) };
|
||||
}
|
||||
Some(raw) => panic!("Invalid raw window handle {raw:?} on macOS"),
|
||||
None => (),
|
||||
// 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(&this, NSWindowAbove) };
|
||||
}
|
||||
|
||||
let view = WinitView::new(&this, pl_attrs.accepts_first_mouse);
|
||||
@@ -477,6 +504,7 @@ impl WinitWindow {
|
||||
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
|
||||
// macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid
|
||||
// always the default system value in favour of the user's code
|
||||
#[allow(deprecated)]
|
||||
view.setWantsBestResolutionOpenGLSurface(!pl_attrs.disallow_hidpi);
|
||||
|
||||
// On Mojave, views automatically become layer-backed shortly after being added to
|
||||
@@ -484,17 +512,17 @@ impl WinitWindow {
|
||||
// the view and its associated OpenGL context. To work around this, on Mojave we
|
||||
// explicitly make the view layer-backed up front so that AppKit doesn't do it
|
||||
// itself and break the association with its context.
|
||||
if NSAppKitVersion::current().floor() > NSAppKitVersion::NSAppKitVersionNumber10_12 {
|
||||
if unsafe { NSAppKitVersionNumber }.floor() > NSAppKitVersionNumber10_12 {
|
||||
view.setWantsLayer(true);
|
||||
}
|
||||
|
||||
// Configure the new view as the "key view" for the window
|
||||
this.setContentView(&view);
|
||||
this.setInitialFirstResponder(&view);
|
||||
this.setContentView(Some(&view));
|
||||
this.setInitialFirstResponder(Some(&view));
|
||||
|
||||
if attrs.transparent {
|
||||
this.setOpaque(false);
|
||||
this.setBackgroundColor(&NSColor::clear());
|
||||
this.setBackgroundColor(Some(unsafe { &NSColor::clearColor() }));
|
||||
}
|
||||
|
||||
if attrs.blur {
|
||||
@@ -517,13 +545,13 @@ impl WinitWindow {
|
||||
|
||||
match attrs.preferred_theme {
|
||||
Some(theme) => {
|
||||
set_ns_theme(Some(theme));
|
||||
set_ns_theme(Some(theme), mtm);
|
||||
let mut state = this.lock_shared_state("WinitWindow::new");
|
||||
state.current_theme = Some(theme);
|
||||
}
|
||||
None => {
|
||||
let mut state = this.lock_shared_state("WinitWindow::new");
|
||||
state.current_theme = Some(get_ns_theme());
|
||||
state.current_theme = Some(get_ns_theme(mtm));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,9 +583,10 @@ impl WinitWindow {
|
||||
Ok((this, delegate))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(super) fn view(&self) -> Id<WinitView> {
|
||||
// SAFETY: The view inside WinitWindow is always `WinitView`
|
||||
unsafe { Id::cast(self.contentView()) }
|
||||
unsafe { Id::cast(self.contentView().unwrap()) }
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -565,14 +594,14 @@ impl WinitWindow {
|
||||
&self,
|
||||
called_from_fn: &'static str,
|
||||
) -> SharedStateMutexGuard<'_> {
|
||||
SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn)
|
||||
SharedStateMutexGuard::new(self.ivars().lock().unwrap(), called_from_fn)
|
||||
}
|
||||
|
||||
fn set_style_mask(&self, mask: NSWindowStyleMask) {
|
||||
self.setStyleMask(mask);
|
||||
// If we don't do this, key handling will break
|
||||
// (at least until the window is clicked again/etc.)
|
||||
let _ = self.makeFirstResponder(Some(&self.contentView()));
|
||||
let _ = self.makeFirstResponder(Some(&self.contentView().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +622,7 @@ impl WinitWindow {
|
||||
// NOTE: in general we want to specify the blur radius, but the choice of 80
|
||||
// should be a reasonable default.
|
||||
let radius = if blur { 80 } else { 0 };
|
||||
let window_number = self.windowNumber();
|
||||
let window_number = unsafe { self.windowNumber() };
|
||||
unsafe {
|
||||
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius);
|
||||
}
|
||||
@@ -646,7 +675,7 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let frame = self.contentView().frame();
|
||||
let frame = self.contentView().unwrap().frame();
|
||||
let logical: LogicalSize<f64> = (frame.size.width as f64, frame.size.height as f64).into();
|
||||
let scale_factor = self.scale_factor();
|
||||
logical.to_physical(scale_factor)
|
||||
@@ -768,9 +797,9 @@ impl WinitWindow {
|
||||
if !fullscreen {
|
||||
let mut mask = self.styleMask();
|
||||
if resizable {
|
||||
mask |= NSWindowStyleMask::NSResizableWindowMask;
|
||||
mask |= NSWindowStyleMaskResizable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::NSResizableWindowMask;
|
||||
mask &= !NSWindowStyleMaskResizable;
|
||||
}
|
||||
self.set_style_mask(mask);
|
||||
}
|
||||
@@ -787,15 +816,15 @@ impl WinitWindow {
|
||||
let mut mask = self.styleMask();
|
||||
|
||||
if buttons.contains(WindowButtons::CLOSE) {
|
||||
mask |= NSWindowStyleMask::NSClosableWindowMask;
|
||||
mask |= NSWindowStyleMaskClosable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::NSClosableWindowMask;
|
||||
mask &= !NSWindowStyleMaskClosable;
|
||||
}
|
||||
|
||||
if buttons.contains(WindowButtons::MINIMIZE) {
|
||||
mask |= NSWindowStyleMask::NSMiniaturizableWindowMask;
|
||||
mask |= NSWindowStyleMaskMiniaturizable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::NSMiniaturizableWindowMask;
|
||||
mask &= !NSWindowStyleMaskMiniaturizable;
|
||||
}
|
||||
|
||||
// This must happen before the button's "enabled" status has been set,
|
||||
@@ -805,7 +834,7 @@ impl WinitWindow {
|
||||
// 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.standardWindowButton(NSWindowButton::Zoom) {
|
||||
if let Some(button) = self.standardWindowButton(NSWindowZoomButton) {
|
||||
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
|
||||
}
|
||||
}
|
||||
@@ -817,7 +846,7 @@ impl WinitWindow {
|
||||
buttons |= WindowButtons::MINIMIZE;
|
||||
}
|
||||
if self
|
||||
.standardWindowButton(NSWindowButton::Zoom)
|
||||
.standardWindowButton(NSWindowZoomButton)
|
||||
.map(|b| b.isEnabled())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
@@ -831,14 +860,14 @@ impl WinitWindow {
|
||||
|
||||
pub fn set_cursor_icon(&self, icon: CursorIcon) {
|
||||
let view = self.view();
|
||||
view.set_cursor_icon(NSCursor::from_icon(icon));
|
||||
view.set_cursor_icon(cursor_from_icon(icon));
|
||||
self.invalidateCursorRectsForView(&view);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
let view = self.view();
|
||||
view.set_cursor_icon(NSCursor::from_image(&cursor.inner));
|
||||
view.set_cursor_icon(cursor.0.clone());
|
||||
self.invalidateCursorRectsForView(&view);
|
||||
}
|
||||
|
||||
@@ -891,8 +920,11 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let event = NSApp().currentEvent();
|
||||
self.performWindowDragWithEvent(event.as_deref());
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let event = NSApplication::sharedApplication(mtm)
|
||||
.currentEvent()
|
||||
.unwrap();
|
||||
self.performWindowDragWithEvent(&event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -915,9 +947,8 @@ impl WinitWindow {
|
||||
// we make it resizable temporalily.
|
||||
let curr_mask = self.styleMask();
|
||||
|
||||
let required =
|
||||
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
||||
let needs_temp_mask = !curr_mask.contains(required);
|
||||
let required = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
|
||||
let needs_temp_mask = !mask_contains(curr_mask, required);
|
||||
if needs_temp_mask {
|
||||
self.set_style_mask(required);
|
||||
}
|
||||
@@ -938,9 +969,9 @@ impl WinitWindow {
|
||||
.take()
|
||||
.unwrap_or_else(|| self.styleMask());
|
||||
if shared_state.resizable {
|
||||
base_mask | NSWindowStyleMask::NSResizableWindowMask
|
||||
base_mask | NSWindowStyleMaskResizable
|
||||
} else {
|
||||
base_mask & !NSWindowStyleMask::NSResizableWindowMask
|
||||
base_mask & !NSWindowStyleMaskResizable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -971,7 +1002,7 @@ impl WinitWindow {
|
||||
if minimized {
|
||||
self.miniaturize(Some(self));
|
||||
} else {
|
||||
self.deminiaturize(Some(self));
|
||||
unsafe { self.deminiaturize(Some(self)) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,6 +1013,7 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let is_zoomed = self.is_zoomed();
|
||||
if is_zoomed == maximized {
|
||||
return;
|
||||
@@ -1000,17 +1032,14 @@ impl WinitWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
if self
|
||||
.styleMask()
|
||||
.contains(NSWindowStyleMask::NSResizableWindowMask)
|
||||
{
|
||||
if mask_contains(self.styleMask(), NSWindowStyleMaskResizable) {
|
||||
drop(shared_state);
|
||||
// Just use the native zoom if resizable
|
||||
self.zoom(None);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if maximized {
|
||||
let screen = NSScreen::main().expect("no screen found");
|
||||
let screen = NSScreen::mainScreen(mtm).expect("no screen found");
|
||||
screen.visibleFrame()
|
||||
} else {
|
||||
shared_state.saved_standard_frame()
|
||||
@@ -1033,6 +1062,9 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
let mut shared_state_lock = self.lock_shared_state("set_fullscreen");
|
||||
if shared_state_lock.is_simple_fullscreen {
|
||||
return;
|
||||
@@ -1064,7 +1096,7 @@ impl WinitWindow {
|
||||
}
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
}
|
||||
.ns_screen()
|
||||
.ns_screen(mtm)
|
||||
.unwrap();
|
||||
|
||||
let old_screen = self.screen().unwrap();
|
||||
@@ -1095,7 +1127,6 @@ impl WinitWindow {
|
||||
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
|
||||
|
||||
if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) {
|
||||
let app = NSApp();
|
||||
let mut shared_state_lock = self.lock_shared_state("set_fullscreen");
|
||||
shared_state_lock.save_presentation_opts = Some(app.presentationOptions());
|
||||
}
|
||||
@@ -1153,7 +1184,7 @@ impl WinitWindow {
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
window.setLevel(NSWindowLevel::Normal);
|
||||
window.setLevel(kCGNormalWindowLevel as NSWindowLevel);
|
||||
window.toggleFullScreen(None);
|
||||
}
|
||||
|
||||
@@ -1163,9 +1194,8 @@ impl WinitWindow {
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
let curr_mask = self.styleMask();
|
||||
let required = NSWindowStyleMask::NSTitledWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
let required = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
|
||||
if !mask_contains(curr_mask, required) {
|
||||
self.set_style_mask(required);
|
||||
self.lock_shared_state("set_fullscreen").saved_style = Some(curr_mask);
|
||||
}
|
||||
@@ -1194,30 +1224,27 @@ impl WinitWindow {
|
||||
// of the menu bar, and this looks broken, so we must make sure
|
||||
// that the menu bar is disabled. This is done in the window
|
||||
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||
let app = NSApp();
|
||||
self.lock_shared_state("set_fullscreen")
|
||||
.save_presentation_opts = Some(app.presentationOptions());
|
||||
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
let window_level =
|
||||
NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1);
|
||||
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
|
||||
self.setLevel(window_level);
|
||||
}
|
||||
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
|
||||
let presentation_options = self
|
||||
.lock_shared_state("set_fullscreen")
|
||||
.save_presentation_opts
|
||||
.unwrap_or_else(|| {
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
||||
});
|
||||
NSApp().setPresentationOptions(presentation_options);
|
||||
.unwrap_or(
|
||||
NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationAutoHideMenuBar,
|
||||
);
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
@@ -1229,7 +1256,7 @@ impl WinitWindow {
|
||||
|
||||
// Restore the normal window level following the Borderless fullscreen
|
||||
// `CGShieldingWindowLevel() + 1` hack.
|
||||
self.setLevel(NSWindowLevel::Normal);
|
||||
self.setLevel(kCGNormalWindowLevel as NSWindowLevel);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -1257,15 +1284,15 @@ impl WinitWindow {
|
||||
|
||||
let new_mask = {
|
||||
let mut new_mask = if decorations {
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSTitledWindowMask
|
||||
NSWindowStyleMaskClosable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
| NSWindowStyleMaskResizable
|
||||
| NSWindowStyleMaskTitled
|
||||
} else {
|
||||
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
|
||||
NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable
|
||||
};
|
||||
if !resizable {
|
||||
new_mask &= !NSWindowStyleMask::NSResizableWindowMask;
|
||||
new_mask &= !NSWindowStyleMaskResizable;
|
||||
}
|
||||
new_mask
|
||||
};
|
||||
@@ -1280,9 +1307,9 @@ impl WinitWindow {
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
let level = match level {
|
||||
WindowLevel::AlwaysOnTop => NSWindowLevel::Floating,
|
||||
WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL,
|
||||
WindowLevel::Normal => NSWindowLevel::Normal,
|
||||
WindowLevel::AlwaysOnTop => kCGFloatingWindowLevel as NSWindowLevel,
|
||||
WindowLevel::AlwaysOnBottom => (kCGNormalWindowLevel - 1) as NSWindowLevel,
|
||||
WindowLevel::Normal => kCGNormalWindowLevel as NSWindowLevel,
|
||||
};
|
||||
self.setLevel(level);
|
||||
}
|
||||
@@ -1317,30 +1344,33 @@ impl WinitWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let is_minimized = self.isMiniaturized();
|
||||
let is_visible = self.isVisible();
|
||||
|
||||
if !is_minimized && is_visible {
|
||||
NSApp().activateIgnoringOtherApps(true);
|
||||
#[allow(deprecated)]
|
||||
NSApplication::sharedApplication(mtm).activateIgnoringOtherApps(true);
|
||||
self.makeKeyAndOrderFront(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let ns_request_type = request_type.map(|ty| match ty {
|
||||
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
|
||||
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
|
||||
UserAttentionType::Critical => NSCriticalRequest,
|
||||
UserAttentionType::Informational => NSInformationalRequest,
|
||||
});
|
||||
if let Some(ty) = ns_request_type {
|
||||
NSApp().requestUserAttention(ty);
|
||||
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// Allow directly accessing the current monitor internally without unwrapping.
|
||||
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
|
||||
let display_id = self.screen()?.display_id();
|
||||
let display_id = get_display_id(&*self.screen()?);
|
||||
Some(MonitorHandle::new(display_id))
|
||||
}
|
||||
|
||||
@@ -1365,7 +1395,7 @@ impl WinitWindow {
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::AppKitHandle::empty();
|
||||
window_handle.ns_window = self as *const Self as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
rwh_04::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -1374,7 +1404,7 @@ impl WinitWindow {
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::AppKitWindowHandle::empty();
|
||||
window_handle.ns_window = self as *const Self as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
rwh_05::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -1386,12 +1416,12 @@ impl WinitWindow {
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let window_handle = rwh_06::AppKitWindowHandle::new({
|
||||
let ptr = Id::as_ptr(&self.contentView()) as *mut _;
|
||||
let ptr = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
|
||||
});
|
||||
Ok(rwh_06::RawWindowHandle::AppKit(window_handle))
|
||||
rwh_06::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
@@ -1424,21 +1454,24 @@ impl WinitWindow {
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
set_ns_theme(theme);
|
||||
self.lock_shared_state("set_theme").current_theme = theme.or_else(|| Some(get_ns_theme()));
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
set_ns_theme(theme, mtm);
|
||||
self.lock_shared_state("set_theme").current_theme =
|
||||
theme.or_else(|| Some(get_ns_theme(mtm)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_content_protected(&self, protected: bool) {
|
||||
self.setSharingType(if protected {
|
||||
NSWindowSharingType::NSWindowSharingNone
|
||||
NSWindowSharingNone
|
||||
} else {
|
||||
NSWindowSharingType::NSWindowSharingReadOnly
|
||||
NSWindowSharingReadOnly
|
||||
})
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
self.title_().to_string()
|
||||
let window: &NSWindow = self;
|
||||
window.title().to_string()
|
||||
}
|
||||
|
||||
pub fn reset_dead_keys(&self) {
|
||||
@@ -1455,9 +1488,10 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
#[inline]
|
||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let mut shared_state_lock = self.lock_shared_state("set_simple_fullscreen");
|
||||
|
||||
let app = NSApp();
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let is_native_fullscreen = shared_state_lock.fullscreen.is_some();
|
||||
let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen;
|
||||
|
||||
@@ -1485,20 +1519,19 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
|
||||
NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
// Hide the titlebar
|
||||
self.toggle_style_mask(NSWindowStyleMask::NSTitledWindowMask, false);
|
||||
self.toggle_style_mask(NSWindowStyleMaskTitled, false);
|
||||
|
||||
// Set the window frame to the screen frame size
|
||||
let screen = self.screen().expect("expected screen to be available");
|
||||
self.setFrame_display(screen.frame(), true);
|
||||
|
||||
// Fullscreen windows can't be resized, minimized, or moved
|
||||
self.toggle_style_mask(NSWindowStyleMask::NSMiniaturizableWindowMask, false);
|
||||
self.toggle_style_mask(NSWindowStyleMask::NSResizableWindowMask, false);
|
||||
self.toggle_style_mask(NSWindowStyleMaskMiniaturizable, false);
|
||||
self.toggle_style_mask(NSWindowStyleMaskResizable, false);
|
||||
self.setMovable(false);
|
||||
|
||||
true
|
||||
@@ -1547,24 +1580,20 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
#[inline]
|
||||
fn select_next_tab(&self) {
|
||||
if let Some(group) = self.tabGroup() {
|
||||
group.selectNextTab();
|
||||
}
|
||||
self.selectNextTab(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_previous_tab(&self) {
|
||||
if let Some(group) = self.tabGroup() {
|
||||
group.selectPreviousTab()
|
||||
}
|
||||
unsafe { self.selectPreviousTab(None) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_tab_at_index(&self, index: usize) {
|
||||
if let Some(group) = self.tabGroup() {
|
||||
if let Some(windows) = group.tabbedWindows() {
|
||||
if let Some(windows) = unsafe { self.tabbedWindows() } {
|
||||
if index < windows.len() {
|
||||
group.setSelectedWindow(&windows[index]);
|
||||
group.setSelectedWindow(Some(&windows[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1572,8 +1601,7 @@ impl WindowExtMacOS for WinitWindow {
|
||||
|
||||
#[inline]
|
||||
fn num_tabs(&self) -> usize {
|
||||
self.tabGroup()
|
||||
.and_then(|group| group.tabbedWindows())
|
||||
unsafe { self.tabbedWindows() }
|
||||
.map(|windows| windows.len())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
@@ -1597,25 +1625,31 @@ impl WindowExtMacOS for WinitWindow {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_ns_theme() -> Theme {
|
||||
let app = NSApp();
|
||||
fn mask_contains(mask: NSWindowStyleMask, value: NSWindowStyleMask) -> bool {
|
||||
mask & value == value
|
||||
}
|
||||
|
||||
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
|
||||
if !has_theme {
|
||||
return Theme::Light;
|
||||
}
|
||||
let appearance = app.effectiveAppearance();
|
||||
let name = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
NSString::from_str("NSAppearanceNameAqua"),
|
||||
NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
]));
|
||||
let name = appearance
|
||||
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
NSString::from_str("NSAppearanceNameAqua"),
|
||||
NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
]))
|
||||
.unwrap();
|
||||
match &*name.to_string() {
|
||||
"NSAppearanceNameDarkAqua" => Theme::Dark,
|
||||
_ => Theme::Light,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ns_theme(theme: Option<Theme>) {
|
||||
let app = NSApp();
|
||||
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
|
||||
if has_theme {
|
||||
let appearance = theme.map(|t| {
|
||||
@@ -1623,7 +1657,7 @@ fn set_ns_theme(theme: Option<Theme>) {
|
||||
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
|
||||
};
|
||||
NSAppearance::appearanceNamed(&name)
|
||||
NSAppearance::appearanceNamed(&name).unwrap()
|
||||
});
|
||||
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::Cell;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::ptr;
|
||||
|
||||
use icrate::Foundation::{NSArray, NSObject, NSSize, NSString};
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::appkit::{
|
||||
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
|
||||
use icrate::AppKit::{
|
||||
NSApplicationPresentationFullScreen, NSApplicationPresentationHideDock,
|
||||
NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSDraggingDestination,
|
||||
NSFilenamesPboardType, NSPasteboard, NSWindowDelegate, NSWindowOcclusionStateVisible,
|
||||
};
|
||||
use icrate::Foundation::{MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSSize, NSString};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::runtime::{AnyObject, ProtocolObject};
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
|
||||
use super::{
|
||||
app_state::AppState,
|
||||
util,
|
||||
@@ -24,7 +27,9 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub(crate) struct State {
|
||||
window: Id<WinitWindow>,
|
||||
|
||||
// This is set when WindowBuilder::with_fullscreen was set,
|
||||
// see comments of `window_did_fail_to_enter_fullscreen`
|
||||
initial_fullscreen: Cell<bool>,
|
||||
@@ -37,72 +42,21 @@ pub struct State {
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WinitWindowDelegate {
|
||||
window: IvarDrop<Id<WinitWindow>, "_window">,
|
||||
|
||||
// TODO: It may be possible for delegate methods to be called
|
||||
// asynchronously, causing data races panics?
|
||||
// TODO: Remove unnecessary boxing here
|
||||
state: IvarDrop<Box<State>, "_state">,
|
||||
}
|
||||
|
||||
mod ivars;
|
||||
pub(crate) struct WinitWindowDelegate;
|
||||
|
||||
unsafe impl ClassType for WinitWindowDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindowDelegate";
|
||||
}
|
||||
|
||||
unsafe impl WinitWindowDelegate {
|
||||
#[method(initWithWindow:initialFullscreen:)]
|
||||
unsafe fn init_with_winit(
|
||||
this: *mut Self,
|
||||
window: &WinitWindow,
|
||||
initial_fullscreen: bool,
|
||||
) -> Option<NonNull<Self>> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };
|
||||
this.map(|this| {
|
||||
let scale_factor = window.scale_factor();
|
||||
|
||||
Ivar::write(&mut this.window, window.retain());
|
||||
Ivar::write(
|
||||
&mut this.state,
|
||||
Box::new(State {
|
||||
initial_fullscreen: Cell::new(initial_fullscreen),
|
||||
previous_position: Cell::new(None),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
}),
|
||||
);
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
this.queue_static_scale_factor_changed_event();
|
||||
}
|
||||
this.window.setDelegate(Some(this));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center: Id<AnyObject> =
|
||||
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
|
||||
let notification_name =
|
||||
NSString::from_str("AppleInterfaceThemeChangedNotification");
|
||||
let _: () = unsafe {
|
||||
msg_send![
|
||||
¬ification_center,
|
||||
addObserver: &*this
|
||||
selector: sel!(effectiveAppearanceDidChange:)
|
||||
name: &*notification_name
|
||||
object: ptr::null::<AnyObject>()
|
||||
]
|
||||
};
|
||||
|
||||
NonNull::from(this)
|
||||
})
|
||||
}
|
||||
impl DeclaredClass for WinitWindowDelegate {
|
||||
type Ivars = State;
|
||||
}
|
||||
|
||||
// NSWindowDelegate + NSDraggingDestination protocols
|
||||
unsafe impl WinitWindowDelegate {
|
||||
unsafe impl NSObjectProtocol for WinitWindowDelegate {}
|
||||
|
||||
unsafe impl NSWindowDelegate for WinitWindowDelegate {
|
||||
#[method(windowShouldClose:)]
|
||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
||||
trace_scope!("windowShouldClose:");
|
||||
@@ -117,7 +71,7 @@ declare_class!(
|
||||
autoreleasepool(|_| {
|
||||
// Since El Capitan, we need to be careful that delegate methods can't
|
||||
// be called after the window closes.
|
||||
self.window.setDelegate(None);
|
||||
self.ivars().window.setDelegate(None);
|
||||
});
|
||||
self.queue_event(WindowEvent::Destroyed);
|
||||
}
|
||||
@@ -134,16 +88,19 @@ declare_class!(
|
||||
trace_scope!("windowWillStartLiveResize:");
|
||||
|
||||
let increments = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen")
|
||||
.resize_increments;
|
||||
self.window.set_resize_increments_inner(increments);
|
||||
self.ivars().window.set_resize_increments_inner(increments);
|
||||
}
|
||||
|
||||
#[method(windowDidEndLiveResize:)]
|
||||
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEndLiveResize:");
|
||||
self.window.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
self.ivars()
|
||||
.window
|
||||
.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
}
|
||||
|
||||
// This won't be triggered if the move was part of a resize.
|
||||
@@ -177,11 +134,183 @@ declare_class!(
|
||||
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
||||
// easily fall out of synchrony with reality. This requires us to emit
|
||||
// a synthetic ModifiersChanged event when we lose focus.
|
||||
self.window.view().reset_modifiers();
|
||||
self.ivars().window.view().reset_modifiers();
|
||||
|
||||
self.queue_event(WindowEvent::Focused(false));
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = self.ivars().window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// 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(_)) => (),
|
||||
// `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
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = self.ivars().window.current_monitor_inner();
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||
// still want to make this configurable for borderless fullscreen. Right now
|
||||
// we don't, for consistency. If we do, it should be documented that the
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.ivars().initial_fullscreen.set(false);
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.ivars().window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
self.ivars().window.restore_state_from_fullscreen();
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.ivars().window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
|
||||
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
|
||||
/// was set).
|
||||
///
|
||||
/// From Apple doc:
|
||||
/// In some cases, the transition to enter full-screen mode can fail,
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[method(windowDidFailToEnterFullScreen:)]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
if self.ivars().initial_fullscreen.get() {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*self.ivars().window,
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<AnyObject>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
};
|
||||
} else {
|
||||
self.ivars().window.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.ivars().window.occlusionState() & NSWindowOcclusionStateVisible
|
||||
== NSWindowOcclusionStateVisible;
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("window_did_change_screen")
|
||||
.is_simple_fullscreen;
|
||||
if is_simple_fullscreen {
|
||||
if let Some(screen) = self.ivars().window.screen() {
|
||||
self.ivars().window.setFrame_display(screen.frame(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl NSDraggingDestination for WinitWindowDelegate {
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[method(draggingEntered:)]
|
||||
fn dragging_entered(&self, sender: &NSObject) -> bool {
|
||||
@@ -190,7 +319,9 @@ declare_class!(
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
@@ -216,7 +347,9 @@ declare_class!(
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
@@ -239,153 +372,9 @@ declare_class!(
|
||||
trace_scope!("draggingExited:");
|
||||
self.queue_event(WindowEvent::HoveredFileCancelled);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
let mut shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = self.window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// 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(_)) => (),
|
||||
// `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
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = self.window.current_monitor_inner();
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||
// still want to make this configurable for borderless fullscreen. Right now
|
||||
// we don't, for consistency. If we do, it should be documented that the
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.state.initial_fullscreen.set(false);
|
||||
let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
self.window.restore_state_from_fullscreen();
|
||||
let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
|
||||
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
|
||||
/// was set).
|
||||
///
|
||||
/// From Apple doc:
|
||||
/// In some cases, the transition to enter full-screen mode can fail,
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[method(windowDidFailToEnterFullScreen:)]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
let mut shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
if self.state.initial_fullscreen.get() {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*self.window,
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<AnyObject>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
};
|
||||
} else {
|
||||
self.window.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
self.queue_event(WindowEvent::Occluded(
|
||||
!self
|
||||
.window
|
||||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
|
||||
))
|
||||
}
|
||||
|
||||
unsafe impl WinitWindowDelegate {
|
||||
// Observe theme change
|
||||
#[method(effectiveAppearanceDidChange:)]
|
||||
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
|
||||
@@ -402,8 +391,10 @@ declare_class!(
|
||||
|
||||
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
||||
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
||||
let theme = get_ns_theme();
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let theme = get_ns_theme(mtm);
|
||||
let mut shared_state = self
|
||||
.ivars()
|
||||
.window
|
||||
.lock_shared_state("effective_appearance_did_change");
|
||||
let current_theme = shared_state.current_theme;
|
||||
@@ -413,71 +404,80 @@ declare_class!(
|
||||
self.queue_event(WindowEvent::ThemeChanged(theme));
|
||||
}
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self
|
||||
.window
|
||||
.lock_shared_state("window_did_change_screen")
|
||||
.is_simple_fullscreen;
|
||||
if is_simple_fullscreen {
|
||||
if let Some(screen) = self.window.screen() {
|
||||
self.window.setFrame_display(screen.frame(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitWindowDelegate {
|
||||
pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
initWithWindow: window,
|
||||
initialFullscreen: initial_fullscreen,
|
||||
]
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let scale_factor = window.scale_factor();
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
window: window.retain(),
|
||||
initial_fullscreen: Cell::new(initial_fullscreen),
|
||||
previous_position: Cell::new(None),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
this.queue_static_scale_factor_changed_event();
|
||||
}
|
||||
window.setDelegate(Some(ProtocolObject::from_ref(&*this)));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center: Id<AnyObject> =
|
||||
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
|
||||
let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification");
|
||||
let _: () = unsafe {
|
||||
msg_send![
|
||||
¬ification_center,
|
||||
addObserver: &*this
|
||||
selector: sel!(effectiveAppearanceDidChange:)
|
||||
name: &*notification_name
|
||||
object: ptr::null::<AnyObject>()
|
||||
]
|
||||
};
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let event = Event::WindowEvent {
|
||||
window_id: WindowId(self.window.id()),
|
||||
window_id: WindowId(self.ivars().window.id()),
|
||||
event,
|
||||
};
|
||||
AppState::queue_event(event);
|
||||
}
|
||||
|
||||
fn queue_static_scale_factor_changed_event(&self) {
|
||||
let scale_factor = self.window.scale_factor();
|
||||
if scale_factor == self.state.previous_scale_factor.get() {
|
||||
let scale_factor = self.ivars().window.scale_factor();
|
||||
if scale_factor == self.ivars().previous_scale_factor.get() {
|
||||
return;
|
||||
};
|
||||
|
||||
self.state.previous_scale_factor.set(scale_factor);
|
||||
self.ivars().previous_scale_factor.set(scale_factor);
|
||||
let suggested_size = self.view_size();
|
||||
AppState::queue_static_scale_factor_changed_event(
|
||||
self.window.clone(),
|
||||
self.ivars().window.clone(),
|
||||
suggested_size.to_physical(scale_factor),
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_move_event(&self) {
|
||||
let rect = self.window.frame();
|
||||
let rect = self.ivars().window.frame();
|
||||
let x = rect.origin.x as f64;
|
||||
let y = util::bottom_left_to_top_left(rect);
|
||||
if self.state.previous_position.get() != Some((x, y)) {
|
||||
self.state.previous_position.set(Some((x, y)));
|
||||
let scale_factor = self.window.scale_factor();
|
||||
if self.ivars().previous_position.get() != Some((x, y)) {
|
||||
self.ivars().previous_position.set(Some((x, y)));
|
||||
let scale_factor = self.ivars().window.scale_factor();
|
||||
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
|
||||
self.queue_event(WindowEvent::Moved(physical_pos));
|
||||
}
|
||||
}
|
||||
|
||||
fn view_size(&self) -> LogicalSize<f64> {
|
||||
let size = self.window.contentView().frame().size;
|
||||
let size = self.ivars().window.contentView().unwrap().frame().size;
|
||||
LogicalSize::new(size.width as f64, size.height as f64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
mod event_loop;
|
||||
|
||||
pub use self::window::Window;
|
||||
pub(crate) use self::window::{OwnedWindowHandle, Window};
|
||||
mod window;
|
||||
|
||||
struct RedoxSocket {
|
||||
@@ -194,6 +194,7 @@ impl Display for OsError {
|
||||
}
|
||||
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
platform_impl::Fullscreen,
|
||||
@@ -13,8 +12,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
|
||||
TimeSocket, WindowId, WindowProperties,
|
||||
EventLoopWindowTarget, MonitorHandle, PlatformCustomCursor,
|
||||
PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties,
|
||||
};
|
||||
|
||||
// These values match the values uses in the `window_new` function in orbital:
|
||||
@@ -26,6 +25,18 @@ const ORBITAL_FLAG_BORDERLESS: char = 'l';
|
||||
const ORBITAL_FLAG_RESIZABLE: char = 'r';
|
||||
const ORBITAL_FLAG_TRANSPARENT: char = 't';
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable.
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
window_socket: Arc<RedoxSocket>,
|
||||
redraws: Arc<Mutex<VecDeque<WindowId>>>,
|
||||
@@ -353,7 +364,7 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub fn set_custom_cursor(&self, _: CustomCursor) {}
|
||||
pub(crate) fn set_custom_cursor(&self, _: PlatformCustomCursor) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
|
||||
@@ -6,65 +6,67 @@ use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::Poll;
|
||||
|
||||
// NOTE: This channel doesn't wake up when all senders or receivers are
|
||||
// dropped. This is acceptable as long as it's only used in `Dispatcher`, which
|
||||
// has it's own `Drop` behavior.
|
||||
|
||||
pub fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let sender = Arc::new(Mutex::new(sender));
|
||||
let inner = Arc::new(Inner {
|
||||
let shared = Arc::new(Shared {
|
||||
closed: AtomicBool::new(false),
|
||||
waker: AtomicWaker::new(),
|
||||
});
|
||||
|
||||
let sender = AsyncSender {
|
||||
sender,
|
||||
inner: Arc::clone(&inner),
|
||||
};
|
||||
let sender = AsyncSender(Arc::new(SenderInner {
|
||||
sender: Mutex::new(sender),
|
||||
shared: Arc::clone(&shared),
|
||||
}));
|
||||
let receiver = AsyncReceiver {
|
||||
receiver: Rc::new(receiver),
|
||||
inner,
|
||||
shared,
|
||||
};
|
||||
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
pub struct AsyncSender<T> {
|
||||
pub struct AsyncSender<T>(Arc<SenderInner<T>>);
|
||||
|
||||
struct SenderInner<T> {
|
||||
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
|
||||
// be accessed on the main thread, as it could block. Additionally we need
|
||||
// to wrap it in an `Arc` to make it clonable on the main thread without
|
||||
// to wrap `Sender` in an `Arc` to make it clonable on the main thread without
|
||||
// having to block.
|
||||
sender: Arc<Mutex<Sender<T>>>,
|
||||
inner: Arc<Inner>,
|
||||
sender: Mutex<Sender<T>>,
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
impl<T> AsyncSender<T> {
|
||||
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
|
||||
self.sender.lock().unwrap().send(event)?;
|
||||
self.inner.waker.wake();
|
||||
self.0.sender.lock().unwrap().send(event)?;
|
||||
self.0.shared.waker.wake();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.inner.closed.store(true, Ordering::Relaxed);
|
||||
self.inner.waker.wake()
|
||||
impl<T> SenderInner<T> {
|
||||
fn close(&self) {
|
||||
self.shared.closed.store(true, Ordering::Relaxed);
|
||||
self.shared.waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for AsyncSender<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
sender: Arc::clone(&self.sender),
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
Self(Arc::clone(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for SenderInner<T> {
|
||||
fn drop(&mut self) {
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncReceiver<T> {
|
||||
receiver: Rc<Receiver<T>>,
|
||||
inner: Arc<Inner>,
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
impl<T> AsyncReceiver<T> {
|
||||
@@ -72,12 +74,12 @@ impl<T> AsyncReceiver<T> {
|
||||
future::poll_fn(|cx| match self.receiver.try_recv() {
|
||||
Ok(event) => Poll::Ready(Ok(event)),
|
||||
Err(TryRecvError::Empty) => {
|
||||
self.inner.waker.register(cx.waker());
|
||||
self.shared.waker.register(cx.waker());
|
||||
|
||||
match self.receiver.try_recv() {
|
||||
Ok(event) => Poll::Ready(Ok(event)),
|
||||
Err(TryRecvError::Empty) => {
|
||||
if self.inner.closed.load(Ordering::Relaxed) {
|
||||
if self.shared.closed.load(Ordering::Relaxed) {
|
||||
Poll::Ready(Err(RecvError))
|
||||
} else {
|
||||
Poll::Pending
|
||||
@@ -104,12 +106,18 @@ impl<T> Clone for AsyncReceiver<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
receiver: Rc::clone(&self.receiver),
|
||||
inner: Arc::clone(&self.inner),
|
||||
shared: Arc::clone(&self.shared),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
impl<T> Drop for AsyncReceiver<T> {
|
||||
fn drop(&mut self) {
|
||||
self.shared.closed.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
closed: AtomicBool,
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
@@ -82,12 +82,6 @@ impl<T> Dispatcher<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Dispatcher<T> {
|
||||
fn drop(&mut self) {
|
||||
self.0.with_sender_data(|sender| sender.close())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DispatchRunner<T: 'static> {
|
||||
wrapper: Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>,
|
||||
receiver: AsyncReceiver<Closure<T>>,
|
||||
|
||||
@@ -3,7 +3,7 @@ mod dispatcher;
|
||||
mod waker;
|
||||
mod wrapper;
|
||||
|
||||
use self::channel::{channel, AsyncReceiver, AsyncSender};
|
||||
pub use self::channel::{channel, AsyncReceiver, AsyncSender};
|
||||
pub use self::dispatcher::{DispatchRunner, Dispatcher};
|
||||
pub use self::waker::{Waker, WakerSpawner};
|
||||
use self::wrapper::Wrapper;
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
ops::Deref,
|
||||
rc::{Rc, Weak},
|
||||
cell::RefCell,
|
||||
future,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
rc::{self, Rc},
|
||||
sync::{self, Arc},
|
||||
task::{Poll, Waker},
|
||||
};
|
||||
|
||||
use crate::cursor::{BadImage, CursorImage};
|
||||
use crate::{
|
||||
cursor::{BadImage, CursorImage},
|
||||
platform_impl::platform::r#async,
|
||||
};
|
||||
use cursor_icon::CursorIcon;
|
||||
use once_cell::sync::Lazy;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{
|
||||
Blob, Document, HtmlCanvasElement, ImageBitmap, ImageBitmapOptions,
|
||||
Blob, Document, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions,
|
||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
||||
};
|
||||
|
||||
use super::backend::Style;
|
||||
use self::thread_safe::ThreadSafe;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum WebCustomCursor {
|
||||
use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CustomCursorBuilder {
|
||||
Image(CursorImage),
|
||||
Url {
|
||||
url: String,
|
||||
@@ -25,72 +36,185 @@ pub enum WebCustomCursor {
|
||||
},
|
||||
}
|
||||
|
||||
impl WebCustomCursor {
|
||||
impl CustomCursorBuilder {
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
Ok(Self::Image(CursorImage::from_rgba(
|
||||
) -> Result<CustomCursorBuilder, BadImage> {
|
||||
Ok(CustomCursorBuilder::Image(CursorImage::from_rgba(
|
||||
rgba, width, height, hotspot_x, hotspot_y,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn build(
|
||||
&self,
|
||||
window: &Window,
|
||||
document: &Document,
|
||||
style: &Style,
|
||||
previous: SelectedCursor,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
) -> SelectedCursor {
|
||||
let previous = previous.into();
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomCursor(Arc<Inner>);
|
||||
|
||||
match self {
|
||||
WebCustomCursor::Image(image) => SelectedCursor::Image(CursorImageState::from_image(
|
||||
window,
|
||||
document.clone(),
|
||||
style.clone(),
|
||||
image,
|
||||
previous,
|
||||
cursor_visible,
|
||||
)),
|
||||
WebCustomCursor::Url {
|
||||
impl Hash for CustomCursor {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomCursor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn build<T>(
|
||||
builder: CustomCursorBuilder,
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
) -> Self {
|
||||
Lazy::force(&DROP_HANDLER);
|
||||
|
||||
Self(match builder {
|
||||
CustomCursorBuilder::Image(image) => ImageState::from_rgba(
|
||||
window_target.runner.window(),
|
||||
window_target.runner.document().clone(),
|
||||
&image,
|
||||
),
|
||||
CustomCursorBuilder::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => {
|
||||
let value = previous.style_with_url(url, *hotspot_x, *hotspot_y);
|
||||
} => ImageState::from_url(url, hotspot_x, hotspot_y),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if cursor_visible.get() {
|
||||
style.set("cursor", &value);
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Inner(Option<ThreadSafe<RefCell<ImageState>>>);
|
||||
|
||||
SelectedCursor::Url {
|
||||
style: value,
|
||||
previous,
|
||||
url: url.clone(),
|
||||
hotspot_x: *hotspot_x,
|
||||
hotspot_y: *hotspot_y,
|
||||
}
|
||||
}
|
||||
static DROP_HANDLER: Lazy<AsyncSender<ThreadSafe<RefCell<ImageState>>>> = Lazy::new(|| {
|
||||
let (sender, receiver) = r#async::channel();
|
||||
wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} });
|
||||
|
||||
sender
|
||||
});
|
||||
|
||||
impl Inner {
|
||||
fn new() -> Arc<Self> {
|
||||
Arc::new(Inner(Some(ThreadSafe::new(RefCell::new(
|
||||
ImageState::Loading(None),
|
||||
)))))
|
||||
}
|
||||
|
||||
fn get(&self) -> &RefCell<ImageState> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.expect("value has accidently already been dropped")
|
||||
.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
let value = self
|
||||
.0
|
||||
.take()
|
||||
.expect("value has accidently already been dropped");
|
||||
|
||||
if !value.in_origin_thread() {
|
||||
DROP_HANDLER
|
||||
.send(value)
|
||||
.expect("sender dropped in main thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SelectedCursor {
|
||||
pub struct CursorState(Rc<RefCell<State>>);
|
||||
|
||||
impl CursorState {
|
||||
pub fn new(style: Style) -> Self {
|
||||
Self(Rc::new(RefCell::new(State {
|
||||
style,
|
||||
visible: true,
|
||||
cursor: SelectedCursor::default(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
|
||||
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
|
||||
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
|
||||
state.take();
|
||||
}
|
||||
}
|
||||
|
||||
this.cursor = SelectedCursor::Named(cursor);
|
||||
this.set_style();
|
||||
}
|
||||
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
|
||||
match cursor.0.get().borrow_mut().deref_mut() {
|
||||
ImageState::Loading(state) => {
|
||||
this.cursor = SelectedCursor::ImageLoading {
|
||||
state: cursor.0.clone(),
|
||||
previous: mem::take(&mut this.cursor).into(),
|
||||
};
|
||||
*state = Some(Rc::downgrade(&self.0));
|
||||
}
|
||||
ImageState::Failed => log::error!("tried to load invalid cursor"),
|
||||
ImageState::Ready(image) => {
|
||||
this.cursor = SelectedCursor::ImageReady(image.clone());
|
||||
this.set_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
if !visible && state.visible {
|
||||
state.visible = false;
|
||||
state.style.set("cursor", "none");
|
||||
} else if visible && !state.visible {
|
||||
state.visible = true;
|
||||
state.set_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
style: Style,
|
||||
visible: bool,
|
||||
cursor: SelectedCursor,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn set_style(&self) {
|
||||
if self.visible {
|
||||
let value = match &self.cursor {
|
||||
SelectedCursor::Named(icon) => icon.name(),
|
||||
SelectedCursor::ImageLoading { previous, .. } => previous.style(),
|
||||
SelectedCursor::ImageReady(image) => &image.style,
|
||||
};
|
||||
|
||||
self.style.set("cursor", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
Url {
|
||||
style: String,
|
||||
ImageLoading {
|
||||
state: Arc<Inner>,
|
||||
previous: Previous,
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
Image(Rc<RefCell<Option<CursorImageState>>>),
|
||||
ImageReady(Rc<Image>),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
@@ -99,71 +223,26 @@ impl Default for SelectedCursor {
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedCursor {
|
||||
pub fn set_style(&self, style: &Style) {
|
||||
let value = match self {
|
||||
SelectedCursor::Named(icon) => icon.name(),
|
||||
SelectedCursor::Url { style, .. } => style,
|
||||
SelectedCursor::Image(image) => {
|
||||
let image = image.borrow();
|
||||
let value = match image.deref().as_ref().unwrap() {
|
||||
CursorImageState::Loading { previous, .. } => previous.style(),
|
||||
CursorImageState::Failed(previous) => previous.style(),
|
||||
CursorImageState::Ready { style, .. } => style,
|
||||
};
|
||||
return style.set("cursor", value);
|
||||
}
|
||||
};
|
||||
|
||||
style.set("cursor", value);
|
||||
impl From<Previous> for SelectedCursor {
|
||||
fn from(previous: Previous) -> Self {
|
||||
match previous {
|
||||
Previous::Named(icon) => Self::Named(icon),
|
||||
Previous::Image(image) => Self::ImageReady(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Previous {
|
||||
Named(CursorIcon),
|
||||
Url {
|
||||
style: String,
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
Image {
|
||||
style: String,
|
||||
image: WebCursorImage,
|
||||
},
|
||||
Image(Rc<Image>),
|
||||
}
|
||||
|
||||
impl Previous {
|
||||
fn style(&self) -> &str {
|
||||
match self {
|
||||
Previous::Named(icon) => icon.name(),
|
||||
Previous::Url { style: url, .. } => url,
|
||||
Previous::Image { style, .. } => style,
|
||||
}
|
||||
}
|
||||
|
||||
fn style_with_url(&self, new_url: &str, new_hotspot_x: u16, new_hotspot_y: u16) -> String {
|
||||
match self {
|
||||
Previous::Named(icon) => format!("url({new_url}) {new_hotspot_x} {new_hotspot_y}, {}", icon.name()),
|
||||
Previous::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
}
|
||||
| Previous::Image {
|
||||
image:
|
||||
WebCursorImage {
|
||||
data_url: url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => format!(
|
||||
"url({new_url}) {new_hotspot_x} {new_hotspot_y}, url({url}) {hotspot_x} {hotspot_y}, auto",
|
||||
),
|
||||
Previous::Image(image) => &image.style,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,66 +251,34 @@ impl From<SelectedCursor> for Previous {
|
||||
fn from(value: SelectedCursor) -> Self {
|
||||
match value {
|
||||
SelectedCursor::Named(icon) => Self::Named(icon),
|
||||
SelectedCursor::Url {
|
||||
style,
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
} => Self::Url {
|
||||
style,
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
},
|
||||
SelectedCursor::Image(image) => {
|
||||
match Rc::try_unwrap(image).unwrap().into_inner().unwrap() {
|
||||
CursorImageState::Loading { previous, .. } => previous,
|
||||
CursorImageState::Failed(previous) => previous,
|
||||
CursorImageState::Ready {
|
||||
style,
|
||||
image: current,
|
||||
..
|
||||
} => Self::Image {
|
||||
style,
|
||||
image: current,
|
||||
},
|
||||
}
|
||||
}
|
||||
SelectedCursor::ImageLoading { previous, .. } => previous,
|
||||
SelectedCursor::ImageReady(image) => Self::Image(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CursorImageState {
|
||||
Loading {
|
||||
style: Style,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
previous: Previous,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
Failed(Previous),
|
||||
Ready {
|
||||
style: String,
|
||||
image: WebCursorImage,
|
||||
previous: Previous,
|
||||
},
|
||||
enum ImageState {
|
||||
Loading(Option<rc::Weak<RefCell<State>>>),
|
||||
Failed,
|
||||
Ready(Rc<Image>),
|
||||
}
|
||||
|
||||
impl CursorImageState {
|
||||
fn from_image(
|
||||
window: &Window,
|
||||
document: Document,
|
||||
style: Style,
|
||||
image: &CursorImage,
|
||||
previous: Previous,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
) -> Rc<RefCell<Option<Self>>> {
|
||||
// Can't create array directly when backed by SharedArrayBuffer.
|
||||
impl ImageState {
|
||||
fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc<Inner> {
|
||||
// 1. Create an `ImageData` from the RGBA data.
|
||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||
// 5. Create an object URL from the `Blob`.
|
||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||
// 7. Change the `CursorState` if queued.
|
||||
|
||||
// 1. Create an `ImageData` from the RGBA data.
|
||||
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
|
||||
#[cfg(target_feature = "atomics")]
|
||||
let image_data = {
|
||||
// Can't share `SharedArrayBuffer` with `ImageData`.
|
||||
let result = {
|
||||
use js_sys::{Uint8Array, Uint8ClampedArray};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
@@ -250,115 +297,283 @@ impl CursorImageState {
|
||||
ImageDataExt::new(array, image.width as u32)
|
||||
.map(JsValue::from)
|
||||
.map(ImageData::unchecked_from_js)
|
||||
.unwrap()
|
||||
};
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
let image_data = ImageData::new_with_u8_clamped_array(
|
||||
let result = ImageData::new_with_u8_clamped_array(
|
||||
wasm_bindgen::Clamped(&image.rgba),
|
||||
image.width as u32,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
let image_data = result.expect("found wrong image size");
|
||||
|
||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||
//
|
||||
// We call `createImageBitmap()` before spawning the future,
|
||||
// to not have to clone the image buffer.
|
||||
let mut options = ImageBitmapOptions::new();
|
||||
options.premultiply_alpha(PremultiplyAlpha::None);
|
||||
let bitmap = JsFuture::from(
|
||||
window
|
||||
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)
|
||||
.unwrap(),
|
||||
.expect("unexpected exception in `createImageBitmap()`"),
|
||||
);
|
||||
|
||||
let state = Rc::new(RefCell::new(Some(Self::Loading {
|
||||
style,
|
||||
cursor_visible,
|
||||
previous,
|
||||
hotspot_x: image.hotspot_x,
|
||||
hotspot_y: image.hotspot_y,
|
||||
})));
|
||||
let this = Inner::new();
|
||||
|
||||
wasm_bindgen_futures::spawn_local({
|
||||
let weak = Rc::downgrade(&state);
|
||||
let CursorImage { width, height, .. } = *image;
|
||||
let weak = Arc::downgrade(&this);
|
||||
let CursorImage {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
} = *image;
|
||||
async move {
|
||||
// Keep checking if all references are dropped between every `await` call.
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let bitmap: ImageBitmap = bitmap.await.unwrap().unchecked_into();
|
||||
let bitmap: ImageBitmap = bitmap
|
||||
.await
|
||||
.expect("found invalid state in `ImageData`")
|
||||
.unchecked_into();
|
||||
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas: HtmlCanvasElement =
|
||||
document.create_element("canvas").unwrap().unchecked_into();
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.expect("invalid tag name")
|
||||
.unchecked_into();
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_width(width as u32);
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_height(height as u32);
|
||||
|
||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||
let context: ImageBitmapRenderingContext = canvas
|
||||
.get_context("bitmaprenderer")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.expect("unexpected exception in `HTMLCanvasElement.getContext()`")
|
||||
.expect("`bitmaprenderer` context unsupported")
|
||||
.unchecked_into();
|
||||
context.transfer_from_image_bitmap(&bitmap);
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_STATE: RefCell<Option<Weak<RefCell<Option<CursorImageState>>>>> = RefCell::new(None);
|
||||
// `HTMLCanvasElement.toBlob()` can't be interrupted. So we have to use a
|
||||
// `Closure` that doesn't need to be garbage-collected.
|
||||
static CALLBACK: Closure<dyn Fn(Option<Blob>)> = Closure::new(|blob| {
|
||||
CURRENT_STATE.with(|weak| {
|
||||
let Some(state) = weak.borrow_mut().take().and_then(|weak| weak.upgrade()) else {
|
||||
return;
|
||||
};
|
||||
// 4. Create a `Blob` from the `HTMLCanvasElement`.
|
||||
//
|
||||
// To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done,
|
||||
// we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted,
|
||||
// but it would increase complexity and isn't possible in this case.
|
||||
// Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately.
|
||||
let value = Rc::new(RefCell::new(None));
|
||||
let waker = Rc::new(RefCell::<Option<Waker>>::new(None));
|
||||
let callback = Closure::once({
|
||||
let value = value.clone();
|
||||
let waker = waker.clone();
|
||||
move |blob: Option<Blob>| {
|
||||
*value.borrow_mut() = Some(blob);
|
||||
if let Some(waker) = waker.borrow_mut().take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
});
|
||||
canvas
|
||||
.to_blob(callback.as_ref().unchecked_ref())
|
||||
.expect("failed with `SecurityError` despite only source coming from memory");
|
||||
let blob = future::poll_fn(|cx| {
|
||||
if let Some(blob) = value.borrow_mut().take() {
|
||||
Poll::Ready(blob)
|
||||
} else {
|
||||
*waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let url = {
|
||||
let Some(this) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut this = this.get().borrow_mut();
|
||||
|
||||
let Some(blob) = blob else {
|
||||
log::error!("creating custom cursor failed");
|
||||
let ImageState::Loading(state) = this.deref_mut() else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
let state = state.take();
|
||||
*this = ImageState::Failed;
|
||||
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
// Extract old state.
|
||||
let CursorImageState::Loading { style, cursor_visible, previous, hotspot_x, hotspot_y, .. } = state.take().unwrap() else {
|
||||
unreachable!("found invalid state")
|
||||
let SelectedCursor::ImageLoading { previous, .. } =
|
||||
mem::take(&mut state.cursor)
|
||||
else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
state.cursor = previous.into();
|
||||
}
|
||||
|
||||
let Some(blob) = blob else {
|
||||
*state = Some(CursorImageState::Failed(previous));
|
||||
return;
|
||||
};
|
||||
let data_url = Url::create_object_url_with_blob(&blob).unwrap();
|
||||
return;
|
||||
};
|
||||
|
||||
let value = previous.style_with_url(&data_url, hotspot_x, hotspot_y);
|
||||
// 5. Create an object URL from the `Blob`.
|
||||
Url::create_object_url_with_blob(&blob)
|
||||
.expect("unexpected exception in `URL.createObjectURL()`")
|
||||
};
|
||||
|
||||
if cursor_visible.get() {
|
||||
style.set("cursor", &value);
|
||||
}
|
||||
|
||||
*state = Some(
|
||||
CursorImageState::Ready {
|
||||
style: value,
|
||||
image: WebCursorImage{ data_url, hotspot_x, hotspot_y },
|
||||
previous,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
CURRENT_STATE.with(|state| *state.borrow_mut() = Some(weak));
|
||||
CALLBACK
|
||||
.with(|callback| canvas.to_blob(callback.as_ref().unchecked_ref()).unwrap());
|
||||
Self::decode(weak, url, true, hotspot_x, hotspot_y).await;
|
||||
}
|
||||
});
|
||||
|
||||
state
|
||||
this
|
||||
}
|
||||
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc<Inner> {
|
||||
let this = Inner::new();
|
||||
wasm_bindgen_futures::spawn_local(Self::decode(
|
||||
Arc::downgrade(&this),
|
||||
url,
|
||||
false,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
async fn decode(
|
||||
weak: sync::Weak<Inner>,
|
||||
url: String,
|
||||
object: bool,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) {
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Decode the image on an `HTMLImageElement` from the URL.
|
||||
let image =
|
||||
HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`");
|
||||
image.set_src(&url);
|
||||
let result = JsFuture::from(image.decode()).await;
|
||||
|
||||
let Some(this) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let mut this = this.get().borrow_mut();
|
||||
|
||||
let ImageState::Loading(state) = this.deref_mut() else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
let state = state.take();
|
||||
|
||||
if let Err(error) = result {
|
||||
log::error!("creating custom cursor failed: {error:?}");
|
||||
*this = ImageState::Failed;
|
||||
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
let SelectedCursor::ImageLoading { previous, .. } = mem::take(&mut state.cursor)
|
||||
else {
|
||||
unreachable!("found invalid state");
|
||||
};
|
||||
state.cursor = previous.into();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let image = Image::new(url, object, image, hotspot_x, hotspot_y);
|
||||
|
||||
// 7. Change the `CursorState` if queued.
|
||||
if let Some(state) = state.and_then(|weak| weak.upgrade()) {
|
||||
let mut state = state.borrow_mut();
|
||||
state.cursor = SelectedCursor::ImageReady(image.clone());
|
||||
state.set_style();
|
||||
}
|
||||
|
||||
*this = ImageState::Ready(image);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebCursorImage {
|
||||
data_url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
pub struct Image {
|
||||
style: String,
|
||||
url: String,
|
||||
object: bool,
|
||||
_image: HtmlImageElement,
|
||||
}
|
||||
|
||||
impl Drop for WebCursorImage {
|
||||
impl Drop for Image {
|
||||
fn drop(&mut self) {
|
||||
Url::revoke_object_url(&self.data_url).unwrap();
|
||||
if self.object {
|
||||
Url::revoke_object_url(&self.url)
|
||||
.expect("unexpected exception in `URL.revokeObjectURL()`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn new(
|
||||
url: String,
|
||||
object: bool,
|
||||
image: HtmlImageElement,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Rc<Self> {
|
||||
let style = format!("url({url}) {hotspot_x} {hotspot_y}, auto");
|
||||
|
||||
Rc::new(Self {
|
||||
style,
|
||||
url,
|
||||
object,
|
||||
_image: image,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod thread_safe {
|
||||
use std::mem;
|
||||
use std::thread::{self, ThreadId};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadSafe<T> {
|
||||
origin_thread: ThreadId,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T> ThreadSafe<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
origin_thread: thread::current().id(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
if self.origin_thread == thread::current().id() {
|
||||
&self.value
|
||||
} else {
|
||||
panic!("value not accessible outside its origin thread")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_origin_thread(&self) -> bool {
|
||||
self.origin_thread == thread::current().id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ThreadSafe<T> {
|
||||
fn drop(&mut self) {
|
||||
if mem::needs_drop::<T>() && self.origin_thread != thread::current().id() {
|
||||
panic!("value can't be dropped outside its origin thread")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for ThreadSafe<T> {}
|
||||
unsafe impl<T> Sync for ThreadSafe<T> {}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
) {
|
||||
let canvas_clone = canvas.clone();
|
||||
let mut canvas = canvas.borrow_mut();
|
||||
#[cfg(any(feature = "rwh_04", feature = "rwh_05"))]
|
||||
canvas.set_attribute("data-raw-handle", &id.0.to_string());
|
||||
|
||||
canvas.on_touch_start(prevent_default);
|
||||
@@ -647,6 +648,8 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
|
||||
|
||||
canvas.on_context_menu(prevent_default);
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
||||
|
||||
@@ -35,9 +35,12 @@ pub(crate) use self::event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
};
|
||||
pub use self::monitor::{MonitorHandle, VideoMode};
|
||||
pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId};
|
||||
pub(crate) use self::window::{
|
||||
OwnedWindowHandle, PlatformSpecificWindowBuilderAttributes, Window, WindowId,
|
||||
};
|
||||
|
||||
pub(crate) use self::keyboard::KeyEventExtra;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
pub(crate) use cursor::WebCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use cursor::CustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use cursor::CustomCursorBuilder as PlatformCustomCursorBuilder;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::cell::Cell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
|
||||
PointerEvent, WheelEvent,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
@@ -41,13 +43,15 @@ pub struct Canvas {
|
||||
on_intersect: Option<IntersectionObserverHandle>,
|
||||
animation_frame_handler: AnimationFrameHandler,
|
||||
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||
}
|
||||
|
||||
pub struct Common {
|
||||
pub window: web_sys::Window,
|
||||
pub document: Document,
|
||||
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
||||
pub raw: HtmlCanvasElement,
|
||||
/// Note: this is read-only because we use a pointer to this for [`WindowHandle`](rwh_06::WindowHandle).
|
||||
raw: Rc<HtmlCanvasElement>,
|
||||
style: Style,
|
||||
old_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
current_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
@@ -99,7 +103,7 @@ impl Canvas {
|
||||
let common = Common {
|
||||
window: window.clone(),
|
||||
document: document.clone(),
|
||||
raw: canvas.clone(),
|
||||
raw: Rc::new(canvas.clone()),
|
||||
style,
|
||||
old_size: Rc::default(),
|
||||
current_size: Rc::default(),
|
||||
@@ -150,6 +154,7 @@ impl Canvas {
|
||||
on_intersect: None,
|
||||
animation_frame_handler: AnimationFrameHandler::new(window),
|
||||
on_touch_end: None,
|
||||
on_context_menu: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -438,6 +443,17 @@ impl Canvas {
|
||||
self.animation_frame_handler.on_animation_frame(f)
|
||||
}
|
||||
|
||||
pub(crate) fn on_context_menu(&mut self, prevent_default: bool) {
|
||||
self.on_context_menu = Some(self.common.add_event(
|
||||
"contextmenu",
|
||||
move |event: PointerEvent| {
|
||||
if prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
fullscreen::request_fullscreen(self.document(), self.raw());
|
||||
}
|
||||
@@ -511,6 +527,7 @@ impl Canvas {
|
||||
self.on_intersect = None;
|
||||
self.animation_frame_handler.cancel();
|
||||
self.on_touch_end = None;
|
||||
self.on_context_menu = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +541,11 @@ impl Common {
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
{
|
||||
EventListenerHandle::new(self.raw.clone(), event_name, Closure::new(handler))
|
||||
EventListenerHandle::new(self.raw.deref().clone(), event_name, Closure::new(handler))
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> &HtmlCanvasElement {
|
||||
&self.raw
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::dpi::LogicalPosition;
|
||||
use crate::event::{MouseButton, MouseScrollDelta};
|
||||
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use smol_str::SmolStr;
|
||||
use std::cell::OnceCell;
|
||||
use std::convert::TryInto;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use js_sys::Promise;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
|
||||
@@ -117,7 +117,7 @@ impl PointerHandler {
|
||||
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
let canvas = canvas_common.raw.clone();
|
||||
let canvas = canvas_common.raw().clone();
|
||||
self.on_pointer_press = Some(canvas_common.add_event(
|
||||
"pointerdown",
|
||||
move |event: PointerEvent| {
|
||||
@@ -174,7 +174,7 @@ impl PointerHandler {
|
||||
B: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, ButtonsState, MouseButton),
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
let canvas = canvas_common.raw.clone();
|
||||
let canvas = canvas_common.raw().clone();
|
||||
self.on_cursor_move = Some(canvas_common.add_event(
|
||||
"pointermove",
|
||||
move |event: PointerEvent| {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use js_sys::{Function, Object, Promise, Reflect};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::cell::OnceCell;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
|
||||
use crate::icon::Icon;
|
||||
@@ -8,15 +7,28 @@ use crate::window::{
|
||||
};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
use super::cursor::SelectedCursor;
|
||||
use super::cursor::CursorState;
|
||||
use super::r#async::Dispatcher;
|
||||
use super::PlatformCustomCursor;
|
||||
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(_handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// Parent windows are currently unsupported, though owned window
|
||||
// handles would be implementable.
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
inner: Dispatcher<Inner>,
|
||||
}
|
||||
@@ -25,8 +37,7 @@ pub struct Inner {
|
||||
id: WindowId,
|
||||
pub window: web_sys::Window,
|
||||
canvas: Rc<RefCell<backend::Canvas>>,
|
||||
selected_cursor: RefCell<SelectedCursor>,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
cursor: CursorState,
|
||||
destroy_fn: Option<Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
@@ -45,6 +56,7 @@ impl Window {
|
||||
let canvas =
|
||||
backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?;
|
||||
let canvas = Rc::new(RefCell::new(canvas));
|
||||
let cursor = CursorState::new(canvas.borrow().style().clone());
|
||||
|
||||
target.register(&canvas, id, prevent_default);
|
||||
|
||||
@@ -55,8 +67,7 @@ impl Window {
|
||||
id,
|
||||
window: window.clone(),
|
||||
canvas,
|
||||
selected_cursor: Default::default(),
|
||||
cursor_visible: Rc::new(Cell::new(true)),
|
||||
cursor,
|
||||
destroy_fn: Some(destroy_fn),
|
||||
};
|
||||
|
||||
@@ -85,6 +96,22 @@ impl Window {
|
||||
.value()
|
||||
.map(|inner| inner.canvas.borrow().raw().clone())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
self.inner
|
||||
.value()
|
||||
.map(|inner| {
|
||||
let canvas = inner.canvas.borrow();
|
||||
// SAFETY: This will only work if the reference to `HtmlCanvasElement` stays valid.
|
||||
let canvas: &wasm_bindgen::JsValue = canvas.raw();
|
||||
let window_handle =
|
||||
rwh_06::WebCanvasWindowHandle::new(std::ptr::NonNull::from(canvas).cast());
|
||||
rwh_06::RawWindowHandle::WebCanvas(window_handle)
|
||||
})
|
||||
.ok_or(rwh_06::HandleError::Unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -198,24 +225,12 @@ impl Inner {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
*self.selected_cursor.borrow_mut() = SelectedCursor::Named(cursor);
|
||||
|
||||
if self.cursor_visible.get() {
|
||||
self.canvas.borrow().style().set("cursor", cursor.name());
|
||||
}
|
||||
self.cursor.set_cursor_icon(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let canvas = self.canvas.borrow();
|
||||
let new_cursor = cursor.inner.build(
|
||||
canvas.window(),
|
||||
canvas.document(),
|
||||
canvas.style(),
|
||||
self.selected_cursor.take(),
|
||||
self.cursor_visible.clone(),
|
||||
);
|
||||
*self.selected_cursor.borrow_mut() = new_cursor;
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
self.cursor.set_custom_cursor(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -241,15 +256,7 @@ impl Inner {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
if !visible && self.cursor_visible.get() {
|
||||
self.canvas.borrow().style().set("cursor", "none");
|
||||
self.cursor_visible.set(false);
|
||||
} else if visible && !self.cursor_visible.get() {
|
||||
self.selected_cursor
|
||||
.borrow()
|
||||
.set_style(self.canvas.borrow().style());
|
||||
self.cursor_visible.set(true);
|
||||
}
|
||||
self.cursor.set_cursor_visible(visible)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -398,13 +405,6 @@ impl Inner {
|
||||
rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let window_handle = rwh_06::WebWindowHandle::new(self.id.0);
|
||||
Ok(rwh_06::RawWindowHandle::Web(window_handle))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub fn raw_display_handle_rwh_06(
|
||||
|
||||
@@ -9,8 +9,8 @@ use windows_sys::Win32::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{
|
||||
DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, MDT_EFFECTIVE_DPI,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
},
|
||||
WindowsAndMessaging::IsProcessDPIAware,
|
||||
},
|
||||
@@ -21,8 +21,6 @@ use crate::platform_impl::platform::util::{
|
||||
SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT,
|
||||
};
|
||||
|
||||
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4;
|
||||
|
||||
pub fn become_dpi_aware() {
|
||||
static ENABLE_DPI_AWARENESS: Once = Once::new();
|
||||
ENABLE_DPI_AWARENESS.call_once(|| {
|
||||
|
||||
@@ -21,7 +21,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
|
||||
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Graphics::Gdi::{
|
||||
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
|
||||
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
|
||||
@@ -35,13 +35,9 @@ use windows_sys::Win32::{
|
||||
Input::{
|
||||
Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW},
|
||||
KeyboardAndMouse::{
|
||||
MapVirtualKeyW, ReleaseCapture, SetCapture, TrackMouseEvent, MAPVK_VK_TO_VSC_EX,
|
||||
TME_LEAVE, TRACKMOUSEEVENT, VK_NUMLOCK, VK_SHIFT,
|
||||
},
|
||||
Pointer::{
|
||||
POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE, POINTER_INFO,
|
||||
POINTER_PEN_INFO, POINTER_TOUCH_INFO,
|
||||
ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT,
|
||||
},
|
||||
Pointer::{POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE},
|
||||
Touch::{
|
||||
CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE,
|
||||
TOUCHEVENTF_UP, TOUCHINPUT,
|
||||
@@ -54,20 +50,19 @@ use windows_sys::Win32::{
|
||||
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos,
|
||||
TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
|
||||
HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN,
|
||||
PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE,
|
||||
SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER,
|
||||
WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY,
|
||||
WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
|
||||
WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
|
||||
WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
|
||||
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
|
||||
WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
|
||||
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
|
||||
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
|
||||
WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
|
||||
WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED,
|
||||
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
|
||||
WS_VISIBLE,
|
||||
PT_TOUCH, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
|
||||
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
|
||||
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
|
||||
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
|
||||
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE,
|
||||
WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
|
||||
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
|
||||
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
|
||||
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
|
||||
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
||||
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP,
|
||||
WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT,
|
||||
WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -80,8 +75,8 @@ use crate::{
|
||||
WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
keyboard::{KeyCode, ModifiersState, PhysicalKey},
|
||||
platform::{pump_events::PumpStatus, scancode::PhysicalKeyExtScancode},
|
||||
keyboard::ModifiersState,
|
||||
platform::pump_events::PumpStatus,
|
||||
platform_impl::platform::{
|
||||
dark_mode::try_theme,
|
||||
dpi::{become_dpi_aware, dpi_to_scale_factor},
|
||||
@@ -103,37 +98,6 @@ use self::runner::RunnerState;
|
||||
|
||||
use super::{window::set_skip_taskbar, SelectedCursor};
|
||||
|
||||
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: u32,
|
||||
entriesCount: *mut u32,
|
||||
pointerCount: *mut u32,
|
||||
pointerInfo: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
|
||||
type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointerDeviceRect: *mut RECT,
|
||||
displayRect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
type GetPointerPenInfo =
|
||||
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
|
||||
|
||||
static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||
|
||||
pub(crate) struct WindowData<T: 'static> {
|
||||
pub window_state: Arc<Mutex<WindowState>>,
|
||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
||||
@@ -1831,9 +1795,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
Some(SkipPointerFrameMessages),
|
||||
Some(GetPointerDeviceRects),
|
||||
) = (
|
||||
*GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*SKIP_POINTER_FRAME_MESSAGES,
|
||||
*GET_POINTER_DEVICE_RECTS,
|
||||
*util::GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*util::SKIP_POINTER_FRAME_MESSAGES,
|
||||
*util::GET_POINTER_DEVICE_RECTS,
|
||||
) {
|
||||
let pointer_id = super::loword(wparam as u32) as u32;
|
||||
let mut entries_count = 0u32;
|
||||
@@ -1915,7 +1879,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
let force = match pointer_info.pointerType {
|
||||
PT_TOUCH => {
|
||||
let mut touch_info = mem::MaybeUninit::uninit();
|
||||
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
match unsafe {
|
||||
GetPointerTouchInfo(
|
||||
pointer_info.pointerId,
|
||||
@@ -1931,7 +1895,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
}
|
||||
PT_PEN => {
|
||||
let mut pen_info = mem::MaybeUninit::uninit();
|
||||
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
match unsafe {
|
||||
GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr())
|
||||
} {
|
||||
@@ -2498,105 +2462,17 @@ unsafe fn handle_raw_input<T: 'static>(userdata: &ThreadMsgTargetData<T>, data:
|
||||
return;
|
||||
}
|
||||
|
||||
let state = if pressed { Pressed } else { Released };
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xE000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xE100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xE11D || scancode == 0xE02A {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equvalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return;
|
||||
if let Some(physical_key) = raw_input::get_keyboard_physical_key(keyboard) {
|
||||
let state = if pressed { Pressed } else { Released };
|
||||
|
||||
userdata.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
}),
|
||||
});
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
PhysicalKey::from_scancode(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
match code {
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9 => {
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
|
||||
// have any information to tell whether it's the left shift or the right shift
|
||||
// that needs to get the fake release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
userdata.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ impl WinCursor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(image: &CursorImage) -> Result<Self, io::Error> {
|
||||
pub(crate) fn new(image: &CursorImage) -> Result<Self, io::Error> {
|
||||
let mut bgra = image.rgba.clone();
|
||||
bgra.chunks_exact_mut(4).for_each(|chunk| chunk.swap(0, 2));
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ use windows_sys::Win32::{
|
||||
UI::{
|
||||
Input::Ime::{
|
||||
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
|
||||
ImmSetCandidateWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM,
|
||||
CFS_EXCLUDE, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN,
|
||||
IACE_DEFAULT,
|
||||
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
|
||||
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM,
|
||||
GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
|
||||
},
|
||||
WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED},
|
||||
},
|
||||
@@ -124,7 +124,7 @@ impl ImeContext {
|
||||
left: x,
|
||||
top: y,
|
||||
right: x + width,
|
||||
bottom: y - height,
|
||||
bottom: y + height,
|
||||
};
|
||||
let candidate_form = CANDIDATEFORM {
|
||||
dwIndex: 0,
|
||||
@@ -132,8 +132,16 @@ impl ImeContext {
|
||||
ptCurrentPos: POINT { x, y },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
let composition_form = COMPOSITIONFORM {
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: POINT { x, y: y + height },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
|
||||
unsafe { ImmSetCandidateWindow(self.himc, &candidate_form) };
|
||||
unsafe {
|
||||
ImmSetCompositionWindow(self.himc, &composition_form);
|
||||
ImmSetCandidateWindow(self.himc, &candidate_form);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) {
|
||||
|
||||
@@ -12,11 +12,12 @@ pub(crate) use self::{
|
||||
},
|
||||
icon::{SelectedCursor, WinIcon},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::Window,
|
||||
window::{OwnedWindowHandle, Window},
|
||||
};
|
||||
|
||||
pub use self::icon::WinIcon as PlatformIcon;
|
||||
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::OnlyCursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
|
||||
use crate::platform_impl::Fullscreen;
|
||||
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
|
||||
@@ -11,21 +11,29 @@ use windows_sys::Win32::{
|
||||
UI::{
|
||||
Input::{
|
||||
GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList,
|
||||
KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT},
|
||||
RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST,
|
||||
RAWINPUTHEADER, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO,
|
||||
RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD,
|
||||
RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
RAWINPUTHEADER, RAWKEYBOARD, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE,
|
||||
RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID,
|
||||
RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID,
|
||||
RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN,
|
||||
RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP,
|
||||
RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN,
|
||||
RI_MOUSE_BUTTON_5_UP,
|
||||
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP,
|
||||
RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN,
|
||||
RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP,
|
||||
RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{event::ElementState, event_loop::DeviceEvents, platform_impl::platform::util};
|
||||
use crate::{
|
||||
event::ElementState,
|
||||
event_loop::DeviceEvents,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
platform::scancode::PhysicalKeyExtScancode,
|
||||
platform_impl::platform::util,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||
@@ -220,3 +228,99 @@ pub fn get_raw_mouse_button_state(button_flags: u32) -> [Option<ElementState>; 5
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option<PhysicalKey> {
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xE000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xE100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xE11D || scancode == 0xE02A {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equvalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return None;
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
PhysicalKey::from_scancode(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
match code {
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9 => {
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
|
||||
// have any information to tell whether it's the left shift or the right shift
|
||||
// that needs to get the fake release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return None;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(physical_key)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use once_cell::sync::Lazy;
|
||||
use windows_sys::{
|
||||
core::{HRESULT, PCWSTR},
|
||||
Win32::{
|
||||
Foundation::{BOOL, HMODULE, HWND, RECT},
|
||||
Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT},
|
||||
Graphics::Gdi::{ClientToScreen, HMONITOR},
|
||||
System::{
|
||||
LibraryLoader::{GetProcAddress, LoadLibraryA},
|
||||
@@ -21,7 +21,10 @@ use windows_sys::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS},
|
||||
Input::KeyboardAndMouse::GetActiveWindow,
|
||||
Input::{
|
||||
KeyboardAndMouse::GetActiveWindow,
|
||||
Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO},
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement,
|
||||
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS,
|
||||
@@ -191,7 +194,9 @@ pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to dynamically load function pointer.
|
||||
// Helper function to dynamically load function pointer as some functions
|
||||
// may not be available on all Windows platforms supported by winit.
|
||||
//
|
||||
// `library` and `function` must be zero-terminated.
|
||||
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
||||
assert_eq!(library.chars().last(), Some('\0'));
|
||||
@@ -237,6 +242,26 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
|
||||
dpi: u32,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: u32,
|
||||
entriesCount: *mut u32,
|
||||
pointerCount: *mut u32,
|
||||
pointerInfo: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
|
||||
pub type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointerDeviceRect: *mut RECT,
|
||||
displayRect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
pub type GetPointerPenInfo =
|
||||
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
|
||||
|
||||
pub static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy<Option<AdjustWindowRectExForDpi>> =
|
||||
@@ -251,3 +276,13 @@ pub static SET_PROCESS_DPI_AWARENESS: Lazy<Option<SetProcessDpiAwareness>> =
|
||||
Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness));
|
||||
pub static SET_PROCESS_DPI_AWARE: Lazy<Option<SetProcessDPIAware>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware));
|
||||
pub static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
pub static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
pub static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
pub static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
pub static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||
|
||||
@@ -55,7 +55,6 @@ use windows_sys::Win32::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
icon::Icon,
|
||||
@@ -73,7 +72,8 @@ use crate::{
|
||||
monitor::{self, MonitorHandle},
|
||||
util,
|
||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||
Fullscreen, PlatformSpecificWindowBuilderAttributes, SelectedCursor, WindowId,
|
||||
Fullscreen, PlatformCustomCursor, PlatformSpecificWindowBuilderAttributes, SelectedCursor,
|
||||
WindowId,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -81,10 +81,27 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedWindowHandle {
|
||||
hwnd: HWND,
|
||||
}
|
||||
|
||||
impl OwnedWindowHandle {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) fn new_parent_window(handle: rwh_06::WindowHandle<'_>) -> Self {
|
||||
// TODO: Do we need to do something to extend the lifetime of the window handle?
|
||||
let hwnd = match handle.as_raw() {
|
||||
rwh_06::RawWindowHandle::Win32(handle) => handle.hwnd.get() as HWND,
|
||||
handle => panic!("invalid window handle {handle:?} on Windows"),
|
||||
};
|
||||
Self { hwnd }
|
||||
}
|
||||
}
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub(crate) struct Window {
|
||||
/// Main handle for the window.
|
||||
window: WindowWrapper,
|
||||
window: HWND,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<WindowState>>,
|
||||
@@ -128,11 +145,11 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::TRANSPARENT, transparent)
|
||||
});
|
||||
});
|
||||
@@ -142,11 +159,11 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::VISIBLE, visible)
|
||||
});
|
||||
});
|
||||
@@ -154,7 +171,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
Some(unsafe { IsWindowVisible(self.window.0) == 1 })
|
||||
Some(unsafe { IsWindowVisible(self.window) == 1 })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -190,10 +207,10 @@ impl Window {
|
||||
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
|
||||
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -247,10 +264,10 @@ impl Window {
|
||||
|
||||
if physical_size != self.inner_size() {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -285,12 +302,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::RESIZABLE, resizable)
|
||||
});
|
||||
});
|
||||
@@ -304,12 +321,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::MINIMIZABLE,
|
||||
buttons.contains(WindowButtons::MINIMIZE),
|
||||
@@ -343,14 +360,14 @@ impl Window {
|
||||
/// Returns the `hwnd` of this window.
|
||||
#[inline]
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.0
|
||||
self.window
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::Win32Handle::empty();
|
||||
window_handle.hwnd = self.window.0 as *mut _;
|
||||
window_handle.hwnd = self.window as *mut _;
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = hinstance as *mut _;
|
||||
rwh_04::RawWindowHandle::Win32(window_handle)
|
||||
@@ -360,7 +377,7 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::Win32WindowHandle::empty();
|
||||
window_handle.hwnd = self.window.0 as *mut _;
|
||||
window_handle.hwnd = self.window as *mut _;
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = hinstance as *mut _;
|
||||
rwh_05::RawWindowHandle::Win32(window_handle)
|
||||
@@ -377,8 +394,7 @@ impl Window {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe {
|
||||
// SAFETY: Handle will never be zero.
|
||||
let window = self.window.0;
|
||||
std::num::NonZeroIsize::new_unchecked(window)
|
||||
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);
|
||||
@@ -405,8 +421,8 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let new_cursor = match WinCursor::new(&cursor.inner) {
|
||||
pub(crate) fn set_custom_cursor(&self, cursor: PlatformCustomCursor) {
|
||||
let new_cursor = match WinCursor::new(&cursor.0) {
|
||||
Ok(cursor) => cursor,
|
||||
Err(err) => {
|
||||
warn!("Failed to create custom cursor: {err}");
|
||||
@@ -429,7 +445,7 @@ impl Window {
|
||||
}
|
||||
};
|
||||
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@@ -439,7 +455,7 @@ impl Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.map_err(|e| ExternalError::Os(os_error!(e)));
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -448,7 +464,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@@ -458,7 +474,7 @@ impl Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window.0, |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);
|
||||
});
|
||||
@@ -488,7 +504,7 @@ impl Window {
|
||||
}
|
||||
|
||||
unsafe fn handle_os_dragging(&self, wparam: WPARAM) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = self.window_state.clone();
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
@@ -516,7 +532,7 @@ impl Window {
|
||||
|
||||
unsafe {
|
||||
PostMessageW(
|
||||
window.0,
|
||||
window,
|
||||
WM_NCLBUTTONDOWN,
|
||||
wparam,
|
||||
&points as *const _ as LPARAM,
|
||||
@@ -631,10 +647,10 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
let window = self.window.clone();
|
||||
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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
|
||||
});
|
||||
});
|
||||
@@ -649,7 +665,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
@@ -659,7 +675,7 @@ impl 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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MINIMIZED, minimized)
|
||||
});
|
||||
});
|
||||
@@ -672,12 +688,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, maximized)
|
||||
});
|
||||
});
|
||||
@@ -697,7 +713,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
let mut window_state_lock = window_state.lock().unwrap();
|
||||
@@ -708,7 +724,7 @@ impl Window {
|
||||
_ if old_fullscreen == fullscreen => return,
|
||||
// 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.0) =>
|
||||
if *monitor == monitor::current_monitor(window) =>
|
||||
{
|
||||
return
|
||||
}
|
||||
@@ -777,7 +793,7 @@ impl Window {
|
||||
}
|
||||
|
||||
// Update window style
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
|
||||
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
|
||||
@@ -793,7 +809,7 @@ impl Window {
|
||||
// this needs to be called before the below fullscreen SetWindowPos as this itself
|
||||
// will generate WM_SIZE messages of the old window size that can race with what we set below
|
||||
unsafe {
|
||||
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
|
||||
taskbar_mark_fullscreen(window, fullscreen.is_some());
|
||||
}
|
||||
|
||||
// Update window bounds
|
||||
@@ -802,7 +818,7 @@ impl Window {
|
||||
// Save window bounds before entering fullscreen
|
||||
let placement = unsafe {
|
||||
let mut placement = mem::zeroed();
|
||||
GetWindowPlacement(window.0, &mut placement);
|
||||
GetWindowPlacement(window, &mut placement);
|
||||
placement
|
||||
};
|
||||
|
||||
@@ -811,7 +827,7 @@ impl Window {
|
||||
let monitor = match &fullscreen {
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window.0),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window),
|
||||
};
|
||||
|
||||
let position: (i32, i32) = monitor.position().into();
|
||||
@@ -819,7 +835,7 @@ impl Window {
|
||||
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
window.0,
|
||||
window,
|
||||
0,
|
||||
position.0,
|
||||
position.1,
|
||||
@@ -827,7 +843,7 @@ impl Window {
|
||||
size.1 as i32,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER,
|
||||
);
|
||||
InvalidateRgn(window.0, 0, false.into());
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -835,8 +851,8 @@ impl Window {
|
||||
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
|
||||
drop(window_state_lock);
|
||||
unsafe {
|
||||
SetWindowPlacement(window.0, &placement);
|
||||
InvalidateRgn(window.0, 0, false.into());
|
||||
SetWindowPlacement(window, &placement);
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,12 +862,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MARKER_DECORATIONS, decorations)
|
||||
});
|
||||
});
|
||||
@@ -867,12 +883,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::ALWAYS_ON_TOP,
|
||||
level == WindowLevel::AlwaysOnTop,
|
||||
@@ -921,21 +937,21 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0).set_ime_cursor_area(spot, size, scale_factor);
|
||||
ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, allowed);
|
||||
ImeContext::set_ime_allowed(window, allowed);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -944,14 +960,13 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let active_window_handle = unsafe { GetActiveWindow() };
|
||||
if window.0 == active_window_handle {
|
||||
if window == active_window_handle {
|
||||
return;
|
||||
}
|
||||
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let _ = &window;
|
||||
let (flags, count) = request_type
|
||||
.map(|ty| match ty {
|
||||
UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX),
|
||||
@@ -961,7 +976,7 @@ impl Window {
|
||||
|
||||
let flash_info = FLASHWINFO {
|
||||
cbSize: mem::size_of::<FLASHWINFO>() as u32,
|
||||
hwnd: window.0,
|
||||
hwnd: window,
|
||||
dwFlags: flags,
|
||||
uCount: count,
|
||||
dwTimeout: 0,
|
||||
@@ -972,7 +987,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
try_theme(self.window.0, theme);
|
||||
try_theme(self.window, theme);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -987,9 +1002,9 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
let len = unsafe { GetWindowTextLengthW(self.window.0) } + 1;
|
||||
let len = unsafe { GetWindowTextLengthW(self.window) } + 1;
|
||||
let mut buf = vec![0; len as usize];
|
||||
unsafe { GetWindowTextW(self.window.0, buf.as_mut_ptr(), len) };
|
||||
unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) };
|
||||
util::decode_wide(&buf).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
@@ -1001,12 +1016,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.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.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow)
|
||||
});
|
||||
});
|
||||
@@ -1014,15 +1029,14 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
let window = self.window.clone();
|
||||
let window_flags = self.window_state_lock().window_flags();
|
||||
|
||||
let is_visible = window_flags.contains(WindowFlags::VISIBLE);
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
let is_foreground = window.0 == unsafe { GetForegroundWindow() };
|
||||
let is_foreground = self.window == unsafe { GetForegroundWindow() };
|
||||
|
||||
if is_visible && !is_minimized && !is_foreground {
|
||||
unsafe { force_window_active(window.0) };
|
||||
unsafe { force_window_active(self.window) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1072,18 +1086,6 @@ impl Drop for Window {
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple non-owning wrapper around a window.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct WindowWrapper(HWND);
|
||||
|
||||
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
unsafe impl Sync for WindowWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub(super) struct InitData<'a, T: 'static> {
|
||||
// inputs
|
||||
pub event_loop: &'a EventLoopWindowTarget<T>,
|
||||
@@ -1131,7 +1133,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
unsafe { ImeContext::set_ime_allowed(window, false) };
|
||||
|
||||
Window {
|
||||
window: WindowWrapper(window),
|
||||
window,
|
||||
window_state,
|
||||
thread_executor: self.event_loop.create_thread_executor(),
|
||||
}
|
||||
@@ -1154,7 +1156,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
|
||||
let file_drop_runner = self.event_loop.runner_shared.clone();
|
||||
let file_drop_handler = FileDropHandler::new(
|
||||
win.window.0,
|
||||
win.window,
|
||||
Box::new(move |event| {
|
||||
if let Ok(e) = event.map_nonuser_event() {
|
||||
file_drop_runner.send_event(e)
|
||||
@@ -1166,7 +1168,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void };
|
||||
|
||||
assert_eq!(
|
||||
unsafe { RegisterDragDrop(win.window.0, handler_interface_ptr) },
|
||||
unsafe { RegisterDragDrop(win.window, handler_interface_ptr) },
|
||||
S_OK
|
||||
);
|
||||
Some(file_drop_handler)
|
||||
@@ -1243,7 +1245,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
|
||||
if attributes.fullscreen.0.is_some() {
|
||||
win.set_fullscreen(attributes.fullscreen.0.map(Into::into));
|
||||
unsafe { force_window_active(win.window.0) };
|
||||
unsafe { force_window_active(win.window) };
|
||||
} else {
|
||||
let size = attributes
|
||||
.inner_size
|
||||
@@ -1316,33 +1318,25 @@ where
|
||||
// so the diffing later can work.
|
||||
window_flags.set(WindowFlags::CLOSABLE, true);
|
||||
|
||||
let mut fallback_parent = || match pl_attribs.owner {
|
||||
Some(parent) => {
|
||||
window_flags.set(WindowFlags::POPUP, true);
|
||||
Some(parent)
|
||||
let parent = if let Some(parent_window) = &attributes.parent_window {
|
||||
window_flags.set(WindowFlags::CHILD, true);
|
||||
if pl_attribs.menu.is_some() {
|
||||
warn!("Setting a menu on a child window is unsupported");
|
||||
}
|
||||
None => {
|
||||
window_flags.set(WindowFlags::ON_TASKBAR, true);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
let parent = match attributes.parent_window.0 {
|
||||
Some(rwh_06::RawWindowHandle::Win32(handle)) => {
|
||||
window_flags.set(WindowFlags::CHILD, true);
|
||||
if pl_attribs.menu.is_some() {
|
||||
warn!("Setting a menu on a child window is unsupported");
|
||||
Some(parent_window.hwnd)
|
||||
} else {
|
||||
match pl_attribs.owner {
|
||||
Some(parent) => {
|
||||
window_flags.set(WindowFlags::POPUP, true);
|
||||
Some(parent)
|
||||
}
|
||||
None => {
|
||||
window_flags.set(WindowFlags::ON_TASKBAR, true);
|
||||
None
|
||||
}
|
||||
Some(handle.hwnd.get() as HWND)
|
||||
}
|
||||
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"),
|
||||
None => fallback_parent(),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "rwh_06"))]
|
||||
let parent = fallback_parent();
|
||||
|
||||
let mut initdata = InitData {
|
||||
event_loop,
|
||||
attributes,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
platform_impl, SendSyncWrapper,
|
||||
};
|
||||
|
||||
pub use crate::cursor::{BadImage, CustomCursor, MAX_CURSOR_SIZE};
|
||||
pub use crate::cursor::{BadImage, CustomCursor, CustomCursorBuilder, MAX_CURSOR_SIZE};
|
||||
pub use crate::icon::{BadIcon, Icon};
|
||||
|
||||
#[doc(inline)]
|
||||
@@ -153,8 +153,7 @@ pub struct WindowAttributes {
|
||||
pub content_protected: bool,
|
||||
pub window_level: WindowLevel,
|
||||
pub active: bool,
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub(crate) parent_window: SendSyncWrapper<Option<rwh_06::RawWindowHandle>>,
|
||||
pub(crate) parent_window: Option<platform_impl::OwnedWindowHandle>,
|
||||
pub(crate) fullscreen: SendSyncWrapper<Option<Fullscreen>>,
|
||||
}
|
||||
|
||||
@@ -180,8 +179,7 @@ impl Default for WindowAttributes {
|
||||
preferred_theme: None,
|
||||
resize_increments: None,
|
||||
content_protected: false,
|
||||
#[cfg(feature = "rwh_06")]
|
||||
parent_window: SendSyncWrapper(None),
|
||||
parent_window: None,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
@@ -190,8 +188,8 @@ impl Default for WindowAttributes {
|
||||
impl WindowAttributes {
|
||||
/// Get the parent window stored on the attributes.
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn parent_window(&self) -> Option<&rwh_06::RawWindowHandle> {
|
||||
self.parent_window.0.as_ref()
|
||||
pub fn parent_window(&self) -> Option<Result<rwh_06::RawWindowHandle, rwh_06::HandleError>> {
|
||||
Some(self.parent_window.as_ref()?.raw_window_handle())
|
||||
}
|
||||
|
||||
/// Get `Fullscreen` option stored on the attributes.
|
||||
@@ -476,10 +474,6 @@ impl WindowBuilder {
|
||||
///
|
||||
/// The default is `None`.
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// `parent_window` must be a valid window handle.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows** : A child window has the WS_CHILD style and is confined
|
||||
@@ -489,11 +483,9 @@ impl WindowBuilder {
|
||||
/// - **Android / iOS / Wayland / Web:** Unsupported.
|
||||
#[cfg(feature = "rwh_06")]
|
||||
#[inline]
|
||||
pub unsafe fn with_parent_window(
|
||||
mut self,
|
||||
parent_window: Option<rwh_06::RawWindowHandle>,
|
||||
) -> Self {
|
||||
self.window.parent_window = SendSyncWrapper(parent_window);
|
||||
pub fn with_parent_window(mut self, parent_window: Option<rwh_06::WindowHandle<'_>>) -> Self {
|
||||
self.window.parent_window =
|
||||
parent_window.map(platform_impl::OwnedWindowHandle::new_parent_window);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1355,7 +1347,7 @@ impl Window {
|
||||
/// - **iOS / Android / Orbital:** Unsupported.
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
let cursor = cursor.clone();
|
||||
let cursor = cursor.inner.clone();
|
||||
self.window
|
||||
.maybe_queue_on_main(move |w| w.set_custom_cursor(cursor))
|
||||
}
|
||||
@@ -1489,10 +1481,6 @@ impl Window {
|
||||
/// Returns the monitor on which the window currently resides.
|
||||
///
|
||||
/// Returns `None` if current monitor can't be detected.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
self.window
|
||||
@@ -1503,10 +1491,6 @@ impl Window {
|
||||
///
|
||||
/// This is the same as [`EventLoopWindowTarget::available_monitors`], and is provided for convenience.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
///
|
||||
/// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
@@ -1525,8 +1509,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
/// **Wayland:** Always returns `None`.
|
||||
/// **Wayland / Web:** Always returns `None`.
|
||||
///
|
||||
/// [`EventLoopWindowTarget::primary_monitor`]: crate::event_loop::EventLoopWindowTarget::primary_monitor
|
||||
#[inline]
|
||||
@@ -1539,12 +1522,10 @@ impl Window {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasWindowHandle for Window {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = self
|
||||
.window
|
||||
.maybe_wait_on_main(|w| w.raw_window_handle_rwh_06().map(SendSyncWrapper))?
|
||||
.0;
|
||||
let raw = self.window.raw_window_handle_rwh_06()?;
|
||||
|
||||
// SAFETY: The window handle will never be deallocated while the window is alive.
|
||||
// SAFETY: The window handle will never be deallocated while the window is alive,
|
||||
// and the main thread safety requirements are upheld internally by each platform.
|
||||
Ok(unsafe { rwh_06::WindowHandle::borrow_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,6 @@ fn ids_send() {
|
||||
|
||||
#[test]
|
||||
fn custom_cursor_send() {
|
||||
needs_send::<winit::window::CustomCursorBuilder>();
|
||||
needs_send::<winit::window::CustomCursor>();
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ fn window_builder_sync() {
|
||||
|
||||
#[test]
|
||||
fn custom_cursor_sync() {
|
||||
needs_sync::<winit::window::CustomCursorBuilder>();
|
||||
needs_sync::<winit::window::CustomCursor>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user