Merge pull request #314 from PolyMeilex/tablet

Graphics tablet support
This commit is contained in:
Victor Berger 2021-07-06 16:28:56 +02:00 committed by GitHub
commit 70826c9d2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1680 additions and 22 deletions

View File

@ -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"]

View File

@ -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::{TabletDescriptor, TabletSeatTrait},
SERIAL_COUNTER as SCOUNTER,
},
};
@ -81,7 +83,7 @@ impl<Backend> AnvilState<Backend> {
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 +95,7 @@ impl<Backend> AnvilState<Backend> {
}
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());
}
@ -238,6 +240,29 @@ impl AnvilState<UdevData> {
InputEvent::PointerMotion { event, .. } => self.on_pointer_move::<B>(event),
InputEvent::PointerButton { event, .. } => self.on_pointer_button::<B>(event),
InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::<B>(event),
InputEvent::TabletToolAxis { event, .. } => self.on_tablet_tool_axis::<B>(event),
InputEvent::TabletToolProximity { event, .. } => self.on_tablet_tool_proximity::<B>(event),
InputEvent::TabletToolTip { event, .. } => self.on_tablet_tool_tip::<B>(event),
InputEvent::TabletToolButton { event, .. } => self.on_tablet_button::<B>(event),
InputEvent::DeviceAdded { device } => {
if device.has_capability(DeviceCapability::TabletTool) {
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(&TabletDescriptor::from(&device));
// 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 +284,127 @@ impl AnvilState<UdevData> {
.motion(self.pointer_location, under, serial, evt.time());
}
fn on_tablet_tool_axis<B: InputBackend>(&mut self, evt: B::TabletToolAxisEvent) {
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| {
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(&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(
*pointer_location,
under,
&tablet,
SCOUNTER.next_serial(),
evt.time(),
);
}
})
.unwrap();
}
fn on_tablet_tool_proximity<B: InputBackend>(&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<B: InputBackend>(&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<B: InputBackend>(&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;

View File

@ -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<BackendData> {
pub pointer_location: (f64, f64),
pub cursor_status: Arc<Mutex<CursorImageStatus>>,
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<BackendData: Backend + 'static> AnvilState<BackendData> {
*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<BackendData: Backend + 'static> AnvilState<BackendData> {
cursor_status,
pointer_location: (0.0, 0.0),
seat_name,
seat,
start_time: std::time::Instant::now(),
#[cfg(feature = "xwayland")]
xwayland,

View File

@ -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,14 @@ 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;
/// Returns device USB (product,vendor) id
fn usb_id(&self) -> Option<(u32, u32)>;
/// Returns the syspath of the device.
///
/// The path is an absolute path and includes the sys mount point.
fn syspath(&self) -> Option<PathBuf>;
}
/// Set of input types a device may provide
@ -105,9 +120,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 +134,7 @@ pub trait PointerButtonEvent<B: InputBackend>: Event<B> {
/// Pressed button of the event
fn button(&self) -> MouseButton;
/// State of the button
fn state(&self) -> MouseButtonState;
fn state(&self) -> ButtonState;
}
impl<B: InputBackend> PointerButtonEvent<B> for UnusedEvent {
@ -127,7 +142,7 @@ impl<B: InputBackend> PointerButtonEvent<B> for UnusedEvent {
match *self {}
}
fn state(&self) -> MouseButtonState {
fn state(&self) -> ButtonState {
match *self {}
}
}
@ -481,6 +496,14 @@ pub trait InputBackend: Sized {
type TouchCancelEvent: TouchCancelEvent<Self>;
/// Type representing touch frame events
type TouchFrameEvent: TouchFrameEvent<Self>;
/// Type representing axis events on tablet devices
type TabletToolAxisEvent: TabletToolAxisEvent<Self>;
/// Type representing proximity events on tablet devices
type TabletToolProximityEvent: TabletToolProximityEvent<Self>;
/// Type representing tip events on tablet devices
type TabletToolTipEvent: TabletToolTipEvent<Self>;
/// Type representing button events on tablet tool devices
type TabletToolButtonEvent: TabletToolButtonEvent<Self>;
/// Special events that are custom to this backend
type SpecialEvent;
@ -557,6 +580,31 @@ pub enum InputEvent<B: InputBackend> {
/// 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),
}

366
src/backend/input/tablet.rs Normal file
View File

@ -0,0 +1,366 @@
use super::{ButtonState, Event, InputBackend, UnusedEvent};
use bitflags::bitflags;
/// 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 Wacoms 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,
}
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
pub trait TabletToolEvent<B: InputBackend> {
/// 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 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;
/// Check if the distance axis was updated in this event.
fn distance_has_changed(&self) -> bool;
/// 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;
/// 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<B: InputBackend> TabletToolEvent<B> 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<B: InputBackend>: TabletToolEvent<B> + Event<B> {}
impl<B: InputBackend> TabletToolAxisEvent<B> 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<B: InputBackend>: TabletToolEvent<B> + Event<B> {
/// 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<B: InputBackend> TabletToolProximityEvent<B> 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<B: InputBackend>: TabletToolEvent<B> + Event<B> {
/// 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<B: InputBackend> TabletToolTipEvent<B> 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<B: InputBackend>: TabletToolEvent<B> + Event<B> {
/// 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<B: InputBackend> TabletToolButtonEvent<B> for UnusedEvent {
fn button(&self) -> u32 {
match *self {}
}
fn seat_button_count(&self) -> u32 {
match *self {}
}
fn button_state(&self) -> ButtonState {
match *self {}
}
}

View File

@ -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,21 @@ impl backend::Device for libinput::Device {
fn has_capability(&self, capability: backend::DeviceCapability) -> bool {
libinput::Device::has_capability(self, capability.into())
}
fn usb_id(&self) -> Option<(u32, u32)> {
Some((
libinput::Device::id_product(self),
libinput::Device::id_vendor(self),
))
}
fn syspath(&self) -> Option<PathBuf> {
#[cfg(feature = "udev")]
return unsafe { libinput::Device::udev_device(self) }.map(|d| d.syspath().to_owned());
#[cfg(not(feature = "udev"))]
None
}
}
impl From<backend::DeviceCapability> for libinput::DeviceCapability {
@ -179,7 +197,7 @@ impl backend::PointerButtonEvent<LibinputInputBackend> for event::pointer::Point
}
}
fn state(&self) -> backend::MouseButtonState {
fn state(&self) -> backend::ButtonState {
self.button_state().into()
}
}
@ -355,6 +373,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 +442,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 +501,11 @@ impl From<event::pointer::AxisSource> for backend::AxisSource {
}
}
impl From<event::pointer::ButtonState> for backend::MouseButtonState {
impl From<event::pointer::ButtonState> 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,
}
}
}

View File

@ -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 IsTabletEvent: tablet_tool::TabletToolEventTrait + EventTrait {}
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<E> backend::Event<LibinputInputBackend> for E
where
E: IsTabletEvent,
{
fn time(&self) -> u32 {
tablet_tool::TabletToolEventTrait::time(self)
}
fn device(&self) -> libinput::Device {
event::EventTrait::device(self)
}
}
impl backend::TabletToolAxisEvent<LibinputInputBackend> for tablet_tool::TabletToolAxisEvent {}
impl backend::TabletToolProximityEvent<LibinputInputBackend> 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<LibinputInputBackend> 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<E> backend::TabletToolEvent<LibinputInputBackend> for E
where
E: IsTabletEvent + 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 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,
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<LibinputInputBackend> 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()
}
}

