1
0
Fork 0
forked from wry/wry

tablet: implement version 2

This commit is contained in:
Julian Orth 2025-04-22 22:22:31 +02:00
parent 1d017ec2c2
commit dee0066f1a
25 changed files with 426 additions and 31 deletions

View file

@ -188,7 +188,7 @@ Jay supports the following wayland protocols:
| zwp_pointer_gestures_v1 | 3 | | | zwp_pointer_gestures_v1 | 3 | |
| zwp_primary_selection_device_manager_v1 | 1 | | | zwp_primary_selection_device_manager_v1 | 1 | |
| zwp_relative_pointer_manager_v1 | 1 | | | zwp_relative_pointer_manager_v1 | 1 | |
| zwp_tablet_manager_v2 | 1 | | | zwp_tablet_manager_v2 | 2 | |
| zwp_text_input_manager_v3 | 1 | | | zwp_text_input_manager_v3 | 1 | |
| zwp_virtual_keyboard_manager_v1 | 1 | Yes | | zwp_virtual_keyboard_manager_v1 | 1 | Yes |
| zxdg_decoration_manager_v1 | 1 | | | zxdg_decoration_manager_v1 | 1 | |

View file

@ -3,6 +3,7 @@
- Floating windows can now be configured to be shown above fullscreen windows - Floating windows can now be configured to be shown above fullscreen windows
by using the `enable-float-above-fullscreen` action. by using the `enable-float-above-fullscreen` action.
- Implement xdg-toplevel-tag-v1. - Implement xdg-toplevel-tag-v1.
- Implement tablet-v2 version 2.
# 1.10.0 (2025-04-22) # 1.10.0 (2025-04-22)

View file

@ -430,6 +430,12 @@ pub enum InputEvent {
source: Option<TabletStripEventSource>, source: Option<TabletStripEventSource>,
position: Option<f64>, position: Option<f64>,
}, },
TabletPadDial {
time_usec: u64,
pad: TabletPadId,
dial: u32,
value120: i32,
},
TouchDown { TouchDown {
time_usec: u64, time_usec: u64,
id: i32, id: i32,

View file

@ -697,6 +697,7 @@ impl InputDevice for MetalInputDevice {
name: dev.name(), name: dev.name(),
pid: dev.product(), pid: dev.product(),
vid: dev.vendor(), vid: dev.vendor(),
bustype: dev.bustype(),
path: self.syspath.as_bytes().as_bstr().to_string(), path: self.syspath.as_bytes().as_bstr().to_string(),
})) }))
} }
@ -718,6 +719,7 @@ impl InputDevice for MetalInputDevice {
let buttons = dev.pad_num_buttons(); let buttons = dev.pad_num_buttons();
let strips = dev.pad_num_strips(); let strips = dev.pad_num_strips();
let rings = dev.pad_num_rings(); let rings = dev.pad_num_rings();
let dials = dev.pad_num_dials();
let mut groups = vec![]; let mut groups = vec![];
for n in 0..dev.pad_num_mode_groups() { for n in 0..dev.pad_num_mode_groups() {
let Some(group) = dev.pad_mode_group(n) else { let Some(group) = dev.pad_mode_group(n) else {
@ -727,6 +729,7 @@ impl InputDevice for MetalInputDevice {
buttons: (0..buttons).filter(|b| group.has_button(*b)).collect(), buttons: (0..buttons).filter(|b| group.has_button(*b)).collect(),
rings: (0..rings).filter(|b| group.has_ring(*b)).collect(), rings: (0..rings).filter(|b| group.has_ring(*b)).collect(),
strips: (0..strips).filter(|b| group.has_strip(*b)).collect(), strips: (0..strips).filter(|b| group.has_strip(*b)).collect(),
dials: (0..dials).filter(|b| group.has_dial(*b)).collect(),
modes: group.num_modes(), modes: group.num_modes(),
mode: group.mode(), mode: group.mode(),
}); });
@ -738,6 +741,7 @@ impl InputDevice for MetalInputDevice {
buttons, buttons,
strips, strips,
rings, rings,
dials,
groups, groups,
})) }))
} }

View file

