Skip to main content

anybox/
click.rs

1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3/// what `click()` and the key and media taps do on a box that can't time them itself
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum ClickTiming {
7    /// a random hold from 35 to 75 ms, the same as MAKCU's own click
8    #[default]
9    Makcu,
10    /// a random hold from 75 to 125 ms, the same as Ferrum's own click
11    Ferrum,
12    Fixed(Duration),
13    /// a click or tap without a hold fails on a box like that, so you have to give it one
14    Off,
15}
16
17impl ClickTiming {
18    fn range(self) -> Option<(Duration, Duration)> {
19        match self {
20            Self::Makcu => Some((Duration::from_millis(35), Duration::from_millis(75))),
21            Self::Ferrum => Some((Duration::from_millis(75), Duration::from_millis(125))),
22            Self::Fixed(hold) => Some((hold, hold)),
23            Self::Off => None,
24        }
25    }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum Timing {
31    #[default]
32    Any,
33    /// fails instead of letting anybox time it from the PC
34    NativeOnly,
35}
36
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38#[non_exhaustive]
39pub struct Click {
40    /// with none, the box uses its own click timing, or the device's `ClickTiming` if the box doesn't have one
41    pub hold: Option<Duration>,
42    pub timing: Timing,
43}
44
45impl Click {
46    pub fn hold(hold: Duration) -> Self {
47        Self {
48            hold: Some(hold),
49            timing: Timing::Any,
50        }
51    }
52
53    pub fn native_only(self) -> Self {
54        Self {
55            timing: Timing::NativeOnly,
56            ..self
57        }
58    }
59}
60
61/// picks how long a click timed on the PC holds. xorshift is plenty for that and doesn't need a dependency
62#[derive(Debug)]
63pub(crate) struct Holds(u64);
64
65impl Holds {
66    pub(crate) fn seeded(seed: u64) -> Self {
67        // xorshift gets stuck forever on a zero seed
68        Self(seed | 1)
69    }
70
71    pub(crate) fn from_clock() -> Self {
72        let nanos = SystemTime::now()
73            .duration_since(UNIX_EPOCH)
74            .map_or(0, |since| since.as_nanos() as u64);
75        Self::seeded(nanos)
76    }
77
78    pub(crate) fn pick(&mut self, timing: ClickTiming) -> Option<Duration> {
79        let (min, max) = timing.range()?;
80        let span = (max - min).as_micros() as u64;
81        if span == 0 {
82            return Some(min);
83        }
84        self.0 ^= self.0 << 13;
85        self.0 ^= self.0 >> 7;
86        self.0 ^= self.0 << 17;
87        Some(min + Duration::from_micros(self.0 % (span + 1)))
88    }
89}
90
91#[cfg(test)]
92mod tests;