winit-core:winit: add tablet input support
The API is integrated into the `WindowEvent::Pointer*` API and is present in form of `TabletTool` variant on corresponding data entries. For now implemented for Web, Windows, and with limitations for Wayland. Fixes #99. Co-authored-by: daxpedda <daxpedda@gmail.com>
This commit is contained in:
parent
ffcdf80192
commit
f046e778aa
23 changed files with 1206 additions and 204 deletions
|
|
@ -13,6 +13,7 @@ use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
|||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
use tracing::warn;
|
||||
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2;
|
||||
use winit_core::event::WindowEvent;
|
||||
use winit_core::keyboard::ModifiersState;
|
||||
|
||||
|
|
@ -50,6 +51,9 @@ pub struct WinitSeatState {
|
|||
/// The text input bound on the seat.
|
||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||
|
||||
/// The tablet input bound on the seat.
|
||||
tablet: Option<Arc<ZwpTabletSeatV2>>,
|
||||
|
||||
/// The relative pointer bound on the seat.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
|
|
@ -156,6 +160,12 @@ impl SeatHandler for WinitState {
|
|||
TextInputData::default(),
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(tablet_state) =
|
||||
seat_state.tablet.is_none().then_some(self.tablet_state.as_ref()).flatten()
|
||||
{
|
||||
seat_state.tablet = Some(Arc::new(tablet_state.get_tablet_seat(&seat, queue_handle)));
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
|
|
@ -177,6 +187,11 @@ impl SeatHandler for WinitState {
|
|||
text_input.destroy();
|
||||
}
|
||||
|
||||
// NOTE: figure out when this should actually be destroyed.
|
||||
if let Some(tablet) = seat_state.tablet.take() {
|
||||
tablet.destroy();
|
||||
}
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch => {
|
||||
if let Some(touch) = seat_state.touch.take() {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use crate::seat::{
|
|||
};
|
||||
use crate::types::kwin_blur::KWinBlurManager;
|
||||
use crate::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::types::wp_tablet_input_v2::TabletManager;
|
||||
use crate::types::wp_viewporter::ViewporterState;
|
||||
use crate::types::xdg_activation::XdgActivationState;
|
||||
use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState;
|
||||
|
|
@ -100,6 +101,9 @@ pub struct WinitState {
|
|||
/// Relative pointer.
|
||||
pub relative_pointer: Option<RelativePointerState>,
|
||||
|
||||
/// Tablet manager.
|
||||
pub tablet_state: Option<TabletManager>,
|
||||
|
||||
/// Pointer constraints to handle pointer locking and confining.
|
||||
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
|
||||
|
||||
|
|
@ -194,6 +198,7 @@ impl WinitState {
|
|||
text_input_state: TextInputState::new(globals, queue_handle).ok(),
|
||||
|
||||
relative_pointer: RelativePointerState::new(globals, queue_handle).ok(),
|
||||
tablet_state: TabletManager::new(globals, queue_handle).ok(),
|
||||
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
|
||||
.map(Arc::new)
|
||||
.ok(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
pub mod cursor;
|
||||
pub mod kwin_blur;
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_tablet_input_v2;
|
||||
pub mod wp_viewporter;
|
||||
pub mod xdg_activation;
|
||||
pub mod xdg_toplevel_icon_manager;
|
||||
|
|
|
|||
343
winit-wayland/src/types/wp_tablet_input_v2.rs
Normal file
343
winit-wayland/src/types/wp_tablet_input_v2.rs
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
//! Handling of wp_tablet_input_v2.
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use dpi::LogicalPosition;
|
||||
use sctk::compositor::SurfaceData;
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::backend::smallvec::SmallVec;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{
|
||||
delegate_dispatch, event_created_child, Connection, Dispatch, Proxy, QueueHandle, WEnum,
|
||||
};
|
||||
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_manager_v2::ZwpTabletManagerV2;
|
||||
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_pad_v2::ZwpTabletPadV2;
|
||||
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::{
|
||||
self, ZwpTabletSeatV2,
|
||||
};
|
||||
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_tool_v2::{
|
||||
ButtonState, Event as ToolEvent, Type as ToolType, ZwpTabletToolV2,
|
||||
};
|
||||
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_v2::ZwpTabletV2;
|
||||
use winit_core::event::{
|
||||
ButtonSource, ElementState, Force, PointerKind, PointerSource, TabletToolButton,
|
||||
TabletToolData as CoreTabletToolData, TabletToolKind, TabletToolTilt, WindowEvent,
|
||||
};
|
||||
|
||||
use crate::state::WinitState;
|
||||
|
||||
/// KWin blur manager.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TabletManager {
|
||||
manager: ZwpTabletManagerV2,
|
||||
}
|
||||
|
||||
impl TabletManager {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
// Ignore v2 since we are not interested in its events.
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { manager })
|
||||
}
|
||||
|
||||
pub fn get_tablet_seat(
|
||||
&self,
|
||||
seat: &WlSeat,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> ZwpTabletSeatV2 {
|
||||
self.manager.get_tablet_seat(seat, queue_handle, ())
|
||||
}
|
||||
}
|
||||
impl Dispatch<ZwpTabletManagerV2, GlobalData, WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletManagerV2,
|
||||
_: <ZwpTabletManagerV2 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("no events defined for zwp_tablet_manager_v2");
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletManagerV2, (), WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletManagerV2,
|
||||
_: <ZwpTabletManagerV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("no events defined for zwp_tablet_manager_v2");
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletSeatV2, (), WinitState> for TabletManager {
|
||||
event_created_child!(WinitState, ZwpTabletSeatV2, [
|
||||
zwp_tablet_seat_v2::EVT_TABLET_ADDED_OPCODE => (ZwpTabletV2, Default::default()),
|
||||
zwp_tablet_seat_v2::EVT_TOOL_ADDED_OPCODE => (ZwpTabletToolV2, Default::default()),
|
||||
zwp_tablet_seat_v2::EVT_PAD_ADDED_OPCODE => (ZwpTabletPadV2, Default::default())
|
||||
]);
|
||||
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletSeatV2,
|
||||
_: <ZwpTabletSeatV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletToolV2, TabletToolData, WinitState> for TabletManager {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
_: &ZwpTabletToolV2,
|
||||
event: <ZwpTabletToolV2 as Proxy>::Event,
|
||||
data: &TabletToolData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let mut data = data.inner.lock().unwrap();
|
||||
|
||||
match event {
|
||||
ToolEvent::Type { tool_type: WEnum::Value(tool_type) } => {
|
||||
data.ty = match tool_type {
|
||||
ToolType::Pen => TabletToolKind::Pen,
|
||||
ToolType::Eraser => TabletToolKind::Eraser,
|
||||
ToolType::Brush => TabletToolKind::Brush,
|
||||
ToolType::Pencil => TabletToolKind::Pencil,
|
||||
ToolType::Airbrush => TabletToolKind::Airbrush,
|
||||
ToolType::Finger => TabletToolKind::Finger,
|
||||
ToolType::Mouse => TabletToolKind::Mouse,
|
||||
ToolType::Lens => TabletToolKind::Lens,
|
||||
_ => return,
|
||||
};
|
||||
},
|
||||
ToolEvent::Capability { .. } => {},
|
||||
ToolEvent::Done => (),
|
||||
ToolEvent::ProximityIn { serial, surface, .. } => {
|
||||
data.pending.push(TabletEvent::Enter { serial, surface });
|
||||
},
|
||||
ToolEvent::ProximityOut => data.pending.push(TabletEvent::Left),
|
||||
ToolEvent::Down { serial } => {
|
||||
let event = TabletEvent::Button {
|
||||
state: ElementState::Pressed,
|
||||
serial: Some(serial),
|
||||
button: TabletToolButton::Contact,
|
||||
};
|
||||
|
||||
data.pending.push(event);
|
||||
},
|
||||
ToolEvent::Up => {
|
||||
let event = TabletEvent::Button {
|
||||
state: ElementState::Released,
|
||||
serial: None,
|
||||
button: TabletToolButton::Contact,
|
||||
};
|
||||
|
||||
data.pending.push(event);
|
||||
},
|
||||
ToolEvent::Tilt { tilt_x, tilt_y } => {
|
||||
data.tool_state.tilt = Some(TabletToolTilt { x: tilt_x as i8, y: tilt_y as i8 });
|
||||
},
|
||||
ToolEvent::Motion { x, y } => {
|
||||
data.position = (x, y).into();
|
||||
data.pending.push(TabletEvent::Moved);
|
||||
},
|
||||
ToolEvent::Pressure { pressure } => {
|
||||
data.tool_state.force = Some(Force::Normalized(pressure as f64 / u16::MAX as f64));
|
||||
},
|
||||
ToolEvent::Rotation { degrees } => {
|
||||
data.tool_state.twist = Some(degrees as u16);
|
||||
},
|
||||
ToolEvent::Button { serial, button, state: WEnum::Value(state) } => {
|
||||
let state = match state {
|
||||
ButtonState::Released => ElementState::Released,
|
||||
ButtonState::Pressed => ElementState::Pressed,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Map similar to SDL.
|
||||
let button = match button {
|
||||
// BTN_STYLUS.
|
||||
0x14b => TabletToolButton::Contact,
|
||||
0x14c => TabletToolButton::Barrel,
|
||||
// BTN_STYLUS3.
|
||||
0x149 => TabletToolButton::Other(1),
|
||||
// There's no defined conversion for any of that.
|
||||
button => TabletToolButton::Other(button as u16),
|
||||
};
|
||||
|
||||
let event = TabletEvent::Button { button, state, serial: Some(serial) };
|
||||
data.pending.push(event);
|
||||
},
|
||||
ToolEvent::Frame { .. } => {
|
||||
let kind = data.ty;
|
||||
for event in std::mem::take(&mut data.pending) {
|
||||
if let TabletEvent::Enter { surface, serial } = &event {
|
||||
data.latest_enter_serial = Some(*serial);
|
||||
data.surface = Some(surface.clone());
|
||||
}
|
||||
|
||||
// Handle events only for top-level surface.
|
||||
let surface = match data
|
||||
.surface
|
||||
.as_ref()
|
||||
.map(|surface| (surface, surface.data::<SurfaceData>()))
|
||||
{
|
||||
Some((surface, Some(surface_data)))
|
||||
if surface_data.parent_surface().is_none() =>
|
||||
{
|
||||
surface
|
||||
},
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let window_id = crate::make_wid(surface);
|
||||
|
||||
// Ensure that window exists.
|
||||
let window = match state.windows.get_mut().get_mut(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let position = data.position.to_physical(window.scale_factor());
|
||||
|
||||
let window_event = match event {
|
||||
TabletEvent::Enter { .. } => WindowEvent::PointerEntered {
|
||||
device_id: None,
|
||||
position,
|
||||
primary: true,
|
||||
kind: PointerKind::TabletTool(kind),
|
||||
},
|
||||
TabletEvent::Moved => WindowEvent::PointerMoved {
|
||||
device_id: None,
|
||||
position,
|
||||
primary: true,
|
||||
source: PointerSource::TabletTool {
|
||||
kind,
|
||||
data: data.tool_state.clone(),
|
||||
},
|
||||
},
|
||||
TabletEvent::Button { button, state, serial } => {
|
||||
// Update serial if we have it.
|
||||
if let Some(serial) = serial {
|
||||
data.latest_button_serial = Some(serial);
|
||||
}
|
||||
|
||||
WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
state,
|
||||
position,
|
||||
primary: true,
|
||||
button: ButtonSource::TabletTool {
|
||||
kind,
|
||||
button,
|
||||
data: data.tool_state.clone(),
|
||||
},
|
||||
}
|
||||
},
|
||||
TabletEvent::Left => WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
position: Some(position),
|
||||
primary: true,
|
||||
kind: PointerKind::TabletTool(kind),
|
||||
},
|
||||
};
|
||||
|
||||
state.events_sink.push_window_event(window_event, window_id);
|
||||
|
||||
// Clear up the surface after we've processed `Left` event.
|
||||
if matches!(event, TabletEvent::Left) {
|
||||
data.surface = None;
|
||||
data.latest_button_serial = None;
|
||||
data.latest_enter_serial = None;
|
||||
data.tool_state = Default::default();
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TabletToolData {
|
||||
inner: Mutex<TabletToolDataInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct TabletToolDataInner {
|
||||
pub(crate) ty: TabletToolKind,
|
||||
|
||||
/// Core tablet tool data.
|
||||
pub(crate) tool_state: CoreTabletToolData,
|
||||
|
||||
/// Pending events until the `frame` is received.
|
||||
pub(crate) pending: SmallVec<[TabletEvent; 4]>,
|
||||
|
||||
/// Surface the tablet most recently entered.
|
||||
pub(crate) surface: Option<WlSurface>,
|
||||
|
||||
/// Position relative to the surface.
|
||||
pub(crate) position: LogicalPosition<f64>,
|
||||
|
||||
// NOTE: even though we don't utilize serials
|
||||
// right now, track them anyway.
|
||||
/// The serial of the latest enter event for the pointer
|
||||
pub(crate) latest_enter_serial: Option<u32>,
|
||||
|
||||
/// The serial of the latest button event for the pointer
|
||||
pub(crate) latest_button_serial: Option<u32>,
|
||||
}
|
||||
|
||||
// Due to wayland using logical coordinates,
|
||||
// delay the conversion to physical until the dispatch actually happens,
|
||||
// so the scaling is applied at the time of actual dispatch, since it
|
||||
// can technically change before the `frame` event.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum TabletEvent {
|
||||
Enter { serial: u32, surface: WlSurface },
|
||||
Left,
|
||||
Moved,
|
||||
Button { button: TabletToolButton, state: ElementState, serial: Option<u32> },
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletV2, (), WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletV2,
|
||||
_: <ZwpTabletV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletPadV2, (), WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletPadV2,
|
||||
_: <ZwpTabletPadV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: GlobalData] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletSeatV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletToolV2: TabletToolData] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletPadV2: ()] => TabletManager);
|
||||
Loading…
Add table
Add a link
Reference in a new issue