mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9809ef54b | ||
|
|
efb5b37fff | ||
|
|
a9baf5ecda | ||
|
|
6bb43fd130 | ||
|
|
17a73f4dd4 | ||
|
|
bccc568345 | ||
|
|
69b8a07ae0 | ||
|
|
3eb731f8b5 | ||
|
|
7035dd554f | ||
|
|
ab4c6bfc82 |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -111,8 +111,13 @@ jobs:
|
||||
cargo generate-lockfile
|
||||
cargo update -p ahash --precise 0.8.7
|
||||
cargo update -p bumpalo --precise 3.14.0
|
||||
cargo update -p softbuffer --precise 0.4.0
|
||||
cargo update -p objc2-encode --precise 4.0.3
|
||||
cargo update -p orbclient --precise 0.3.47
|
||||
cargo update -p image --precise 0.25.0
|
||||
cargo update -p gethostname@1.1.0 --precise 1.0.2
|
||||
cargo update -p unicode-ident --precise 1.0.10
|
||||
cargo update -p syn --precise 2.0.114
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.30.12"
|
||||
version = "0.30.13"
|
||||
authors = [
|
||||
"The winit contributors",
|
||||
"Pierre Krieger <pierre.krieger1708@gmail.com>",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.30.12"
|
||||
winit = "0.30.13"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
@@ -56,7 +56,7 @@ allow = [
|
||||
crate = "android-activity"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["ci/*", "githooks/*"]
|
||||
allow-globs = ["ci/*", "githooks/*", "cargo.sh"]
|
||||
crate = "zerocopy"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
|
||||
@@ -39,7 +39,3 @@ The migration guide could reference other migration examples in the current
|
||||
changelog entry.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix crash on macOS 26 by using objc2's `relax-sign-encoding` feature.
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
## 0.30.13
|
||||
|
||||
### Added
|
||||
|
||||
- On Wayland, add `Window::set_resize_increments`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fixed crash when dragging non-file content onto window.
|
||||
- On X11, fix `set_hittest` not working on some window managers.
|
||||
- On X11, fix debug mode overflow panic in `set_timestamp`.
|
||||
- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`.
|
||||
- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`.
|
||||
- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()`.
|
||||
|
||||
## 0.30.12
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -621,7 +621,7 @@ pub struct KeyEvent {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// In games, you often want to ignore repated key events - this can be
|
||||
/// In games, you often want to ignore repeated key events - this can be
|
||||
/// done by ignoring events where this property is set.
|
||||
///
|
||||
/// ```
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
#![cfg_attr(clippy, deny(warnings))]
|
||||
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly
|
||||
// doc
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![warn(clippy::uninlined_format_args)]
|
||||
// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it.
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
|
||||
//! with `cargo apk`, then the minimal changes would be:
|
||||
//! 1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.12",
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.13",
|
||||
//! 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
|
||||
|
||||
@@ -446,6 +446,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut self.combining_accent,
|
||||
);
|
||||
|
||||
let logical_key = keycodes::to_logical(key_char, keycode);
|
||||
let text = if state == event::ElementState::Pressed {
|
||||
logical_key.to_text().map(smol_str::SmolStr::new)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
@@ -453,10 +460,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
event: event::KeyEvent {
|
||||
state,
|
||||
physical_key: keycodes::to_physical_key(keycode),
|
||||
logical_key: keycodes::to_logical(key_char, keycode),
|
||||
logical_key,
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: None,
|
||||
text,
|
||||
platform_specific: KeyEventExtra {},
|
||||
},
|
||||
is_synthetic: false,
|
||||
|
||||
@@ -166,6 +166,12 @@ impl Window {
|
||||
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
|
||||
}
|
||||
|
||||
// Apply resize increments.
|
||||
if let Some(increments) = attributes.resize_increments {
|
||||
let increments = increments.to_logical(window_state.scale_factor());
|
||||
window_state.set_resize_increments(Some(increments));
|
||||
}
|
||||
|
||||
// Activate the window when the token is passed.
|
||||
if let (Some(xdg_activation), Some(token)) =
|
||||
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
|
||||
@@ -333,12 +339,19 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
None
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state
|
||||
.resize_increments()
|
||||
.map(|size| super::logical_to_physical_rounded(size, scale_factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resize_increments(&self, _increments: Option<Size>) {
|
||||
warn!("`set_resize_increments` is not implemented for Wayland");
|
||||
pub fn set_resize_increments(&self, increments: Option<Size>) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
let increments = increments.map(|size| size.to_logical(scale_factor));
|
||||
window_state.set_resize_increments(increments);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -127,6 +127,7 @@ pub struct WindowState {
|
||||
/// Min size.
|
||||
min_inner_size: LogicalSize<u32>,
|
||||
max_inner_size: Option<LogicalSize<u32>>,
|
||||
resize_increments: Option<LogicalSize<u32>>,
|
||||
|
||||
/// The size of the window when no states were applied to it. The primary use for it
|
||||
/// is to fallback to original window size, before it was maximized, if the compositor
|
||||
@@ -202,6 +203,7 @@ impl WindowState {
|
||||
last_configure: None,
|
||||
max_inner_size: None,
|
||||
min_inner_size: MIN_WINDOW_SIZE,
|
||||
resize_increments: None,
|
||||
pointer_constraints,
|
||||
pointers: Default::default(),
|
||||
queue_handle: queue_handle.clone(),
|
||||
@@ -340,6 +342,42 @@ impl WindowState {
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
// Apply size increments.
|
||||
//
|
||||
// We conditionally apply increments to avoid conflicts with the compositor's layout rules:
|
||||
// 1. If the window is floating (constrain == true), we snap to increments to ensure the
|
||||
// app's grid alignment.
|
||||
// 2. If the user is interactively resizing (is_resizing), we snap the size to provide
|
||||
// feedback.
|
||||
//
|
||||
// However, we MUST NOT snap if the compositor enforces a specific size (constrain == false,
|
||||
// or states like Maximized/Tiled). Snapping in these cases (e.g. corner tiling) would
|
||||
// shrink the window below the allocated area, creating visible gaps between valid
|
||||
// windows or screen edges.
|
||||
if (constrain || configure.is_resizing())
|
||||
&& !configure.is_maximized()
|
||||
&& !configure.is_fullscreen()
|
||||
&& !configure.is_tiled()
|
||||
{
|
||||
if let Some(increments) = self.resize_increments {
|
||||
// We use min size as a base size for the increments, similar to how X11 does it.
|
||||
//
|
||||
// This ensures that we can always reach the min size and the increments are
|
||||
// calculated from it.
|
||||
let (delta_width, delta_height) = (
|
||||
new_size.width.saturating_sub(self.min_inner_size.width),
|
||||
new_size.height.saturating_sub(self.min_inner_size.height),
|
||||
);
|
||||
|
||||
let width =
|
||||
self.min_inner_size.width + (delta_width / increments.width) * increments.width;
|
||||
let height = self.min_inner_size.height
|
||||
+ (delta_height / increments.height) * increments.height;
|
||||
|
||||
new_size = (width, height).into();
|
||||
}
|
||||
}
|
||||
|
||||
let new_state = configure.state;
|
||||
let old_state = self.last_configure.as_ref().map(|configure| configure.state);
|
||||
|
||||
@@ -725,6 +763,18 @@ impl WindowState {
|
||||
self.selected_cursor = SelectedCursor::Custom(cursor);
|
||||
}
|
||||
|
||||
/// Set the resize increments of the window.
|
||||
pub fn set_resize_increments(&mut self, increments: Option<LogicalSize<u32>>) {
|
||||
self.resize_increments = increments;
|
||||
// NOTE: We don't update the window size here, because it will be done on the next resize
|
||||
// or configure event.
|
||||
}
|
||||
|
||||
/// Get the resize increments of the window.
|
||||
pub fn resize_increments(&self) -> Option<LogicalSize<u32>> {
|
||||
self.resize_increments
|
||||
}
|
||||
|
||||
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
self.apply_on_pointer(|pointer, data| {
|
||||
let surface = pointer.surface();
|
||||
|
||||
@@ -81,7 +81,7 @@ extern "C" fn preedit_draw_callback(
|
||||
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() {
|
||||
tracing::warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_length={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
@@ -158,7 +158,7 @@ impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
|
||||
preedit_start_callback as usize,
|
||||
preedit_start_callback as *const () as usize,
|
||||
)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
|
||||
@@ -51,10 +51,9 @@ impl ImeInner {
|
||||
}
|
||||
|
||||
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)
|
||||
match self.im.as_ref() {
|
||||
Some(im) if !self.is_destroyed => unsafe { close_im(&self.xconn, im.im).map(|_| true) },
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ use std::{cmp, env};
|
||||
use tracing::{debug, info, warn};
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
|
||||
use x11rb::protocol::shape::SK;
|
||||
use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper};
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle};
|
||||
use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO};
|
||||
use x11rb::protocol::xproto::{self, ClipOrdering, ConnectionExt as _, Rectangle};
|
||||
use x11rb::protocol::{randr, xinput};
|
||||
|
||||
use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
|
||||
@@ -1622,6 +1621,15 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
// Implement cursor hittest for X11 by either setting an empty or full window input shape.
|
||||
|
||||
// In X11, every window has two "shapes":
|
||||
// * Bounding shape: defines the visible outline of the window.
|
||||
// * Input shape: defines the region of the window that receives pointer/keyboard events.
|
||||
// If the input shape is the full window rectangle, the window behaves normally.
|
||||
// If the input shape is empty, the window is completely click‑through.
|
||||
// Here, we implement hit test by mapping `hittest = true` to "restore a full input shape"
|
||||
// and `hittest = false` to "clear the input shape" (empty list of rectangles).
|
||||
let mut rectangles: Vec<Rectangle> = Vec::new();
|
||||
if hittest {
|
||||
let size = self.inner_size();
|
||||
@@ -1632,11 +1640,18 @@ impl UnownedWindow {
|
||||
height: size.height as u16,
|
||||
})
|
||||
}
|
||||
let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles)
|
||||
.map_err(|_e| ExternalError::Ignored)?;
|
||||
|
||||
self.xconn
|
||||
.xcb_connection()
|
||||
.xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region())
|
||||
.shape_rectangles(
|
||||
SO::SET,
|
||||
SK::INPUT,
|
||||
ClipOrdering::UNSORTED,
|
||||
self.xwindow,
|
||||
0,
|
||||
0,
|
||||
&rectangles,
|
||||
)
|
||||
.map_err(|_e| ExternalError::Ignored)?;
|
||||
self.shared_state_lock().cursor_hittest = Some(hittest);
|
||||
Ok(())
|
||||
|
||||
@@ -234,9 +234,7 @@ impl XConnection {
|
||||
// Store the timestamp in the slot if it's greater than the last one.
|
||||
let mut last_timestamp = self.timestamp.load(Ordering::Relaxed);
|
||||
loop {
|
||||
let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32);
|
||||
|
||||
if wrapping_sub(timestamp, last_timestamp) <= 0 {
|
||||
if (timestamp as i32).wrapping_sub(last_timestamp as i32) <= 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
|
||||
// include its effect in the key. For example if -on the Germay layout- one
|
||||
// include its effect in the key. For example if -on the German layout- one
|
||||
// presses alt+8, the logical key should be "{"
|
||||
// Also not checking if this is a release event because then this issue would
|
||||
// still affect the key release.
|
||||
|
||||
@@ -316,9 +316,15 @@ declare_class!(
|
||||
// sending a `None` cursor range.
|
||||
None
|
||||
} else {
|
||||
// Clamp to string length to avoid NSRangeException from out-of-bounds
|
||||
// indices sent by macOS IME (e.g. native Pinyin, see
|
||||
// https://github.com/alacritty/alacritty/issues/8791).
|
||||
let len = string.length();
|
||||
let location = selected_range.location.min(len);
|
||||
let end = selected_range.end().min(len);
|
||||
// Convert the selected range from UTF-16 indices to UTF-8 indices.
|
||||
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
|
||||
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) };
|
||||
let sub_string_a = unsafe { string.substringToIndex(location) };
|
||||
let sub_string_b = unsafe { string.substringToIndex(end) };
|
||||
let lowerbound_utf8 = sub_string_a.len();
|
||||
let upperbound_utf8 = sub_string_b.len();
|
||||
Some((lowerbound_utf8, upperbound_utf8))
|
||||
|
||||
@@ -373,7 +373,10 @@ declare_class!(
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames = match pb.propertyListForType(unsafe { NSFilenamesPboardType }) {
|
||||
Some(filenames) => filenames,
|
||||
None => return false.into(),
|
||||
};
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
@@ -399,7 +402,10 @@ declare_class!(
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames = match pb.propertyListForType(unsafe { NSFilenamesPboardType }) {
|
||||
Some(filenames) => filenames,
|
||||
None => return false.into(),
|
||||
};
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
|
||||
@@ -574,9 +574,11 @@ fn main_thread_id() -> u32 {
|
||||
//
|
||||
// See: https://doc.rust-lang.org/stable/reference/abi.html#the-link_section-attribute
|
||||
#[link_section = ".CRT$XCU"]
|
||||
static INIT_MAIN_THREAD_ID: unsafe fn() = {
|
||||
unsafe fn initer() {
|
||||
unsafe { MAIN_THREAD_ID = GetCurrentThreadId() };
|
||||
static INIT_MAIN_THREAD_ID: unsafe extern "C" fn() = {
|
||||
unsafe extern "C" fn initer() {
|
||||
unsafe {
|
||||
MAIN_THREAD_ID = GetCurrentThreadId();
|
||||
}
|
||||
}
|
||||
initer
|
||||
};
|
||||
@@ -1610,9 +1612,9 @@ unsafe fn public_window_callback_inner(
|
||||
},
|
||||
|
||||
WM_IME_SETCONTEXT => {
|
||||
// Hide composing text drawn by IME.
|
||||
let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize);
|
||||
result = ProcResult::DefWindowProc(wparam);
|
||||
// IME UI visibility flags are in lparam.
|
||||
let lparam = lparam & !(ISC_SHOWUICOMPOSITIONWINDOW as isize);
|
||||
result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) });
|
||||
},
|
||||
|
||||
// this is necessary for us to maintain minimize/restore state
|
||||
@@ -2639,7 +2641,7 @@ unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) {
|
||||
}
|
||||
|
||||
enum PointerMoveKind {
|
||||
/// Pointer enterd to the window.
|
||||
/// Pointer entered to the window.
|
||||
Enter,
|
||||
/// Pointer leaved the window client area.
|
||||
Leave,
|
||||
|
||||
@@ -884,7 +884,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`].
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns [`None`].
|
||||
#[inline]
|
||||
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
let _span = tracing::debug_span!("winit::Window::resize_increments",).entered();
|
||||
@@ -900,7 +900,6 @@ impl Window {
|
||||
///
|
||||
/// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole
|
||||
/// numbers.
|
||||
/// - **Wayland:** Not implemented.
|
||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||
#[inline]
|
||||
pub fn set_resize_increments<S: Into<Size>>(&self, increments: Option<S>) {
|
||||
@@ -1825,10 +1824,11 @@ pub enum WindowLevel {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub enum ImePurpose {
|
||||
/// No special hints for the IME (default).
|
||||
#[default]
|
||||
Normal,
|
||||
/// The IME is used for password input.
|
||||
Password,
|
||||
@@ -1838,12 +1838,6 @@ pub enum ImePurpose {
|
||||
Terminal,
|
||||
}
|
||||
|
||||
impl Default for ImePurpose {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque token used to activate the [`Window`].
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
|
||||
Reference in New Issue
Block a user