use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::time::Duration;
use crate::click::ClickTiming;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Family {
Medius,
MakcuV4,
Ferrum,
Ysc,
}
impl fmt::Display for Family {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Medius => "Medius",
Self::MakcuV4 => "MAKCU V4",
Self::Ferrum => "Ferrum",
Self::Ysc => "YSC 8K",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Button {
Left,
Right,
Middle,
Side1,
Side2,
Extra(u8),
}
impl fmt::Display for Button {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Left => f.write_str("left"),
Self::Right => f.write_str("right"),
Self::Middle => f.write_str("middle"),
Self::Side1 => f.write_str("side1"),
Self::Side2 => f.write_str("side2"),
Self::Extra(n) => write!(f, "extra {n}"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Axis {
X,
Y,
Wheel,
Pan,
}
impl fmt::Display for Axis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::X => "X",
Self::Y => "Y",
Self::Wheel => "wheel",
Self::Pan => "pan",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Direction {
Both,
Positive,
Negative,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Action {
Connect,
Move,
MoveNow,
Press,
Release,
Click,
Wheel,
Pan,
DiscardPendingMoves,
ButtonEvents,
MotionEvents,
KeyEvents,
MediaEvents,
KeyPress,
KeyRelease,
KeyTap,
MediaPress,
MediaRelease,
MediaTap,
LockButton,
LockAxis,
LockKey,
ScaleAxis,
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Connect => "connect",
Self::Move => "move",
Self::MoveNow => "move now",
Self::Press => "press",
Self::Release => "release",
Self::Click => "click",
Self::Wheel => "scroll",
Self::Pan => "scroll sideways",
Self::DiscardPendingMoves => "discard pending moves",
Self::ButtonEvents => "report button events",
Self::MotionEvents => "report motion events",
Self::KeyEvents => "report key events",
Self::MediaEvents => "report media key events",
Self::LockButton => "lock or unlock a button",
Self::LockAxis => "lock or unlock an axis",
Self::LockKey => "lock or unlock a key",
Self::ScaleAxis => "scale an axis",
Self::KeyPress => "press a key",
Self::KeyRelease => "release a key",
Self::KeyTap => "tap a key",
Self::MediaPress => "press a media key",
Self::MediaRelease => "release a media key",
Self::MediaTap => "tap a media key",
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Execution {
Native,
Host,
Unsupported {
reason: Cow<'static, str>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Quirk {
ForcedUpOnRelease { min: Duration, max: Duration },
PhysicalOverride,
WholeAxisOnly,
NotchesPerCommand(u16),
FixedClickTiming,
MaxHold(Duration),
BoxClickTiming { min: Duration, max: Duration },
HostClickTiming(ClickTiming),
KeepsHeld,
MaxKeysHeld(u8),
KeepsLocked,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Capability {
pub execution: Execution,
pub quirks: Vec<Quirk>,
}
impl Capability {
pub(crate) fn native() -> Self {
Self {
execution: Execution::Native,
quirks: Vec::new(),
}
}
pub(crate) fn host() -> Self {
Self {
execution: Execution::Host,
quirks: Vec::new(),
}
}
pub(crate) fn unsupported(reason: impl Into<Cow<'static, str>>) -> Self {
Self {
execution: Execution::Unsupported {
reason: reason.into(),
},
quirks: Vec::new(),
}
}
pub(crate) fn with(mut self, quirk: Quirk) -> Self {
self.quirks.push(quirk);
self
}
pub fn is_supported(&self) -> bool {
!matches!(self.execution, Execution::Unsupported { .. })
}
pub(crate) fn reason(&self) -> Cow<'static, str> {
match &self.execution {
Execution::Unsupported { reason } => reason.clone(),
_ => "the box can't do it".into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Capabilities {
actions: HashMap<Action, Capability>,
buttons: Vec<Button>,
axes: Vec<Axis>,
missing: Capability,
}
impl Capabilities {
pub(crate) fn new(buttons: Vec<Button>) -> Self {
Self {
actions: HashMap::new(),
buttons,
axes: Vec::new(),
missing: Capability::unsupported("this box doesn't have it"),
}
}
pub(crate) fn set(mut self, action: Action, capability: Capability) -> Self {
self.actions.insert(action, capability);
self
}
pub fn get(&self, action: Action) -> &Capability {
self.actions.get(&action).unwrap_or(&self.missing)
}
pub fn buttons(&self) -> &[Button] {
&self.buttons
}
pub fn axes(&self) -> &[Axis] {
&self.axes
}
pub(crate) fn with_axes(mut self, axes: Vec<Axis>) -> Self {
self.axes = axes;
self
}
pub fn iter(&self) -> impl Iterator<Item = (Action, &Capability)> {
self.actions
.iter()
.map(|(action, capability)| (*action, capability))
}
pub(crate) fn with_host_taps(mut self, timing: ClickTiming) -> Self {
for (tap, press, release) in [
(Action::Click, Action::Press, Action::Release),
(Action::KeyTap, Action::KeyPress, Action::KeyRelease),
(Action::MediaTap, Action::MediaPress, Action::MediaRelease),
] {
let native = self.get(tap).is_supported();
let parts = self.get(press).is_supported() && self.get(release).is_supported();
if !native && parts {
self.actions
.insert(tap, Capability::host().with(Quirk::HostClickTiming(timing)));
}
}
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Firmware {
Version {
major: u16,
minor: u16,
patch: u16,
},
Build(u32),
Protocol(u8),
Text(String),
}
impl fmt::Display for Firmware {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Version {
major,
minor,
patch,
} => write!(f, "{major}.{minor}.{patch}"),
Self::Build(build) => write!(f, "build {build}"),
Self::Protocol(protocol) => write!(f, "protocol {protocol}"),
Self::Text(text) => f.write_str(text),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StableId {
Mac([u8; 6]),
UsbSerial(String),
}
impl fmt::Display for StableId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Mac(mac) => {
let hex: Vec<String> = mac.iter().map(|byte| format!("{byte:02x}")).collect();
write!(f, "MAC {}", hex.join(":"))
}
Self::UsbSerial(serial) => write!(f, "USB serial {serial}"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Info {
pub port: String,
pub family: Family,
pub model: Option<String>,
pub firmware: Option<Firmware>,
pub id: Option<StableId>,
pub ferrum_api: Option<FerrumApi>,
pub peripheral: Option<UsbIds>,
}
impl Info {
pub(crate) fn new(family: Family) -> Self {
Self {
port: String::new(),
family,
model: None,
firmware: None,
id: None,
ferrum_api: None,
peripheral: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FerrumApi {
Software,
Legacy,
}
impl fmt::Display for FerrumApi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Software => "the Ferrum app's port",
Self::Legacy => "the box's own legacy port",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct UsbIds {
pub vid: u16,
pub pid: u16,
}
impl UsbIds {
pub fn new(vid: u16, pid: u16) -> Self {
Self { vid, pid }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Snapshot {
pub info: Info,
pub capabilities: Capabilities,
}
impl Snapshot {
pub(crate) fn new(info: Info, capabilities: Capabilities) -> Self {
Self { info, capabilities }
}
}