use std::time::Duration;
use crate::capability::{Action, Family, FerrumAction, MakcuAction, YscAction};
use crate::device::Device;
use crate::error::{Error, Result};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Interpolation {
Auto,
Percent(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum YscProfile {
Linear,
EaseInOut,
MinJerk,
Other(u32),
}
impl YscProfile {
pub(crate) fn number(self) -> u32 {
match self {
Self::Linear => 0,
Self::EaseInOut => 1,
Self::MinJerk => 2,
Self::Other(n) => n,
}
}
pub(crate) fn from_number(n: u32) -> Self {
match n {
0 => Self::Linear,
1 => Self::EaseInOut,
2 => Self::MinJerk,
n => Self::Other(n),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct YscInterpolation {
pub enabled: bool,
pub profile: YscProfile,
pub window: Option<Duration>,
pub max_window: Option<Duration>,
pub measured_interval: Option<Duration>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct YscInterpolationSettings {
pub enabled: bool,
pub profile: YscProfile,
pub window: Duration,
pub max_window: Duration,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct YscSerial {
pub custom: Option<String>,
pub physical: Option<String>,
pub active: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Extension {
MakcuInterpolation,
MakcuSetInterpolation(Interpolation),
MakcuMoving,
MakcuAutorelease,
MakcuSetAutorelease(Option<Duration>),
YscInterpolation,
YscSetInterpolation(YscInterpolationSettings),
YscResetInterpolation,
YscSerial,
FerrumCatchXy {
window: Duration,
with_software: bool,
},
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ExtensionReply {
Done,
Interpolation(Interpolation),
Moving(bool),
Autorelease(Option<Duration>),
YscInterpolation(YscInterpolation),
YscSerial(YscSerial),
Xy(i32, i32),
}
pub(crate) const MAX_AUTORELEASE: Duration = Duration::from_secs(60);
pub(crate) const MAX_YSC_WINDOW: Duration = Duration::from_secs(60);
pub(crate) const MAX_CATCH: Duration = Duration::from_secs(1);
impl Extension {
pub(crate) fn action(self) -> Action {
match self {
Self::MakcuInterpolation | Self::MakcuSetInterpolation(_) => {
Action::Makcu(MakcuAction::Interpolation)
}
Self::MakcuMoving => Action::Makcu(MakcuAction::Moving),
Self::MakcuAutorelease | Self::MakcuSetAutorelease(_) => {
Action::Makcu(MakcuAction::Autorelease)
}
Self::YscInterpolation | Self::YscSetInterpolation(_) | Self::YscResetInterpolation => {
Action::Ysc(YscAction::Interpolation)
}
Self::YscSerial => Action::Ysc(YscAction::Serial),
Self::FerrumCatchXy { .. } => Action::Ferrum(FerrumAction::CatchXy),
}
}
pub(crate) fn check(self) -> Result<()> {
let action = self.action();
let bad = |detail: String| {
Err(Error::InvalidArgument {
action,
detail: detail.into(),
})
};
match self {
Self::MakcuSetInterpolation(Interpolation::Percent(n)) if n > 100 => bad(format!(
"interpolation goes up to 100 percent, {n} is too much"
)),
Self::MakcuSetAutorelease(Some(after)) => {
whole_ms(action, after, "autorelease")?;
if after.is_zero() || after > MAX_AUTORELEASE {
return bad(format!(
"autorelease takes 1 ms to 60 s, {after:?} isn't in it, use none to turn it off"
));
}
Ok(())
}
Self::YscSetInterpolation(settings) => {
for (name, window) in [
("the window", settings.window),
("the max window", settings.max_window),
] {
whole_ms(action, window, name)?;
if window > MAX_YSC_WINDOW {
return bad(format!("{name} goes up to 60 s, {window:?} is too long"));
}
}
Ok(())
}
Self::FerrumCatchXy { window, .. } => {
whole_ms(action, window, "the window")?;
if window > MAX_CATCH {
return bad(format!("the window goes up to 1 s, {window:?} is too long"));
}
Ok(())
}
_ => Ok(()),
}
}
}
fn whole_ms(action: Action, duration: Duration, name: &str) -> Result<()> {
if !duration.subsec_nanos().is_multiple_of(1_000_000) {
return Err(Error::InvalidArgument {
action,
detail: format!("{name} has to be whole milliseconds, {duration:?} isn't").into(),
});
}
Ok(())
}
pub struct Makcu<'a> {
device: &'a Device,
}
impl<'a> Makcu<'a> {
pub(crate) fn new(device: &'a Device) -> Self {
Self { device }
}
pub fn interpolation(&self) -> Result<Interpolation> {
match self.device.extension(Extension::MakcuInterpolation)? {
ExtensionReply::Interpolation(value) => Ok(value),
other => Err(unexpected(Extension::MakcuInterpolation, other)),
}
}
pub fn set_interpolation(&self, value: Interpolation) -> Result<()> {
self.device
.extension(Extension::MakcuSetInterpolation(value))
.map(|_| ())
}
pub fn moving(&self) -> Result<bool> {
match self.device.extension(Extension::MakcuMoving)? {
ExtensionReply::Moving(moving) => Ok(moving),
other => Err(unexpected(Extension::MakcuMoving, other)),
}
}
pub fn autorelease(&self) -> Result<Option<Duration>> {
match self.device.extension(Extension::MakcuAutorelease)? {
ExtensionReply::Autorelease(after) => Ok(after),
other => Err(unexpected(Extension::MakcuAutorelease, other)),
}
}
pub fn set_autorelease(&self, after: Option<Duration>) -> Result<()> {
self.device
.extension(Extension::MakcuSetAutorelease(after))
.map(|_| ())
}
}
pub struct Ysc<'a> {
device: &'a Device,
}
impl<'a> Ysc<'a> {
pub(crate) fn new(device: &'a Device) -> Self {
Self { device }
}
pub fn interpolation(&self) -> Result<YscInterpolation> {
match self.device.extension(Extension::YscInterpolation)? {
ExtensionReply::YscInterpolation(value) => Ok(value),
other => Err(unexpected(Extension::YscInterpolation, other)),
}
}
pub fn set_interpolation(&self, settings: YscInterpolationSettings) -> Result<()> {
self.device
.extension(Extension::YscSetInterpolation(settings))
.map(|_| ())
}
pub fn reset_interpolation(&self) -> Result<YscInterpolation> {
match self.device.extension(Extension::YscResetInterpolation)? {
ExtensionReply::YscInterpolation(value) => Ok(value),
other => Err(unexpected(Extension::YscResetInterpolation, other)),
}
}
pub fn serial(&self) -> Result<YscSerial> {
match self.device.extension(Extension::YscSerial)? {
ExtensionReply::YscSerial(serial) => Ok(serial),
other => Err(unexpected(Extension::YscSerial, other)),
}
}
}
pub struct Ferrum<'a> {
device: &'a Device,
}
impl<'a> Ferrum<'a> {
pub(crate) fn new(device: &'a Device) -> Self {
Self { device }
}
pub fn catch_xy(&self, window: Duration, with_software: bool) -> Result<(i32, i32)> {
let request = Extension::FerrumCatchXy {
window,
with_software,
};
match self.device.extension(request)? {
ExtensionReply::Xy(x, y) => Ok((x, y)),
other => Err(unexpected(request, other)),
}
}
}
fn unexpected(request: Extension, reply: ExtensionReply) -> Error {
Error::Rejected {
action: request.action(),
detail: format!("anybox got {reply:?} back for {request:?}, which is a bug"),
}
}
pub(crate) fn family(request: Extension) -> Family {
match request.action() {
Action::Ysc(_) => Family::Ysc,
Action::Ferrum(_) => Family::Ferrum,
_ => Family::MakcuV4,
}
}