View File

@ -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,14 @@ impl Device for WinitVirtualDevice {
DeviceCapability::Keyboard | DeviceCapability::Pointer | DeviceCapability::Touch
)
}
fn usb_id(&self) -> Option<(u32, u32)> {
None
}
fn syspath(&self) -> Option<PathBuf> {
None
}
}
/// Errors that may happen when driving the event loop of [`WinitInputBackend`]
@ -460,7 +468,7 @@ impl PointerButtonEvent<WinitInputBackend> for WinitMouseInputEvent {
self.button.into()
}
fn state(&self) -> MouseButtonState {
fn state(&self) -> ButtonState {
self.state.into()
}
}
@ -619,6 +627,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 +858,11 @@ impl From<ElementState> for KeyState {
}
}
impl From<ElementState> for MouseButtonState {
impl From<ElementState> 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,
}
}
}

View File

@ -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.
///

View File

@ -0,0 +1,89 @@
//! 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(),
//! usb_id: 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::<TabletSeatHandle>().unwrap().clone()
}
}
/// Initialize a tablet manager global.
pub fn init_tablet_manager_global(display: &mut Display) -> Global<ZwpTabletManagerV2> {
display.create_global::<ZwpTabletManagerV2, _>(
MANAGER_VERSION,
Filter::new(
move |(manager, _version): (Main<ZwpTabletManagerV2>, 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::<TabletSeatHandle>().unwrap();
tablet_seat.add_instance(instance);
}
zwp_tablet_manager_v2::Request::Destroy => {
// Nothing to do
}
_ => {}
});
},
),
)
}

