input: Add pointer-emulation grab for tablet tools
This commit is contained in:
parent
6467f79025
commit
9125f8a2a7
3 changed files with 351 additions and 23 deletions
100
src/input/mod.rs
100
src/input/mod.rs
|
|
@ -9,7 +9,10 @@ use crate::{
|
||||||
cosmic_modifiers_from_smithay,
|
cosmic_modifiers_from_smithay,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
input::gestures::{GestureState, SwipeAction},
|
input::{
|
||||||
|
gestures::{GestureState, SwipeAction},
|
||||||
|
tablet_emu::PointerEmulationGrab,
|
||||||
|
},
|
||||||
shell::{
|
shell::{
|
||||||
SeatExt, Trigger,
|
SeatExt, Trigger,
|
||||||
focus::{
|
focus::{
|
||||||
|
|
@ -52,7 +55,7 @@ use smithay::{
|
||||||
keyboard::KeyboardHandle,
|
keyboard::KeyboardHandle,
|
||||||
keyboard::{FilterResult, KeyboardSource, KeysymHandle, ModifiersState},
|
keyboard::{FilterResult, KeyboardSource, KeysymHandle, ModifiersState},
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent as PointerButtonEvent, GestureHoldBeginEvent,
|
AxisFrame, ButtonEvent as PointerButtonEvent, Focus, GestureHoldBeginEvent,
|
||||||
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||||
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||||
GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent, PointerGrab, PointerHandle,
|
GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent, PointerGrab, PointerHandle,
|
||||||
|
|
@ -89,6 +92,7 @@ use std::{
|
||||||
|
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod gestures;
|
pub mod gestures;
|
||||||
|
pub mod tablet_emu;
|
||||||
|
|
||||||
/// Identifies the input backend instance an event came from, used to disambiguate device ids
|
/// Identifies the input backend instance an event came from, used to disambiguate device ids
|
||||||
/// (which are only unique within a single backend instance, see
|
/// (which are only unique within a single backend instance, see
|
||||||
|
|
@ -1609,6 +1613,26 @@ impl State {
|
||||||
let tool = tablet_seat.get_tool(&event.tool());
|
let tool = tablet_seat.get_tool(&event.tool());
|
||||||
|
|
||||||
if let Some(tool) = tool {
|
if let Some(tool) = tool {
|
||||||
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
if !tool.is_grabbed()
|
||||||
|
&& under
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(target, _)| !target.supports_tool(&tool))
|
||||||
|
{
|
||||||
|
let start_data = tool::GrabStartData {
|
||||||
|
focus: under.clone(),
|
||||||
|
trigger: tool::GrabTrigger::Proximity,
|
||||||
|
location: position.as_logical(),
|
||||||
|
};
|
||||||
|
tool.set_grab(
|
||||||
|
self,
|
||||||
|
PointerEmulationGrab::new(start_data, seat.clone()),
|
||||||
|
event.time(),
|
||||||
|
serial,
|
||||||
|
Focus::Keep,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let frame = tool::AxisFrame {
|
let frame = tool::AxisFrame {
|
||||||
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
||||||
distance: event.distance_has_changed().then(|| event.distance()),
|
distance: event.distance_has_changed().then(|| event.distance()),
|
||||||
|
|
@ -1629,7 +1653,7 @@ impl State {
|
||||||
under,
|
under,
|
||||||
&tool::MotionEvent {
|
&tool::MotionEvent {
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial,
|
||||||
time: event.time(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1683,6 +1707,25 @@ impl State {
|
||||||
if let Some(tablet) = tablet {
|
if let Some(tablet) = tablet {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
|
||||||
|
if !tool.is_grabbed()
|
||||||
|
&& under
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(target, _)| !target.supports_tool(&tool))
|
||||||
|
{
|
||||||
|
let start_data = tool::GrabStartData {
|
||||||
|
focus: under.clone(),
|
||||||
|
trigger: tool::GrabTrigger::Proximity,
|
||||||
|
location: position.as_logical(),
|
||||||
|
};
|
||||||
|
tool.set_grab(
|
||||||
|
self,
|
||||||
|
PointerEmulationGrab::new(start_data, seat.clone()),
|
||||||
|
event.time(),
|
||||||
|
serial,
|
||||||
|
Focus::Keep,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let frame = tool::AxisFrame {
|
let frame = tool::AxisFrame {
|
||||||
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
||||||
distance: event.distance_has_changed().then(|| event.distance()),
|
distance: event.distance_has_changed().then(|| event.distance()),
|
||||||
|
|
@ -1697,24 +1740,39 @@ impl State {
|
||||||
};
|
};
|
||||||
|
|
||||||
match event.state() {
|
match event.state() {
|
||||||
ProximityState::In => tool.proximity_in(
|
ProximityState::In => {
|
||||||
self,
|
tool.proximity_in(
|
||||||
under,
|
self,
|
||||||
tablet,
|
under,
|
||||||
&tool::ProximityInEvent {
|
tablet,
|
||||||
location: position.as_logical(),
|
&tool::ProximityInEvent {
|
||||||
axis: Some(frame),
|
location: position.as_logical(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
axis: Some(frame),
|
||||||
time: event.time(),
|
serial,
|
||||||
},
|
time: event.time(),
|
||||||
),
|
},
|
||||||
ProximityState::Out => tool.proximity_out(
|
);
|
||||||
self,
|
}
|
||||||
&tool::ProximityOutEvent {
|
ProximityState::Out => {
|
||||||
serial,
|
tool.proximity_out(
|
||||||
time: event.time(),
|
self,
|
||||||
},
|
&tool::ProximityOutEvent {
|
||||||
),
|
serial,
|
||||||
|
time: event.time(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if let Some(pointer) = seat.get_pointer() {
|
||||||
|
pointer.motion(
|
||||||
|
self,
|
||||||
|
None,
|
||||||
|
&PointerMotionEvent {
|
||||||
|
location: position.as_logical(),
|
||||||
|
serial,
|
||||||
|
time: event.time(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tool.frame(self, event.time());
|
tool.frame(self, event.time());
|
||||||
|
|
|
||||||
260
src/input/tablet_emu.rs
Normal file
260
src/input/tablet_emu.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
//! Pointer Emulating TabletTool grab.
|
||||||
|
|
||||||
|
use crate::{shell::focus::target::PointerFocusTarget, state::State};
|
||||||
|
use smithay::{
|
||||||
|
backend::input::{ButtonState, InputTime, MouseButton},
|
||||||
|
input::{
|
||||||
|
Seat, pointer,
|
||||||
|
tablet::{
|
||||||
|
self,
|
||||||
|
tool::{
|
||||||
|
AxisFrame, ButtonEvent, DownEvent, GrabStartData, ProximityInEvent,
|
||||||
|
ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle, UpEvent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
utils::{Logical, Point, SERIAL_COUNTER},
|
||||||
|
};
|
||||||
|
|
||||||
|
// from https://gitlab.freedesktop.org/libinput/libinput/-/blob/main/include/linux/linux/input-event-codes.h
|
||||||
|
const BTN_STYLUS: u32 = 0x14b;
|
||||||
|
const BTN_STYLUS_2: u32 = 0x14c;
|
||||||
|
const BTN_LEFT: u32 = 0x110;
|
||||||
|
const BTN_RIGHT: u32 = 0x111;
|
||||||
|
const BTN_MIDDLE: u32 = 0x112;
|
||||||
|
const BTN_FORWARD: u32 = 0x115;
|
||||||
|
const BTN_BACK: u32 = 0x116;
|
||||||
|
|
||||||
|
pub struct PointerEmulationGrab {
|
||||||
|
start_data: GrabStartData<State>,
|
||||||
|
seat: Seat<State>,
|
||||||
|
|
||||||
|
above_source: bool,
|
||||||
|
tip_down: bool,
|
||||||
|
button_down: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerEmulationGrab {
|
||||||
|
pub fn new(start_data: GrabStartData<State>, seat: Seat<State>) -> Self {
|
||||||
|
Self {
|
||||||
|
start_data,
|
||||||
|
seat,
|
||||||
|
|
||||||
|
above_source: true,
|
||||||
|
tip_down: false,
|
||||||
|
button_down: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button_to_mouse(&self, button: u32) -> Option<MouseButton> {
|
||||||
|
// FIXME: This should be configurable.
|
||||||
|
if button == BTN_STYLUS {
|
||||||
|
Some(MouseButton::Right)
|
||||||
|
} else if button == BTN_STYLUS_2 {
|
||||||
|
Some(MouseButton::Middle)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pointer_button(&self, data: &mut State, button: MouseButton, state: ButtonState) {
|
||||||
|
if let Some(pointer) = self.seat.get_pointer() {
|
||||||
|
let button = match button {
|
||||||
|
MouseButton::Left => BTN_LEFT,
|
||||||
|
MouseButton::Right => BTN_RIGHT,
|
||||||
|
MouseButton::Middle => BTN_MIDDLE,
|
||||||
|
MouseButton::Back => BTN_BACK,
|
||||||
|
MouseButton::Forward => BTN_FORWARD,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
pointer.button(
|
||||||
|
data,
|
||||||
|
&pointer::ButtonEvent {
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
button,
|
||||||
|
state,
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
pointer.frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Type = tablet::tool::MotionEvent;
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for PointerEmulationGrab {
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_in(data, focus.clone(), event);
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if let Some(pointer) = self.seat.get_pointer() {
|
||||||
|
pointer.motion(
|
||||||
|
data,
|
||||||
|
focus,
|
||||||
|
&pointer::MotionEvent {
|
||||||
|
location: event.location,
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pointer.frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_out(data, event);
|
||||||
|
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &DownEvent,
|
||||||
|
) {
|
||||||
|
handle.down(data, event);
|
||||||
|
self.tip_down = true;
|
||||||
|
|
||||||
|
self.pointer_button(data, MouseButton::Left, ButtonState::Pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &UpEvent,
|
||||||
|
) {
|
||||||
|
handle.up(data, event);
|
||||||
|
|
||||||
|
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
|
||||||
|
self.tip_down = false;
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
|
event: &Type,
|
||||||
|
) {
|
||||||
|
self.above_source = focus.as_ref().map(|(target, _)| target)
|
||||||
|
== self.start_data.focus.as_ref().map(|(target, _)| target);
|
||||||
|
handle.motion(data, self.start_data.focus.clone(), event);
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if let Some(pointer) = self.seat.get_pointer() {
|
||||||
|
pointer.motion(
|
||||||
|
data,
|
||||||
|
focus,
|
||||||
|
&pointer::MotionEvent {
|
||||||
|
location: event.location,
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pointer.frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event);
|
||||||
|
|
||||||
|
self.button_down.retain(|b| b != &event.button);
|
||||||
|
if matches!(event.state, ButtonState::Pressed) {
|
||||||
|
self.button_down.push(event.button);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(button) = self.button_to_mouse(event.button) {
|
||||||
|
self.pointer_button(data, button, event.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: AxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
if self.tip_down {
|
||||||
|
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = std::mem::take(&mut self.button_down);
|
||||||
|
for button in buttons.into_iter() {
|
||||||
|
if let Some(button) = self.button_to_mouse(button) {
|
||||||
|
self.pointer_button(data, button, ButtonState::Released);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &GrabStartData<State> {
|
||||||
|
&self.start_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,8 +33,8 @@ use smithay::{
|
||||||
Tablet,
|
Tablet,
|
||||||
tool::{
|
tool::{
|
||||||
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||||
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolHandle,
|
||||||
UpEvent as ToolUpEvent,
|
TabletToolTarget, UpEvent as ToolUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
|
|
@ -306,6 +306,16 @@ impl PointerFocusTarget {
|
||||||
session.set_cursor_hotspot(cursor_hotspot);
|
session.set_cursor_hotspot(cursor_hotspot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_tool(&self, tool_handle: &TabletToolHandle<State>) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::WlSurface { surface, .. } if surface.client().is_some() => tool_handle
|
||||||
|
.client_tools(&surface.client().unwrap())
|
||||||
|
.next()
|
||||||
|
.is_some(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyboardFocusTarget {
|
impl KeyboardFocusTarget {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue