Compare commits

..

3 Commits

Author SHA1 Message Date
Kirill Chibisov
1c5fcf3309 Winit version 0.29.12 2024-03-01 14:05:35 +04:00
Kirill Chibisov
58c89c1ffc On X11, fix use after free during xinput2 processing
Fixes #3536.
2024-03-01 14:05:35 +04:00
John Nunley
2dae807c4f On X11, filter out tiny device mouse events
Usually, if mouse events are equal to (0, 0) we filter them out.
However, if the event is very close to zero it will still be given to
the user. In some cases this can be caused by bad float math on the X11
server side.

Fix it by filtering absolute values smaller than floating point epsilon.

Signed-off-by: John Nunley <dev@notgull.net>
Closes: #3500
2024-03-01 14:05:35 +04:00
8 changed files with 153 additions and 65 deletions

View File

@@ -11,6 +11,11 @@ Unreleased` header.
# Unreleased
# 0.29.12
- On X11, fix use after free during xinput2 handling.
- On X11, filter close to zero values in mouse device events
# 0.29.11
- On Wayland, fix DeviceEvent::Motion not being sent

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.29.11"
version = "0.29.12"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.29.11"
winit = "0.29.12"
```
## [Documentation](https://docs.rs/winit)
@@ -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.11", features = [ "android-native-activity" ] }`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.12", 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).

View File

