use std::collections::HashMap;
use std::io::Read;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub(crate) mod parser;
#[derive(Error, Debug)]
pub enum Error {
#[error("Invalid Argument: {message}")]
InvalidArgument { message: String },
#[error("Parsing failed: {message}")]
ParserError { message: String },
#[error("Failed to load DB: {message}")]
IO { message: String },
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error::IO {
message: format!("{e}"),
}
}
}
pub(crate) type Result<T> = std::result::Result<T, Error>;
pub struct CacheBuilder {
paths: Vec<PathBuf>,
}
impl CacheBuilder {
pub fn new() -> Self {
CacheBuilder { paths: Vec::new() }
}
pub fn add_default_includes(mut self) -> Self {
self.paths.push("/usr/share/libwacom".into());
self.paths.push("/etc/libwacom".into());
let xdg_dirs = xdg::BaseDirectories::with_prefix("libwacom").unwrap();
self.paths.push(xdg_dirs.get_config_home());
self
}
pub fn add_include(mut self, path: &Path) -> Self {
self.paths.push(path.into());
self
}
fn find_files(&self, suffix: &str) -> Result<Vec<PathBuf>> {
let mut files: HashMap<String, PathBuf> = HashMap::new();
for path in &self.paths {
std::fs::read_dir(path)?
.filter_map(|f| f.ok())
.filter(|f| f.file_type().map(|f| f.is_file()).is_ok())
.filter(|f| f.file_name().to_string_lossy().ends_with(suffix))
.for_each(|f| {
files.insert(f.file_name().to_string_lossy().to_string(), f.path());
});
}
let files: Vec<PathBuf> = files.into_values().collect();
Ok(files)
}
fn build_tablets(&self) -> Result<Vec<parser::TabletEntry>> {
let tablets = self
.find_files(".tablet")?
.iter()
.map(|path| parser::TabletFile::new(path))
.inspect(|res| match res {
Ok(_) => {}
Err(e) => log::warn!("{e}"),
})
.filter_map(|res| res.ok())
.flat_map(|tf| tf.entries)
.collect::<Vec<parser::TabletEntry>>();
Ok(tablets)
}
fn build_styli(&self) -> Result<Vec<parser::StylusEntry>> {
let styli = self
.find_files(".stylus")?
.iter()
.map(|path| parser::StylusFile::new(path))
.inspect(|res| match res {
Ok(_) => {}
Err(e) => log::warn!("{e}"),
})
.filter_map(|res| res.ok())
.flat_map(|sf| sf.styli)
.collect::<Vec<parser::StylusEntry>>();
Ok(styli)
}
pub fn build(self) -> Result<Cache> {
let mut tablet_entries = self.build_tablets()?;
let stylus_entries = self.build_styli()?;
let mut groups: HashMap<String, Vec<StylusId>> = HashMap::new();
for stylus in &stylus_entries {
if let Some(stylus_group) = &stylus.group {
groups
.entry(stylus_group.clone())
.or_default()
.push(stylus.id);
}
}
for tablet in &mut tablet_entries {
tablet.styli = tablet
.styli
.iter_mut()
.flat_map(|r| match r {
StylusRef::ID(id) => vec![StylusRef::ID(*id)],
StylusRef::Group(group) => groups
.get(group)
.unwrap_or(&Vec::<StylusId>::new())
.iter()
.map(|id: &StylusId| StylusRef::ID(*id))
.collect(),
})
.collect();
}
let tools: Vec<Tool> = stylus_entries
.iter()
.enumerate()
.filter(|(_, entry)| {
entry
.paired_ids
.map(|id| stylus_entries.iter().any(|p| p.id == id))
.unwrap_or(true)
})
.filter(|(_, entry)| entry.eraser_type != Some(EraserType::Invert))
.map(|(idx, s)| {
if s.tool_type == parser::ToolType::Puck {
Ok(Tool::Mouse(Mouse {
idx,
id: s.id,
axes: s.axes.clone(),
name: s.name.clone(),
buttons: (0..s.num_buttons).map(|_| ToolButton {}).collect(),
has_lens: s.has_lens,
}))
} else {
let st = match s.tool_type {
parser::ToolType::General => StylusType::General,
parser::ToolType::Inking => StylusType::Inking,
parser::ToolType::Airbrush => StylusType::Airbrush,
parser::ToolType::Classic => StylusType::Classic,
parser::ToolType::Marker => StylusType::Marker,
parser::ToolType::Stroke => StylusType::Stroke,
parser::ToolType::Pen3D => StylusType::Pen3D,
parser::ToolType::Mobile => StylusType::Mobile,
_ => {
return Err(Error::ParserError {
message: format!("Unsupported tool type {:?}", s.tool_type),
})
}
};
let stylus = Stylus {
idx,
id: s.id,
stylus_type: st,
eraser_type: s.eraser_type,
axes: s.axes.clone(),
name: s.name.clone(),
buttons: (0..s.num_buttons).map(|_| ToolButton {}).collect(),
};
if s.eraser_type.is_none_or(|t| t == EraserType::Button) {
Ok(Tool::Stylus(stylus))
} else {
let paired_id = s.paired_ids.unwrap();
let eraser: Eraser = stylus_entries
.iter()
.filter(|e| e.id == paired_id)
.map(|e| Eraser {
idx,
id: e.id,
eraser_type: e.eraser_type.unwrap(),
axes: e.axes.clone(),
name: e.name.clone(),
buttons: Vec::new(),
})
.take(1)
.next()
.unwrap();
Ok(Tool::StylusWithEraser(stylus, eraser))
}
}
})
.collect::<Result<Vec<Tool>>>()?;
let tablets = tablet_entries
.into_iter()
.enumerate()
.map(|(idx, t)| Tablet {
idx,
name: t.name,
model_name: t.model_name,
kernel_name: t.device_match.name,
fw_version: t.device_match.fw,
layout: t.layout,
width: Length { inches: t.width },
height: Length { inches: t.height },
form_factor: FormFactor::from_flags(&t.integrated_in),
paired_id: t.paired_id.map(|m| m.to_device_id()),
bustype: t.device_match.bustype,
vid: t.device_match.vid,
pid: t.device_match.pid,
is_reversible: t.reversible,
has_touch: t.touch,
has_touchswitch: t.touchswitch,
has_stylus: t.stylus,
buttons: t
.buttons
.iter()
.map(|b| crate::Button {
index: crate::ButtonIndex(b.index.0),
location: b.location,
evdev_code: crate::EvdevCode(b.evdev_code.0),
})
.collect(),
rings: t.rings,
strips: t.strips,
dials: t.dials,
tools: tools
.iter()
.filter(|tool| {
t.styli.iter().any(|id| match id {
StylusRef::ID(id) => {
tool.vendor_id() == id.vendor_id() && tool.tool_id() == id.tool_id()
}
_ => false,
})
})
.cloned()
.collect::<Vec<Tool>>(),
})
.collect();
Ok(Cache { tablets, tools })
}
}
impl Default for CacheBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub enum BusType {
USB,
Bluetooth,
Serial,
I2C,
}
impl std::fmt::Display for BusType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
BusType::USB => "usb",
BusType::Bluetooth => "bluetooth",
BusType::Serial => "serial",
BusType::I2C => "i2c",
}
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct VendorId(u16);
impl std::ops::Deref for VendorId {
type Target = u16;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::LowerHex for VendorId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = self.0;
std::fmt::LowerHex::fmt(&val, f)
}
}
impl std::fmt::UpperHex for VendorId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = self.0;
std::fmt::UpperHex::fmt(&val, f)
}
}
impl From<VendorId> for u16 {
fn from(vid: VendorId) -> u16 {
vid.0
}
}
impl From<&VendorId> for u16 {
fn from(vid: &VendorId) -> u16 {
vid.0
}
}
impl From<u16> for VendorId {
fn from(vid: u16) -> VendorId {
VendorId(vid)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct ProductId(pub u16);
impl std::ops::Deref for ProductId {
type Target = u16;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::LowerHex for ProductId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = self.0;
std::fmt::LowerHex::fmt(&val, f)
}
}
impl std::fmt::UpperHex for ProductId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = self.0;
std::fmt::UpperHex::fmt(&val, f)
}
}
impl From<ProductId> for u16 {
fn from(pid: ProductId) -> u16 {
pid.0
}
}
impl From<&ProductId> for u16 {
fn from(pid: &ProductId) -> u16 {
pid.0
}
}
impl From<u16> for ProductId {
fn from(pid: u16) -> ProductId {
ProductId(pid)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct ToolId(pub u32);
impl std::ops::Deref for ToolId {
type Target = u32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::LowerHex for ToolId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = self.0;
std::fmt::LowerHex::fmt(&val, f)
}
}
impl std::fmt::UpperHex for ToolId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = self.0;
std::fmt::UpperHex::fmt(&val, f)
}
}
impl From<ToolId> for u32 {
fn from(pid: ToolId) -> u32 {
pid.0
}
}
impl From<&ToolId> for u32 {
fn from(pid: &ToolId) -> u32 {
pid.0
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct DeviceId {
bustype: BusType,
vid: VendorId,
pid: ProductId,
}
impl DeviceId {
pub fn bus_type(&self) -> BusType {
self.bustype
}
pub fn vendor_id(&self) -> VendorId {
self.vid
}
pub fn product_id(&self) -> ProductId {
self.pid
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct StylusId {
vid: VendorId,
pid: ToolId,
}
impl StylusId {
pub fn vendor_id(&self) -> VendorId {
self.vid
}
pub fn tool_id(&self) -> ToolId {
self.pid
}
}
pub trait Units {
fn inches(&self) -> f32;
fn cm(&self) -> f32 {
self.inches() * 2.54
}
fn mm(&self) -> f32 {
self.inches() * 25.4
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
pub struct Length {
inches: usize,
}
impl Units for Length {
fn inches(&self) -> f32 {
self.inches as f32
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
pub enum FormFactor {
ExternalDisplay,
InternalDisplay,
Remote,
}
impl FormFactor {
fn from_flags(flags: &[parser::IntegrationFlags]) -> Option<FormFactor> {
let ff = flags.iter().fold(None, |acc, f| match (acc, f) {
(_, parser::IntegrationFlags::Remote) => Some(FormFactor::Remote),
(_, parser::IntegrationFlags::System) => Some(FormFactor::InternalDisplay),
(Some(FormFactor::InternalDisplay), parser::IntegrationFlags::Display) => {
Some(FormFactor::InternalDisplay)
}
(_, parser::IntegrationFlags::Display) => Some(FormFactor::ExternalDisplay),
});
ff
}
}
#[derive(Debug, Clone)]
pub struct Cache {
tablets: Vec<Tablet>,
tools: Vec<Tool>,
}
impl Cache {
pub fn new() -> Result<Self> {
CacheBuilder::new().add_default_includes().build()
}
pub fn iter(&self) -> impl Iterator<Item = &Tablet> {
self.tablets()
}
pub fn tablets(&self) -> impl Iterator<Item = &Tablet> {
self.tablets.iter()
}
pub fn tools(&self) -> impl Iterator<Item = &Tool> {
self.tools.iter()
}
}
impl IntoIterator for Cache {
type Item = Tablet;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.tablets.into_iter()
}
}
#[derive(Debug)]
pub struct TabletInfo {
bustype: Option<BusType>,
vid: Option<VendorId>,
pid: Option<ProductId>,
name: Option<String>,
kernel_name: Option<String>,
uniq: Option<String>,
}
impl TabletInfo {
pub fn new() -> TabletInfo {
TabletInfo {
bustype: None,
pid: None,
vid: None,
name: None,
kernel_name: None,
uniq: None,
}
}
pub fn new_from_path(path: &Path) -> Result<TabletInfo> {
let sysfs = if path.starts_with("/dev/input")
&& path
.file_name()
.unwrap()
.to_string_lossy()
.starts_with("event")
{
let node = path.file_name().ok_or(Error::InvalidArgument {
message: format!("Invalid path {path:?}"),
})?;
PathBuf::from("/sys/class/input")
.join(node)
.join(String::from("device"))
} else if path.starts_with("/sys") && path.is_dir() {
let pathbuf = PathBuf::from(path);
if pathbuf.join("id").as_path().is_dir() {
pathbuf
} else {
pathbuf.join("device")
}
} else {
return Err(Error::InvalidArgument {
message: format!("Don't know how to handle {path:?}"),
});
};
let mut bustype = String::new();
std::fs::File::open(sysfs.join("id").join("bustype"))?.read_to_string(&mut bustype)?;
let mut vid = String::new();
std::fs::File::open(sysfs.join("id").join("vendor"))?.read_to_string(&mut vid)?;
let mut pid = String::new();
std::fs::File::open(sysfs.join("id").join("product"))?.read_to_string(&mut pid)?;
let mut name = String::new();
std::fs::File::open(sysfs.join("name"))?.read_to_string(&mut name)?;
let mut uniq = String::new();
std::fs::File::open(sysfs.join("uniq"))?.read_to_string(&mut uniq)?;
let bustype: BusType = u16::from_str_radix(bustype.as_str().trim(), 16)
.map_err(|_| Error::IO {
message: format!("Failed to parse bustype {bustype}"),
})
.map(|b| match b {
0x3 => Ok(BusType::USB),
0x5 => Ok(BusType::Bluetooth),
0x11 => Ok(BusType::Serial),
0x18 => Ok(BusType::I2C),
_ => Err(Error::InvalidArgument {
message: format!("Unsupported bus type {b}"),
}),
})??;
let vid = u16::from_str_radix(vid.as_str().trim(), 16).map_err(|_| Error::IO {
message: format!("Failed to parse vendor {vid}"),
})?;
let pid = u16::from_str_radix(pid.as_str().trim(), 16).map_err(|_| Error::IO {
message: format!("Failed to parse pid {pid}"),
})?;
Ok(TabletInfo::new()
.bustype(bustype)
.vid(vid.into())
.pid(pid.into())
.kernel_name(name.trim().into())
.uniq(uniq.trim().into()))
}
pub fn bustype(mut self, bustype: BusType) -> Self {
self.bustype = Some(bustype);
self
}
pub fn vid(mut self, vid: VendorId) -> Self {
self.vid = Some(vid);
self
}
pub fn pid(mut self, pid: ProductId) -> Self {
self.pid = Some(pid);
self
}
pub fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
pub fn kernel_name(mut self, name: String) -> Self {
self.kernel_name = Some(name);
self
}
pub fn uniq(mut self, uniq: String) -> Self {
if !uniq.is_empty() {
self.uniq = Some(uniq);
}
self
}
}
impl Default for TabletInfo {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct EvdevCode(u16);
impl EvdevCode {
pub fn code(&self) -> u16 {
self.0
}
}
impl From<&EvdevCode> for u16 {
fn from(e: &EvdevCode) -> u16 {
e.0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ButtonIndex(usize);
impl ButtonIndex {
pub fn number(&self) -> usize {
self.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Button {
index: ButtonIndex,
location: Location,
evdev_code: EvdevCode,
}
impl Button {
pub fn index(&self) -> ButtonIndex {
self.index
}
pub fn location(&self) -> Location {
self.location
}
pub fn evdev_code(&self) -> EvdevCode {
self.evdev_code
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Location {
Left,
Right,
Top,
Bottom,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FeatureType {
Ring,
Strip,
Dial,
}
pub trait Feature {
fn num_modes(&self) -> usize;
fn feature_type(&self) -> FeatureType;
fn index(&self) -> usize;
fn has_status_led(&self) -> bool;
fn buttons(&self) -> impl Iterator<Item = &ButtonIndex>;
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ring {
index: usize,
num_modes: usize,
has_status_led: bool,
buttons: Vec<ButtonIndex>,
}
impl Feature for Ring {
fn feature_type(&self) -> FeatureType {
FeatureType::Ring
}
fn num_modes(&self) -> usize {
self.num_modes
}
fn index(&self) -> usize {
self.index
}
fn has_status_led(&self) -> bool {
self.has_status_led
}
fn buttons(&self) -> impl Iterator<Item = &ButtonIndex> {
self.buttons.iter()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Dial {
index: usize,
num_modes: usize,
has_status_led: bool,
buttons: Vec<ButtonIndex>,
}
impl Feature for Dial {
fn feature_type(&self) -> FeatureType {
FeatureType::Dial
}
fn num_modes(&self) -> usize {
self.num_modes
}
fn index(&self) -> usize {
self.index
}
fn has_status_led(&self) -> bool {
self.has_status_led
}
fn buttons(&self) -> impl Iterator<Item = &ButtonIndex> {
self.buttons.iter()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Strip {
index: usize,
num_modes: usize,
has_status_led: bool,
buttons: Vec<ButtonIndex>,
}
impl Feature for Strip {
fn feature_type(&self) -> FeatureType {
FeatureType::Strip
}
fn num_modes(&self) -> usize {
self.num_modes
}
fn index(&self) -> usize {
self.index
}
fn has_status_led(&self) -> bool {
self.has_status_led
}
fn buttons(&self) -> impl Iterator<Item = &ButtonIndex> {
self.buttons.iter()
}
}
#[derive(Debug, Clone)]
enum StylusRef {
Group(String),
ID(StylusId),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Tablet {
idx: usize,
bustype: BusType,
vid: VendorId,
pid: ProductId,
name: String,
model_name: Option<String>,
kernel_name: Option<String>,
fw_version: Option<String>,
layout: Option<PathBuf>,
paired_id: Option<DeviceId>,
width: Length,
height: Length,
is_reversible: bool,
has_stylus: bool,
has_touch: bool,
has_touchswitch: bool,
form_factor: Option<FormFactor>,
buttons: Vec<Button>,
rings: Vec<Ring>,
dials: Vec<Dial>,
strips: Vec<Strip>,
tools: Vec<Tool>,
}
impl Tablet {
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn model_name(&self) -> Option<&str> {
self.model_name.as_deref()
}
pub fn kernel_name(&self) -> Option<&str> {
self.kernel_name.as_deref()
}
pub fn firmware_version(&self) -> Option<&str> {
self.fw_version.as_deref()
}
pub fn layout(&self) -> Option<&Path> {
self.layout.as_deref()
}
pub fn bustype(&self) -> BusType {
self.bustype
}
pub fn vendor_id(&self) -> VendorId {
self.vid
}
pub fn product_id(&self) -> ProductId {
self.pid
}
pub fn width(&self) -> Length {
self.width
}
pub fn height(&self) -> Length {
self.height
}
pub fn paired_id(&self) -> Option<DeviceId> {
self.paired_id
}
pub fn is_reversible(&self) -> bool {
self.is_reversible
}
pub fn form_factor(&self) -> Option<FormFactor> {
self.form_factor
}
pub fn supports_stylus(&self) -> bool {
self.has_stylus
}
pub fn supports_touch(&self) -> bool {
self.has_touch
}
pub fn has_touchswitch(&self) -> bool {
self.has_touchswitch
}
pub fn buttons(&self) -> impl Iterator<Item = &Button> {
self.buttons.iter()
}
pub fn rings(&self) -> impl Iterator<Item = &Ring> {
self.rings.iter()
}
pub fn dials(&self) -> impl Iterator<Item = &Dial> {
self.dials.iter()
}
pub fn strips(&self) -> impl Iterator<Item = &Strip> {
self.strips.iter()
}
pub fn tools(&self) -> impl Iterator<Item = &Tool> {
self.tools.iter()
}
pub fn matches(&self, info: &TabletInfo) -> bool {
info.bustype.map(|b| b == self.bustype).unwrap_or(true)
&& info.vid.map(|v| v == self.vid).unwrap_or(true)
&& info.pid.map(|p| p == self.pid).unwrap_or(true)
&& info.name.as_ref().map(|n| n == &self.name).unwrap_or(true)
&& info
.kernel_name
.as_ref()
.map(|n| self.kernel_name.as_ref().map(|kn| n == kn).unwrap_or(true))
.unwrap_or(true)
}
}
impl PartialEq<TabletInfo> for Tablet {
fn eq(&self, other: &TabletInfo) -> bool {
self.matches(other)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum StylusType {
General,
Inking,
Airbrush,
Classic,
Marker,
Stroke,
Pen3D,
Mobile,
}
impl std::fmt::Display for StylusType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
StylusType::General => "General",
StylusType::Inking => "Inking",
StylusType::Airbrush => "Airbrush",
StylusType::Classic => "Classic",
StylusType::Marker => "Marker",
StylusType::Stroke => "Stroke",
StylusType::Pen3D => "Pen3D",
StylusType::Mobile => "Mobile",
}
)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum EraserType {
Invert,
Button,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Axis {
Pressure,
Tilt,
Distance,
Slider,
RotationZ,
Wheel,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolButton {}
pub trait ToolFeatures {
fn name(&self) -> &str;
fn vendor_id(&self) -> VendorId;
fn tool_id(&self) -> ToolId;
fn buttons(&self) -> &[ToolButton];
fn axes(&self) -> &[Axis];
}
#[derive(Debug, Clone, PartialEq)]
pub enum Tool {
Stylus(Stylus),
StylusWithEraser(Stylus, Eraser),
Mouse(Mouse),
}
impl ToolFeatures for Tool {
fn name(&self) -> &str {
match self {
Tool::Stylus(s) => s.name(),
Tool::StylusWithEraser(s, _) => s.name(),
Tool::Mouse(m) => m.name(),
}
}
fn vendor_id(&self) -> VendorId {
match self {
Tool::Stylus(s) => s.vendor_id(),
Tool::StylusWithEraser(s, _) => s.vendor_id(),
Tool::Mouse(m) => m.vendor_id(),
}
}
fn tool_id(&self) -> ToolId {
match self {
Tool::Stylus(s) => s.tool_id(),
Tool::StylusWithEraser(s, _) => s.tool_id(),
Tool::Mouse(m) => m.tool_id(),
}
}
fn axes(&self) -> &[Axis] {
match self {
Tool::Stylus(s) => s.axes(),
Tool::StylusWithEraser(s, _) => s.axes(),
Tool::Mouse(m) => m.axes(),
}
}
fn buttons(&self) -> &[ToolButton] {
match self {
Tool::Stylus(s) => s.buttons(),
Tool::StylusWithEraser(s, _) => s.buttons(),
Tool::Mouse(m) => m.buttons(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Mouse {
idx: usize,
name: String,
id: StylusId,
axes: Vec<Axis>,
buttons: Vec<ToolButton>,
has_lens: bool,
}
impl ToolFeatures for Mouse {
fn name(&self) -> &str {
&self.name
}
fn vendor_id(&self) -> VendorId {
self.id.vid
}
fn tool_id(&self) -> ToolId {
self.id.pid
}
fn axes(&self) -> &[Axis] {
&self.axes
}
fn buttons(&self) -> &[ToolButton] {
&self.buttons
}
}
impl Mouse {
pub fn has_lens(&self) -> bool {
self.has_lens
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Eraser {
idx: usize,
name: String,
id: StylusId,
eraser_type: EraserType,
axes: Vec<Axis>,
buttons: Vec<ToolButton>,
}
impl ToolFeatures for Eraser {
fn name(&self) -> &str {
&self.name
}
fn vendor_id(&self) -> VendorId {
self.id.vid
}
fn tool_id(&self) -> ToolId {
self.id.pid
}
fn axes(&self) -> &[Axis] {
&self.axes
}
fn buttons(&self) -> &[ToolButton] {
&self.buttons
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Stylus {
idx: usize,
name: String,
id: StylusId,
stylus_type: StylusType,
eraser_type: Option<EraserType>,
buttons: Vec<ToolButton>,
axes: Vec<Axis>,
}
impl ToolFeatures for Stylus {
fn name(&self) -> &str {
&self.name
}
fn vendor_id(&self) -> VendorId {
self.id.vid
}
fn tool_id(&self) -> ToolId {
self.id.pid
}
fn axes(&self) -> &[Axis] {
&self.axes
}
fn buttons(&self) -> &[ToolButton] {
&self.buttons
}
}
impl Stylus {
pub fn has_eraser_button(&self) -> bool {
!self.eraser_type.is_none_or(|t| t != EraserType::Button)
}
pub fn stylus_type(&self) -> StylusType {
self.stylus_type
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_format() {
let vid = VendorId(0x12ab);
assert_eq!(format!("{vid:x}"), "12ab");
assert_eq!(format!("{vid:X}"), "12AB");
let pid = ProductId(0xbc12);
assert_eq!(format!("{pid:x}"), "bc12");
assert_eq!(format!("{pid:X}"), "BC12");
let tid = ToolId(0xd12e);
assert_eq!(format!("{tid:x}"), "d12e");
assert_eq!(format!("{tid:X}"), "D12E");
}
}