@ -71,7 +71,7 @@ impl MetalBackend {
break; break;
} }
Ok(n) if n.intersects(c::POLLERR | c::POLLHUP) => { Ok(n) if n.intersects(c::POLLERR | c::POLLHUP) => {
log::error!("libinput fd fd is in an error state"); log::error!("libinput fd is in an error state");
break; break;
} }
_ => {} _ => {}
@ -121,6 +121,7 @@ impl MetalBackend {
c::LIBINPUT_EVENT_TABLET_PAD_BUTTON => self.handle_tablet_pad_button(event), c::LIBINPUT_EVENT_TABLET_PAD_BUTTON => self.handle_tablet_pad_button(event),
c::LIBINPUT_EVENT_TABLET_PAD_RING => self.handle_tablet_pad_ring(event), c::LIBINPUT_EVENT_TABLET_PAD_RING => self.handle_tablet_pad_ring(event),
c::LIBINPUT_EVENT_TABLET_PAD_STRIP => self.handle_tablet_pad_strip(event), c::LIBINPUT_EVENT_TABLET_PAD_STRIP => self.handle_tablet_pad_strip(event),
c::LIBINPUT_EVENT_TABLET_PAD_DIAL => self.handle_tablet_pad_dial(event),
c::LIBINPUT_EVENT_TOUCH_DOWN => self.handle_touch_down(event), c::LIBINPUT_EVENT_TOUCH_DOWN => self.handle_touch_down(event),
c::LIBINPUT_EVENT_TOUCH_UP => self.handle_touch_up(event), c::LIBINPUT_EVENT_TOUCH_UP => self.handle_touch_up(event),
c::LIBINPUT_EVENT_TOUCH_MOTION => self.handle_touch_motion(event), c::LIBINPUT_EVENT_TOUCH_MOTION => self.handle_touch_motion(event),
@ -545,6 +546,25 @@ impl MetalBackend {
}); });
} }
fn handle_tablet_pad_dial(self: &Rc<Self>, event: LibInputEvent) {
let (event, dev) = unpack!(self, event, tablet_pad_event);
let Some(dial) = event.dial_number() else {
return;
};
let Some(value120) = event.dial_delta_v120() else {
return;
};
dev.event(InputEvent::TabletPadDial {
time_usec: event.time_usec(),
pad: match dev.tablet_pad_id.get() {
None => return,
Some(id) => id,
},
dial,
value120: value120 as _,
});
}
fn handle_touch_down(self: &Rc<Self>, event: LibInputEvent) { fn handle_touch_down(self: &Rc<Self>, event: LibInputEvent) {
let (event, dev) = unpack!(self, event, touch_event); let (event, dev) = unpack!(self, event, touch_event);
dev.event(InputEvent::TouchDown { dev.event(InputEvent::TouchDown {

View file

@ -12,13 +12,13 @@ use {
Axis120, AxisFrame, AxisInverted, AxisPx, AxisSource, AxisStop, Button, HoldBegin, Axis120, AxisFrame, AxisInverted, AxisPx, AxisSource, AxisStop, Button, HoldBegin,
HoldEnd, Key, Modifiers, PinchBegin, PinchEnd, PinchUpdate, PointerAbs, PointerRel, HoldEnd, Key, Modifiers, PinchBegin, PinchEnd, PinchUpdate, PointerAbs, PointerRel,
SwipeBegin, SwipeEnd, SwipeUpdate, SwitchEvent, TabletPadButton, SwipeBegin, SwipeEnd, SwipeUpdate, SwitchEvent, TabletPadButton,
TabletPadModeSwitch, TabletPadRingAngle, TabletPadRingFrame, TabletPadRingSource, TabletPadDialDelta, TabletPadDialFrame, TabletPadModeSwitch, TabletPadRingAngle,
TabletPadRingStop, TabletPadStripFrame, TabletPadStripPosition, TabletPadRingFrame, TabletPadRingSource, TabletPadRingStop, TabletPadStripFrame,
TabletPadStripSource, TabletPadStripStop, TabletToolButton, TabletToolDistance, TabletPadStripPosition, TabletPadStripSource, TabletPadStripStop, TabletToolButton,
TabletToolDown, TabletToolFrame, TabletToolMotion, TabletToolPressure, TabletToolDistance, TabletToolDown, TabletToolFrame, TabletToolMotion,
TabletToolProximityIn, TabletToolProximityOut, TabletToolRotation, TabletToolPressure, TabletToolProximityIn, TabletToolProximityOut,
TabletToolSlider, TabletToolTilt, TabletToolUp, TabletToolWheel, TouchCancel, TabletToolRotation, TabletToolSlider, TabletToolTilt, TabletToolUp,
TouchDown, TouchMotion, TouchUp, TabletToolWheel, TouchCancel, TouchDown, TouchMotion, TouchUp,
}, },
}, },
}, },
@ -82,6 +82,11 @@ pub struct PendingTabletPadRing {
stop: bool, stop: bool,
} }
#[derive(Default, Debug, Copy, Clone)]
pub struct PendingTabletPadDial {
value120: Option<i32>,
}
async fn run(seat_test: Rc<SeatTest>) { async fn run(seat_test: Rc<SeatTest>) {
let tc = &seat_test.tc; let tc = &seat_test.tc;
let comp = tc.jay_compositor().await; let comp = tc.jay_compositor().await;
@ -586,6 +591,30 @@ async fn run(seat_test: Rc<SeatTest>) {
} }
println!(); println!();
}); });
let tt = Rc::new(RefCell::new(PendingTabletPadDial::default()));
TabletPadDialDelta::handle(tc, se, tt.clone(), move |tt, ev| {
tt.borrow_mut().value120 = Some(ev.value120);
});
let st = seat_test.clone();
TabletPadDialFrame::handle(tc, se, tt.clone(), move |tt, ev| {
let tt = tt.take();
if !all && ev.seat != seat {
return;
}
if all {
print!("Seat: {}, ", st.name(ev.seat));
}
print!(
"Time: {:.4}, Device: {}, Dial: {}",
time(ev.time_usec),
ev.input_device,
ev.dial,
);
if let Some(val) = tt.value120 {
print!(", delta: {val}/120");
}
println!();
});
let st = seat_test.clone(); let st = seat_test.clone();
TouchDown::handle(tc, se, (), move |_, ev| { TouchDown::handle(tc, se, (), move |_, ev| {
if all || ev.seat == seat { if all || ev.seat == seat {

View file

@ -470,6 +470,27 @@ impl JaySeatEvents {
}); });
} }
pub fn send_tablet_pad_dial(
&self,
seat: SeatId,
pad: InputDeviceId,
time_usec: u64,
value120: i32,
dial: u32,
) {
self.client.event(TabletPadDialDelta {
self_id: self.id,
value120,
});
self.client.event(TabletPadDialFrame {
self_id: self.id,
seat: seat.raw(),
time_usec,
input_device: pad.raw(),
dial,
});
}
pub fn send_touch_down(&self, seat: SeatId, time_usec: u64, id: i32, x: Fixed, y: Fixed) { pub fn send_touch_down(&self, seat: SeatId, time_usec: u64, id: i32, x: Fixed, y: Fixed) {
self.client.event(TouchDown { self.client.event(TouchDown {
self_id: self.id, self_id: self.id,

View file

@ -266,6 +266,7 @@ impl WlSeatGlobal {
| InputEvent::TabletPadModeSwitch { time_usec, .. } | InputEvent::TabletPadModeSwitch { time_usec, .. }
| InputEvent::TabletPadRing { time_usec, .. } | InputEvent::TabletPadRing { time_usec, .. }
| InputEvent::TabletPadStrip { time_usec, .. } | InputEvent::TabletPadStrip { time_usec, .. }
| InputEvent::TabletPadDial { time_usec, .. }
| InputEvent::TouchFrame { time_usec, .. } => { | InputEvent::TouchFrame { time_usec, .. } => {
self.last_input_usec.set(time_usec); self.last_input_usec.set(time_usec);
if self.idle_notifications.is_not_empty() { if self.idle_notifications.is_not_empty() {
@ -314,6 +315,7 @@ impl WlSeatGlobal {
InputEvent::TabletPadModeSwitch { .. } => {} InputEvent::TabletPadModeSwitch { .. } => {}
InputEvent::TabletPadRing { .. } => {} InputEvent::TabletPadRing { .. } => {}
InputEvent::TabletPadStrip { .. } => {} InputEvent::TabletPadStrip { .. } => {}
InputEvent::TabletPadDial { .. } => {}
InputEvent::TouchDown { .. } => {} InputEvent::TouchDown { .. } => {}
InputEvent::TouchUp { .. } => {} InputEvent::TouchUp { .. } => {}
InputEvent::TouchMotion { .. } => {} InputEvent::TouchMotion { .. } => {}
@ -458,6 +460,12 @@ impl WlSeatGlobal {
source, source,
position, position,
} => self.tablet_event_pad_strip(pad, strip, source, position, time_usec), } => self.tablet_event_pad_strip(pad, strip, source, position, time_usec),
InputEvent::TabletPadDial {
time_usec,
pad,
dial,
value120,
} => self.tablet_event_pad_dial(pad, dial, value120, time_usec),
InputEvent::TouchDown { InputEvent::TouchDown {
time_usec, time_usec,
id, id,

View file

@ -7,7 +7,8 @@ use {
WlSeatGlobal, WlSeatGlobal,
tablet::{ tablet::{
pad_owner::PadOwnerHolder, tablet_bindings::TabletBindings, pad_owner::PadOwnerHolder, tablet_bindings::TabletBindings,
tool_owner::ToolOwnerHolder, zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2, tool_owner::ToolOwnerHolder, zwp_tablet_pad_dial_v2::ZwpTabletPadDialV2,
zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2,
zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2,
zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2,
zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_seat_v2::ZwpTabletSeatV2, zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_seat_v2::ZwpTabletSeatV2,
@ -35,6 +36,7 @@ mod tablet_bindings;
mod tool; mod tool;
pub mod tool_owner; pub mod tool_owner;
pub mod zwp_tablet_manager_v2; pub mod zwp_tablet_manager_v2;
pub mod zwp_tablet_pad_dial_v2;
pub mod zwp_tablet_pad_group_v2; pub mod zwp_tablet_pad_group_v2;
pub mod zwp_tablet_pad_ring_v2; pub mod zwp_tablet_pad_ring_v2;
pub mod zwp_tablet_pad_strip_v2; pub mod zwp_tablet_pad_strip_v2;
@ -58,6 +60,7 @@ pub struct TabletInit {
pub name: String, pub name: String,
pub pid: u32, pub pid: u32,
pub vid: u32, pub vid: u32,
pub bustype: Option<u32>,
pub path: String, pub path: String,
} }
@ -79,6 +82,7 @@ pub struct TabletPadInit {
pub buttons: u32, pub buttons: u32,
pub strips: u32, pub strips: u32,
pub rings: u32, pub rings: u32,
pub dials: u32,
pub groups: Vec<TabletPadGroupInit>, pub groups: Vec<TabletPadGroupInit>,
} }
@ -87,6 +91,7 @@ pub struct TabletPadGroupInit {
pub buttons: Vec<u32>, pub buttons: Vec<u32>,
pub rings: Vec<u32>, pub rings: Vec<u32>,
pub strips: Vec<u32>, pub strips: Vec<u32>,
pub dials: Vec<u32>,
pub modes: u32, pub modes: u32,
pub mode: u32, pub mode: u32,
} }
@ -112,6 +117,7 @@ pub struct Tablet {
name: String, name: String,
pid: u32, pid: u32,
vid: u32, vid: u32,
bustype: Option<u32>,
path: String, path: String,
bindings: TabletBindings<ZwpTabletV2>, bindings: TabletBindings<ZwpTabletV2>,
tools: CopyHashMap<TabletToolId, Rc<TabletTool>>, tools: CopyHashMap<TabletToolId, Rc<TabletTool>>,
@ -186,6 +192,7 @@ pub struct TabletPad {
groups: Vec<Rc<TabletPadGroup>>, groups: Vec<Rc<TabletPadGroup>>,
strips: Vec<Rc<TabletPadStrip>>, strips: Vec<Rc<TabletPadStrip>>,
rings: Vec<Rc<TabletPadRing>>, rings: Vec<Rc<TabletPadRing>>,
dials: Vec<Rc<TabletPadDial>>,
node: CloneCell<Rc<dyn Node>>, node: CloneCell<Rc<dyn Node>>,
pub(super) pad_owner: PadOwnerHolder, pub(super) pad_owner: PadOwnerHolder,
} }
@ -196,6 +203,7 @@ pub struct TabletPadGroup {
modes: u32, modes: u32,
rings: Vec<u32>, rings: Vec<u32>,
strips: Vec<u32>, strips: Vec<u32>,
dials: Vec<u32>,
bindings: TabletBindings<ZwpTabletPadGroupV2>, bindings: TabletBindings<ZwpTabletPadGroupV2>,
} }
@ -207,6 +215,10 @@ pub struct TabletPadRing {
bindings: TabletBindings<ZwpTabletPadRingV2>, bindings: TabletBindings<ZwpTabletPadRingV2>,
} }
pub struct TabletPadDial {
bindings: TabletBindings<ZwpTabletPadDialV2>,
}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum TabletRingEventSource { pub enum TabletRingEventSource {
Finger, Finger,
@ -269,6 +281,7 @@ impl WlSeatGlobal {
name: init.name.clone(), name: init.name.clone(),
pid: init.pid, pid: init.pid,
vid: init.vid, vid: init.vid,
bustype: init.bustype,
path: init.path.clone(), path: init.path.clone(),
bindings: Default::default(), bindings: Default::default(),
tools: Default::default(), tools: Default::default(),
@ -319,6 +332,9 @@ impl WlSeatGlobal {
for strips in &pad.strips { for strips in &pad.strips {
strips.bindings.clear(); strips.bindings.clear();
} }
for dials in &pad.dials {
dials.bindings.clear();
}
} }
} }

View file

@ -6,9 +6,10 @@ use {
wl_seat::{ wl_seat::{
WlSeatGlobal, WlSeatGlobal,
tablet::{ tablet::{
PadButtonState, TabletPad, TabletPadGroup, TabletPadId, TabletPadInit, PadButtonState, TabletPad, TabletPadDial, TabletPadGroup, TabletPadId,
TabletPadRing, TabletPadStrip, TabletRingEventSource, TabletStripEventSource, TabletPadInit, TabletPadRing, TabletPadStrip, TabletRingEventSource,
normalizeu, zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_v2::ZwpTabletV2, TabletStripEventSource, normalizeu, zwp_tablet_pad_v2::ZwpTabletPadV2,
zwp_tablet_v2::ZwpTabletV2,
}, },
}, },
wl_surface::WlSurface, wl_surface::WlSurface,
@ -33,6 +34,12 @@ impl WlSeatGlobal {
bindings: Default::default(), bindings: Default::default(),
})); }));
} }
let mut dials = Vec::new();
for _ in 0..init.dials {
dials.push(Rc::new(TabletPadDial {
bindings: Default::default(),
}));
}
let mut groups = Vec::new(); let mut groups = Vec::new();
for group_init in &init.groups { for group_init in &init.groups {
groups.push(Rc::new(TabletPadGroup { groups.push(Rc::new(TabletPadGroup {
@ -41,6 +48,7 @@ impl WlSeatGlobal {
modes: group_init.modes, modes: group_init.modes,
rings: group_init.rings.clone(), rings: group_init.rings.clone(),
strips: group_init.strips.clone(), strips: group_init.strips.clone(),
dials: group_init.dials.clone(),
bindings: Default::default(), bindings: Default::default(),
})); }));
} }
@ -56,6 +64,7 @@ impl WlSeatGlobal {
groups, groups,
strips, strips,
rings, rings,
dials,
node: CloneCell::new(self.state.root.clone()), node: CloneCell::new(self.state.root.clone()),
pad_owner: Default::default(), pad_owner: Default::default(),
}); });
@ -161,6 +170,26 @@ impl WlSeatGlobal {
} }
} }
} }
pub fn tablet_event_pad_dial(
self: &Rc<Self>,
pad: TabletPadId,
dial: u32,
value120: i32,
time_usec: u64,
) {
if let Some(pad) = self.tablet.pads.get(&pad) {
self.state.for_each_seat_tester(|t| {
t.send_tablet_pad_dial(self.id, pad.dev, time_usec, value120, dial)
});
if pad.tablet.is_some() {
if let Some(dial) = pad.dials.get(dial as usize) {
let node = self.keyboard_node.get();
node.node_on_tablet_pad_dial(&pad, dial, value120, time_usec);
}
}
}
}
} }
impl TabletPad { impl TabletPad {
@ -235,6 +264,22 @@ impl TabletPad {
}); });
} }
pub fn surface_dial(
self: &Rc<Self>,
n: &WlSurface,
dial: &Rc<TabletPadDial>,
value120: i32,
time_usec: u64,
) {
let time = usec_to_msec(time_usec);
self.seat.tablet_for_each_seat(n, |s| {
if let Some(dial) = dial.bindings.get(&s) {
dial.send_delta(value120);
dial.send_frame(time);
}
});
}
pub fn surface_strip( pub fn surface_strip(
self: &Rc<Self>, self: &Rc<Self>,
n: &WlSurface, n: &WlSurface,

View file

@ -57,7 +57,7 @@ impl Global for ZwpTabletManagerV2Global {
} }
fn version(&self) -> u32 { fn version(&self) -> u32 {
1 2
} }
} }