View File

@ -0,0 +1,95 @@
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,vendor) id
pub usb_id: Option<(u32, u32)>,
/// Path to the device
pub syspath: Option<PathBuf>,
}
impl<D: Device> From<&D> for TabletDescriptor {
fn from(device: &D) -> Self {
TabletDescriptor {
name: device.name(),
syspath: device.syspath(),
usb_id: device.usb_id(),
}
}
}
#[derive(Debug, Default)]
struct Tablet {
instances: Vec<ZwpTabletV2>,
}
/// Handle to a tablet device
///
/// Tablet represents one graphics tablet device
#[derive(Debug, Default, Clone)]
pub struct TabletHandle {
inner: Rc<RefCell<Tablet>>,
}
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::<ZwpTabletV2>(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, id_vendor)) = tablet.usb_id {
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<F>(&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);
}
}
}

View File

@ -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<ZwpTabletSeatV2>,
tablets: HashMap<TabletDescriptor, TabletHandle>,
tools: HashMap<TabletToolDescriptor, TabletToolHandle>,
cursor_callback: Option<Box<dyn FnMut(&TabletToolDescriptor, CursorImageStatus)>>,
}
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 available graphics tablets and tools to wayland clients
#[derive(Default, Debug, Clone)]
pub struct TabletSeatHandle {
inner: Rc<RefCell<TabletSeat>>,
}
impl TabletSeatHandle {
pub(super) fn add_instance(&self, seat: Main<ZwpTabletSeatV2>) {
let mut inner = self.inner.borrow_mut();
// Notify new instance about available tablets
for (desc, tablet) in inner.tablets.iter_mut() {
tablet.new_instance(seat.deref(), desc);
}
// 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| {
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<F>(&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 [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.
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<TabletHandle> {
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 [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);
}
/// 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<TabletToolHandle> {
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();
}
}

View File

@ -0,0 +1,477 @@
use std::ops::Deref as _;
use std::sync::Mutex;
use std::{cell::RefCell, rc::Rc};
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,
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<ZwpTabletToolV2>,
focus: Option<WlSurface>,
is_down: bool,
pending_pressure: Option<f64>,
pending_distance: Option<f64>,
pending_tilt: Option<(f64, f64)>,
pending_slider: Option<f64>,
pending_rotation: Option<f64>,
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<RefCell<TabletTool>>,
}
impl TabletToolHandle {
pub(super) fn new_instance<F>(&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::<ZwpTabletToolV2>(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::<Mutex<CursorImageAttributes>>()
.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.contains(TabletToolCapabilitys::PRESSURE) {
wl_tool.capability(zwp_tablet_tool_v2::Capability::Pressure);
}
if tool.capabilitys.contains(TabletToolCapabilitys::DISTANCE) {
wl_tool.capability(zwp_tablet_tool_v2::Capability::Distance);
}
if tool.capabilitys.contains(TabletToolCapabilitys::TILT) {
wl_tool.capability(zwp_tablet_tool_v2::Capability::Tilt);
}
if tool.capabilitys.contains(TabletToolCapabilitys::SLIDER) {
wl_tool.capability(zwp_tablet_tool_v2::Capability::Slider);
}
if tool.capabilitys.contains(TabletToolCapabilitys::ROTATION) {
wl_tool.capability(zwp_tablet_tool_v2::Capability::Rotation);
}
if tool.capabilitys.contains(TabletToolCapabilitys::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<TabletToolType> 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<ButtonState> 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,
}
}
}