From 1c9e3fe9039b266969cb3c7cb1985efaf6afd6c6 Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 30 Jun 2021 16:58:23 +0200 Subject: [PATCH 1/4] input: Add tablet events --- Cargo.toml | 4 +- anvil/src/input_handler.rs | 4 +- src/backend/input.rs | 61 +++++- src/backend/input/tablet.rs | 361 +++++++++++++++++++++++++++++++++ src/backend/libinput.rs | 45 +++- src/backend/libinput/tablet.rs | 188 +++++++++++++++++ src/backend/winit.rs | 30 ++- 7 files changed, 673 insertions(+), 20 deletions(-) create mode 100644 src/backend/input/tablet.rs create mode 100644 src/backend/libinput/tablet.rs diff --git a/Cargo.toml b/Cargo.toml index b48ca8d..5c96f88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ drm-fourcc = "^2.1.1" drm = { version = "0.4.0", optional = true } drm-ffi = { version = "0.1.0", optional = true } gbm = { version = "0.6.0", optional = true, default-features = false, features = ["drm-support"] } -input = { version = "0.5", default-features = false, optional = true } +input = { version = "0.5", default-features = false, features=["libinput_1_14"], optional = true } image = { version = "0.23.14", default-features = false, optional = true } lazy_static = "1" libc = "0.2.70" @@ -57,7 +57,7 @@ backend_gbm = ["gbm"] backend_egl = ["gl_generator"] backend_libinput = ["input"] backend_session = [] -backend_udev = ["udev"] +backend_udev = ["udev", "input/udev"] backend_session_logind = ["dbus", "backend_session", "pkg-config"] backend_session_elogind = ["backend_session_logind"] backend_session_libseat = ["backend_session", "libseat"] diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 2a2b803..284f62a 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -81,7 +81,7 @@ impl AnvilState { input::MouseButton::Other(b) => b as u32, }; let state = match evt.state() { - input::MouseButtonState::Pressed => { + input::ButtonState::Pressed => { // change the keyboard focus unless the pointer is grabbed if !self.pointer.is_grabbed() { let under = self @@ -93,7 +93,7 @@ impl AnvilState { } wl_pointer::ButtonState::Pressed } - input::MouseButtonState::Released => wl_pointer::ButtonState::Released, + input::ButtonState::Released => wl_pointer::ButtonState::Released, }; self.pointer.button(button, state, serial, evt.time()); } diff --git a/src/backend/input.rs b/src/backend/input.rs index 91091ac..29911da 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -1,6 +1,13 @@ //! Common traits for input backends to receive input from. -use std::error::Error; +use std::{error::Error, path::PathBuf}; + +mod tablet; + +pub use tablet::{ + ProximityState, TabletToolAxisEvent, TabletToolButtonEvent, TabletToolCapabilitys, TabletToolDescriptor, + TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TabletToolType, +}; /// Trait for generic functions every input device does provide pub trait Device: PartialEq + Eq + std::hash::Hash { @@ -12,6 +19,17 @@ pub trait Device: PartialEq + Eq + std::hash::Hash { fn name(&self) -> String; /// Test if this device has a specific capability fn has_capability(&self, capability: DeviceCapability) -> bool; + + /// Get the product ID for this device. + fn id_product(&self) -> Option; + + /// Get the vendor ID for this device. + fn id_vendor(&self) -> Option; + + /// Returns the syspath of the device. + /// + /// The path is an absolute path and includes the sys mount point. + fn syspath(&self) -> Option; } /// Set of input types a device may provide @@ -105,9 +123,9 @@ pub enum MouseButton { Other(u8), } -/// State of a button on a mouse. Either pressed or released +/// State of a button on a pointer device, like mouse or tablet tool. Either pressed or released #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum MouseButtonState { +pub enum ButtonState { /// Button is released Released, /// Button is pressed @@ -119,7 +137,7 @@ pub trait PointerButtonEvent: Event { /// Pressed button of the event fn button(&self) -> MouseButton; /// State of the button - fn state(&self) -> MouseButtonState; + fn state(&self) -> ButtonState; } impl PointerButtonEvent for UnusedEvent { @@ -127,7 +145,7 @@ impl PointerButtonEvent for UnusedEvent { match *self {} } - fn state(&self) -> MouseButtonState { + fn state(&self) -> ButtonState { match *self {} } } @@ -481,6 +499,14 @@ pub trait InputBackend: Sized { type TouchCancelEvent: TouchCancelEvent; /// Type representing touch frame events type TouchFrameEvent: TouchFrameEvent; + /// Type representing axis events on tablet devices + type TabletToolAxisEvent: TabletToolAxisEvent; + /// Type representing proximity events on tablet devices + type TabletToolProximityEvent: TabletToolProximityEvent; + /// Type representing tip events on tablet devices + type TabletToolTipEvent: TabletToolTipEvent; + /// Type representing button events on tablet tool devices + type TabletToolButtonEvent: TabletToolButtonEvent; /// Special events that are custom to this backend type SpecialEvent; @@ -557,6 +583,31 @@ pub enum InputEvent { /// The touch frame event event: B::TouchFrameEvent, }, + + /// A tablet tool axis was emited + TabletToolAxis { + /// The tablet tool axis event + event: B::TabletToolAxisEvent, + }, + + /// A tablet tool proximity was emited + TabletToolProximity { + /// The tablet tool proximity event + event: B::TabletToolProximityEvent, + }, + + /// A tablet tool tip event was emited + TabletToolTip { + /// The tablet tool axis event + event: B::TabletToolTipEvent, + }, + + /// A tablet tool button was pressed or released + TabletToolButton { + /// The pointer button event + event: B::TabletToolButtonEvent, + }, + /// Special event specific of this backend Special(B::SpecialEvent), } diff --git a/src/backend/input/tablet.rs b/src/backend/input/tablet.rs new file mode 100644 index 0000000..4ebcb33 --- /dev/null +++ b/src/backend/input/tablet.rs @@ -0,0 +1,361 @@ +use super::{ButtonState, Event, InputBackend, UnusedEvent}; + +/// Description of physical tablet tool +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct TabletToolDescriptor { + /// The tool type is the high-level type of the tool and usually decides the interaction expected from this tool. + pub tool_type: TabletToolType, + /// Unique hardware serial number of the tool + pub hardware_serial: u64, + /// Hardware id in Wacom’s format + pub hardware_id_wacom: u64, + /// Tool capability + /// Notifies the client of any capabilities of this tool, beyond the main set of x/y axes and tip up/down detection + pub capabilitys: TabletToolCapabilitys, +} + +/// Describes the physical type of a tool. The physical type of a tool generally defines its base usage. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum TabletToolType { + /// A generic pen. + Pen, + /// Eraser + Eraser, + /// A paintbrush-like tool. + Brush, + /// Physical drawing tool, e.g. Wacom Inking Pen + Pencil, + /// An airbrush-like tool. + Airbrush, + /// A mouse bound to the tablet. + Mouse, + /// A mouse tool with a lens. + Lens, + /// A rotary device with positional and rotation data. + Totem, +} + +/// Describes extra capabilities on a tablet. +/// +/// Any tool must provide x and y values, extra axes are device-specific. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct TabletToolCapabilitys { + /// Tilt axes + pub tilt: bool, + /// Pressure axis + pub pressure: bool, + /// Distance axis + pub distance: bool, + /// Z-rotation axis + pub rotation: bool, + /// Slider axis + pub slider: bool, + /// Wheel axis + pub wheel: bool, +} + +/// Tablet tool event +pub trait TabletToolEvent { + /// Get tablet tool that caused this event + fn tool(&self) -> TabletToolDescriptor; + + /// Delta between the last and new pointer device position interpreted as pixel movement + fn delta(&self) -> (f64, f64) { + (self.delta_x(), self.delta_y()) + } + + /// Tool position in the device's native coordinate space + fn position(&self) -> (f64, f64) { + (self.x(), self.y()) + } + + /// Tool position converted into the target coordinate space. + fn position_transformed(&self, coordinate_space: (u32, u32)) -> (f64, f64) { + ( + self.x_transformed(coordinate_space.0), + self.y_transformed(coordinate_space.1), + ) + } + + /// Returns the current tilt along the (X,Y) axis of the tablet's current logical + /// orientation, in degrees off the tablet's z axis. + /// + /// That is, if the tool is perfectly orthogonal to the tablet, the tilt angle is 0. + /// When the top tilts towards the logical top/left of the tablet, the x/y tilt + /// angles are negative, if the top tilts towards the logical bottom/right of the + /// tablet, the x/y tilt angles are positive. + /// + /// If this axis does not exist on the current tool, this function returns (0,0). + fn tilt(&self) -> (f64, f64) { + (self.tilt_x(), self.tilt_y()) + } + + /// Check if the tilt was updated in this event. + fn tilt_has_changed(&self) -> bool { + self.tilt_x_has_changed() || self.tilt_y_has_changed() + } + + /// Delta on the x axis between the last and new pointer device position interpreted as pixel movement + fn delta_x(&self) -> f64; + /// Delta on the y axis between the last and new pointer device position interpreted as pixel movement + fn delta_y(&self) -> f64; + + /// Returns the x coordinate of the tablet tool, in mm from the top left corner of the tablet in its current logical orientation. + fn x(&self) -> f64; + /// Returns the y coordinate of the tablet tool, in mm from the top left corner of the tablet in its current logical orientation. + fn y(&self) -> f64; + + /// Return the current absolute X coordinate of the tablet tool event, transformed to screen coordinates. + fn x_transformed(&self, width: u32) -> f64; + /// Return the current absolute Y coordinate of the tablet tool event, transformed to screen coordinates. + fn y_transformed(&self, height: u32) -> f64; + + /// Returns the current pressure being applied on the tool in use, normalized to the range [0, 1]. + /// + /// If this axis does not exist on the current tool, this function returns 0. + fn distance(&self) -> f64; + + /// Check if the distance axis was updated in this event. + fn distance_has_changed(&self) -> bool; + + /// Returns the current distance from the tablet's sensor, normalized to the range [0, 1] + /// + /// If this axis does not exist on the current tool, this function returns 0. + fn pressure(&self) -> f64; + + /// Check if the pressure axis was updated in this event. + fn pressure_has_changed(&self) -> bool; + + /// Returns the current position of the slider on the tool, normalized to the range + /// [-1, 1]. + /// + /// The logical zero is the neutral position of the slider, or the logical center of + /// the axis. This axis is available on e.g. the Wacom Airbrush. + /// + /// If this axis does not exist on the current tool, this function returns 0. + fn slider_position(&self) -> f64; + + /// Check if the slider axis was updated in this event. + fn slider_has_changed(&self) -> bool; + + /// Returns the current tilt along the X axis of the tablet's current logical + /// orientation, in degrees off the tablet's z axis. + /// + /// That is, if the tool is perfectly orthogonal to the tablet, the tilt angle is 0. + /// When the top tilts towards the logical top/left of the tablet, the x/y tilt + /// angles are negative, if the top tilts towards the logical bottom/right of the + /// tablet, the x/y tilt angles are positive. + /// + /// If this axis does not exist on the current tool, this function returns 0. + fn tilt_x(&self) -> f64; + + /// Check if the tilt x axis was updated in this event. + fn tilt_x_has_changed(&self) -> bool; + + /// Returns the current tilt along the Y axis of the tablet's current logical + /// orientation, in degrees off the tablet's z axis. + /// + /// That is, if the tool is perfectly orthogonal to the tablet, the tilt angle is 0. + /// When the top tilts towards the logical top/left of the tablet, the x/y tilt + /// angles are negative, if the top tilts towards the logical bottom/right of the + /// tablet, the x/y tilt angles are positive. + /// + /// If this axis does not exist on the current tool, this function returns 0. + fn tilt_y(&self) -> f64; + + /// Check if the tilt y axis was updated in this event. + fn tilt_y_has_changed(&self) -> bool; + + /// Returns the current z rotation of the tool in degrees, clockwise from the tool's logical neutral position. + /// + /// For tools of type Mouse and Lens the logical neutral position is pointing to the current logical north of the tablet. For tools of type Brush, the logical neutral position is with the buttons pointing up. + /// + /// If this axis does not exist on the current tool, this function returns 0. + fn rotation(&self) -> f64; + + /// Check if the z-rotation axis was updated in this event. + fn rotation_has_changed(&self) -> bool; + + /// Return the delta for the wheel in degrees. + fn wheel_delta(&self) -> f64; + /// Return the delta for the wheel in discrete steps (e.g. wheel clicks). + fn wheel_delta_discrete(&self) -> i32; + /// Check if the wheel axis was updated in this event. + fn wheel_has_changed(&self) -> bool; +} + +impl TabletToolEvent for UnusedEvent { + fn tool(&self) -> TabletToolDescriptor { + match *self {} + } + fn delta_x(&self) -> f64 { + match *self {} + } + fn delta_y(&self) -> f64 { + match *self {} + } + fn x(&self) -> f64 { + match *self {} + } + fn y(&self) -> f64 { + match *self {} + } + fn x_transformed(&self, _width: u32) -> f64 { + match *self {} + } + fn y_transformed(&self, _height: u32) -> f64 { + match *self {} + } + fn distance(&self) -> f64 { + match *self {} + } + fn distance_has_changed(&self) -> bool { + match *self {} + } + fn pressure(&self) -> f64 { + match *self {} + } + fn pressure_has_changed(&self) -> bool { + match *self {} + } + fn slider_position(&self) -> f64 { + match *self {} + } + fn slider_has_changed(&self) -> bool { + match *self {} + } + fn tilt_x(&self) -> f64 { + match *self {} + } + fn tilt_x_has_changed(&self) -> bool { + match *self {} + } + fn tilt_y(&self) -> f64 { + match *self {} + } + fn tilt_y_has_changed(&self) -> bool { + match *self {} + } + fn rotation(&self) -> f64 { + match *self {} + } + fn rotation_has_changed(&self) -> bool { + match *self {} + } + fn wheel_delta(&self) -> f64 { + match *self {} + } + fn wheel_delta_discrete(&self) -> i32 { + match *self {} + } + fn wheel_has_changed(&self) -> bool { + match *self {} + } +} + +/// Trait for axis tablet tool events. +pub trait TabletToolAxisEvent: TabletToolEvent + Event {} + +impl TabletToolAxisEvent for UnusedEvent {} + +/// The state of proximity for a tool on a device. +/// +/// The proximity of a tool is a binary state signalling whether the tool is within a +/// detectable distance of the tablet device. A tool that is out of proximity cannot +/// generate events. +/// +/// On some hardware a tool goes out of proximity when it ceases to touch the surface. On /// other hardware, the tool is still detectable within a short distance (a few cm) off +/// the surface. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ProximityState { + /// Out of proximity + Out, + /// In proximity + In, +} + +/// Trait for tablet tool proximity events. +pub trait TabletToolProximityEvent: TabletToolEvent + Event { + /// Returns the new proximity state of a tool from a proximity event. + /// + /// Used to check whether or not a tool came in or out of proximity during an + /// `TabletToolProximityEvent`. + /// + /// See [Handling of proximity events](https://wayland.freedesktop.org/libinput/doc/latest/tablet-support.html#tablet-fake-proximity) + /// for recommendations on proximity handling. + fn state(&self) -> ProximityState; +} + +impl TabletToolProximityEvent for UnusedEvent { + fn state(&self) -> ProximityState { + match *self {} + } +} + +/// The tip contact state for a tool on a device. +/// +/// The tip contact state of a tool is a binary state signalling whether the tool is +/// touching the surface of the tablet device. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TabletToolTipState { + /// Not touching the surface + Up, + /// Touching the surface + Down, +} + +/// Signals that a tool has come in contact with the surface of a device with the +/// `DeviceCapability::TabletTool` capability. +/// +/// On devices without distance proximity detection, the `TabletToolTipEvent` is sent +/// immediately after `TabletToolProximityEvent` for the tip down event, and +/// immediately before for the tip up event. +/// +/// The decision when a tip touches the surface is device-dependent and may be +/// derived from pressure data or other means. If the tip state is changed by axes +/// changing state, the `TabletToolTipEvent` includes the changed axes and no +/// additional axis event is sent for this state change. In other words, a caller +/// must look at both `TabletToolAxisEvent` and `TabletToolTipEvent` events to know +/// the current state of the axes. +/// +/// If a button state change occurs at the same time as a tip state change, the order +/// of events is device-dependent. +pub trait TabletToolTipEvent: TabletToolEvent + Event { + /// Returns the new tip state of a tool from a tip event. + /// + /// Used to check whether or not a tool came in contact with the tablet surface or + /// left contact with the tablet surface during an `TabletToolTipEvent`. + fn tip_state(&self) -> TabletToolTipState; +} + +impl TabletToolTipEvent for UnusedEvent { + fn tip_state(&self) -> TabletToolTipState { + match *self {} + } +} + +/// Signals that a tool has changed a logical button state on a device with the DeviceCapability::TabletTool capability. +pub trait TabletToolButtonEvent: TabletToolEvent + Event { + /// Return the button that triggered this event. + fn button(&self) -> u32; + + /// For the button of a TabletToolButtonEvent, return the total number of buttons pressed on all devices on the associated seat after the the event was triggered. + fn seat_button_count(&self) -> u32; + + /// Return the button state of the event. + fn button_state(&self) -> ButtonState; +} + +impl TabletToolButtonEvent for UnusedEvent { + fn button(&self) -> u32 { + match *self {} + } + + fn seat_button_count(&self) -> u32 { + match *self {} + } + + fn button_state(&self) -> ButtonState { + match *self {} + } +} diff --git a/src/backend/libinput.rs b/src/backend/libinput.rs index e4a5114..6ceb91b 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -14,12 +14,15 @@ use std::path::Path; use std::{ io::Error as IoError, os::unix::io::{AsRawFd, RawFd}, + path::PathBuf, }; use calloop::{EventSource, Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory}; use slog::{info, o}; +mod tablet; + // No idea if this is the same across unix platforms // Lets make this linux exclusive for now, once someone tries to build it for // any BSD-like system, they can verify if this is right and make a PR to change this. @@ -93,6 +96,22 @@ impl backend::Device for libinput::Device { fn has_capability(&self, capability: backend::DeviceCapability) -> bool { libinput::Device::has_capability(self, capability.into()) } + + fn id_product(&self) -> Option { + Some(libinput::Device::id_product(self)) + } + + fn id_vendor(&self) -> Option { + Some(libinput::Device::id_vendor(self)) + } + + fn syspath(&self) -> Option { + #[cfg(feature = "udev")] + return unsafe { libinput::Device::udev_device(self) }.map(|d| d.syspath().to_owned()); + + #[cfg(not(feature = "udev"))] + None + } } impl From for libinput::DeviceCapability { @@ -179,7 +198,7 @@ impl backend::PointerButtonEvent for event::pointer::Point } } - fn state(&self) -> backend::MouseButtonState { + fn state(&self) -> backend::ButtonState { self.button_state().into() } } @@ -355,6 +374,10 @@ impl InputBackend for LibinputInputBackend { type TouchMotionEvent = event::touch::TouchMotionEvent; type TouchCancelEvent = event::touch::TouchCancelEvent; type TouchFrameEvent = event::touch::TouchFrameEvent; + type TabletToolAxisEvent = event::tablet_tool::TabletToolAxisEvent; + type TabletToolProximityEvent = event::tablet_tool::TabletToolProximityEvent; + type TabletToolTipEvent = event::tablet_tool::TabletToolTipEvent; + type TabletToolButtonEvent = event::tablet_tool::TabletToolButtonEvent; type SpecialEvent = backend::UnusedEvent; @@ -420,6 +443,20 @@ impl InputBackend for LibinputInputBackend { callback(InputEvent::PointerButton { event: button_event }); } }, + libinput::Event::Tablet(tablet_event) => match tablet_event { + event::TabletToolEvent::Axis(event) => { + callback(InputEvent::TabletToolAxis { event }); + } + event::TabletToolEvent::Proximity(event) => { + callback(InputEvent::TabletToolProximity { event }); + } + event::TabletToolEvent::Tip(event) => { + callback(InputEvent::TabletToolTip { event }); + } + event::TabletToolEvent::Button(event) => { + callback(InputEvent::TabletToolButton { event }); + } + }, _ => {} //FIXME: What to do with the rest. } } @@ -465,11 +502,11 @@ impl From for backend::AxisSource { } } -impl From for backend::MouseButtonState { +impl From for backend::ButtonState { fn from(libinput: event::pointer::ButtonState) -> Self { match libinput { - event::pointer::ButtonState::Pressed => backend::MouseButtonState::Pressed, - event::pointer::ButtonState::Released => backend::MouseButtonState::Released, + event::pointer::ButtonState::Pressed => backend::ButtonState::Pressed, + event::pointer::ButtonState::Released => backend::ButtonState::Released, } } } diff --git a/src/backend/libinput/tablet.rs b/src/backend/libinput/tablet.rs new file mode 100644 index 0000000..fd0744d --- /dev/null +++ b/src/backend/libinput/tablet.rs @@ -0,0 +1,188 @@ +use crate::backend::input::{ + self as backend, TabletToolCapabilitys, TabletToolDescriptor, TabletToolTipState, TabletToolType, +}; + +use input as libinput; +use input::event; +use input::event::{tablet_tool, EventTrait}; + +use super::LibinputInputBackend; + +/// Marker for tablet tool events +pub trait IsTabetEvent: tablet_tool::TabletToolEventTrait + EventTrait {} + +impl IsTabetEvent for tablet_tool::TabletToolAxisEvent {} +impl IsTabetEvent for tablet_tool::TabletToolProximityEvent {} +impl IsTabetEvent for tablet_tool::TabletToolTipEvent {} +impl IsTabetEvent for tablet_tool::TabletToolButtonEvent {} + +impl backend::Event for E +where + E: IsTabetEvent, +{ + fn time(&self) -> u32 { + tablet_tool::TabletToolEventTrait::time(self) + } + + fn device(&self) -> libinput::Device { + event::EventTrait::device(self) + } +} + +impl backend::TabletToolAxisEvent for tablet_tool::TabletToolAxisEvent {} + +impl backend::TabletToolProximityEvent for tablet_tool::TabletToolProximityEvent { + fn state(&self) -> backend::ProximityState { + match tablet_tool::TabletToolProximityEvent::proximity_state(self) { + tablet_tool::ProximityState::In => backend::ProximityState::In, + tablet_tool::ProximityState::Out => backend::ProximityState::Out, + } + } +} + +impl backend::TabletToolTipEvent for tablet_tool::TabletToolTipEvent { + fn tip_state(&self) -> TabletToolTipState { + match tablet_tool::TabletToolTipEvent::tip_state(self) { + tablet_tool::TipState::Up => backend::TabletToolTipState::Up, + tablet_tool::TipState::Down => backend::TabletToolTipState::Down, + } + } +} + +impl backend::TabletToolEvent for E +where + E: IsTabetEvent + event::EventTrait, +{ + fn tool(&self) -> TabletToolDescriptor { + let tool = self.tool(); + + let tool_type = match tool.tool_type() { + tablet_tool::TabletToolType::Pen => TabletToolType::Pen, + tablet_tool::TabletToolType::Eraser => TabletToolType::Eraser, + tablet_tool::TabletToolType::Brush => TabletToolType::Brush, + tablet_tool::TabletToolType::Pencil => TabletToolType::Pencil, + tablet_tool::TabletToolType::Airbrush => TabletToolType::Airbrush, + tablet_tool::TabletToolType::Mouse => TabletToolType::Mouse, + tablet_tool::TabletToolType::Lens => TabletToolType::Lens, + tablet_tool::TabletToolType::Totem => TabletToolType::Totem, + }; + + let hardware_serial = tool.serial(); + let hardware_id_wacom = tool.tool_id(); + + let capabilitys = TabletToolCapabilitys { + tilt: tool.has_tilt(), + pressure: tool.has_pressure(), + distance: tool.has_distance(), + rotation: tool.has_rotation(), + slider: tool.has_slider(), + wheel: tool.has_wheel(), + }; + + TabletToolDescriptor { + tool_type, + hardware_serial, + hardware_id_wacom, + capabilitys, + } + } + + fn delta_x(&self) -> f64 { + tablet_tool::TabletToolEventTrait::dx(self) + } + + fn delta_y(&self) -> f64 { + tablet_tool::TabletToolEventTrait::dy(self) + } + + fn x(&self) -> f64 { + tablet_tool::TabletToolEventTrait::x(self) + } + + fn y(&self) -> f64 { + tablet_tool::TabletToolEventTrait::y(self) + } + + fn x_transformed(&self, width: u32) -> f64 { + tablet_tool::TabletToolEventTrait::x_transformed(self, width) + } + + fn y_transformed(&self, height: u32) -> f64 { + tablet_tool::TabletToolEventTrait::y_transformed(self, height) + } + + fn distance(&self) -> f64 { + tablet_tool::TabletToolEventTrait::distance(self) + } + + fn distance_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::distance_has_changed(self) + } + + fn pressure(&self) -> f64 { + tablet_tool::TabletToolEventTrait::pressure(self) + } + + fn pressure_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::pressure_has_changed(self) + } + + fn slider_position(&self) -> f64 { + tablet_tool::TabletToolEventTrait::slider_position(self) + } + + fn slider_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::slider_has_changed(self) + } + + fn tilt_x(&self) -> f64 { + tablet_tool::TabletToolEventTrait::dx(self) + } + + fn tilt_x_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::tilt_x_has_changed(self) + } + + fn tilt_y(&self) -> f64 { + tablet_tool::TabletToolEventTrait::dy(self) + } + + fn tilt_y_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::tilt_y_has_changed(self) + } + + fn rotation(&self) -> f64 { + tablet_tool::TabletToolEventTrait::rotation(self) + } + + fn rotation_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::rotation_has_changed(self) + } + + fn wheel_delta(&self) -> f64 { + tablet_tool::TabletToolEventTrait::wheel_delta(self) + } + + fn wheel_delta_discrete(&self) -> i32 { + // I have no idea why f64 is returend by this fn, in libinput's api wheel clicks are always i32 + tablet_tool::TabletToolEventTrait::wheel_delta_discrete(self) as i32 + } + + fn wheel_has_changed(&self) -> bool { + tablet_tool::TabletToolEventTrait::wheel_has_changed(self) + } +} + +impl backend::TabletToolButtonEvent for tablet_tool::TabletToolButtonEvent { + fn button(&self) -> u32 { + tablet_tool::TabletToolButtonEvent::button(&self) + } + + fn seat_button_count(&self) -> u32 { + tablet_tool::TabletToolButtonEvent::seat_button_count(&self) + } + + fn button_state(&self) -> backend::ButtonState { + tablet_tool::TabletToolButtonEvent::button_state(&self).into() + } +} diff --git a/src/backend/winit.rs b/src/backend/winit.rs index b560611..4b18f10 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -23,8 +23,8 @@ use crate::backend::egl::display::EGLDisplay; use crate::backend::{ egl::{context::GlAttributes, native, EGLContext, EGLSurface, Error as EGLError}, input::{ - Axis, AxisSource, Device, DeviceCapability, Event as BackendEvent, InputBackend, InputEvent, - KeyState, KeyboardKeyEvent, MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, + Axis, AxisSource, ButtonState, Device, DeviceCapability, Event as BackendEvent, InputBackend, + InputEvent, KeyState, KeyboardKeyEvent, MouseButton, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent, TouchCancelEvent, TouchDownEvent, TouchMotionEvent, TouchSlot, TouchUpEvent, UnusedEvent, }, @@ -33,7 +33,7 @@ use crate::backend::{ Bind, Renderer, Transform, Unbind, }, }; -use std::{cell::RefCell, rc::Rc, time::Instant}; +use std::{cell::RefCell, path::PathBuf, rc::Rc, time::Instant}; use wayland_egl as wegl; use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalSize}, @@ -307,6 +307,18 @@ impl Device for WinitVirtualDevice { DeviceCapability::Keyboard | DeviceCapability::Pointer | DeviceCapability::Touch ) } + + fn id_product(&self) -> Option { + None + } + + fn id_vendor(&self) -> Option { + None + } + + fn syspath(&self) -> Option { + None + } } /// Errors that may happen when driving the event loop of [`WinitInputBackend`] @@ -460,7 +472,7 @@ impl PointerButtonEvent for WinitMouseInputEvent { self.button.into() } - fn state(&self) -> MouseButtonState { + fn state(&self) -> ButtonState { self.state.into() } } @@ -619,6 +631,10 @@ impl InputBackend for WinitInputBackend { type TouchMotionEvent = WinitTouchMovedEvent; type TouchCancelEvent = WinitTouchCancelledEvent; type TouchFrameEvent = UnusedEvent; + type TabletToolAxisEvent = UnusedEvent; + type TabletToolProximityEvent = UnusedEvent; + type TabletToolTipEvent = UnusedEvent; + type TabletToolButtonEvent = UnusedEvent; type SpecialEvent = WinitEvent; @@ -846,11 +862,11 @@ impl From for KeyState { } } -impl From for MouseButtonState { +impl From for ButtonState { fn from(state: ElementState) -> Self { match state { - ElementState::Pressed => MouseButtonState::Pressed, - ElementState::Released => MouseButtonState::Released, + ElementState::Pressed => ButtonState::Pressed, + ElementState::Released => ButtonState::Released, } } } From 90a62aeae7e17ee563b61b9339dcd3b59810126f Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 30 Jun 2021 17:21:54 +0200 Subject: [PATCH 2/4] wayland.tablet: Add tablet manager protocol --- src/wayland/mod.rs | 1 + src/wayland/tablet_manager/mod.rs | 92 +++++ src/wayland/tablet_manager/tablet.rs | 98 +++++ src/wayland/tablet_manager/tablet_seat.rs | 189 +++++++++ src/wayland/tablet_manager/tablet_tool.rs | 477 ++++++++++++++++++++++ 5 files changed, 857 insertions(+) create mode 100644 src/wayland/tablet_manager/mod.rs create mode 100644 src/wayland/tablet_manager/tablet.rs create mode 100644 src/wayland/tablet_manager/tablet_seat.rs create mode 100644 src/wayland/tablet_manager/tablet_tool.rs diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 3cd7b0e..37f2ab6 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -62,6 +62,7 @@ pub mod output; pub mod seat; pub mod shell; pub mod shm; +pub mod tablet_manager; /// A global [`SerialCounter`] for use in your compositor. /// diff --git a/src/wayland/tablet_manager/mod.rs b/src/wayland/tablet_manager/mod.rs new file mode 100644 index 0000000..3dbcd39 --- /dev/null +++ b/src/wayland/tablet_manager/mod.rs @@ -0,0 +1,92 @@ +//! Utilities for graphics tablet support +//! +//! This module provides helpers to handle graphics tablets. +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! # use smithay::wayland::compositor::compositor_init; +//! +//! use smithay::wayland::seat::{Seat}; +//! use smithay::wayland::tablet_manager::{init_tablet_manager_global, TabletSeatTrait, TabletDescriptor}; +//! +//! # let mut display = wayland_server::Display::new(); +//! # compositor_init(&mut display, |_, _| {}, None); +//! // First we nee a reguler seat +//! let (seat, seat_global) = Seat::new( +//! &mut display, +//! "seat-0".into(), +//! None +//! ); +//! +//! // Insert the manager global into your event loop +//! init_tablet_manager_global(&mut display); +//! +//! seat +//! .tablet_seat() // Get TabletSeat asosiated with this seat +//! .add_tablet(&TabletDescriptor { // Add a new tablet to a seat +//! name: "Test".into(), +//! id_product: None, +//! id_vendor: None, +//! syspath: None, +//! }); +//! +//! ``` + +use crate::wayland::seat::Seat; +use wayland_protocols::unstable::tablet::v2::server::zwp_tablet_manager_v2::{self, ZwpTabletManagerV2}; +use wayland_server::{Display, Filter, Global, Main}; + +const MANAGER_VERSION: u32 = 1; + +mod tablet; +mod tablet_seat; +mod tablet_tool; + +pub use tablet::{TabletDescriptor, TabletHandle}; +pub use tablet_seat::TabletSeatHandle; +pub use tablet_tool::TabletToolHandle; + +/// Extends [Seat] with graphic tablet specyfic functionality +pub trait TabletSeatTrait { + /// Get tablet seat asosiated with this seat + fn tablet_seat(&self) -> TabletSeatHandle; +} + +impl TabletSeatTrait for Seat { + fn tablet_seat(&self) -> TabletSeatHandle { + let user_data = self.user_data(); + user_data.insert_if_missing(TabletSeatHandle::default); + user_data.get::().unwrap().clone() + } +} + +/// Initialize a tablet manager global. +pub fn init_tablet_manager_global(display: &mut Display) -> Global { + display.create_global::( + MANAGER_VERSION, + Filter::new( + move |(manager, _version): (Main, u32), _, _| { + manager.quick_assign(|_manager, req, _| match req { + zwp_tablet_manager_v2::Request::GetTabletSeat { tablet_seat, seat } => { + let seat = Seat::from_resource(&seat).unwrap(); + + let user_data = seat.user_data(); + user_data.insert_if_missing(TabletSeatHandle::default); + + let instance = tablet_seat; + let tablet_seat = user_data.get::(); + + if let Some(tablet_seat) = tablet_seat { + tablet_seat.add_instance(instance); + } + } + zwp_tablet_manager_v2::Request::Destroy => { + // Nothing to do + } + _ => {} + }); + }, + ), + ) +} diff --git a/src/wayland/tablet_manager/tablet.rs b/src/wayland/tablet_manager/tablet.rs new file mode 100644 index 0000000..e6ec81f --- /dev/null +++ b/src/wayland/tablet_manager/tablet.rs @@ -0,0 +1,98 @@ +use std::ops::Deref as _; +use std::path::PathBuf; +use std::{cell::RefCell, rc::Rc}; + +use wayland_protocols::unstable::tablet::v2::server::zwp_tablet_seat_v2::ZwpTabletSeatV2; +use wayland_protocols::unstable::tablet::v2::server::zwp_tablet_v2::ZwpTabletV2; +use wayland_server::protocol::wl_surface::WlSurface; +use wayland_server::Filter; + +use crate::backend::input::Device; + +/// Description of graphics tablet device +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct TabletDescriptor { + /// Tablet device name + pub name: String, + /// Tablet device USB product id + pub id_product: Option, + /// Tablet device USB vendor id + pub id_vendor: Option, + /// Path to the device + pub syspath: Option, +} + +impl From for TabletDescriptor { + fn from(device: D) -> Self { + TabletDescriptor { + name: device.name(), + syspath: device.syspath(), + id_product: device.id_product(), + id_vendor: device.id_vendor(), + } + } +} + +#[derive(Debug, Default)] +struct Tablet { + instances: Vec, +} + +/// Handle to a tablet device +/// +/// Tablet represents one graphics tablet device +#[derive(Debug, Default, Clone)] +pub struct TabletHandle { + inner: Rc>, +} + +impl TabletHandle { + pub(super) fn new_instance(&mut self, seat: &ZwpTabletSeatV2, tablet: &TabletDescriptor) { + if let Some(client) = seat.as_ref().client() { + let wl_tablet = client + .create_resource::(seat.as_ref().version()) + .unwrap(); + + wl_tablet.quick_assign(|_, _req, _| {}); + + let inner = self.inner.clone(); + wl_tablet.assign_destructor(Filter::new(move |instance: ZwpTabletV2, _, _| { + inner + .borrow_mut() + .instances + .retain(|i| !i.as_ref().equals(&instance.as_ref())); + })); + + seat.tablet_added(&wl_tablet); + + wl_tablet.name(tablet.name.clone()); + + if let (Some(id_product), Some(id_vendor)) = (tablet.id_product, tablet.id_vendor) { + wl_tablet.id(id_product, id_vendor); + } + + if let Some(syspath) = tablet.syspath.as_ref().and_then(|p| p.to_str()) { + wl_tablet.path(syspath.to_owned()); + } + + wl_tablet.done(); + + self.inner.borrow_mut().instances.push(wl_tablet.deref().clone()); + } + } + + pub(super) fn with_focused_tablet(&self, focus: &WlSurface, cb: F) + where + F: Fn(&ZwpTabletV2), + { + if let Some(instance) = self + .inner + .borrow() + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.as_ref())) + { + cb(instance); + } + } +} diff --git a/src/wayland/tablet_manager/tablet_seat.rs b/src/wayland/tablet_manager/tablet_seat.rs new file mode 100644 index 0000000..94683be --- /dev/null +++ b/src/wayland/tablet_manager/tablet_seat.rs @@ -0,0 +1,189 @@ +use wayland_protocols::unstable::tablet::v2::server::zwp_tablet_seat_v2::ZwpTabletSeatV2; +use wayland_server::{Filter, Main}; + +use crate::backend::input::TabletToolDescriptor; +use crate::wayland::seat::CursorImageStatus; + +use super::tablet::{TabletDescriptor, TabletHandle}; +use super::tablet_tool::TabletToolHandle; + +use std::convert::AsRef; +use std::fmt; +use std::ops::Deref as _; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +#[derive(Default)] +struct TabletSeat { + instances: Vec, + tablets: HashMap, + tools: HashMap, + + cursor_callback: Option>, +} + +impl fmt::Debug for TabletSeat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TabletSeat") + .field("instances", &self.instances) + .field("tablets", &self.tablets) + .field("tools", &self.tools) + .field( + "cursor_callback", + if self.cursor_callback.is_some() { + &"Some(...)" + } else { + &"None" + }, + ) + .finish() + } +} + +/// Handle to a tablet seat +/// +/// TabletSeat extends `Seat` with graphic tablet specyfic functionality +/// +/// TabletSeatHandle can be used to advertise avalible graphics tablets and tools to wayland clients +#[derive(Default, Debug, Clone)] +pub struct TabletSeatHandle { + inner: Rc>, +} + +impl TabletSeatHandle { + pub(super) fn add_instance(&self, seat: Main) { + let mut inner = self.inner.borrow_mut(); + + // Notify new instance about avaluble tablets + for (desc, tablet) in inner.tablets.iter_mut() { + tablet.new_instance(seat.deref(), desc); + } + + // Notify new instance about avalible tools + for (desc, tool) in inner.tools.iter_mut() { + let inner = self.inner.clone(); + tool.new_instance(seat.deref(), desc, move |desc, status| { + if let Some(ref mut cursor_callback) = inner.borrow_mut().cursor_callback { + cursor_callback(desc, status); + } + }); + } + + inner.instances.push(seat.deref().clone()); + + let inner = self.inner.clone(); + seat.assign_destructor(Filter::new(move |seat: ZwpTabletSeatV2, _, _| { + inner + .borrow_mut() + .instances + .retain(|i| !i.as_ref().equals(&seat.as_ref())); + })); + } + + /// Add a callback to SetCursor event + pub fn on_cursor_surface(&mut self, cb: F) + where + F: FnMut(&TabletToolDescriptor, CursorImageStatus) + 'static, + { + self.inner.borrow_mut().cursor_callback = Some(Box::new(cb)); + } + + /// Add a new tablet to a seat. + /// + /// You can either add tablet on [LibinputEvent::NewDevice](crate::backend::libinput::LibinputEvent::NewDevice) event, + /// or you can add tablet based on tool event, then clients will not know about devices that are not being used + /// + /// Returns new [TabletHandle] if tablet was not know by this seat, if tablet was allready know it returns exsisting handle. + pub fn add_tablet(&self, tablet_desc: &TabletDescriptor) -> TabletHandle { + let inner = &mut *self.inner.borrow_mut(); + + let tablets = &mut inner.tablets; + let instances = &inner.instances; + + let tablet = tablets.entry(tablet_desc.clone()).or_insert_with(|| { + let mut tablet = TabletHandle::default(); + // Create new tablet instance for every seat instance + for seat in instances.iter() { + tablet.new_instance(seat, tablet_desc); + } + tablet + }); + + tablet.clone() + } + + /// Get a handle to a tablet + pub fn get_tablet(&self, tablet_desc: &TabletDescriptor) -> Option { + self.inner.borrow().tablets.get(tablet_desc).cloned() + } + + /// Count all tablet devices + pub fn count_tablets(&self) -> usize { + self.inner.borrow_mut().tablets.len() + } + + /// Remove tablet device + /// + /// Called when tablet is no longer avalible + /// For example on [LibinputEvent::RemovedDevice](crate::backend::libinput::LibinputEvent::RemovedDevice) event. + pub fn remove_tablet(&self, tablet_desc: &TabletDescriptor) { + self.inner.borrow_mut().tablets.remove(tablet_desc); + } + + /// Remove all tablet devices + pub fn clear_tablets(&self) { + self.inner.borrow_mut().tablets.clear(); + } + + /// Add a new tool to a seat. + /// + /// Tool is usually added on [TabletToolProximityEvent](crate::backend::input::InputEvent::TabletToolProximity) event. + /// + /// Returns new [TabletToolHandle] if tool was not know by this seat, if tool was allready know it returns exsisting handle, + /// it allows you to send tool input events to clients. + pub fn add_tool(&self, tool_desc: &TabletToolDescriptor) -> TabletToolHandle { + let inner = &mut *self.inner.borrow_mut(); + + let tools = &mut inner.tools; + let instances = &inner.instances; + + let tool = tools.entry(tool_desc.clone()).or_insert_with(|| { + let mut tool = TabletToolHandle::default(); + // Create new tool instance for every seat instance + for seat in instances.iter() { + let inner = self.inner.clone(); + tool.new_instance(seat.deref(), tool_desc, move |desc, status| { + if let Some(ref mut cursor_callback) = inner.borrow_mut().cursor_callback { + cursor_callback(desc, status); + } + }); + } + tool + }); + + tool.clone() + } + + /// Get a handle to a tablet tool + pub fn get_tool(&self, tool_desc: &TabletToolDescriptor) -> Option { + self.inner.borrow().tools.get(tool_desc).cloned() + } + + /// Count all tablet tool devices + pub fn count_tools(&self) -> usize { + self.inner.borrow_mut().tools.len() + } + + /// Remove tablet tool device + /// + /// Policy of tool removal is a compositor-specific. + /// + /// One posible policy would be to remove a tool when all tablets the tool was used on are removed. + pub fn remove_tool(&self, tool_desc: &TabletToolDescriptor) { + self.inner.borrow_mut().tools.remove(tool_desc); + } + + /// Remove all tablet tool devices + pub fn clear_tools(&self) { + self.inner.borrow_mut().tools.clear(); + } +} diff --git a/src/wayland/tablet_manager/tablet_tool.rs b/src/wayland/tablet_manager/tablet_tool.rs new file mode 100644 index 0000000..ffba6a3 --- /dev/null +++ b/src/wayland/tablet_manager/tablet_tool.rs @@ -0,0 +1,477 @@ +use std::ops::Deref as _; +use std::sync::Mutex; +use std::{cell::RefCell, rc::Rc}; + +use crate::backend::input::{ButtonState, TabletToolDescriptor, TabletToolType}; +use crate::wayland::seat::{CursorImageAttributes, CursorImageStatus}; +use wayland_protocols::unstable::tablet::v2::server::{ + zwp_tablet_seat_v2::ZwpTabletSeatV2, + zwp_tablet_tool_v2::{self, ZwpTabletToolV2}, +}; +use wayland_server::protocol::wl_surface::WlSurface; +use wayland_server::Filter; + +use crate::wayland::{compositor, Serial}; + +use super::tablet::TabletHandle; + +static CURSOR_IMAGE_ROLE: &str = "cursor_image"; + +#[derive(Debug, Default)] +struct TabletTool { + instances: Vec, + focus: Option, + + is_down: bool, + + pending_pressure: Option, + pending_distance: Option, + pending_tilt: Option<(f64, f64)>, + pending_slider: Option, + pending_rotation: Option, + pending_wheel: Option<(f64, i32)>, +} + +impl TabletTool { + fn proximity_in( + &mut self, + (x, y): (f64, f64), + (focus, (sx, sy)): (WlSurface, (f64, f64)), + tablet: &TabletHandle, + serial: Serial, + time: u32, + ) { + let wl_tool = self + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.as_ref())); + + if let Some(wl_tool) = wl_tool { + tablet.with_focused_tablet(&focus, |wl_tablet| { + wl_tool.proximity_in(serial.into(), wl_tablet, &focus); + // proximity_in has to be followed by motion event (required by protocol) + wl_tool.motion(x - sx, y - sy); + wl_tool.frame(time); + }); + } + + self.focus = Some(focus.clone()); + } + + fn proximity_out(&mut self, time: u32) { + if let Some(ref focus) = self.focus { + let wl_tool = self + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.as_ref())); + + if let Some(wl_tool) = wl_tool { + if self.is_down { + wl_tool.up(); + self.is_down = false; + } + wl_tool.proximity_out(); + wl_tool.frame(time); + } + } + + self.focus = None; + } + + fn tip_down(&mut self, serial: Serial, time: u32) { + if let Some(ref focus) = self.focus { + if let Some(wl_tool) = self + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.as_ref())) + { + if !self.is_down { + wl_tool.down(serial.into()); + wl_tool.frame(time); + } + } + } + + self.is_down = true; + } + + fn tip_up(&mut self, time: u32) { + if let Some(ref focus) = self.focus { + if let Some(wl_tool) = self + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.as_ref())) + { + if self.is_down { + wl_tool.up(); + wl_tool.frame(time); + } + } + } + + self.is_down = false; + } + + fn motion( + &mut self, + pos: (f64, f64), + focus: Option<(WlSurface, (f64, f64))>, + tablet: &TabletHandle, + serial: Serial, + time: u32, + ) { + match (focus, self.focus.as_ref()) { + (Some(focus), Some(prev_focus)) => { + if &focus.0 == prev_focus { + if let Some(wl_tool) = self + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.0.as_ref())) + { + let (x, y) = pos; + let (sx, sy) = focus.1; + wl_tool.motion(x - sx, y - sy); + + if let Some(pressure) = self.pending_pressure.take() { + wl_tool.pressure((pressure * 65535.0).round() as u32); + } + + if let Some(distance) = self.pending_distance.take() { + wl_tool.distance((distance * 65535.0).round() as u32); + } + + if let Some((x, y)) = self.pending_tilt.take() { + wl_tool.tilt(x, y); + } + + if let Some(slider) = self.pending_slider.take() { + wl_tool.slider((slider * 65535.0).round() as i32); + } + + if let Some(rotation) = self.pending_rotation.take() { + wl_tool.rotation(rotation); + } + + if let Some((degrees, clicks)) = self.pending_wheel.take() { + wl_tool.wheel(degrees, clicks) + } + + wl_tool.frame(time); + } + } else { + // If surface has changed + + // Unfocus previous surface + self.proximity_out(time); + // Focuss a new one + self.proximity_in(pos, focus, tablet, serial, time) + } + } + // New surface in focus + (Some(focus), None) => self.proximity_in(pos, focus, tablet, serial, time), + // No surface in focus + (None, _) => self.proximity_out(time), + } + } + + fn pressure(&mut self, pressure: f64) { + self.pending_pressure = Some(pressure); + } + + fn distance(&mut self, distance: f64) { + self.pending_distance = Some(distance); + } + + fn tilt(&mut self, tilt: (f64, f64)) { + self.pending_tilt = Some(tilt); + } + + fn rotation(&mut self, rotation: f64) { + self.pending_rotation = Some(rotation); + } + + fn slider_position(&mut self, slider: f64) { + self.pending_slider = Some(slider); + } + + fn wheel(&mut self, degrees: f64, clicks: i32) { + self.pending_wheel = Some((degrees, clicks)); + } + + /// Sent whenever a button on the tool is pressed or released. + fn button(&self, button: u32, state: ButtonState, serial: Serial, time: u32) { + if let Some(ref focus) = self.focus { + if let Some(wl_tool) = self + .instances + .iter() + .find(|i| i.as_ref().same_client_as(focus.as_ref())) + { + wl_tool.button(serial.into(), button, state.into()); + wl_tool.frame(time); + } + } + } +} + +impl Drop for TabletTool { + fn drop(&mut self) { + for instance in self.instances.iter() { + // This event is sent when the tool is removed from the system and will send no further events. + instance.removed(); + } + } +} + +/// Handle to a tablet tool device +/// +/// TabletTool represents a physical tool that has been, or is currently in use with a tablet in seat. +/// +/// A TabletTool relation to a physical tool depends on the tablet's ability to report serial numbers. If the tablet supports this capability, then the object represents a specific physical tool and can be identified even when used on multiple tablets. +#[derive(Debug, Default, Clone)] +pub struct TabletToolHandle { + inner: Rc>, +} + +impl TabletToolHandle { + pub(super) fn new_instance(&mut self, seat: &ZwpTabletSeatV2, tool: &TabletToolDescriptor, mut cb: F) + where + F: FnMut(&TabletToolDescriptor, CursorImageStatus) + 'static, + { + if let Some(client) = seat.as_ref().client() { + let wl_tool = client + .create_resource::(seat.as_ref().version()) + .unwrap(); + + let desc = tool.clone(); + let inner = self.inner.clone(); + wl_tool.quick_assign(move |tool, req, _| { + use wayland_protocols::unstable::tablet::v2::server::zwp_tablet_tool_v2::Request; + match req { + Request::SetCursor { + surface, + hotspot_x, + hotspot_y, + .. + } => { + let inner = inner.borrow(); + + if let Some(ref focus) = inner.focus { + if focus.as_ref().same_client_as(&tool.as_ref()) { + if let Some(surface) = surface { + // tolerate re-using the same surface + if compositor::give_role(&surface, CURSOR_IMAGE_ROLE).is_err() + && compositor::get_role(&surface) != Some(CURSOR_IMAGE_ROLE) + { + tool.as_ref().post_error( + zwp_tablet_tool_v2::Error::Role as u32, + "Given wl_surface has another role.".into(), + ); + return; + } + + compositor::with_states(&surface, |states| { + states.data_map.insert_if_missing_threadsafe(|| { + Mutex::new(CursorImageAttributes { hotspot: (0, 0) }) + }); + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot = (hotspot_x, hotspot_y); + }) + .unwrap(); + + cb(&desc, CursorImageStatus::Image(surface)); + } else { + cb(&desc, CursorImageStatus::Hidden); + }; + } + } + } + Request::Destroy => { + // Handled by our destructor + } + _ => {} + } + }); + + let inner = self.inner.clone(); + wl_tool.assign_destructor(Filter::new(move |instance: ZwpTabletToolV2, _, _| { + inner + .borrow_mut() + .instances + .retain(|i| !i.as_ref().equals(&instance.as_ref())); + })); + + seat.tool_added(&wl_tool); + + wl_tool._type(tool.tool_type.into()); + + let high: u32 = (tool.hardware_serial >> 16) as u32; + let low: u32 = tool.hardware_serial as u32; + + wl_tool.hardware_serial(high, low); + + let high: u32 = (tool.hardware_id_wacom >> 16) as u32; + let low: u32 = tool.hardware_id_wacom as u32; + wl_tool.hardware_id_wacom(high, low); + + if tool.capabilitys.pressure { + wl_tool.capability(zwp_tablet_tool_v2::Capability::Pressure); + } + + if tool.capabilitys.distance { + wl_tool.capability(zwp_tablet_tool_v2::Capability::Distance); + } + + if tool.capabilitys.tilt { + wl_tool.capability(zwp_tablet_tool_v2::Capability::Tilt); + } + + if tool.capabilitys.slider { + wl_tool.capability(zwp_tablet_tool_v2::Capability::Slider); + } + + if tool.capabilitys.rotation { + wl_tool.capability(zwp_tablet_tool_v2::Capability::Rotation); + } + + if tool.capabilitys.wheel { + wl_tool.capability(zwp_tablet_tool_v2::Capability::Wheel); + } + + wl_tool.done(); + self.inner.borrow_mut().instances.push(wl_tool.deref().clone()); + } + } + + /// Notifify that this tool is focused on a certain surface. + /// + /// You provide the location of the tool, in the form of: + /// + /// - The coordinates of the tool in the global compositor space + /// - The surface on top of which the tool is, and the coordinates of its + /// origin in the global compositor space. + pub fn proximity_in( + &self, + pos: (f64, f64), + focus: (WlSurface, (f64, f64)), + tablet: &TabletHandle, + serial: Serial, + time: u32, + ) { + self.inner + .borrow_mut() + .proximity_in(pos, focus, tablet, serial, time) + } + + /// Notifify that this tool has left proximity. + pub fn proximity_out(&self, time: u32) { + self.inner.borrow_mut().proximity_out(time); + } + + /// Tablet tool is making contact + pub fn tip_down(&self, serial: Serial, time: u32) { + self.inner.borrow_mut().tip_down(serial, time); + } + + /// Tablet tool is no longer making contact + pub fn tip_up(&self, time: u32) { + self.inner.borrow_mut().tip_up(time); + } + + /// Notify that the tool moved + /// + /// You provide the new location of the tool, in the form of: + /// + /// - The coordinates of the tool in the global compositor space + /// - The surface on top of which the tool is, and the coordinates of its + /// origin in the global compositor space (or `None` of the pointer is not + /// on top of a client surface). + /// + /// This will internally take care of notifying the appropriate client objects + /// of proximity_in/proximity_out events. + pub fn motion( + &self, + pos: (f64, f64), + focus: Option<(WlSurface, (f64, f64))>, + tablet: &TabletHandle, + serial: Serial, + time: u32, + ) { + self.inner.borrow_mut().motion(pos, focus, tablet, serial, time) + } + + /// Queue tool pressure update + /// + /// It will be sent alongside next motion event + pub fn pressure(&self, pressure: f64) { + self.inner.borrow_mut().pressure(pressure); + } + + /// Queue tool distance update + /// + /// It will be sent alongside next motion event + pub fn distance(&self, distance: f64) { + self.inner.borrow_mut().distance(distance); + } + + /// Queue tool tilt update + /// + /// It will be sent alongside next motion event + pub fn tilt(&self, tilt: (f64, f64)) { + self.inner.borrow_mut().tilt(tilt); + } + + /// Queue tool rotation update + /// + /// It will be sent alongside next motion event + pub fn rotation(&self, rotation: f64) { + self.inner.borrow_mut().rotation(rotation); + } + + /// Queue tool slider update + /// + /// It will be sent alongside next motion event + pub fn slider_position(&self, slider: f64) { + self.inner.borrow_mut().slider_position(slider); + } + + /// Queue tool wheel update + /// + /// It will be sent alongside next motion event + pub fn wheel(&self, degrees: f64, clicks: i32) { + self.inner.borrow_mut().wheel(degrees, clicks); + } + + /// Button on the tool was pressed or released + pub fn button(&self, button: u32, state: ButtonState, serial: Serial, time: u32) { + self.inner.borrow().button(button, state, serial, time); + } +} + +impl From for zwp_tablet_tool_v2::Type { + fn from(from: TabletToolType) -> zwp_tablet_tool_v2::Type { + match from { + TabletToolType::Pen => zwp_tablet_tool_v2::Type::Pen, + TabletToolType::Eraser => zwp_tablet_tool_v2::Type::Eraser, + TabletToolType::Brush => zwp_tablet_tool_v2::Type::Brush, + TabletToolType::Pencil => zwp_tablet_tool_v2::Type::Pencil, + TabletToolType::Airbrush => zwp_tablet_tool_v2::Type::Airbrush, + TabletToolType::Mouse => zwp_tablet_tool_v2::Type::Mouse, + TabletToolType::Lens => zwp_tablet_tool_v2::Type::Lens, + _ => zwp_tablet_tool_v2::Type::Pen, + } + } +} + +impl From for zwp_tablet_tool_v2::ButtonState { + fn from(from: ButtonState) -> zwp_tablet_tool_v2::ButtonState { + match from { + ButtonState::Pressed => zwp_tablet_tool_v2::ButtonState::Pressed, + ButtonState::Released => zwp_tablet_tool_v2::ButtonState::Released, + } + } +} From c557adc6a6472ba6d2d332278830d7b1ca777adf Mon Sep 17 00:00:00 2001 From: Poly Date: Wed, 30 Jun 2021 16:58:59 +0200 Subject: [PATCH 3/4] anvil: Add tablet support --- anvil/src/input_handler.rs | 132 ++++++++++++++++++++++++++++++++++++- anvil/src/state.rs | 11 ++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 284f62a..7a21b0f 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -8,13 +8,15 @@ use crate::AnvilState; use smithay::{ backend::input::{ - self, Event, InputBackend, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, - PointerButtonEvent, + self, Device, DeviceCapability, Event, InputBackend, InputEvent, KeyState, KeyboardKeyEvent, + PointerAxisEvent, PointerButtonEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent, + TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, }, reexports::wayland_server::protocol::wl_pointer, wayland::{ output::Mode, seat::{keysyms as xkb, AxisFrame, Keysym, ModifiersState}, + tablet_manager::TabletSeatTrait, SERIAL_COUNTER as SCOUNTER, }, }; @@ -238,6 +240,27 @@ impl AnvilState { InputEvent::PointerMotion { event, .. } => self.on_pointer_move::(event), InputEvent::PointerButton { event, .. } => self.on_pointer_button::(event), InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::(event), + InputEvent::TabletToolAxis { event, .. } => self.on_tablet_tool_axis::(event), + InputEvent::TabletToolProximity { event, .. } => self.on_tablet_tool_proximity::(event), + InputEvent::TabletToolTip { event, .. } => self.on_tablet_tool_tip::(event), + InputEvent::TabletToolButton { event, .. } => self.on_tablet_button::(event), + InputEvent::DeviceAdded { device } => { + if device.has_capability(DeviceCapability::TabletTool) { + self.seat.tablet_seat().add_tablet(&device.into()); + } + } + InputEvent::DeviceRemoved { device } => { + if device.has_capability(DeviceCapability::TabletTool) { + let tablet_seat = self.seat.tablet_seat(); + + tablet_seat.remove_tablet(&device.into()); + + // If there are no tablets in seat we can remove all tools + if tablet_seat.count_tablets() == 0 { + tablet_seat.clear_tools(); + } + } + } _ => { // other events are not handled in anvil (yet) } @@ -259,6 +282,111 @@ impl AnvilState { .motion(self.pointer_location, under, serial, evt.time()); } + fn on_tablet_tool_axis(&mut self, evt: B::TabletToolAxisEvent) { + if let Some(out) = self.backend_data.output_map.first() { + self.pointer_location = evt.position_transformed(out.size); + + let under = self.window_map.borrow().get_surface_under(self.pointer_location); + let tablet = self.seat.tablet_seat().get_tablet(&evt.device().into()); + let tool = self.seat.tablet_seat().get_tool(&evt.tool()); + + if let (Some(tablet), Some(tool)) = (tablet, tool) { + if evt.pressure_has_changed() { + tool.pressure(evt.pressure()); + } + if evt.distance_has_changed() { + tool.distance(evt.distance()); + } + if evt.tilt_has_changed() { + tool.tilt(evt.tilt()); + } + if evt.slider_has_changed() { + tool.slider_position(evt.slider_position()); + } + if evt.rotation_has_changed() { + tool.rotation(evt.rotation()); + } + if evt.wheel_has_changed() { + tool.wheel(evt.wheel_delta(), evt.wheel_delta_discrete()); + } + + tool.motion( + self.pointer_location, + under, + &tablet, + SCOUNTER.next_serial(), + evt.time(), + ); + } + } + } + + fn on_tablet_tool_proximity(&mut self, evt: B::TabletToolProximityEvent) { + if let Some(out) = self.backend_data.output_map.first() { + let tool = evt.tool(); + self.seat.tablet_seat().add_tool(&tool); + + self.pointer_location = evt.position_transformed(out.size); + + let under = self.window_map.borrow().get_surface_under(self.pointer_location); + let tablet = self.seat.tablet_seat().get_tablet(&evt.device().into()); + let tool = self.seat.tablet_seat().get_tool(&tool); + + if let (Some(under), Some(tablet), Some(tool)) = (under, tablet, tool) { + match evt.state() { + ProximityState::In => tool.proximity_in( + self.pointer_location, + under, + &tablet, + SCOUNTER.next_serial(), + evt.time(), + ), + ProximityState::Out => tool.proximity_out(evt.time()), + } + } + } + } + + fn on_tablet_tool_tip(&mut self, evt: B::TabletToolTipEvent) { + let tool = self.seat.tablet_seat().get_tool(&evt.tool()); + + if let Some(tool) = tool { + match evt.tip_state() { + TabletToolTipState::Down => { + tool.tip_down(SCOUNTER.next_serial(), evt.time()); + + // change the keyboard focus unless the pointer is grabbed + if !self.pointer.is_grabbed() { + let under = self + .window_map + .borrow_mut() + .get_surface_and_bring_to_top(self.pointer_location); + + let serial = SCOUNTER.next_serial(); + self.keyboard + .set_focus(under.as_ref().map(|&(ref s, _)| s), serial); + } + } + TabletToolTipState::Up => { + tool.tip_up(evt.time()); + } + } + } + } + + fn on_tablet_button(&mut self, evt: B::TabletToolButtonEvent) { + let tool = self.seat.tablet_seat().get_tool(&evt.tool()); + + if let Some(tool) = tool { + tool.button( + evt.button(), + evt.button_state(), + SCOUNTER.next_serial(), + evt.time(), + ); + } + } + fn clamp_coords(&self, pos: (f64, f64)) -> (f64, f64) { if self.output_map.borrow().is_empty() { return pos; diff --git a/anvil/src/state.rs b/anvil/src/state.rs index b197677..fc7068d 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -16,6 +16,7 @@ use smithay::{ data_device::{default_action_chooser, init_data_device, set_data_device_focus, DataDeviceEvent}, seat::{CursorImageStatus, KeyboardHandle, PointerHandle, Seat, XkbConfig}, shm::init_shm_global, + tablet_manager::{init_tablet_manager_global, TabletSeatTrait}, }, }; @@ -41,6 +42,7 @@ pub struct AnvilState { pub pointer_location: (f64, f64), pub cursor_status: Arc>, pub seat_name: String, + pub seat: Seat, pub start_time: std::time::Instant, // things we must keep alive #[cfg(feature = "xwayland")] @@ -121,6 +123,14 @@ impl AnvilState { *cursor_status2.lock().unwrap() = new_status }); + init_tablet_manager_global(&mut display.borrow_mut()); + + let cursor_status3 = cursor_status.clone(); + seat.tablet_seat().on_cursor_surface(move |_tool, new_status| { + // TODO: tablet tools should have their own cursors + *cursor_status3.lock().unwrap() = new_status; + }); + let keyboard = seat .add_keyboard(XkbConfig::default(), 200, 25, |seat, focus| { set_data_device_focus(seat, focus.and_then(|s| s.as_ref().client())) @@ -159,6 +169,7 @@ impl AnvilState { cursor_status, pointer_location: (0.0, 0.0), seat_name, + seat, start_time: std::time::Instant::now(), #[cfg(feature = "xwayland")] xwayland, From 07743faad2cbe2cdefbed51b7441975d824b9294 Mon Sep 17 00:00:00 2001 From: Poly Date: Mon, 5 Jul 2021 23:57:25 +0200 Subject: [PATCH 4/4] Apply suggestions from code review & Rebase --- anvil/src/input_handler.rs | 130 ++++++++++++---------- src/backend/input.rs | 7 +- src/backend/input/tablet.rs | 49 ++++---- src/backend/libinput.rs | 11 +- src/backend/libinput/tablet.rs | 30 ++--- src/backend/winit.rs | 6 +- src/wayland/tablet_manager/mod.rs | 9 +- src/wayland/tablet_manager/tablet.rs | 15 +-- src/wayland/tablet_manager/tablet_seat.rs | 10 +- src/wayland/tablet_manager/tablet_tool.rs | 14 +-- 10 files changed, 145 insertions(+), 136 deletions(-) diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 7a21b0f..07a07c0 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -16,7 +16,7 @@ use smithay::{ wayland::{ output::Mode, seat::{keysyms as xkb, AxisFrame, Keysym, ModifiersState}, - tablet_manager::TabletSeatTrait, + tablet_manager::{TabletDescriptor, TabletSeatTrait}, SERIAL_COUNTER as SCOUNTER, }, }; @@ -246,14 +246,16 @@ impl AnvilState { InputEvent::TabletToolButton { event, .. } => self.on_tablet_button::(event), InputEvent::DeviceAdded { device } => { if device.has_capability(DeviceCapability::TabletTool) { - self.seat.tablet_seat().add_tablet(&device.into()); + self.seat + .tablet_seat() + .add_tablet(&TabletDescriptor::from(&device)); } } InputEvent::DeviceRemoved { device } => { if device.has_capability(DeviceCapability::TabletTool) { let tablet_seat = self.seat.tablet_seat(); - tablet_seat.remove_tablet(&device.into()); + tablet_seat.remove_tablet(&TabletDescriptor::from(&device)); // If there are no tablets in seat we can remove all tools if tablet_seat.count_tablets() == 0 { @@ -283,68 +285,84 @@ impl AnvilState { } fn on_tablet_tool_axis(&mut self, evt: B::TabletToolAxisEvent) { - if let Some(out) = self.backend_data.output_map.first() { - self.pointer_location = evt.position_transformed(out.size); + let output_map = self.output_map.borrow(); + let pointer_location = &mut self.pointer_location; + let tablet_seat = self.seat.tablet_seat(); + let window_map = self.window_map.borrow(); - let under = self.window_map.borrow().get_surface_under(self.pointer_location); - let tablet = self.seat.tablet_seat().get_tablet(&evt.device().into()); - let tool = self.seat.tablet_seat().get_tool(&evt.tool()); + output_map + .with_primary(|_, rect| { + pointer_location.0 = evt.x_transformed(rect.width as u32) + rect.x as f64; + pointer_location.1 = evt.y_transformed(rect.height as u32) + rect.y as f64; - if let (Some(tablet), Some(tool)) = (tablet, tool) { - if evt.pressure_has_changed() { - tool.pressure(evt.pressure()); - } - if evt.distance_has_changed() { - tool.distance(evt.distance()); - } - if evt.tilt_has_changed() { - tool.tilt(evt.tilt()); - } - if evt.slider_has_changed() { - tool.slider_position(evt.slider_position()); - } - if evt.rotation_has_changed() { - tool.rotation(evt.rotation()); - } - if evt.wheel_has_changed() { - tool.wheel(evt.wheel_delta(), evt.wheel_delta_discrete()); - } + let under = window_map.get_surface_under(*pointer_location); + let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&evt.device())); + let tool = tablet_seat.get_tool(&evt.tool()); - tool.motion( - self.pointer_location, - under, - &tablet, - SCOUNTER.next_serial(), - evt.time(), - ); - } - } - } + if let (Some(tablet), Some(tool)) = (tablet, tool) { + if evt.pressure_has_changed() { + tool.pressure(evt.pressure()); + } + if evt.distance_has_changed() { + tool.distance(evt.distance()); + } + if evt.tilt_has_changed() { + tool.tilt(evt.tilt()); + } + if evt.slider_has_changed() { + tool.slider_position(evt.slider_position()); + } + if evt.rotation_has_changed() { + tool.rotation(evt.rotation()); + } + if evt.wheel_has_changed() { + tool.wheel(evt.wheel_delta(), evt.wheel_delta_discrete()); + } - fn on_tablet_tool_proximity(&mut self, evt: B::TabletToolProximityEvent) { - if let Some(out) = self.backend_data.output_map.first() { - let tool = evt.tool(); - self.seat.tablet_seat().add_tool(&tool); - - self.pointer_location = evt.position_transformed(out.size); - - let under = self.window_map.borrow().get_surface_under(self.pointer_location); - let tablet = self.seat.tablet_seat().get_tablet(&evt.device().into()); - let tool = self.seat.tablet_seat().get_tool(&tool); - - if let (Some(under), Some(tablet), Some(tool)) = (under, tablet, tool) { - match evt.state() { - ProximityState::In => tool.proximity_in( - self.pointer_location, + tool.motion( + *pointer_location, under, &tablet, SCOUNTER.next_serial(), evt.time(), - ), - ProximityState::Out => tool.proximity_out(evt.time()), + ); } - } - } + }) + .unwrap(); + } + + fn on_tablet_tool_proximity(&mut self, evt: B::TabletToolProximityEvent) { + let output_map = self.output_map.borrow(); + let pointer_location = &mut self.pointer_location; + let tablet_seat = self.seat.tablet_seat(); + let window_map = self.window_map.borrow(); + + output_map + .with_primary(|_, rect| { + let tool = evt.tool(); + tablet_seat.add_tool(&tool); + + pointer_location.0 = evt.x_transformed(rect.width as u32) + rect.x as f64; + pointer_location.1 = evt.y_transformed(rect.height as u32) + rect.y as f64; + + let under = window_map.get_surface_under(*pointer_location); + let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&evt.device())); + let tool = tablet_seat.get_tool(&tool); + + if let (Some(under), Some(tablet), Some(tool)) = (under, tablet, tool) { + match evt.state() { + ProximityState::In => tool.proximity_in( + *pointer_location, + under, + &tablet, + SCOUNTER.next_serial(), + evt.time(), + ), + ProximityState::Out => tool.proximity_out(evt.time()), + } + } + }) + .unwrap(); } fn on_tablet_tool_tip(&mut self, evt: B::TabletToolTipEvent) { diff --git a/src/backend/input.rs b/src/backend/input.rs index 29911da..7def5a8 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -20,11 +20,8 @@ pub trait Device: PartialEq + Eq + std::hash::Hash { /// Test if this device has a specific capability fn has_capability(&self, capability: DeviceCapability) -> bool; - /// Get the product ID for this device. - fn id_product(&self) -> Option; - - /// Get the vendor ID for this device. - fn id_vendor(&self) -> Option; + /// Returns device USB (product,vendor) id + fn usb_id(&self) -> Option<(u32, u32)>; /// Returns the syspath of the device. /// diff --git a/src/backend/input/tablet.rs b/src/backend/input/tablet.rs index 4ebcb33..91813c7 100644 --- a/src/backend/input/tablet.rs +++ b/src/backend/input/tablet.rs @@ -1,4 +1,5 @@ use super::{ButtonState, Event, InputBackend, UnusedEvent}; +use bitflags::bitflags; /// Description of physical tablet tool #[derive(Debug, Clone, Hash, Eq, PartialEq)] @@ -35,23 +36,24 @@ pub enum TabletToolType { Totem, } -/// Describes extra capabilities on a tablet. -/// -/// Any tool must provide x and y values, extra axes are device-specific. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct TabletToolCapabilitys { - /// Tilt axes - pub tilt: bool, - /// Pressure axis - pub pressure: bool, - /// Distance axis - pub distance: bool, - /// Z-rotation axis - pub rotation: bool, - /// Slider axis - pub slider: bool, - /// Wheel axis - pub wheel: bool, +bitflags! { + /// Describes extra capabilities on a tablet. + /// + /// Any tool must provide x and y values, extra axes are device-specific. + pub struct TabletToolCapabilitys: u32 { + /// Tilt axes + const TILT = 1; + /// Pressure axis + const PRESSURE = 2; + /// Distance axis + const DISTANCE = 4; + /// Z-rotation axis + const ROTATION = 16; + /// Slider axis + const SLIDER = 32; + /// Wheel axis + const WHEEL = 64; + } } /// Tablet tool event @@ -110,7 +112,7 @@ pub trait TabletToolEvent { /// Return the current absolute Y coordinate of the tablet tool event, transformed to screen coordinates. fn y_transformed(&self, height: u32) -> f64; - /// Returns the current pressure being applied on the tool in use, normalized to the range [0, 1]. + /// Returns the current distance from the tablet's sensor, normalized to the range [0, 1] /// /// If this axis does not exist on the current tool, this function returns 0. fn distance(&self) -> f64; @@ -118,7 +120,7 @@ pub trait TabletToolEvent { /// Check if the distance axis was updated in this event. fn distance_has_changed(&self) -> bool; - /// Returns the current distance from the tablet's sensor, normalized to the range [0, 1] + /// Returns the current pressure being applied on the tool in use, normalized to the range [0, 1]. /// /// If this axis does not exist on the current tool, this function returns 0. fn pressure(&self) -> f64; @@ -168,7 +170,8 @@ pub trait TabletToolEvent { /// Returns the current z rotation of the tool in degrees, clockwise from the tool's logical neutral position. /// - /// For tools of type Mouse and Lens the logical neutral position is pointing to the current logical north of the tablet. For tools of type Brush, the logical neutral position is with the buttons pointing up. + /// For tools of type Mouse and Lens the logical neutral position is pointing to the current logical north of the tablet. + /// For tools of type Brush, the logical neutral position is with the buttons pointing up. /// /// If this axis does not exist on the current tool, this function returns 0. fn rotation(&self) -> f64; @@ -264,7 +267,8 @@ impl TabletToolAxisEvent for UnusedEvent {} /// detectable distance of the tablet device. A tool that is out of proximity cannot /// generate events. /// -/// On some hardware a tool goes out of proximity when it ceases to touch the surface. On /// other hardware, the tool is still detectable within a short distance (a few cm) off +/// On some hardware a tool goes out of proximity when it ceases to touch the surface. On +/// other hardware, the tool is still detectable within a short distance (a few cm) off /// the surface. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ProximityState { @@ -339,7 +343,8 @@ pub trait TabletToolButtonEvent: TabletToolEvent + Event /// Return the button that triggered this event. fn button(&self) -> u32; - /// For the button of a TabletToolButtonEvent, return the total number of buttons pressed on all devices on the associated seat after the the event was triggered. + /// For the button of a TabletToolButtonEvent, + /// return the total number of buttons pressed on all devices on the associated seat after the the event was triggered. fn seat_button_count(&self) -> u32; /// Return the button state of the event. diff --git a/src/backend/libinput.rs b/src/backend/libinput.rs index 6ceb91b..8da6a29 100644 --- a/src/backend/libinput.rs +++ b/src/backend/libinput.rs @@ -97,12 +97,11 @@ impl backend::Device for libinput::Device { libinput::Device::has_capability(self, capability.into()) } - fn id_product(&self) -> Option { - Some(libinput::Device::id_product(self)) - } - - fn id_vendor(&self) -> Option { - Some(libinput::Device::id_vendor(self)) + fn usb_id(&self) -> Option<(u32, u32)> { + Some(( + libinput::Device::id_product(self), + libinput::Device::id_vendor(self), + )) } fn syspath(&self) -> Option { diff --git a/src/backend/libinput/tablet.rs b/src/backend/libinput/tablet.rs index fd0744d..f32eed8 100644 --- a/src/backend/libinput/tablet.rs +++ b/src/backend/libinput/tablet.rs @@ -9,16 +9,16 @@ use input::event::{tablet_tool, EventTrait}; use super::LibinputInputBackend; /// Marker for tablet tool events -pub trait IsTabetEvent: tablet_tool::TabletToolEventTrait + EventTrait {} +pub trait IsTabletEvent: tablet_tool::TabletToolEventTrait + EventTrait {} -impl IsTabetEvent for tablet_tool::TabletToolAxisEvent {} -impl IsTabetEvent for tablet_tool::TabletToolProximityEvent {} -impl IsTabetEvent for tablet_tool::TabletToolTipEvent {} -impl IsTabetEvent for tablet_tool::TabletToolButtonEvent {} +impl IsTabletEvent for tablet_tool::TabletToolAxisEvent {} +impl IsTabletEvent for tablet_tool::TabletToolProximityEvent {} +impl IsTabletEvent for tablet_tool::TabletToolTipEvent {} +impl IsTabletEvent for tablet_tool::TabletToolButtonEvent {} impl backend::Event for E where - E: IsTabetEvent, + E: IsTabletEvent, { fn time(&self) -> u32 { tablet_tool::TabletToolEventTrait::time(self) @@ -51,7 +51,7 @@ impl backend::TabletToolTipEvent for tablet_tool::TabletTo impl backend::TabletToolEvent for E where - E: IsTabetEvent + event::EventTrait, + E: IsTabletEvent + event::EventTrait, { fn tool(&self) -> TabletToolDescriptor { let tool = self.tool(); @@ -70,14 +70,14 @@ where let hardware_serial = tool.serial(); let hardware_id_wacom = tool.tool_id(); - let capabilitys = TabletToolCapabilitys { - tilt: tool.has_tilt(), - pressure: tool.has_pressure(), - distance: tool.has_distance(), - rotation: tool.has_rotation(), - slider: tool.has_slider(), - wheel: tool.has_wheel(), - }; + let mut capabilitys = TabletToolCapabilitys::empty(); + + capabilitys.set(TabletToolCapabilitys::TILT, tool.has_tilt()); + capabilitys.set(TabletToolCapabilitys::PRESSURE, tool.has_pressure()); + capabilitys.set(TabletToolCapabilitys::DISTANCE, tool.has_distance()); + capabilitys.set(TabletToolCapabilitys::ROTATION, tool.has_rotation()); + capabilitys.set(TabletToolCapabilitys::SLIDER, tool.has_slider()); + capabilitys.set(TabletToolCapabilitys::WHEEL, tool.has_wheel()); TabletToolDescriptor { tool_type, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 4b18f10..e62200a 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -308,11 +308,7 @@ impl Device for WinitVirtualDevice { ) } - fn id_product(&self) -> Option { - None - } - - fn id_vendor(&self) -> Option { + fn usb_id(&self) -> Option<(u32, u32)> { None } diff --git a/src/wayland/tablet_manager/mod.rs b/src/wayland/tablet_manager/mod.rs index 3dbcd39..b083859 100644 --- a/src/wayland/tablet_manager/mod.rs +++ b/src/wayland/tablet_manager/mod.rs @@ -26,8 +26,7 @@ //! .tablet_seat() // Get TabletSeat asosiated with this seat //! .add_tablet(&TabletDescriptor { // Add a new tablet to a seat //! name: "Test".into(), -//! id_product: None, -//! id_vendor: None, +//! usb_id: None, //! syspath: None, //! }); //! @@ -75,11 +74,9 @@ pub fn init_tablet_manager_global(display: &mut Display) -> Global(); + let tablet_seat = user_data.get::().unwrap(); - if let Some(tablet_seat) = tablet_seat { - tablet_seat.add_instance(instance); - } + tablet_seat.add_instance(instance); } zwp_tablet_manager_v2::Request::Destroy => { // Nothing to do diff --git a/src/wayland/tablet_manager/tablet.rs b/src/wayland/tablet_manager/tablet.rs index e6ec81f..bb1c8a2 100644 --- a/src/wayland/tablet_manager/tablet.rs +++ b/src/wayland/tablet_manager/tablet.rs @@ -14,21 +14,18 @@ use crate::backend::input::Device; pub struct TabletDescriptor { /// Tablet device name pub name: String, - /// Tablet device USB product id - pub id_product: Option, - /// Tablet device USB vendor id - pub id_vendor: Option, + /// Tablet device USB (product,vendor) id + pub usb_id: Option<(u32, u32)>, /// Path to the device pub syspath: Option, } -impl From for TabletDescriptor { - fn from(device: D) -> Self { +impl From<&D> for TabletDescriptor { + fn from(device: &D) -> Self { TabletDescriptor { name: device.name(), syspath: device.syspath(), - id_product: device.id_product(), - id_vendor: device.id_vendor(), + usb_id: device.usb_id(), } } } @@ -67,7 +64,7 @@ impl TabletHandle { wl_tablet.name(tablet.name.clone()); - if let (Some(id_product), Some(id_vendor)) = (tablet.id_product, tablet.id_vendor) { + if let Some((id_product, id_vendor)) = tablet.usb_id { wl_tablet.id(id_product, id_vendor); } diff --git a/src/wayland/tablet_manager/tablet_seat.rs b/src/wayland/tablet_manager/tablet_seat.rs index 94683be..ea3d489 100644 --- a/src/wayland/tablet_manager/tablet_seat.rs +++ b/src/wayland/tablet_manager/tablet_seat.rs @@ -43,7 +43,7 @@ impl fmt::Debug for TabletSeat { /// /// TabletSeat extends `Seat` with graphic tablet specyfic functionality /// -/// TabletSeatHandle can be used to advertise avalible graphics tablets and tools to wayland clients +/// TabletSeatHandle can be used to advertise available graphics tablets and tools to wayland clients #[derive(Default, Debug, Clone)] pub struct TabletSeatHandle { inner: Rc>, @@ -53,12 +53,12 @@ impl TabletSeatHandle { pub(super) fn add_instance(&self, seat: Main) { let mut inner = self.inner.borrow_mut(); - // Notify new instance about avaluble tablets + // Notify new instance about available tablets for (desc, tablet) in inner.tablets.iter_mut() { tablet.new_instance(seat.deref(), desc); } - // Notify new instance about avalible tools + // Notify new instance about available tools for (desc, tool) in inner.tools.iter_mut() { let inner = self.inner.clone(); tool.new_instance(seat.deref(), desc, move |desc, status| { @@ -89,7 +89,7 @@ impl TabletSeatHandle { /// Add a new tablet to a seat. /// - /// You can either add tablet on [LibinputEvent::NewDevice](crate::backend::libinput::LibinputEvent::NewDevice) event, + /// You can either add tablet on [input::Event::DeviceAdded](crate::backend::input::InputEvent::DeviceAdded) event, /// or you can add tablet based on tool event, then clients will not know about devices that are not being used /// /// Returns new [TabletHandle] if tablet was not know by this seat, if tablet was allready know it returns exsisting handle. @@ -124,7 +124,7 @@ impl TabletSeatHandle { /// Remove tablet device /// /// Called when tablet is no longer avalible - /// For example on [LibinputEvent::RemovedDevice](crate::backend::libinput::LibinputEvent::RemovedDevice) event. + /// For example on [input::Event::DeviceRemoved](crate::backend::input::InputEvent::DeviceRemoved) event. pub fn remove_tablet(&self, tablet_desc: &TabletDescriptor) { self.inner.borrow_mut().tablets.remove(tablet_desc); } diff --git a/src/wayland/tablet_manager/tablet_tool.rs b/src/wayland/tablet_manager/tablet_tool.rs index ffba6a3..19cb770 100644 --- a/src/wayland/tablet_manager/tablet_tool.rs +++ b/src/wayland/tablet_manager/tablet_tool.rs @@ -2,7 +2,7 @@ use std::ops::Deref as _; use std::sync::Mutex; use std::{cell::RefCell, rc::Rc}; -use crate::backend::input::{ButtonState, TabletToolDescriptor, TabletToolType}; +use crate::backend::input::{ButtonState, TabletToolCapabilitys, TabletToolDescriptor, TabletToolType}; use crate::wayland::seat::{CursorImageAttributes, CursorImageStatus}; use wayland_protocols::unstable::tablet::v2::server::{ zwp_tablet_seat_v2::ZwpTabletSeatV2, @@ -318,27 +318,27 @@ impl TabletToolHandle { let low: u32 = tool.hardware_id_wacom as u32; wl_tool.hardware_id_wacom(high, low); - if tool.capabilitys.pressure { + if tool.capabilitys.contains(TabletToolCapabilitys::PRESSURE) { wl_tool.capability(zwp_tablet_tool_v2::Capability::Pressure); } - if tool.capabilitys.distance { + if tool.capabilitys.contains(TabletToolCapabilitys::DISTANCE) { wl_tool.capability(zwp_tablet_tool_v2::Capability::Distance); } - if tool.capabilitys.tilt { + if tool.capabilitys.contains(TabletToolCapabilitys::TILT) { wl_tool.capability(zwp_tablet_tool_v2::Capability::Tilt); } - if tool.capabilitys.slider { + if tool.capabilitys.contains(TabletToolCapabilitys::SLIDER) { wl_tool.capability(zwp_tablet_tool_v2::Capability::Slider); } - if tool.capabilitys.rotation { + if tool.capabilitys.contains(TabletToolCapabilitys::ROTATION) { wl_tool.capability(zwp_tablet_tool_v2::Capability::Rotation); } - if tool.capabilitys.wheel { + if tool.capabilitys.contains(TabletToolCapabilitys::WHEEL) { wl_tool.capability(zwp_tablet_tool_v2::Capability::Wheel); }