From cf9ae91e96631b8e2f246a5c6e63dc7cfaf341e5 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 28 Dec 2023 13:36:59 -0800 Subject: [PATCH] Initial support for tablet input --- src/input/mod.rs | 267 +++++++++++++++++++------ src/state.rs | 2 + src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/tablet_manager.rs | 6 + 4 files changed, 218 insertions(+), 58 deletions(-) create mode 100644 src/wayland/handlers/tablet_manager.rs diff --git a/src/input/mod.rs b/src/input/mod.rs index 50580d10..7d050d23 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -27,7 +27,9 @@ use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, InputBackend, - InputEvent, KeyState, PointerAxisEvent, TouchEvent, + InputEvent, KeyState, PointerAxisEvent, ProximityState, TabletToolButtonEvent, + TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, + TouchEvent, }, desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::{ @@ -41,18 +43,14 @@ use smithay::{ Seat, SeatState, }, output::Output, - reexports::{ - input::event::touch::{ - TouchDownEvent as LibinputTouchDownEvent, TouchMotionEvent as LibinputTouchMotionEvent, - }, - wayland_server::DisplayHandle, - }, + reexports::{input::Device as InputDevice, wayland_server::DisplayHandle}, utils::{Point, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, pointer_constraints::{with_pointer_constraint, PointerConstraint}, seat::WaylandFocus, shell::wlr_layer::Layer as WlrLayer, + tablet_manager::{TabletDescriptor, TabletSeatTrait}, }, xwayland::X11Surface, }; @@ -143,11 +141,15 @@ impl Devices { fn add_device(&self, device: &D) -> Vec { let id = device.id(); let mut map = self.0.borrow_mut(); - let caps = [DeviceCapability::Keyboard, DeviceCapability::Pointer] - .iter() - .cloned() - .filter(|c| device.has_capability(*c)) - .collect::>(); + let caps = [ + DeviceCapability::Keyboard, + DeviceCapability::Pointer, + DeviceCapability::TabletTool, + ] + .iter() + .cloned() + .filter(|c| device.has_capability(*c)) + .collect::>(); let new_caps = caps .iter() .cloned() @@ -221,9 +223,7 @@ impl State { event: InputEvent, needs_key_repetition: bool, ) where - ::PointerAxisEvent: 'static, - ::TouchDownEvent: 'static, - ::TouchMotionEvent: 'static, + ::Device: 'static, { use smithay::backend::input::Event; match event { @@ -233,7 +233,13 @@ impl State { let devices = userdata.get::().unwrap(); for cap in devices.add_device(&device) { match cap { - // TODO: Handle touch, tablet + DeviceCapability::TabletTool => { + seat.tablet_seat().add_tablet::( + &self.common.display_handle, + &TabletDescriptor::from(&device), + ); + } + // TODO: Handle touch _ => {} } } @@ -249,7 +255,11 @@ impl State { if devices.has_device(&device) { for cap in devices.remove_device(&device) { match cap { - // TODO: Handle touch, tablet + DeviceCapability::TabletTool => { + seat.tablet_seat() + .remove_tablet(&TabletDescriptor::from(&device)); + } + // TODO: Handle touch _ => {} } } @@ -1001,13 +1011,12 @@ impl State { } } InputEvent::PointerAxis { event, .. } => { - let scroll_factor = if let Some(event) = - ::downcast_ref::(&event) - { - self.common.config.scroll_factor(&event.device()) - } else { - 1.0 - }; + let scroll_factor = + if let Some(device) = ::downcast_ref::(&event.device()) { + self.common.config.scroll_factor(device) + } else { + 1.0 + }; if let Some(seat) = self.common.seat_with_device(&event.device()) { #[cfg(feature = "debug")] @@ -1174,23 +1183,7 @@ impl State { } InputEvent::TouchDown { event, .. } => { if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() { - // TODO Is it possible to determine mapping for external touchscreen? - let map_to_output = if let Some(event) = - ::downcast_ref::(&event) - { - self.common - .config - .map_to_output(&event.device()) - .and_then(|name| { - self.common - .shell - .outputs() - .find(|output| output.name() == name) - }) - } else { - None - }; - let Some(output) = map_to_output.or_else(|| self.common.shell.builtin_output()).cloned() else { + let Some(output) = mapped_output_for_device(&self.common, &event.device()).cloned() else { return; }; @@ -1230,22 +1223,7 @@ impl State { } InputEvent::TouchMotion { event, .. } => { if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() { - let map_to_output = if let Some(event) = - ::downcast_ref::(&event) - { - self.common - .config - .map_to_output(&event.device()) - .and_then(|name| { - self.common - .shell - .outputs() - .find(|output| output.name() == name) - }) - } else { - None - }; - let Some(output) = map_to_output.or_else(|| self.common.shell.builtin_output()).cloned() else { + let Some(output) = mapped_output_for_device(&self.common, &event.device()).cloned() else { return; }; @@ -1292,7 +1270,163 @@ impl State { } } InputEvent::TouchFrame { event: _, .. } => {} - _ => { /* TODO e.g. tablet events */ } + InputEvent::TabletToolAxis { event, .. } => { + if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() { + let Some(output) = mapped_output_for_device(&self.common, &event.device()).cloned() else { + return; + }; + let geometry = output.geometry(); + + let position = event + .position_transformed(geometry.size.as_logical()) + .as_global() + + geometry.loc.to_f64(); + + let overview = self.common.shell.overview_mode(); + let workspace = self.common.shell.workspaces.active_mut(&output); + let under = State::surface_under( + position, + &output, + &self.common.shell.override_redirect_windows, + overview.0.clone(), + workspace, + self.common.shell.session_lock.as_ref(), + ) + .map(|(target, pos)| (target, pos.as_logical())); + + let pointer = seat.get_pointer().unwrap(); + pointer.motion( + self, + under.clone(), + &MotionEvent { + location: position.as_logical(), + serial: SERIAL_COUNTER.next_serial(), + time: 0, + }, + ); + + let tablet_seat = seat.tablet_seat(); + + let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device())); + let tool = tablet_seat.get_tool(&event.tool()); + + if let (Some(tablet), Some(tool)) = (tablet, tool) { + if event.pressure_has_changed() { + tool.pressure(event.pressure()); + } + if event.distance_has_changed() { + tool.distance(event.distance()); + } + if event.tilt_has_changed() { + tool.tilt(event.tilt()); + } + if event.slider_has_changed() { + tool.slider_position(event.slider_position()); + } + if event.rotation_has_changed() { + tool.rotation(event.rotation()); + } + if event.wheel_has_changed() { + tool.wheel(event.wheel_delta(), event.wheel_delta_discrete()); + } + + tool.motion( + position.as_logical(), + under.and_then(|(f, loc)| f.wl_surface().map(|s| (s, loc))), + &tablet, + SERIAL_COUNTER.next_serial(), + event.time_msec(), + ); + } + } + } + InputEvent::TabletToolProximity { event, .. } => { + if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() { + let Some(output) = mapped_output_for_device(&self.common, &event.device()).cloned() else { + return; + }; + let geometry = output.geometry(); + + let position = event + .position_transformed(geometry.size.as_logical()) + .as_global() + + geometry.loc.to_f64(); + + let overview = self.common.shell.overview_mode(); + let workspace = self.common.shell.workspaces.active_mut(&output); + let under = State::surface_under( + position, + &output, + &self.common.shell.override_redirect_windows, + overview.0.clone(), + workspace, + self.common.shell.session_lock.as_ref(), + ) + .map(|(target, pos)| (target, pos.as_logical())); + + let pointer = seat.get_pointer().unwrap(); + pointer.motion( + self, + under.clone(), + &MotionEvent { + location: position.as_logical(), + serial: SERIAL_COUNTER.next_serial(), + time: 0, + }, + ); + + let tablet_seat = seat.tablet_seat(); + + let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device())); + let tool = tablet_seat.get_tool(&event.tool()); + + if let (Some(tablet), Some(tool)) = (tablet, tool) { + match event.state() { + ProximityState::In => { + if let Some(under) = + under.and_then(|(f, loc)| f.wl_surface().map(|s| (s, loc))) + { + tool.proximity_in( + position.as_logical(), + under, + &tablet, + SERIAL_COUNTER.next_serial(), + event.time_msec(), + ) + } + } + ProximityState::Out => tool.proximity_out(event.time_msec()), + } + } + } + } + InputEvent::TabletToolTip { event, .. } => { + if let Some(seat) = self.common.seat_with_device(&event.device()) { + if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) { + match event.tip_state() { + TabletToolTipState::Down => { + tool.tip_down(SERIAL_COUNTER.next_serial(), event.time_msec()); + } + TabletToolTipState::Up => { + tool.tip_up(event.time_msec()); + } + } + } + } + } + InputEvent::TabletToolButton { event, .. } => { + if let Some(seat) = self.common.seat_with_device(&event.device()) { + if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) { + tool.button( + event.button(), + event.button_state(), + SERIAL_COUNTER.next_serial(), + event.time_msec(), + ); + } + } + } + InputEvent::Special(_) => {} } } @@ -2189,3 +2323,20 @@ fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator>() .into_iter() } + +// TODO Is it possible to determine mapping for external touchscreen? +// Support map_to_region like sway? +fn mapped_output_for_device<'a, D: Device + 'static>( + state: &'a Common, + device: &D, +) -> Option<&'a Output> { + let map_to_output = if let Some(device) = ::downcast_ref::(device) { + state + .config + .map_to_output(device) + .and_then(|name| state.shell.outputs().find(|output| output.name() == name)) + } else { + None + }; + map_to_output.or_else(|| state.shell.builtin_output()) +} diff --git a/src/state.rs b/src/state.rs index 2da7c2e2..9f78b277 100644 --- a/src/state.rs +++ b/src/state.rs @@ -78,6 +78,7 @@ use smithay::{ session_lock::SessionLockManagerState, shell::{kde::decoration::KdeDecorationState, xdg::decoration::XdgDecorationState}, shm::ShmState, + tablet_manager::TabletManagerState, text_input::TextInputManagerState, viewporter::ViewporterState, virtual_keyboard::VirtualKeyboardManagerState, @@ -364,6 +365,7 @@ impl State { XWaylandKeyboardGrabState::new::(&dh); PointerConstraintsState::new::(&dh); PointerGesturesState::new::(&dh); + TabletManagerState::new::(&dh); SecurityContextState::new::(&dh, client_has_no_security_context); InputMethodManagerState::new::(&dh, client_should_see_privileged_protocols); TextInputManagerState::new::(&dh); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 23ae737b..5dae5d80 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -25,6 +25,7 @@ pub mod security_context; pub mod selection; pub mod session_lock; pub mod shm; +pub mod tablet_manager; pub mod text_input; pub mod toplevel_info; pub mod toplevel_management; diff --git a/src/wayland/handlers/tablet_manager.rs b/src/wayland/handlers/tablet_manager.rs new file mode 100644 index 00000000..9490421a --- /dev/null +++ b/src/wayland/handlers/tablet_manager.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::delegate_tablet_manager; + +delegate_tablet_manager!(State);