View file

@ -0,0 +1,74 @@
use {
crate::{
client::{Client, ClientError},
ifs::wl_seat::tablet::{TabletPadDial, zwp_tablet_seat_v2::ZwpTabletSeatV2},
leaks::Tracker,
object::{Object, Version},
wire::{ZwpTabletPadDialV2Id, zwp_tablet_pad_dial_v2::*},
},
std::rc::Rc,
thiserror::Error,
};
pub struct ZwpTabletPadDialV2 {
pub id: ZwpTabletPadDialV2Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
pub seat: Rc<ZwpTabletSeatV2>,
pub dial: Rc<TabletPadDial>,
}
impl ZwpTabletPadDialV2 {
pub fn detach(&self) {
self.dial.bindings.remove(&self.seat);
}
pub fn send_delta(&self, value120: i32) {
self.client.event(Delta {
self_id: self.id,
value120,
});
}
pub fn send_frame(&self, time: u32) {
self.client.event(Frame {
self_id: self.id,
time,
});
}
}
impl ZwpTabletPadDialV2RequestHandler for ZwpTabletPadDialV2 {
type Error = ZwpTabletPadDialV2Error;
fn set_feedback(&self, _req: SetFeedback<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = ZwpTabletPadDialV2;
version = self.version;
}
impl Object for ZwpTabletPadDialV2 {
fn break_loops(&self) {
self.detach();
}
}
simple_add_obj!(ZwpTabletPadDialV2);
#[derive(Debug, Error)]
pub enum ZwpTabletPadDialV2Error {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(ZwpTabletPadDialV2Error, ClientError);

View file

@ -2,7 +2,8 @@ use {
crate::{ crate::{
client::{Client, ClientError}, client::{Client, ClientError},
ifs::wl_seat::tablet::{ ifs::wl_seat::tablet::{
TabletPadGroup, zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, TabletPadGroup, zwp_tablet_pad_dial_v2::ZwpTabletPadDialV2,
zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2,
zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_seat_v2::ZwpTabletSeatV2, zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_seat_v2::ZwpTabletSeatV2,
}, },
leaks::Tracker, leaks::Tracker,
@ -48,6 +49,13 @@ impl ZwpTabletPadGroupV2 {
}); });
} }
pub fn send_dial(&self, dial: &ZwpTabletPadDialV2) {
self.client.event(Dial {
self_id: self.id,
dial: dial.id,
});
}
pub fn send_modes(&self, modes: u32) { pub fn send_modes(&self, modes: u32) {
self.client.event(Modes { self.client.event(Modes {
self_id: self.id, self_id: self.id,

View file

@ -4,7 +4,8 @@ use {
ifs::wl_seat::{ ifs::wl_seat::{
WlSeatGlobal, WlSeatGlobal,
tablet::{ tablet::{
Tablet, TabletPad, TabletTool, zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2, Tablet, TabletPad, TabletTool, zwp_tablet_pad_dial_v2::ZwpTabletPadDialV2,
zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2,
zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2, zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2,
zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_pad_v2::ZwpTabletPadV2, zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2, zwp_tablet_pad_v2::ZwpTabletPadV2,
zwp_tablet_tool_v2::ZwpTabletToolV2, zwp_tablet_v2::ZwpTabletV2, zwp_tablet_tool_v2::ZwpTabletToolV2, zwp_tablet_v2::ZwpTabletV2,
@ -18,6 +19,9 @@ use {
thiserror::Error, thiserror::Error,
}; };
const BUSTYPE_SINCE: Version = Version(2);
const DIALS_SINCE: Version = Version(2);
pub struct ZwpTabletSeatV2 { pub struct ZwpTabletSeatV2 {
pub id: ZwpTabletSeatV2Id, pub id: ZwpTabletSeatV2Id,
pub client: Rc<Client>, pub client: Rc<Client>,
@ -53,6 +57,11 @@ impl ZwpTabletSeatV2 {
obj.send_name(&tablet.name); obj.send_name(&tablet.name);
obj.send_id(tablet.vid, tablet.pid); obj.send_id(tablet.vid, tablet.pid);
obj.send_path(&tablet.path); obj.send_path(&tablet.path);
if obj.version >= BUSTYPE_SINCE {
if let Some(bustype) = tablet.bustype {
obj.send_bustype(bustype);
}
}
obj.send_done(); obj.send_done();
tablet.bindings.add(self, &obj); tablet.bindings.add(self, &obj);
} }
@ -161,6 +170,25 @@ impl ZwpTabletSeatV2 {
group_obj.send_strip(&strip_obj); group_obj.send_strip(&strip_obj);
strip.bindings.add(self, &strip_obj); strip.bindings.add(self, &strip_obj);
} }
if self.version >= DIALS_SINCE {
for dial in &group.dials {
let Some(dial) = pad.dials.get(*dial as usize) else {
continue;
};
let dial_obj = Rc::new(ZwpTabletPadDialV2 {
id: id!(),
client: self.client.clone(),
seat: self.clone(),
tracker: Default::default(),
version: self.version,
dial: dial.clone(),
});
track!(self.client, dial_obj);
self.client.add_server_obj(&dial_obj);
group_obj.send_dial(&dial_obj);
dial.bindings.add(self, &dial_obj);
}
}
group_obj.send_done(); group_obj.send_done();
} }
obj.send_done(); obj.send_done();

View file

@ -53,6 +53,13 @@ impl ZwpTabletV2 {
pub fn send_removed(&self) { pub fn send_removed(&self) {
self.client.event(Removed { self_id: self.id }); self.client.event(Removed { self_id: self.id });
} }
pub fn send_bustype(&self, bustype: u32) {
self.client.event(Bustype {
self_id: self.id,
bustype,
});
}
} }
impl ZwpTabletV2RequestHandler for ZwpTabletV2 { impl ZwpTabletV2RequestHandler for ZwpTabletV2 {

View file

@ -39,9 +39,9 @@ use {
wl_seat::{ wl_seat::{
Dnd, NodeSeatState, SeatId, WlSeatGlobal, Dnd, NodeSeatState, SeatId, WlSeatGlobal,
tablet::{ tablet::{
PadButtonState, TabletPad, TabletPadGroup, TabletPadRing, TabletPadStrip, PadButtonState, TabletPad, TabletPadDial, TabletPadGroup, TabletPadRing,
TabletRingEventSource, TabletStripEventSource, TabletTool, TabletToolChanges, TabletPadStrip, TabletRingEventSource, TabletStripEventSource, TabletTool,
ToolButtonState, TabletToolChanges, ToolButtonState,
}, },
text_input::TextInputConnection, text_input::TextInputConnection,
wl_pointer::PendingScroll, wl_pointer::PendingScroll,
@ -2016,6 +2016,16 @@ impl Node for WlSurface {
pad.surface_strip(self, strip, source, position, time_usec); pad.surface_strip(self, strip, source, position, time_usec);
} }
fn node_on_tablet_pad_dial(
&self,
pad: &Rc<TabletPad>,
dial: &Rc<TabletPadDial>,
value120: i32,
time_usec: u64,
) {
pad.surface_dial(self, dial, value120, time_usec);
}
fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, time_usec: u64) { fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, time_usec: u64) {
tool.surface_leave(self, time_usec); tool.surface_leave(self, time_usec);
} }

View file

@ -140,6 +140,7 @@ cenum! {
LIBINPUT_EVENT_TABLET_PAD_RING = 701, LIBINPUT_EVENT_TABLET_PAD_RING = 701,
LIBINPUT_EVENT_TABLET_PAD_STRIP = 702, LIBINPUT_EVENT_TABLET_PAD_STRIP = 702,
LIBINPUT_EVENT_TABLET_PAD_KEY = 703, LIBINPUT_EVENT_TABLET_PAD_KEY = 703,
LIBINPUT_EVENT_TABLET_PAD_DIAL = 704,
LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800, LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800,
LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE = 801, LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE = 801,
LIBINPUT_EVENT_GESTURE_SWIPE_END = 802, LIBINPUT_EVENT_GESTURE_SWIPE_END = 802,

View file

@ -25,18 +25,19 @@ use {
libinput_device_config_tap_set_drag_enabled, libinput_device_config_tap_set_drag_enabled,
libinput_device_config_tap_set_drag_lock_enabled, libinput_device_config_tap_set_drag_lock_enabled,
libinput_device_config_tap_set_enabled, libinput_device_get_device_group, libinput_device_config_tap_set_enabled, libinput_device_get_device_group,
libinput_device_get_id_product, libinput_device_get_id_vendor, libinput_device_get_id_bustype, libinput_device_get_id_product,
libinput_device_get_name, libinput_device_get_user_data, libinput_device_group, libinput_device_get_id_vendor, libinput_device_get_name, libinput_device_get_user_data,
libinput_device_group_get_user_data, libinput_device_group_set_user_data, libinput_device_group, libinput_device_group_get_user_data,
libinput_device_has_capability, libinput_device_set_user_data, libinput_device_group_set_user_data, libinput_device_has_capability,
libinput_device_tablet_pad_get_mode_group, libinput_device_tablet_pad_get_num_buttons, libinput_device_set_user_data, libinput_device_tablet_pad_get_mode_group,
libinput_device_tablet_pad_get_num_buttons, libinput_device_tablet_pad_get_num_dials,
libinput_device_tablet_pad_get_num_mode_groups, libinput_device_tablet_pad_get_num_mode_groups,
libinput_device_tablet_pad_get_num_rings, libinput_device_tablet_pad_get_num_strips, libinput_device_tablet_pad_get_num_rings, libinput_device_tablet_pad_get_num_strips,
libinput_device_unref, libinput_path_remove_device, libinput_tablet_pad_mode_group, libinput_device_unref, libinput_path_remove_device, libinput_tablet_pad_mode_group,
libinput_tablet_pad_mode_group_get_index, libinput_tablet_pad_mode_group_get_mode, libinput_tablet_pad_mode_group_get_index, libinput_tablet_pad_mode_group_get_mode,
libinput_tablet_pad_mode_group_get_num_modes, libinput_tablet_pad_mode_group_get_num_modes,
libinput_tablet_pad_mode_group_has_button, libinput_tablet_pad_mode_group_has_ring, libinput_tablet_pad_mode_group_has_button, libinput_tablet_pad_mode_group_has_dial,
libinput_tablet_pad_mode_group_has_strip, libinput_tablet_pad_mode_group_has_ring, libinput_tablet_pad_mode_group_has_strip,
}, },
}, },
bstr::ByteSlice, bstr::ByteSlice,
@ -223,6 +224,10 @@ impl<'a> LibInputDevice<'a> {
unsafe { libinput_device_get_id_vendor(self.dev) as u32 } unsafe { libinput_device_get_id_vendor(self.dev) as u32 }
} }
pub fn bustype(&self) -> Option<u32> {
libinput_device_get_id_bustype.map(|f| unsafe { f(self.dev) as u32 })
}
pub fn pad_num_buttons(&self) -> u32 { pub fn pad_num_buttons(&self) -> u32 {
match unsafe { libinput_device_tablet_pad_get_num_buttons(self.dev) } { match unsafe { libinput_device_tablet_pad_get_num_buttons(self.dev) } {
-1 => 0, -1 => 0,
@ -244,6 +249,17 @@ impl<'a> LibInputDevice<'a> {
} }
} }
pub fn pad_num_dials(&self) -> u32 {
match unsafe {
libinput_device_tablet_pad_get_num_dials
.map(|f| f(self.dev))
.unwrap_or_default()
} {
-1 => 0,
n => n as u32,
}
}
pub fn pad_num_mode_groups(&self) -> u32 { pub fn pad_num_mode_groups(&self) -> u32 {
match unsafe { libinput_device_tablet_pad_get_num_mode_groups(self.dev) } { match unsafe { libinput_device_tablet_pad_get_num_mode_groups(self.dev) } {
-1 => 0, -1 => 0,
@ -316,6 +332,15 @@ impl<'a> LibInputTabletPadModeGroup<'a> {
pub fn has_strip(&self, strip: u32) -> bool { pub fn has_strip(&self, strip: u32) -> bool {
unsafe { libinput_tablet_pad_mode_group_has_strip(self.group, strip as _) != 0 } unsafe { libinput_tablet_pad_mode_group_has_strip(self.group, strip as _) != 0 }
} }
pub fn has_dial(&self, dial: u32) -> bool {
unsafe {
libinput_tablet_pad_mode_group_has_dial
.map(|f| f(self.group, dial as _))
.unwrap_or_default()
!= 0
}
}
} }
impl RegisteredDevice { impl RegisteredDevice {

View file

@ -27,7 +27,9 @@ use {
libinput_event_switch, libinput_event_switch_get_switch, libinput_event_switch, libinput_event_switch_get_switch,
libinput_event_switch_get_switch_state, libinput_event_switch_get_time_usec, libinput_event_switch_get_switch_state, libinput_event_switch_get_time_usec,
libinput_event_tablet_pad, libinput_event_tablet_pad_get_button_number, libinput_event_tablet_pad, libinput_event_tablet_pad_get_button_number,
libinput_event_tablet_pad_get_button_state, libinput_event_tablet_pad_get_mode, libinput_event_tablet_pad_get_button_state,
libinput_event_tablet_pad_get_dial_delta_v120,
libinput_event_tablet_pad_get_dial_number, libinput_event_tablet_pad_get_mode,
libinput_event_tablet_pad_get_mode_group, libinput_event_tablet_pad_get_ring_number, libinput_event_tablet_pad_get_mode_group, libinput_event_tablet_pad_get_ring_number,
libinput_event_tablet_pad_get_ring_position, libinput_event_tablet_pad_get_ring_source, libinput_event_tablet_pad_get_ring_position, libinput_event_tablet_pad_get_ring_source,
libinput_event_tablet_pad_get_strip_number, libinput_event_tablet_pad_get_strip_number,
@ -459,6 +461,14 @@ impl<'a> LibInputEventTabletPad<'a> {
unsafe { TabletPadStripAxisSource(libinput_event_tablet_pad_get_strip_source(self.event)) } unsafe { TabletPadStripAxisSource(libinput_event_tablet_pad_get_strip_source(self.event)) }
} }
pub fn dial_number(&self) -> Option<u32> {
libinput_event_tablet_pad_get_dial_number.map(|f| unsafe { f(self.event) as u32 })
}
pub fn dial_delta_v120(&self) -> Option<f64> {
libinput_event_tablet_pad_get_dial_delta_v120.map(|f| unsafe { f(self.event) })
}
pub fn button_number(&self) -> u32 { pub fn button_number(&self) -> u32 {
unsafe { libinput_event_tablet_pad_get_button_number(self.event) } unsafe { libinput_event_tablet_pad_get_button_number(self.event) }
} }

View file

@ -1,4 +1,4 @@
use uapi::c; use {libloading::os::unix::Library, std::sync::LazyLock, uapi::c};
include!(concat!(env!("OUT_DIR"), "/libinput_tys.rs")); include!(concat!(env!("OUT_DIR"), "/libinput_tys.rs"));
@ -393,3 +393,38 @@ pub struct libinput_interface {
) -> c::c_int, ) -> c::c_int,
pub close_restricted: unsafe extern "C" fn(fd: c::c_int, user_data: *mut c::c_void), pub close_restricted: unsafe extern "C" fn(fd: c::c_int, user_data: *mut c::c_void),
} }
macro_rules! dynload {
(
$(
fn $name:ident($($arg:ident: $ty:ty),* $(,)?) -> $ret:ty;
)*
) => {
$(
#[expect(non_upper_case_globals)]
pub static $name: LazyLock<Option<unsafe extern "C" fn($($arg: $ty),*) -> $ret>> = LazyLock::new(|| {
unsafe {
Library::this()
.get(concat!(stringify!($name), "\0").as_bytes())
.ok()
.map(|sym| *sym)
}
});
)*
};
}
dynload! {
fn libinput_device_get_id_bustype(device: *mut libinput_device) -> c::c_uint;
fn libinput_event_tablet_pad_get_dial_delta_v120(event: *mut libinput_event_tablet_pad) -> f64;
fn libinput_event_tablet_pad_get_dial_number(event: *mut libinput_event_tablet_pad) -> c::c_uint;
fn libinput_device_tablet_pad_get_num_dials(device: *mut libinput_device) -> c::c_int;
fn libinput_tablet_pad_mode_group_has_dial(
group: *mut libinput_tablet_pad_mode_group,
dial: c::c_uint,
) -> c::c_int;
}

View file

@ -7,9 +7,9 @@ use {
wl_seat::{ wl_seat::{
Dnd, NodeSeatState, WlSeatGlobal, Dnd, NodeSeatState, WlSeatGlobal,
tablet::{ tablet::{
PadButtonState, TabletPad, TabletPadGroup, TabletPadRing, TabletPadStrip, PadButtonState, TabletPad, TabletPadDial, TabletPadGroup, TabletPadRing,
TabletRingEventSource, TabletStripEventSource, TabletTool, TabletToolChanges, TabletPadStrip, TabletRingEventSource, TabletStripEventSource, TabletTool,
ToolButtonState, TabletToolChanges, ToolButtonState,
}, },
wl_pointer::PendingScroll, wl_pointer::PendingScroll,
}, },
@ -464,6 +464,19 @@ pub trait Node: 'static {
let _ = position; let _ = position;
} }
fn node_on_tablet_pad_dial(
&self,
pad: &Rc<TabletPad>,
dial: &Rc<TabletPadDial>,
value120: i32,
time_usec: u64,
) {
let _ = pad;
let _ = time_usec;
let _ = dial;
let _ = value120;
}
fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, time_usec: u64) { fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, time_usec: u64) {
let _ = tool; let _ = tool;
let _ = time_usec; let _ = time_usec;

View file

@ -267,3 +267,14 @@ event touch_cancel {
time_usec: pod(u64), time_usec: pod(u64),
id: i32, id: i32,
} }
event tablet_pad_dial_delta {
value120: i32,
}
event tablet_pad_dial_frame {
seat: u32,
time_usec: pod(u64),
input_device: u32,
dial: u32,
}

View file

@ -0,0 +1,15 @@
request set_feedback {
description: str,
serial: u32,
}
request destroy {
}
event delta {
value120: i32,
}
event frame {
time: u32,
}

View file

@ -25,3 +25,7 @@ event mode_switch {
serial: u32, serial: u32,
mode: u32, mode: u32,
} }
event dial {
dial: id(zwp_tablet_pad_dial_v2),
}

View file

@ -19,3 +19,7 @@ event done {
event removed { event removed {
} }
event bustype {
bustype: u32,
}