diff --git a/clippy.toml b/clippy.toml index d2db8030..74ba0ee0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,6 +10,9 @@ disallowed-methods = [ { allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" }, { allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" }, { allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" }, + { allow-invalid = true, path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" }, + { allow-invalid = true, path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" }, + { allow-invalid = true, path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" }, { allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" }, { allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" }, { allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" }, diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index cb3c606c..7ad984ef 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -1,4 +1,7 @@ //! The event enums and assorted supporting types. +use std::cell::LazyCell; +use std::cmp::Ordering; +use std::f64; use std::path::PathBuf; use std::sync::{Mutex, Weak}; @@ -435,6 +438,7 @@ pub enum PointerKind { /// /// **macOS:** Unsupported. Touch(FingerId), + TabletTool(TabletToolKind), Unknown, } @@ -485,6 +489,13 @@ pub enum PointerSource { /// force will be 0.5 when a button is pressed or 0.0 otherwise. force: Option, }, + TabletTool { + /// Describes as which tool kind the interaction happened. + kind: TabletToolKind, + + /// Describes how the tool was held and used. + data: TabletToolData, + }, Unknown, } @@ -493,6 +504,7 @@ impl From for PointerKind { match source { PointerSource::Mouse => Self::Mouse, PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id), + PointerSource::TabletTool { kind, .. } => Self::TabletTool(kind), PointerSource::Unknown => Self::Unknown, } } @@ -502,7 +514,7 @@ impl From for PointerKind { /// /// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the /// system. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum ButtonSource { Mouse(MouseButton), /// See [`PointerSource::Touch`] for more details. @@ -514,6 +526,11 @@ pub enum ButtonSource { finger_id: FingerId, force: Option, }, + TabletTool { + kind: TabletToolKind, + button: TabletToolButton, + data: TabletToolData, + }, Unknown(u16), } @@ -525,6 +542,7 @@ impl ButtonSource { match self { ButtonSource::Mouse(mouse) => mouse, ButtonSource::Touch { .. } => MouseButton::Left, + ButtonSource::TabletTool { button, .. } => button.into(), ButtonSource::Unknown(button) => match button { 0 => MouseButton::Left, 1 => MouseButton::Middle, @@ -981,6 +999,7 @@ pub enum TouchPhase { /// Describes the force of a touch event #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[doc(alias = "Pressure")] pub enum Force { /// On iOS, the force is calibrated so that the same number corresponds to /// roughly the same amount of pressure on the screen regardless of the @@ -991,7 +1010,7 @@ pub enum Force { /// /// The force reported by Apple Pencil is measured along the axis of the /// pencil. If you want a force perpendicular to the device, you need to - /// calculate this value using the `altitude_angle` value. + /// calculate this value using the [`TabletToolAngle::altitude`] value. force: f64, /// The maximum possible force for a touch. /// @@ -1012,9 +1031,17 @@ impl Force { /// Instead of normalizing the force, you should prefer to handle /// [`Force::Calibrated`] so that the amount of force the user has to apply is /// consistent across devices. - pub fn normalized(&self) -> f64 { + /// + /// Passing in a [`TabletToolAngle`], returns the perpendicular force. + pub fn normalized(&self, angle: Option) -> f64 { match self { - Force::Calibrated { force, max_possible_force } => force / max_possible_force, + Force::Calibrated { force, max_possible_force } => { + let force = match angle { + Some(TabletToolAngle { altitude, .. }) => force / altitude.sin(), + None => *force, + }; + force / max_possible_force + }, Force::Normalized(force) => *force, } } @@ -1026,6 +1053,264 @@ pub type AxisId = u32; /// Identifier for a specific button on some device. pub type ButtonId = u32; +/// Tablet of the tablet tool. +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[non_exhaustive] +pub enum TabletToolKind { + #[default] + Pen, + Eraser, + Brush, + Pencil, + Airbrush, + Finger, + Mouse, + Lens, +} + +#[derive(Default, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct TabletToolData { + /// The force applied to the tool against the surface. + /// + /// When the force information is not available, [`None`] is returned. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`]. + pub force: Option, + /// Represents normalized tangential pressure, also known as barrel pressure. In the range of + /// -1 to 1. 0 means no tangential pressure is applied. [`None`] means backend or device has no + /// support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value + /// of 0. + pub tangential_force: Option, + /// The clockwise rotation in degrees of a tool around its own major axis. E.g. twisting a pen + /// around its length. In the range of 0 to 359. [`None`] means backend or device has no + /// support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value + /// of 0. + pub twist: Option, + /// The plane angle in degrees. [`None`] means backend or device has no support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with default + /// values. + pub tilt: Option, + /// The angular position in radians. [`None`] means backend or device has no support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect device support, so this will always be [`Some`] with + /// default values unless browser support is lacking. + pub angle: Option, +} + +impl TabletToolData { + /// Returns [`TabletToolTilt`] if present or calculates it from [`TabletToolAngle`]. + pub fn tilt(self) -> Option { + if let Some(tilt) = self.tilt { + Some(tilt) + } else { + self.angle.map(TabletToolAngle::tilt) + } + } + + /// Returns [`TabletToolAngle`] if present or calculates it from [`TabletToolTilt`]. + pub fn angle(self) -> Option { + if let Some(angle) = self.angle { + Some(angle) + } else { + self.tilt.map(TabletToolTilt::angle) + } + } +} + +/// The plane angle in degrees of a tool. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct TabletToolTilt { + /// The plane angle in degrees between the surface Y-Z plane and the plane containing the tool + /// and the surface Y axis. Positive values are to the right. In the range of -90 to 90. 0 + /// means the tool is perpendicular to the surface and is the default. + /// + /// ![Tilt X](https://raw.githubusercontent.com/rust-windowing/winit/master/winit/docs/res/tool_tilt_x.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub x: i8, + /// The plane angle in degrees between the surface X-Z plane and the plane containing the tool + /// and the surface X axis. Positive values are towards the user. In the range of -90 to + /// 90. 0 means the tool is perpendicular to the surface and is the default. + /// + /// ![Tilt Y](https://raw.githubusercontent.com/rust-windowing/winit/master/winit/docs/res/tool_tilt_y.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub y: i8, +} + +impl TabletToolTilt { + pub fn angle(self) -> TabletToolAngle { + // See . + + use std::f64::consts::*; + + const PI_0_5: f64 = FRAC_PI_2; + const PI_1_5: f64 = 3. * FRAC_PI_2; + const PI_2: f64 = 2. * PI; + + let x = LazyCell::new(|| f64::from(self.x).to_radians()); + let y = LazyCell::new(|| f64::from(self.y).to_radians()); + + let mut azimuth = 0.; + + if self.x == 0 { + match self.y.cmp(&0) { + Ordering::Greater => azimuth = PI_0_5, + Ordering::Less => azimuth = PI_1_5, + Ordering::Equal => (), + } + } else if self.y == 0 { + if self.x < 0 { + azimuth = PI; + } + } else if self.x.abs() == 90 || self.y.abs() == 90 { + // not enough information to calculate azimuth + azimuth = 0.; + } else { + // Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90 + azimuth = f64::atan2(y.tan(), x.tan()); + + if azimuth < 0. { + azimuth += PI_2; + } + } + + let altitude; + + if self.x.abs() == 90 || self.y.abs() == 90 { + altitude = 0.; + } else if self.x == 0 { + altitude = PI_0_5 - y.abs(); + } else if self.y == 0 { + altitude = PI_0_5 - x.abs(); + } else { + // Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90 + altitude = f64::atan(1. / f64::sqrt(x.tan().powi(2) + y.tan().powi(2))); + } + + TabletToolAngle { altitude, azimuth } + } +} + +/// The angular position in radians of a tool. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct TabletToolAngle { + /// The altitude angle in radians between the tools perpendicular position to the surface and + /// the surface X-Y plane. In the range of 0, parallel to the surface, to π/2, perpendicular to + /// the surface. π/2 means the tool is perpendicular to the surface and is the default. + /// + /// ![Altitude angle](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_altitude.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub altitude: f64, + /// The azimuth angle in radiants representing the rotation between the major axis of the tool + /// and the surface X-Y plane. In the range of 0, 3 o'clock, progressively increasing clockwise + /// to 2π. 0 means the tool is at 3 o'clock or is perpendicular to the surface (`altitude` of + /// π/2) and is the default. + /// + /// ![Azimuth angle](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_azimuth.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub azimuth: f64, +} + +impl Default for TabletToolAngle { + fn default() -> Self { + Self { altitude: f64::consts::FRAC_2_PI, azimuth: 0. } + } +} + +impl TabletToolAngle { + pub fn tilt(self) -> TabletToolTilt { + // See . + + use std::f64::consts::*; + + const PI_0_5: f64 = FRAC_PI_2; + const PI_1_5: f64 = 3. * FRAC_PI_2; + const PI_2: f64 = 2. * PI; + + let mut x = 0.; + let mut y = 0.; + + if self.altitude == 0. { + if self.azimuth == 0. || self.azimuth == PI_2 { + x = FRAC_PI_2; + } else if self.azimuth == PI_0_5 { + y = FRAC_PI_2; + } else if self.azimuth == PI { + x = -FRAC_PI_2; + } else if self.azimuth == PI_1_5 { + y = -FRAC_PI_2; + } else if self.azimuth > 0. && self.azimuth < PI_0_5 { + x = FRAC_PI_2; + y = FRAC_PI_2; + } else if self.azimuth > PI_0_5 && self.azimuth < PI { + x = -FRAC_PI_2; + y = FRAC_PI_2; + } else if self.azimuth > PI && self.azimuth < PI_1_5 { + x = -FRAC_PI_2; + y = -FRAC_PI_2; + } else if self.azimuth > PI_1_5 && self.azimuth < PI_2 { + x = FRAC_PI_2; + y = -FRAC_PI_2; + } + } + + if self.altitude != 0. { + let altitude = self.altitude.tan(); + + x = f64::atan(f64::cos(self.azimuth) / altitude); + y = f64::atan(f64::sin(self.azimuth) / altitude); + } + + TabletToolTilt { x: x.to_degrees().round() as i8, y: y.to_degrees().round() as i8 } + } +} + /// Describes the input state of a key. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -1058,6 +1343,28 @@ pub enum MouseButton { Other(u16), } +/// Describes a button of a tool, e.g. a pen. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub enum TabletToolButton { + Contact, + Barrel, + Other(u16), +} + +impl From for MouseButton { + fn from(tool: TabletToolButton) -> Self { + match tool { + TabletToolButton::Contact => MouseButton::Left, + TabletToolButton::Barrel => MouseButton::Right, + TabletToolButton::Other(1) => MouseButton::Middle, + TabletToolButton::Other(3) => MouseButton::Back, + TabletToolButton::Other(4) => MouseButton::Forward, + TabletToolButton::Other(other) => MouseButton::Other(other), + } + } +} + /// Describes a difference in the mouse scroll wheel state. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -1241,16 +1548,85 @@ mod tests { }); } + #[test] + fn test_tilt_angle_conversions() { + use std::f64::consts::*; + + use event::{TabletToolAngle, TabletToolTilt}; + + // See . + const TILT_TO_ANGLE: &[(TabletToolTilt, TabletToolAngle)] = &[ + (TabletToolTilt { x: 0, y: 0 }, TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }), + (TabletToolTilt { x: 0, y: 90 }, TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }), + (TabletToolTilt { x: 0, y: -90 }, TabletToolAngle { + altitude: 0., + azimuth: 3. * FRAC_PI_2, + }), + (TabletToolTilt { x: 90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: 0. }), + (TabletToolTilt { x: 90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }), + (TabletToolTilt { x: 90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }), + (TabletToolTilt { x: -90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: PI }), + (TabletToolTilt { x: -90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }), + (TabletToolTilt { x: -90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }), + (TabletToolTilt { x: 0, y: 45 }, TabletToolAngle { + altitude: FRAC_PI_4, + azimuth: FRAC_PI_2, + }), + (TabletToolTilt { x: 0, y: -45 }, TabletToolAngle { + altitude: FRAC_PI_4, + azimuth: 3. * FRAC_PI_2, + }), + (TabletToolTilt { x: 45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }), + (TabletToolTilt { x: -45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }), + ]; + + for (tilt, angle) in TILT_TO_ANGLE { + assert_eq!(tilt.angle(), *angle, "{tilt:?}"); + } + + // See . + const ANGLE_TO_TILT: &[(TabletToolAngle, TabletToolTilt)] = &[ + (TabletToolAngle { altitude: 0., azimuth: 0. }, TabletToolTilt { x: 90, y: 0 }), + (TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }, TabletToolTilt { x: 45, y: 0 }), + (TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }, TabletToolTilt { x: 0, y: 0 }), + (TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }, TabletToolTilt { x: 0, y: 90 }), + (TabletToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 }, TabletToolTilt { + x: 0, + y: 45, + }), + (TabletToolAngle { altitude: 0., azimuth: PI }, TabletToolTilt { x: -90, y: 0 }), + (TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }, TabletToolTilt { x: -45, y: 0 }), + (TabletToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 }, TabletToolTilt { + x: 0, + y: -90, + }), + (TabletToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 }, TabletToolTilt { + x: 0, + y: -45, + }), + ]; + + for (angle, tilt) in ANGLE_TO_TILT { + assert_eq!(angle.tilt(), *tilt, "{angle:?}"); + } + } + #[test] fn test_force_normalize() { let force = event::Force::Normalized(0.0); - assert_eq!(force.normalized(), 0.0); + assert_eq!(force.normalized(None), 0.0); let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 }; - assert_eq!(force2.normalized(), 2.0); + assert_eq!(force2.normalized(None), 2.0); let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 }; - assert_eq!(force3.normalized(), 2.0); + assert_eq!( + force3.normalized(Some(event::TabletToolAngle { + altitude: std::f64::consts::PI / 2.0, + azimuth: 0. + })), + 2.0 + ); } #[allow(clippy::clone_on_copy)] diff --git a/winit-uikit/src/view.rs b/winit-uikit/src/view.rs index 194bf034..8ec238f9 100644 --- a/winit-uikit/src/view.rs +++ b/winit-uikit/src/view.rs @@ -461,6 +461,10 @@ impl WinitView { let window = self.window().unwrap(); let mut touch_events = Vec::new(); for touch in touches { + if let UITouchType::Pencil = touch.r#type() { + continue; + } + let logical_location = touch.locationInView(None); let touch_type = touch.r#type(); let force = if let UITouchType::Pencil = touch_type { diff --git a/winit-wayland/src/seat/mod.rs b/winit-wayland/src/seat/mod.rs index 38f84ae1..77637d0a 100644 --- a/winit-wayland/src/seat/mod.rs +++ b/winit-wayland/src/seat/mod.rs @@ -13,6 +13,7 @@ use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use tracing::warn; use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1; +use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2; use winit_core::event::WindowEvent; use winit_core::keyboard::ModifiersState; @@ -50,6 +51,9 @@ pub struct WinitSeatState { /// The text input bound on the seat. text_input: Option>, + /// The tablet input bound on the seat. + tablet: Option>, + /// The relative pointer bound on the seat. relative_pointer: Option, @@ -156,6 +160,12 @@ impl SeatHandler for WinitState { TextInputData::default(), ))); } + + if let Some(tablet_state) = + seat_state.tablet.is_none().then_some(self.tablet_state.as_ref()).flatten() + { + seat_state.tablet = Some(Arc::new(tablet_state.get_tablet_seat(&seat, queue_handle))); + } } fn remove_capability( @@ -177,6 +187,11 @@ impl SeatHandler for WinitState { text_input.destroy(); } + // NOTE: figure out when this should actually be destroyed. + if let Some(tablet) = seat_state.tablet.take() { + tablet.destroy(); + } + match capability { SeatCapability::Touch => { if let Some(touch) = seat_state.touch.take() { diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 2ca1de86..c254c872 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -30,6 +30,7 @@ use crate::seat::{ }; use crate::types::kwin_blur::KWinBlurManager; use crate::types::wp_fractional_scaling::FractionalScalingManager; +use crate::types::wp_tablet_input_v2::TabletManager; use crate::types::wp_viewporter::ViewporterState; use crate::types::xdg_activation::XdgActivationState; use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState; @@ -100,6 +101,9 @@ pub struct WinitState { /// Relative pointer. pub relative_pointer: Option, + /// Tablet manager. + pub tablet_state: Option, + /// Pointer constraints to handle pointer locking and confining. pub pointer_constraints: Option>, @@ -194,6 +198,7 @@ impl WinitState { text_input_state: TextInputState::new(globals, queue_handle).ok(), relative_pointer: RelativePointerState::new(globals, queue_handle).ok(), + tablet_state: TabletManager::new(globals, queue_handle).ok(), pointer_constraints: PointerConstraintsState::new(globals, queue_handle) .map(Arc::new) .ok(), diff --git a/winit-wayland/src/types/mod.rs b/winit-wayland/src/types/mod.rs index e27a98ff..03b31bf5 100644 --- a/winit-wayland/src/types/mod.rs +++ b/winit-wayland/src/types/mod.rs @@ -3,6 +3,7 @@ pub mod cursor; pub mod kwin_blur; pub mod wp_fractional_scaling; +pub mod wp_tablet_input_v2; pub mod wp_viewporter; pub mod xdg_activation; pub mod xdg_toplevel_icon_manager; diff --git a/winit-wayland/src/types/wp_tablet_input_v2.rs b/winit-wayland/src/types/wp_tablet_input_v2.rs new file mode 100644 index 00000000..0d7e5a1e --- /dev/null +++ b/winit-wayland/src/types/wp_tablet_input_v2.rs @@ -0,0 +1,343 @@ +//! Handling of wp_tablet_input_v2. + +use std::sync::Mutex; + +use dpi::LogicalPosition; +use sctk::compositor::SurfaceData; +use sctk::globals::GlobalData; +use sctk::reexports::client::backend::smallvec::SmallVec; +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::{ + delegate_dispatch, event_created_child, Connection, Dispatch, Proxy, QueueHandle, WEnum, +}; +use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_manager_v2::ZwpTabletManagerV2; +use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_pad_v2::ZwpTabletPadV2; +use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::{ + self, ZwpTabletSeatV2, +}; +use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_tool_v2::{ + ButtonState, Event as ToolEvent, Type as ToolType, ZwpTabletToolV2, +}; +use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_v2::ZwpTabletV2; +use winit_core::event::{ + ButtonSource, ElementState, Force, PointerKind, PointerSource, TabletToolButton, + TabletToolData as CoreTabletToolData, TabletToolKind, TabletToolTilt, WindowEvent, +}; + +use crate::state::WinitState; + +/// KWin blur manager. +#[derive(Debug, Clone)] +pub struct TabletManager { + manager: ZwpTabletManagerV2, +} + +impl TabletManager { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + // Ignore v2 since we are not interested in its events. + let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { manager }) + } + + pub fn get_tablet_seat( + &self, + seat: &WlSeat, + queue_handle: &QueueHandle, + ) -> ZwpTabletSeatV2 { + self.manager.get_tablet_seat(seat, queue_handle, ()) + } +} +impl Dispatch for TabletManager { + fn event( + _: &mut WinitState, + _: &ZwpTabletManagerV2, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("no events defined for zwp_tablet_manager_v2"); + } +} + +impl Dispatch for TabletManager { + fn event( + _: &mut WinitState, + _: &ZwpTabletManagerV2, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("no events defined for zwp_tablet_manager_v2"); + } +} + +impl Dispatch for TabletManager { + event_created_child!(WinitState, ZwpTabletSeatV2, [ + zwp_tablet_seat_v2::EVT_TABLET_ADDED_OPCODE => (ZwpTabletV2, Default::default()), + zwp_tablet_seat_v2::EVT_TOOL_ADDED_OPCODE => (ZwpTabletToolV2, Default::default()), + zwp_tablet_seat_v2::EVT_PAD_ADDED_OPCODE => (ZwpTabletPadV2, Default::default()) + ]); + + fn event( + _: &mut WinitState, + _: &ZwpTabletSeatV2, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for TabletManager { + fn event( + state: &mut WinitState, + _: &ZwpTabletToolV2, + event: ::Event, + data: &TabletToolData, + _: &Connection, + _: &QueueHandle, + ) { + let mut data = data.inner.lock().unwrap(); + + match event { + ToolEvent::Type { tool_type: WEnum::Value(tool_type) } => { + data.ty = match tool_type { + ToolType::Pen => TabletToolKind::Pen, + ToolType::Eraser => TabletToolKind::Eraser, + ToolType::Brush => TabletToolKind::Brush, + ToolType::Pencil => TabletToolKind::Pencil, + ToolType::Airbrush => TabletToolKind::Airbrush, + ToolType::Finger => TabletToolKind::Finger, + ToolType::Mouse => TabletToolKind::Mouse, + ToolType::Lens => TabletToolKind::Lens, + _ => return, + }; + }, + ToolEvent::Capability { .. } => {}, + ToolEvent::Done => (), + ToolEvent::ProximityIn { serial, surface, .. } => { + data.pending.push(TabletEvent::Enter { serial, surface }); + }, + ToolEvent::ProximityOut => data.pending.push(TabletEvent::Left), + ToolEvent::Down { serial } => { + let event = TabletEvent::Button { + state: ElementState::Pressed, + serial: Some(serial), + button: TabletToolButton::Contact, + }; + + data.pending.push(event); + }, + ToolEvent::Up => { + let event = TabletEvent::Button { + state: ElementState::Released, + serial: None, + button: TabletToolButton::Contact, + }; + + data.pending.push(event); + }, + ToolEvent::Tilt { tilt_x, tilt_y } => { + data.tool_state.tilt = Some(TabletToolTilt { x: tilt_x as i8, y: tilt_y as i8 }); + }, + ToolEvent::Motion { x, y } => { + data.position = (x, y).into(); + data.pending.push(TabletEvent::Moved); + }, + ToolEvent::Pressure { pressure } => { + data.tool_state.force = Some(Force::Normalized(pressure as f64 / u16::MAX as f64)); + }, + ToolEvent::Rotation { degrees } => { + data.tool_state.twist = Some(degrees as u16); + }, + ToolEvent::Button { serial, button, state: WEnum::Value(state) } => { + let state = match state { + ButtonState::Released => ElementState::Released, + ButtonState::Pressed => ElementState::Pressed, + _ => return, + }; + + // Map similar to SDL. + let button = match button { + // BTN_STYLUS. + 0x14b => TabletToolButton::Contact, + 0x14c => TabletToolButton::Barrel, + // BTN_STYLUS3. + 0x149 => TabletToolButton::Other(1), + // There's no defined conversion for any of that. + button => TabletToolButton::Other(button as u16), + }; + + let event = TabletEvent::Button { button, state, serial: Some(serial) }; + data.pending.push(event); + }, + ToolEvent::Frame { .. } => { + let kind = data.ty; + for event in std::mem::take(&mut data.pending) { + if let TabletEvent::Enter { surface, serial } = &event { + data.latest_enter_serial = Some(*serial); + data.surface = Some(surface.clone()); + } + + // Handle events only for top-level surface. + let surface = match data + .surface + .as_ref() + .map(|surface| (surface, surface.data::())) + { + Some((surface, Some(surface_data))) + if surface_data.parent_surface().is_none() => + { + surface + }, + _ => continue, + }; + + let window_id = crate::make_wid(surface); + + // Ensure that window exists. + let window = match state.windows.get_mut().get_mut(&window_id) { + Some(window) => window.lock().unwrap(), + None => continue, + }; + + let position = data.position.to_physical(window.scale_factor()); + + let window_event = match event { + TabletEvent::Enter { .. } => WindowEvent::PointerEntered { + device_id: None, + position, + primary: true, + kind: PointerKind::TabletTool(kind), + }, + TabletEvent::Moved => WindowEvent::PointerMoved { + device_id: None, + position, + primary: true, + source: PointerSource::TabletTool { + kind, + data: data.tool_state.clone(), + }, + }, + TabletEvent::Button { button, state, serial } => { + // Update serial if we have it. + if let Some(serial) = serial { + data.latest_button_serial = Some(serial); + } + + WindowEvent::PointerButton { + device_id: None, + state, + position, + primary: true, + button: ButtonSource::TabletTool { + kind, + button, + data: data.tool_state.clone(), + }, + } + }, + TabletEvent::Left => WindowEvent::PointerLeft { + device_id: None, + position: Some(position), + primary: true, + kind: PointerKind::TabletTool(kind), + }, + }; + + state.events_sink.push_window_event(window_event, window_id); + + // Clear up the surface after we've processed `Left` event. + if matches!(event, TabletEvent::Left) { + data.surface = None; + data.latest_button_serial = None; + data.latest_enter_serial = None; + data.tool_state = Default::default(); + } + } + }, + _ => (), + } + } +} + +#[derive(Debug, Default)] +struct TabletToolData { + inner: Mutex, +} + +#[derive(Debug, Default)] +pub(crate) struct TabletToolDataInner { + pub(crate) ty: TabletToolKind, + + /// Core tablet tool data. + pub(crate) tool_state: CoreTabletToolData, + + /// Pending events until the `frame` is received. + pub(crate) pending: SmallVec<[TabletEvent; 4]>, + + /// Surface the tablet most recently entered. + pub(crate) surface: Option, + + /// Position relative to the surface. + pub(crate) position: LogicalPosition, + + // NOTE: even though we don't utilize serials + // right now, track them anyway. + /// The serial of the latest enter event for the pointer + pub(crate) latest_enter_serial: Option, + + /// The serial of the latest button event for the pointer + pub(crate) latest_button_serial: Option, +} + +// Due to wayland using logical coordinates, +// delay the conversion to physical until the dispatch actually happens, +// so the scaling is applied at the time of actual dispatch, since it +// can technically change before the `frame` event. +#[derive(Debug, Clone)] +pub(crate) enum TabletEvent { + Enter { serial: u32, surface: WlSurface }, + Left, + Moved, + Button { button: TabletToolButton, state: ElementState, serial: Option }, +} + +impl Dispatch for TabletManager { + fn event( + _: &mut WinitState, + _: &ZwpTabletV2, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for TabletManager { + fn event( + _: &mut WinitState, + _: &ZwpTabletPadV2, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +delegate_dispatch!(WinitState: [ZwpTabletManagerV2: GlobalData] => TabletManager); +delegate_dispatch!(WinitState: [ZwpTabletManagerV2: ()] => TabletManager); +delegate_dispatch!(WinitState: [ZwpTabletSeatV2: ()] => TabletManager); +delegate_dispatch!(WinitState: [ZwpTabletV2: ()] => TabletManager); +delegate_dispatch!(WinitState: [ZwpTabletToolV2: TabletToolData] => TabletManager); +delegate_dispatch!(WinitState: [ZwpTabletPadV2: ()] => TabletManager); diff --git a/winit-web/src/cursor.rs b/winit-web/src/cursor.rs index d5035481..6aeb156b 100644 --- a/winit-web/src/cursor.rs +++ b/winit-web/src/cursor.rs @@ -216,7 +216,7 @@ impl CursorHandler { Cursor::Custom(cursor) => { let cursor = match cursor.cast_ref::() { Some(cursor) => cursor, - None => todo!(), + None => return, }; if let SelectedCursor::Loading { cursor: old_cursor, .. } diff --git a/winit-web/src/event_loop/runner.rs b/winit-web/src/event_loop/runner.rs index 4aeaf383..6712df2d 100644 --- a/winit-web/src/event_loop/runner.rs +++ b/winit-web/src/event_loop/runner.rs @@ -24,7 +24,7 @@ use crate::event_loop::ActiveEventLoop; use crate::main_thread::MainThreadMarker; use crate::monitor::MonitorHandler; use crate::r#async::DispatchRunner; -use crate::web_sys::event::mouse_button_to_id; +use crate::web_sys::event::ButtonsState; use crate::window::Inner; use crate::{backend, event, EventLoop, PollStrategy, WaitUntilStrategy}; @@ -318,8 +318,10 @@ impl Shared { // chorded button event let device_id = event::mkdid(event.pointer_id()); - if let Some(button) = backend::event::mouse_button(&event) { - let state = if backend::event::mouse_buttons(&event).contains(button.into()) { + if let Some(button) = backend::event::raw_button(&event) { + let state = if backend::event::pointer_buttons(&event) + .contains(ButtonsState::from_bits_retain(button)) + { ElementState::Pressed } else { ElementState::Released @@ -327,10 +329,7 @@ impl Shared { runner.send_event(Event::DeviceEvent { device_id, - event: DeviceEvent::Button { - button: mouse_button_to_id(button).into(), - state, - }, + event: DeviceEvent::Button { button: button.into(), state }, }); return; @@ -338,14 +337,16 @@ impl Shared { // pointer move event let mut delta = backend::event::MouseDelta::init(&navigator, &event); - runner.send_events(backend::event::pointer_move_event(event).map(|event| { - let delta = delta.delta(&event).to_physical(backend::scale_factor(&window)); + runner.send_events(backend::event::pointer_move_event(event).map( + |event: web_sys::PointerEvent| { + let delta = delta.delta(&event).to_physical(backend::scale_factor(&window)); - Event::DeviceEvent { - device_id, - event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) }, - } - })); + Event::DeviceEvent { + device_id, + event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) }, + } + }, + )); }), )); let runner = self.clone(); @@ -375,11 +376,11 @@ impl Shared { return; } - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); + let button = backend::event::raw_button(&event).expect("no pointer button pressed"); runner.send_event(Event::DeviceEvent { device_id: event::mkdid(event.pointer_id()), event: DeviceEvent::Button { - button: mouse_button_to_id(button).into(), + button: button.into(), state: ElementState::Pressed, }, }); @@ -394,11 +395,11 @@ impl Shared { return; } - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); + let button = backend::event::raw_button(&event).expect("no pointer button pressed"); runner.send_event(Event::DeviceEvent { device_id: event::mkdid(event.pointer_id()), event: DeviceEvent::Button { - button: mouse_button_to_id(button).into(), + button: button.into(), state: ElementState::Released, }, }); diff --git a/winit-web/src/web_sys/event.rs b/winit-web/src/web_sys/event.rs index 69a1635a..ad654314 100644 --- a/winit-web/src/web_sys/event.rs +++ b/winit-web/src/web_sys/event.rs @@ -1,11 +1,16 @@ use std::cell::OnceCell; +use std::f64; use dpi::{LogicalPosition, PhysicalPosition, Position}; use smol_str::SmolStr; +use tracing::warn; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; -use web_sys::{KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent}; -use winit_core::event::{FingerId, MouseButton, MouseScrollDelta, PointerKind}; +use web_sys::{Event, KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent}; +use winit_core::event::{ + ButtonSource, FingerId, Force, MouseButton, MouseScrollDelta, PointerKind, PointerSource, + TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TabletToolTilt, +}; use winit_core::keyboard::{ Key, KeyCode, KeyLocation, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }; @@ -16,11 +21,12 @@ bitflags::bitflags! { // https://www.w3.org/TR/pointerevents3/#the-buttons-property #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ButtonsState: u16 { - const LEFT = 0b00001; - const RIGHT = 0b00010; - const MIDDLE = 0b00100; - const BACK = 0b01000; - const FORWARD = 0b10000; + const LEFT = 0b000001; + const RIGHT = 0b000010; + const MIDDLE = 0b000100; + const BACK = 0b001000; + const FORWARD = 0b010000; + const ERASER = 0b100000; } } @@ -37,6 +43,15 @@ impl From for MouseButton { } } +impl From for ButtonsState { + fn from(value: ButtonSource) -> Self { + match value { + ButtonSource::TabletTool { button, .. } => button.into(), + other => ButtonsState::from(other.mouse_button()), + } + } +} + impl From for ButtonsState { fn from(value: MouseButton) -> Self { match value { @@ -50,37 +65,133 @@ impl From for ButtonsState { } } -pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState { +impl From for ButtonsState { + fn from(tool: TabletToolButton) -> Self { + match tool { + TabletToolButton::Contact => ButtonsState::LEFT, + TabletToolButton::Barrel => ButtonsState::RIGHT, + TabletToolButton::Other(value) => Self::from_bits_retain(value), + } + } +} + +pub fn pointer_buttons(event: &MouseEvent) -> ButtonsState { + #[allow(clippy::disallowed_methods)] ButtonsState::from_bits_retain(event.buttons()) } -pub fn mouse_button(event: &MouseEvent) -> Option { +pub fn raw_button(event: &MouseEvent) -> Option { // https://www.w3.org/TR/pointerevents3/#the-button-property - match event.button() { - -1 => None, - 0 => Some(MouseButton::Left), - 1 => Some(MouseButton::Middle), - 2 => Some(MouseButton::Right), - 3 => Some(MouseButton::Back), - 4 => Some(MouseButton::Forward), - i => { - Some(MouseButton::Other(i.try_into().expect("unexpected negative mouse button value"))) - }, + #[allow(clippy::disallowed_methods)] + let button = event.button(); + + if button == -1 { + None + } else { + Some(button.try_into().expect("unexpected negative mouse button value")) } } -pub fn mouse_button_to_id(button: MouseButton) -> u16 { +pub fn mouse_button(button: u16) -> MouseButton { match button { - MouseButton::Left => 0, - MouseButton::Right => 1, - MouseButton::Middle => 2, - MouseButton::Back => 3, - MouseButton::Forward => 4, - MouseButton::Other(value) => value, + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + 3 => MouseButton::Back, + 4 => MouseButton::Forward, + other => MouseButton::Other(other), } } -pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { +pub fn tool_button(button: u16) -> TabletToolButton { + match button { + 0 => TabletToolButton::Contact, + 2 => TabletToolButton::Barrel, + other => TabletToolButton::Other(other), + } +} + +#[derive(Clone, Copy)] +pub enum WebPointerType { + Mouse, + Touch, + Pen, +} + +impl WebPointerType { + pub fn from_event(event: &PointerEvent) -> Option { + #[allow(clippy::disallowed_methods)] + let r#type = event.pointer_type(); + + match r#type.as_ref() { + "mouse" => Some(Self::Mouse), + "touch" => Some(Self::Touch), + "pen" => Some(Self::Pen), + r#type => { + warn!("found unknown pointer type: {type}"); + None + }, + } + } +} + +pub fn pointer_kind(event: &PointerEvent, pointer_id: i32) -> PointerKind { + match WebPointerType::from_event(event) { + Some(WebPointerType::Mouse) => PointerKind::Mouse, + Some(WebPointerType::Touch) => PointerKind::Touch(FingerId::from_raw(pointer_id as usize)), + Some(WebPointerType::Pen) => { + PointerKind::TabletTool(if pointer_buttons(event).contains(ButtonsState::ERASER) { + TabletToolKind::Eraser + } else { + TabletToolKind::Pen + }) + }, + None => PointerKind::Unknown, + } +} + +pub fn pointer_source(event: &PointerEvent, kind: PointerKind) -> PointerSource { + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(extends = PointerEvent, extends = MouseEvent, extends = Event)] + pub type PointerEventExt; + + #[wasm_bindgen(method, getter, js_name = altitudeAngle)] + pub fn altitude_angle(this: &PointerEventExt) -> Option; + + #[wasm_bindgen(method, getter, js_name = azimuthAngle)] + pub fn azimuth_angle(this: &PointerEventExt) -> f64; + } + + let event: &PointerEventExt = event.unchecked_ref(); + + match kind { + PointerKind::Mouse => PointerSource::Mouse, + PointerKind::Touch(id) => PointerSource::Touch { + finger_id: id, + force: Some(Force::Normalized(event.pressure().into())), + }, + PointerKind::TabletTool(tool) => { + let data = TabletToolData { + force: Some(Force::Normalized(event.pressure().into())), + tangential_force: Some(event.tangential_pressure()), + twist: Some(event.twist().try_into().expect("found invalid `twist`")), + tilt: Some(TabletToolTilt { + x: event.tilt_x().try_into().expect("found invalid `tiltX`"), + y: event.tilt_y().try_into().expect("found invalid `tiltY`"), + }), + angle: event + .altitude_angle() + .map(|altitude| TabletToolAngle { altitude, azimuth: event.azimuth_angle() }), + }; + + PointerSource::TabletTool { kind: tool, data } + }, + PointerKind::Unknown => PointerSource::Unknown, + } +} + +pub fn pointer_position(event: &MouseEvent) -> LogicalPosition { #[wasm_bindgen] extern "C" { type MouseEventExt; @@ -113,7 +224,7 @@ impl MouseDelta { Some(Engine::Chromium) => Self::Chromium, // Firefox has wrong movement values in coalesced events. Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko { - old_position: mouse_position(event), + old_position: pointer_position(event), old_delta: LogicalPosition::new( event.movement_x() as f64, event.movement_y() as f64, @@ -129,7 +240,7 @@ impl MouseDelta { PhysicalPosition::new(event.movement_x(), event.movement_y()).into() }, MouseDelta::Gecko { old_position, old_delta } => { - let new_position = mouse_position(event); + let new_position = pointer_position(event); let x = new_position.x - old_position.x + old_delta.x; let y = new_position.y - old_position.y + old_delta.y; *old_position = new_position; @@ -160,14 +271,6 @@ pub fn mouse_scroll_delta( } } -pub fn pointer_type(event: &PointerEvent, pointer_id: i32) -> PointerKind { - match event.pointer_type().as_str() { - "mouse" => PointerKind::Mouse, - "touch" => PointerKind::Touch(FingerId::from_raw(pointer_id as usize)), - _ => PointerKind::Unknown, - } -} - pub fn key_code(event: &KeyboardEvent) -> PhysicalKey { // Use keyboard-types' parsing (it is based on the W3C standard). match event.code().parse() { diff --git a/winit-web/src/web_sys/pointer.rs b/winit-web/src/web_sys/pointer.rs index 0187947e..fe609cfa 100644 --- a/winit-web/src/web_sys/pointer.rs +++ b/winit-web/src/web_sys/pointer.rs @@ -3,14 +3,14 @@ use std::rc::Rc; use dpi::PhysicalPosition; use web_sys::PointerEvent; -use winit_core::event::{ButtonSource, DeviceId, ElementState, Force, PointerKind, PointerSource}; +use winit_core::event::{ButtonSource, DeviceId, ElementState, PointerKind, PointerSource}; use winit_core::keyboard::ModifiersState; use super::canvas::Common; use super::event; use super::event_handle::EventListenerHandle; use crate::event::mkdid; -use crate::web_sys::event::mouse_button_to_id; +use crate::web_sys::event::ButtonsState; #[allow(dead_code)] pub(super) struct PointerHandler { @@ -46,8 +46,8 @@ impl PointerHandler { let pointer_id = event.pointer_id(); let device_id = mkdid(pointer_id); let position = - event::mouse_position(&event).to_physical(super::scale_factor(&window)); - let kind = event::pointer_type(&event, pointer_id); + event::pointer_position(&event).to_physical(super::scale_factor(&window)); + let kind = event::pointer_kind(&event, pointer_id); handler(modifiers, device_id, event.is_primary(), position, kind); })); } @@ -64,8 +64,8 @@ impl PointerHandler { let pointer_id = event.pointer_id(); let device_id = mkdid(pointer_id); let position = - event::mouse_position(&event).to_physical(super::scale_factor(&window)); - let kind = event::pointer_type(&event, pointer_id); + event::pointer_position(&event).to_physical(super::scale_factor(&window)); + let kind = event::pointer_kind(&event, pointer_id); handler(modifiers, device_id, event.is_primary(), position, kind); })); } @@ -80,24 +80,25 @@ impl PointerHandler { Some(canvas_common.add_event("pointerup", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); let pointer_id = event.pointer_id(); - let kind = event::pointer_type(&event, pointer_id); + let kind = event::pointer_kind(&event, pointer_id); + let button = event::raw_button(&event).expect("no button pressed"); - let button = event::mouse_button(&event).expect("no mouse button pressed"); - - let source = match kind { - PointerKind::Mouse => ButtonSource::Mouse(button), - PointerKind::Touch(finger_id) => ButtonSource::Touch { - finger_id, - force: Some(Force::Normalized(event.pressure().into())), + let source = match event::pointer_source(&event, kind) { + PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)), + PointerSource::Touch { finger_id, force } => { + ButtonSource::Touch { finger_id, force } }, - PointerKind::Unknown => ButtonSource::Unknown(mouse_button_to_id(button)), + PointerSource::TabletTool { kind, data } => { + ButtonSource::TabletTool { kind, button: event::tool_button(button), data } + }, + PointerSource::Unknown => ButtonSource::Unknown(button), }; handler( modifiers, mkdid(pointer_id), event.is_primary(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::pointer_position(&event).to_physical(super::scale_factor(&window)), source, ) })); @@ -125,11 +126,11 @@ impl PointerHandler { let modifiers = event::mouse_modifiers(&event); let pointer_id = event.pointer_id(); - let kind = event::pointer_type(&event, pointer_id); - let button = event::mouse_button(&event).expect("no mouse button pressed"); + let kind = event::pointer_kind(&event, pointer_id); + let button = event::raw_button(&event).expect("no button pressed"); - let source = match kind { - PointerKind::Mouse => { + let source = match event::pointer_source(&event, kind) { + PointerSource::Mouse => { // Error is swallowed here since the error would occur every time the // mouse is clicked when the cursor is // grabbed, and there is probably not a @@ -137,20 +138,29 @@ impl PointerHandler { // care if it fails. let _e = canvas.set_pointer_capture(pointer_id); - ButtonSource::Mouse(button) + ButtonSource::Mouse(event::mouse_button(button)) }, - PointerKind::Touch(finger_id) => ButtonSource::Touch { - finger_id, - force: Some(Force::Normalized(event.pressure().into())), + PointerSource::Touch { finger_id, force } => { + ButtonSource::Touch { finger_id, force } }, - PointerKind::Unknown => ButtonSource::Unknown(mouse_button_to_id(button)), + PointerSource::TabletTool { kind, data } => { + // Error is swallowed here since the error would occur every time the + // mouse is clicked when the cursor is + // grabbed, and there is probably not a + // situation where this could fail, that we + // care if it fails. + let _e = canvas.set_pointer_capture(pointer_id); + + ButtonSource::TabletTool { kind, button: event::tool_button(button), data } + }, + PointerSource::Unknown => ButtonSource::Unknown(button), }; handler( modifiers, mkdid(pointer_id), event.is_primary(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::pointer_position(&event).to_physical(super::scale_factor(&window)), source, ) })); @@ -186,11 +196,11 @@ impl PointerHandler { Some(canvas_common.add_event("pointermove", move |event: PointerEvent| { let pointer_id = event.pointer_id(); let device_id = mkdid(pointer_id); - let kind = event::pointer_type(&event, pointer_id); + let kind = event::pointer_kind(&event, pointer_id); let primary = event.is_primary(); // chorded button event - if let Some(button) = event::mouse_button(&event) { + if let Some(button) = event::raw_button(&event) { if prevent_default.get() { // prevent text selection event.prevent_default(); @@ -198,34 +208,36 @@ impl PointerHandler { let _ = canvas.focus(); } - let state = if event::mouse_buttons(&event).contains(button.into()) { + let state = if event::pointer_buttons(&event) + .contains(ButtonsState::from_bits_retain(button)) + { ElementState::Pressed } else { ElementState::Released }; - let button = match kind { - PointerKind::Mouse => ButtonSource::Mouse(button), - PointerKind::Touch(finger_id) => { - let button_id = mouse_button_to_id(button); - - if button_id != 1 { - tracing::error!("unexpected touch button id: {button_id}"); + let button = match event::pointer_source(&event, kind) { + PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)), + PointerSource::Touch { finger_id, force } => { + if button != 0 { + tracing::error!("unexpected touch button id: {button}"); } - ButtonSource::Touch { - finger_id, - force: Some(Force::Normalized(event.pressure().into())), - } + ButtonSource::Touch { finger_id, force } }, - PointerKind::Unknown => todo!(), + PointerSource::TabletTool { kind, data } => ButtonSource::TabletTool { + kind, + button: event::tool_button(button), + data, + }, + PointerSource::Unknown => ButtonSource::Unknown(button), }; button_handler( event::mouse_modifiers(&event), device_id, primary, - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::pointer_position(&event).to_physical(super::scale_factor(&window)), state, button, ); @@ -242,15 +254,8 @@ impl PointerHandler { ( event::mouse_modifiers(&event), event.is_primary(), - event::mouse_position(&event).to_physical(scale), - match kind { - PointerKind::Mouse => PointerSource::Mouse, - PointerKind::Touch(finger_id) => PointerSource::Touch { - finger_id, - force: Some(Force::Normalized(event.pressure().into())), - }, - PointerKind::Unknown => PointerSource::Unknown, - }, + event::pointer_position(&event).to_physical(scale), + event::pointer_source(&event, kind), ) }), ); diff --git a/winit-win32/src/event_loop.rs b/winit-win32/src/event_loop.rs index 974170a5..7c3c3227 100644 --- a/winit-win32/src/event_loop.rs +++ b/winit-win32/src/event_loop.rs @@ -44,28 +44,29 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, SystemParametersInfoW, TranslateMessage, CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, - MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT, - RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, - SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, - SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, - WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, 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_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_SIZING, 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, + MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PEN_FLAG_BARREL, PEN_FLAG_ERASER, + PEN_MASK_PRESSURE, PEN_MASK_ROTATION, PEN_MASK_TILT_X, PEN_MASK_TILT_Y, PM_REMOVE, PT_PEN, + PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, + SIZE_MAXIMIZED, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, + SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, + WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, + 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_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_SIZING, 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, }; use winit_core::application::ApplicationHandler; use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; use winit_core::event::{ - DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TouchPhase, - WindowEvent, + DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TabletToolButton, + TabletToolData, TabletToolKind, TabletToolTilt, TouchPhase, WindowEvent, }; use winit_core::event_loop::pump_events::PumpStatus; use winit_core::event_loop::{ @@ -106,6 +107,7 @@ pub(crate) struct WindowData { pub key_event_builder: KeyEventBuilder, pub _file_drop_handler: Option, pub userdata_removed: Cell, + pub last_tablet_down_button_state: Cell, pub recurse_depth: Cell, } @@ -2085,22 +2087,6 @@ unsafe fn public_window_callback_inner( continue; } - let force = if let PT_TOUCH = pointer_info.pointerType { - let mut touch_info = mem::MaybeUninit::uninit(); - util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { - match unsafe { - GetPointerTouchInfo(pointer_info.pointerId, touch_info.as_mut_ptr()) - } { - 0 => None, - _ => normalize_pointer_pressure(unsafe { - touch_info.assume_init().pressure - }), - } - }) - } else { - None - }; - let x = location.x as f64 + x.fract(); let y = location.y as f64 + y.fract(); let position = PhysicalPosition::new(x, y); @@ -2108,60 +2094,83 @@ unsafe fn public_window_callback_inner( let finger_id = FingerId::from_raw(pointer_info.pointerId as usize); let primary = util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_PRIMARY); - if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_DOWN) { - userdata.send_window_event(window, WindowEvent::PointerEntered { - device_id: None, - primary, - position, - kind: if let PT_TOUCH = pointer_info.pointerType { - PointerKind::Touch(finger_id) - } else { - PointerKind::Unknown + let is_down = util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_DOWN); + if is_down || util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) { + let (kind, button) = match pointer_info.pointerType { + PT_TOUCH => (PointerKind::Touch(finger_id), ButtonSource::Touch { + finger_id, + force: force_for_touch(pointer_info.pointerId), + }), + PT_PEN => { + let kind = PointerKind::TabletTool(TabletToolKind::Pen); + let (mut pen_flags, data) = + tablet_tool_info_for_pen(pointer_info.pointerId); + let old_pen_flags = + userdata.last_tablet_down_button_state.replace(pen_flags); + // For release, use a diff. + if !is_down { + pen_flags ^= old_pen_flags; + }; + + let button = ButtonSource::TabletTool { + kind: TabletToolKind::Pen, + button: pen_flags_to_button(pen_flags), + data, + }; + + (kind, button) }, - }); - userdata.send_window_event(window, WindowEvent::PointerButton { - device_id: None, - primary, - state: Pressed, - position, - button: if let PT_TOUCH = pointer_info.pointerType { - ButtonSource::Touch { finger_id, force } - } else { - ButtonSource::Unknown(0) - }, - }); - } else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) { - userdata.send_window_event(window, WindowEvent::PointerButton { - device_id: None, - primary, - state: Released, - position, - button: if let PT_TOUCH = pointer_info.pointerType { - ButtonSource::Touch { finger_id, force } - } else { - ButtonSource::Unknown(0) - }, - }); - userdata.send_window_event(window, WindowEvent::PointerLeft { - device_id: None, - primary, - position: Some(position), - kind: if let PT_TOUCH = pointer_info.pointerType { - PointerKind::Touch(finger_id) - } else { - PointerKind::Unknown - }, - }); + _ => (PointerKind::Unknown, ButtonSource::Unknown(0)), + }; + + if is_down { + userdata.send_window_event(window, WindowEvent::PointerEntered { + device_id: None, + primary, + position, + kind, + }); + + userdata.send_window_event(window, WindowEvent::PointerButton { + device_id: None, + primary, + state: Pressed, + position, + button, + }); + } else { + userdata.send_window_event(window, WindowEvent::PointerButton { + device_id: None, + primary, + state: Released, + position, + button, + }); + userdata.send_window_event(window, WindowEvent::PointerLeft { + device_id: None, + primary, + position: Some(position), + kind, + }); + } } else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UPDATE) { + let source = match pointer_info.pointerType { + PT_TOUCH => PointerSource::Touch { + finger_id, + force: force_for_touch(pointer_info.pointerId), + }, + PT_PEN => PointerSource::TabletTool { + kind: TabletToolKind::Pen, + data: tablet_tool_info_for_pen(pointer_info.pointerId).1, + }, + _ => PointerSource::Unknown, + }; + userdata.send_window_event(window, WindowEvent::PointerMoved { device_id: None, primary, position, - source: if let PT_TOUCH = pointer_info.pointerType { - PointerSource::Touch { finger_id, force } - } else { - PointerSource::Unknown - }, + source, }); } else { continue; @@ -2691,3 +2700,57 @@ fn apply_win10_dpi_adjustment( conservative_rect } + +fn force_for_touch(pointer_id: u32) -> Option { + let mut touch_info = mem::MaybeUninit::uninit(); + util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { + match unsafe { GetPointerTouchInfo(pointer_id, touch_info.as_mut_ptr()) } { + 0 => None, + _ => normalize_pointer_pressure(unsafe { touch_info.assume_init().pressure }), + } + }) +} + +// Information is stored on the same thing, so we don't save anything by +// splitting the functions. +fn tablet_tool_info_for_pen(pointer_id: u32) -> (u32, TabletToolData) { + let mut tool_data = TabletToolData::default(); + let mut tool_button = 0; + + let mut tablet_info = mem::MaybeUninit::uninit(); + util::GET_POINTER_PEN_INFO.map(|GetPointerPenInfo| { + if unsafe { GetPointerPenInfo(pointer_id, tablet_info.as_mut_ptr()) } == 0 { + return; + } + + let pen_info = unsafe { tablet_info.assume_init() }; + if pen_info.penMask & PEN_MASK_PRESSURE != 0 { + tool_data.force = normalize_pointer_pressure(pen_info.pressure); + } + if pen_info.penMask & (PEN_MASK_TILT_X | PEN_MASK_TILT_Y) != 0 { + tool_data.tilt = + Some(TabletToolTilt { x: pen_info.tiltX as i8, y: pen_info.tiltY as i8 }); + } + if pen_info.penMask & PEN_MASK_ROTATION != 0 { + tool_data.twist = Some(pen_info.rotation as u16); + } + + tool_button = pen_info.penFlags; + }); + + (tool_button, tool_data) +} + +// NOTE: According to firefox, the buttons while can be combined, in +// reality they are not. +fn pen_flags_to_button(flags: u32) -> TabletToolButton { + if flags & PEN_FLAG_BARREL != 0 { + TabletToolButton::Barrel + } else if flags & PEN_FLAG_ERASER != 0 { + // NOTE: this is likely not a correct thing to do, but route + // it that way for now. + TabletToolButton::Other(PEN_FLAG_ERASER as u16) + } else { + TabletToolButton::Contact + } +} diff --git a/winit-win32/src/util.rs b/winit-win32/src/util.rs index 4d1ee935..46316954 100644 --- a/winit-win32/src/util.rs +++ b/winit-win32/src/util.rs @@ -16,7 +16,7 @@ use windows_sys::Win32::UI::HiDpi::{ DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, }; use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow; -use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_TOUCH_INFO}; +use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO}; use windows_sys::Win32::UI::WindowsAndMessaging::{ ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement, GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, @@ -254,6 +254,8 @@ pub type GetPointerDeviceRects = unsafe extern "system" fn( pub type GetPointerTouchInfo = unsafe extern "system" fn(pointer_id: u32, touch_info: *mut POINTER_TOUCH_INFO) -> BOOL; +pub type GetPointerPenInfo = + unsafe extern "system" fn(pointer_id: u32, pen_info: *mut POINTER_PEN_INFO) -> BOOL; pub(crate) static WIN10_BUILD_VERSION: LazyLock> = LazyLock::new(|| { type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; @@ -306,6 +308,8 @@ pub(crate) static GET_POINTER_DEVICE_RECTS: LazyLock> = LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo)); +pub(crate) static GET_POINTER_PEN_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerPenInfo)); pub(crate) fn wrap_device_id(id: u32) -> DeviceId { DeviceId::from_raw(id as i64) diff --git a/winit-win32/src/window.rs b/winit-win32/src/window.rs index 61a9d2ed..e07384be 100644 --- a/winit-win32/src/window.rs +++ b/winit-win32/src/window.rs @@ -1263,6 +1263,7 @@ impl InitData<'_> { _file_drop_handler: file_drop_handler, userdata_removed: Cell::new(false), recurse_depth: Cell::new(0), + last_tablet_down_button_state: Cell::new(0), } } diff --git a/winit-x11/src/atoms.rs b/winit-x11/src/atoms.rs index 59f271d5..e5ac78f4 100644 --- a/winit-x11/src/atoms.rs +++ b/winit-x11/src/atoms.rs @@ -103,7 +103,14 @@ atom_manager! { _NET_SUPPORTED, _NET_SUPPORTING_WM_CHECK, _XEMBED, - _XSETTINGS_SETTINGS + _XSETTINGS_SETTINGS, + + // Stylus Atoms + ABS_X: b"Abs X", + ABS_Y: b"Abs Y", + ABS_PRESSURE: b"Abs Pressure", + ABS_TILT_X: b"Abs Tilt X", + ABS_TILT_Y: b"Abs Tilt Y" } impl Index for Atoms { diff --git a/winit-x11/src/event_loop.rs b/winit-x11/src/event_loop.rs index b1b309ec..cd65c71a 100644 --- a/winit-x11/src/event_loop.rs +++ b/winit-x11/src/event_loop.rs @@ -1006,6 +1006,15 @@ pub struct Device { // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. pub(crate) attachment: c_int, + pub(crate) r#type: DeviceType, +} + +#[derive(Clone, Copy, Debug)] +pub(crate) enum DeviceType { + Mouse, + Touch, + Pen, + Eraser, } #[derive(Debug, Copy, Clone)] @@ -1022,9 +1031,10 @@ pub(crate) enum ScrollOrientation { } impl Device { - pub(crate) fn new(info: &ffi::XIDeviceInfo) -> Self { + pub(crate) fn new(info: &ffi::XIDeviceInfo, atoms: &Atoms) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); + let mut r#type = None; if Device::physical_device(info) { // Identify scroll axes @@ -1041,12 +1051,34 @@ impl Device { }, position: 0.0, })); + } else if ty == ffi::XITouchClass { + r#type = Some(DeviceType::Touch); + } else if r#type.is_none() && ty == ffi::XIValuatorClass { + let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) }; + let atom = info.label as xproto::Atom; + + if atom == atoms[ABS_X] + || atom == atoms[ABS_Y] + || atom == atoms[ABS_PRESSURE] + || atom == atoms[ABS_TILT_X] + || atom == atoms[ABS_TILT_Y] + { + if name.contains("eraser") { + r#type = Some(DeviceType::Eraser); + } else { + r#type = Some(DeviceType::Pen); + } + } } } } - let mut device = - Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment }; + let mut device = Device { + _name: name.into_owned(), + scroll_axes, + attachment: info.attachment, + r#type: r#type.unwrap_or(DeviceType::Mouse), + }; device.reset_scroll_position(info); device } diff --git a/winit-x11/src/event_processor.rs b/winit-x11/src/event_processor.rs index eaf50260..2274f065 100644 --- a/winit-x11/src/event_processor.rs +++ b/winit-x11/src/event_processor.rs @@ -33,8 +33,8 @@ use xkbcommon_dl::xkb_mod_mask_t; use crate::atoms::*; use crate::dnd::{Dnd, DndState}; use crate::event_loop::{ - mkdid, mkwid, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, ScrollOrientation, - ALL_DEVICES, + mkdid, mkwid, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, DeviceType, + ScrollOrientation, ALL_DEVICES, }; use crate::ime::{ImeEvent, ImeEventReceiver, ImeReceiver, ImeRequest}; use crate::util; @@ -323,8 +323,10 @@ impl EventProcessor { pub fn init_device(&self, device: xinput::DeviceId) { let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&self.target.xconn, device as _) { + let atoms = self.target.x_connection().atoms(); + for info in info.iter() { - devices.insert(mkdid(info.deviceid as xinput::DeviceId), Device::new(info)); + devices.insert(mkdid(info.deviceid as xinput::DeviceId), Device::new(info, atoms)); } } } @@ -974,6 +976,15 @@ impl EventProcessor { // Set the timestamp. self.target.xconn.set_timestamp(event.time as xproto::Timestamp); + let Some(DeviceType::Mouse) = self + .devices + .borrow() + .get(&mkdid(event.sourceid as xinput::DeviceId)) + .map(|device| device.r#type) + else { + return; + }; + // Deliver multi-touch events instead of emulated mouse events. if (event.flags & xinput2::XIPointerEmulated) != 0 { return; @@ -1051,6 +1062,15 @@ impl EventProcessor { // Set the timestamp. self.target.xconn.set_timestamp(event.time as xproto::Timestamp); + let Some(DeviceType::Mouse) = self + .devices + .borrow() + .get(&mkdid(event.sourceid as xinput::DeviceId)) + .map(|device| device.r#type) + else { + return; + }; + let device_id = Some(mkdid(event.deviceid as xinput::DeviceId)); let window = event.event as xproto::Window; let window_id = mkwid(window); @@ -1376,7 +1396,6 @@ impl EventProcessor { self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); let did = Some(mkdid(xev.deviceid as xinput::DeviceId)); - let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; let mut value = xev.raw_values; @@ -1401,6 +1420,15 @@ impl EventProcessor { value = unsafe { value.offset(1) }; } + let Some(DeviceType::Mouse) = self + .devices + .borrow() + .get(&mkdid(xev.sourceid as xinput::DeviceId)) + .map(|device| device.r#type) + else { + return; + }; + if let Some(mouse_delta) = mouse_delta.consume() { app.device_event(&self.target, did, DeviceEvent::PointerMotion { delta: mouse_delta }); } diff --git a/winit/docs/ATTRIBUTION.md b/winit/docs/ATTRIBUTION.md index adf1e4fc..dfc6c2a2 100644 --- a/winit/docs/ATTRIBUTION.md +++ b/winit/docs/ATTRIBUTION.md @@ -8,3 +8,10 @@ These files are created by [Mads Marquart](https://github.com/madsmtm) using [draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/). They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license. + +## tool_*.webp + +These files are converted versions of +[W3C Pointer Events spec images](https://github.com/w3c/pointerevents/tree/93938ae7fe0172e2ae7587ad7d7c4fc8562d7153/images) +by [patrickhlauke](https://github.com/patrickhlauke). It is licensed under the +[W3C Software and Document License](https://www.w3.org/copyright/software-license). diff --git a/winit/docs/res/tool_altitude.webp b/winit/docs/res/tool_altitude.webp new file mode 100644 index 00000000..cf70af63 Binary files /dev/null and b/winit/docs/res/tool_altitude.webp differ diff --git a/winit/docs/res/tool_azimuth.webp b/winit/docs/res/tool_azimuth.webp new file mode 100644 index 00000000..66b5a358 Binary files /dev/null and b/winit/docs/res/tool_azimuth.webp differ diff --git a/winit/docs/res/tool_tilt_x.webp b/winit/docs/res/tool_tilt_x.webp new file mode 100644 index 00000000..d4eb72ac Binary files /dev/null and b/winit/docs/res/tool_tilt_x.webp differ diff --git a/winit/docs/res/tool_tilt_y.webp b/winit/docs/res/tool_tilt_y.webp new file mode 100644 index 00000000..6488addc Binary files /dev/null and b/winit/docs/res/tool_tilt_y.webp differ diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index c59decc8..b4bfe7e7 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -87,6 +87,7 @@ changelog entry. - Add `Ime::DeleteSurrounding` to let the input method delete text. - Add more `ImePurpose` values. - Add `ImeHints` to request particular IME behaviour. +- Add Pen input support on Wayland, Windows, and Web via new Pointer event. ### Changed @@ -210,6 +211,7 @@ changelog entry. - Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`. - Use `NamedKey`, `Code` and `Location` from the `keyboard-types` v0.8 crate. - Deprecate `Window::set_ime_allowed`, `Window::set_ime_cursor_area`, and `Window::set_ime_purpose`. +- `Force::normalized()` now takes a `Option` to calculate the perpendicular force. ### Removed @@ -249,6 +251,7 @@ changelog entry. - Remove `NamedKey::Space`, match on `Key::Character(" ")` instead. - Remove `PartialEq` impl for `WindowAttributes`. - `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead. +- Remove `Force::Calibrated::altitude_angle` in favor of `ToolAngle::altitude`. ### Fixed @@ -264,3 +267,4 @@ changelog entry. - On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`. - On Windows, `Window::set_theme` will change the title bar color immediately now. - On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI. +- On Web, device events are emitted regardless of cursor type.