@@ -33,9 +33,10 @@ use crate::platform_impl::platform::common::xkb::Context;
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
use crate::platform_impl::platform::x11::EventLoopWindowTarget;
use crate::platform_impl::platform::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use crate::platform_impl::x11::util::cookie::GenericEventCookie;
use crate::platform_impl::x11::{
atoms::*, mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, Dnd, DndState,
GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
};
/// The maximum amount of X modifiers to replay.
@@ -184,14 +185,15 @@ impl<T: 'static> EventProcessor<T> {
}
xlib::GenericEvent => {
let wt = Self::window_target(&self.target);
let xev = match GenericEventCookie::from_event(&wt.xconn, *xev) {
Some(xev) if xev.cookie.extension as u8 == self.xi2ext.major_opcode => {
xev.cookie
}
_ => return,
};
let xev: GenericEventCookie =
match GenericEventCookie::from_event(wt.xconn.clone(), *xev) {
Some(xev) if xev.extension() == self.xi2ext.major_opcode => xev,
_ => return,
};
match xev.evtype {
let evtype = xev.evtype();
match evtype {
ty @ xinput2::XI_ButtonPress | ty @ xinput2::XI_ButtonRelease => {
let state = if ty == xinput2::XI_ButtonPress {
ElementState::Pressed
@@ -199,7 +201,7 @@ impl<T: 'static> EventProcessor<T> {
ElementState::Released
};
let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIDeviceEvent = unsafe { xev.as_event() };
self.update_mods_from_xinput2_event(
&xev.mods,
&xev.group,
@@ -209,7 +211,7 @@ impl<T: 'static> EventProcessor<T> {
self.xinput2_button_input(xev, state, &mut callback);
}
xinput2::XI_Motion => {
let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIDeviceEvent = unsafe { xev.as_event() };
self.update_mods_from_xinput2_event(
&xev.mods,
&xev.group,
@@ -219,11 +221,11 @@ impl<T: 'static> EventProcessor<T> {
self.xinput2_mouse_motion(xev, &mut callback);
}
xinput2::XI_Enter => {
let xev: &XIEnterEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIEnterEvent = unsafe { xev.as_event() };
self.xinput2_mouse_enter(xev, &mut callback);
}
xinput2::XI_Leave => {
let xev: &XILeaveEvent = unsafe { &*(xev.data as *const _) };
let xev: &XILeaveEvent = unsafe { xev.as_event() };
self.update_mods_from_xinput2_event(
&xev.mods,
&xev.group,
@@ -233,51 +235,51 @@ impl<T: 'static> EventProcessor<T> {
self.xinput2_mouse_left(xev, &mut callback);
}
xinput2::XI_FocusIn => {
let xev: &XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIFocusInEvent = unsafe { xev.as_event() };
self.xinput2_focused(xev, &mut callback);
}
xinput2::XI_FocusOut => {
let xev: &XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIFocusOutEvent = unsafe { xev.as_event() };
self.xinput2_unfocused(xev, &mut callback);
}
xinput2::XI_TouchBegin | xinput2::XI_TouchUpdate | xinput2::XI_TouchEnd => {
let phase = match xev.evtype {
let phase = match evtype {
xinput2::XI_TouchBegin => TouchPhase::Started,
xinput2::XI_TouchUpdate => TouchPhase::Moved,
xinput2::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!(),
};
let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIDeviceEvent = unsafe { xev.as_event() };
self.xinput2_touch(xev, phase, &mut callback);
}
xinput2::XI_RawButtonPress | xinput2::XI_RawButtonRelease => {
let state = match xev.evtype {
let state = match evtype {
xinput2::XI_RawButtonPress => ElementState::Pressed,
xinput2::XI_RawButtonRelease => ElementState::Released,
_ => unreachable!(),
};
let xev: &XIRawEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIRawEvent = unsafe { xev.as_event() };
self.xinput2_raw_button_input(xev, state, &mut callback);
}
xinput2::XI_RawMotion => {
let xev: &XIRawEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIRawEvent = unsafe { xev.as_event() };
self.xinput2_raw_mouse_motion(xev, &mut callback);
}
xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => {
let state = match xev.evtype {
let state = match evtype {
xinput2::XI_RawKeyPress => ElementState::Pressed,
xinput2::XI_RawKeyRelease => ElementState::Released,
_ => unreachable!(),
};
let xev: &xinput2::XIRawEvent = unsafe { &*(xev.data as *const _) };
let xev: &xinput2::XIRawEvent = unsafe { xev.as_event() };
self.xinput2_raw_key_input(xev, state, &mut callback);
}
xinput2::XI_HierarchyChanged => {
let xev: &XIHierarchyEvent = unsafe { &*(xev.data as *const _) };
let xev: &XIHierarchyEvent = unsafe { xev.as_event() };
self.xinput2_hierarchy_changed(xev, &mut callback);
}
_ => {}
@@ -1502,8 +1504,8 @@ impl<T: 'static> EventProcessor<T> {
let mask =
unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
let mut value = xev.raw_values;
let mut mouse_delta = (0.0, 0.0);
let mut scroll_delta = (0.0, 0.0);
let mut mouse_delta = util::Delta::default();
let mut scroll_delta = util::Delta::default();
for i in 0..xev.valuators.mask_len * 8 {
if !xinput2::XIMaskIsSet(mask, i) {
continue;
@@ -1513,10 +1515,10 @@ impl<T: 'static> EventProcessor<T> {
// We assume that every XInput2 device with analog axes is a pointing device emitting
// relative coordinates.
match i {
0 => mouse_delta.0 = x,
1 => mouse_delta.1 = x,
2 => scroll_delta.0 = x as f32,
3 => scroll_delta.1 = x as f32,
0 => mouse_delta.set_x(x),
1 => mouse_delta.set_y(x),
2 => scroll_delta.set_x(x as f32),
3 => scroll_delta.set_y(x as f32),
_ => {}
}
@@ -1532,7 +1534,7 @@ impl<T: 'static> EventProcessor<T> {
value = unsafe { value.offset(1) };
}
if mouse_delta != (0.0, 0.0) {
if let Some(mouse_delta) = mouse_delta.consume() {
let event = Event::DeviceEvent {
device_id: did,
event: DeviceEvent::MouseMotion { delta: mouse_delta },
@@ -1540,7 +1542,7 @@ impl<T: 'static> EventProcessor<T> {
callback(&self.target, event);
}
if scroll_delta != (0.0, 0.0) {
if let Some(scroll_delta) = scroll_delta.consume() {
let event = Event::DeviceEvent {
device_id: did,
event: DeviceEvent::MouseWheel {

View File

@@ -69,6 +69,9 @@ const ALL_DEVICES: u16 = 0;
const ALL_MASTER_DEVICES: u16 = 1;
const ICONIC_STATE: u32 = 3;
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
type X11Source = Generic<BorrowedFd<'static>>;
struct WakeSender<T> {
@@ -989,9 +992,6 @@ impl From<xsettings::ParserError> for X11Error {
}
}
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
/// Type alias for a void cookie.
type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
@@ -1007,34 +1007,6 @@ 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 _))
}

View File

@@ -0,0 +1,55 @@
use std::ffi::c_int;
use std::sync::Arc;
use x11_dl::xlib::{self, XEvent, XGenericEventCookie};
use crate::platform_impl::x11::XConnection;
/// 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
pub struct GenericEventCookie {
cookie: XGenericEventCookie,
xconn: Arc<XConnection>,
}
impl GenericEventCookie {
pub fn from_event(xconn: Arc<XConnection>, event: XEvent) -> Option<GenericEventCookie> {
unsafe {
let mut cookie: XGenericEventCookie = From::from(event);
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == xlib::True {
Some(GenericEventCookie { cookie, xconn })
} else {
None
}
}
}
#[inline]
pub fn extension(&self) -> u8 {
self.cookie.extension as u8
}
#[inline]
pub fn evtype(&self) -> c_int {
self.cookie.evtype
}
/// Borrow inner event data as `&T`.
///
/// ## SAFETY
///
/// The caller must ensure that the event has the `T` inside of it.
#[inline]
pub unsafe fn as_event<T>(&self) -> &T {
unsafe { &*(self.cookie.data as *const _) }
}
}
impl Drop for GenericEventCookie {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
}
}
}

View File

@@ -8,6 +8,7 @@ use std::{
};
mod client_msg;
pub mod cookie;
mod cursor;
mod geometry;
mod hint;
@@ -15,13 +16,14 @@ mod icon;
mod input;
pub mod keys;
pub(crate) mod memory;
mod mouse;
mod randr;
mod window_property;
mod wm;
mod xmodmap;
pub use self::{
geometry::*, hint::*, input::*, window_property::*, wm::*, xmodmap::ModifierKeymap,
geometry::*, hint::*, input::*, mouse::*, window_property::*, wm::*, xmodmap::ModifierKeymap,
};
use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};

View File

@@ -0,0 +1,52 @@
//! Utilities for handling mouse events.
/// Recorded mouse delta designed to filter out noise.
pub struct Delta<T> {
x: T,
y: T,
}
impl<T: Default> Default for Delta<T> {
fn default() -> Self {
Self {
x: Default::default(),
y: Default::default(),
}
}
}
impl<T: Default> Delta<T> {
pub(crate) fn set_x(&mut self, x: T) {
self.x = x;
}
pub(crate) fn set_y(&mut self, y: T) {
self.y = y;
}
}
macro_rules! consume {
($this:expr, $ty:ty) => {{
let this = $this;
let (x, y) = match (this.x.abs() < <$ty>::EPSILON, this.y.abs() < <$ty>::EPSILON) {
(true, true) => return None,
(true, false) => (this.x, 0.0),
(false, true) => (0.0, this.y),
(false, false) => (this.x, this.y),
};
Some((x, y))
}};
}
impl Delta<f32> {
pub(crate) fn consume(self) -> Option<(f32, f32)> {
consume!(self, f32)
}
}
impl Delta<f64> {
pub(crate) fn consume(self) -> Option<(f64, f64)> {
consume!(self, f64)
}
}