Skip to main content

anybox/
capability.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt;
4use std::time::Duration;
5
6use crate::click::ClickTiming;
7
8/// the box families anybox knows how to talk to
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10#[non_exhaustive]
11pub enum Family {
12    Medius,
13    MakcuV4,
14    Ferrum,
15    Ysc,
16}
17
18impl fmt::Display for Family {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.write_str(match self {
21            Self::Medius => "Medius",
22            Self::MakcuV4 => "MAKCU V4",
23            Self::Ferrum => "Ferrum",
24            Self::Ysc => "YSC 8K",
25        })
26    }
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub enum Button {
32    Left,
33    Right,
34    Middle,
35    Side1,
36    Side2,
37    /// a button past side2, numbered from 5. Medius has these when the mouse behind it does, MAKCU only reports
38    /// them as input
39    Extra(u8),
40}
41
42impl fmt::Display for Button {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::Left => f.write_str("left"),
46            Self::Right => f.write_str("right"),
47            Self::Middle => f.write_str("middle"),
48            Self::Side1 => f.write_str("side1"),
49            Self::Side2 => f.write_str("side2"),
50            Self::Extra(n) => write!(f, "extra {n}"),
51        }
52    }
53}
54
55/// a physical axis you can lock or scale
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57#[non_exhaustive]
58pub enum Axis {
59    X,
60    Y,
61    Wheel,
62    /// horizontal scroll
63    Pan,
64}
65
66impl fmt::Display for Axis {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.write_str(match self {
69            Self::X => "X",
70            Self::Y => "Y",
71            Self::Wheel => "wheel",
72            Self::Pan => "pan",
73        })
74    }
75}
76
77/// which way along an axis. positive is the way the box moves for a positive value, so right for X, down for
78/// Y, up for the wheel and right for pan
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80#[non_exhaustive]
81pub enum Direction {
82    Both,
83    Positive,
84    Negative,
85}
86
87/// every action a box gets asked whether it can do, and the one an error is about
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
89#[non_exhaustive]
90pub enum Action {
91    Connect,
92    Move,
93    MoveNow,
94    Press,
95    Release,
96    Click,
97    Wheel,
98    Pan,
99    DiscardPendingMoves,
100    ButtonEvents,
101    MotionEvents,
102    KeyEvents,
103    MediaEvents,
104    KeyPress,
105    KeyRelease,
106    KeyTap,
107    MediaPress,
108    MediaRelease,
109    MediaTap,
110    LockButton,
111    LockAxis,
112    LockKey,
113    ScaleAxis,
114}
115
116impl fmt::Display for Action {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.write_str(match self {
119            Self::Connect => "connect",
120            Self::Move => "move",
121            Self::MoveNow => "move now",
122            Self::Press => "press",
123            Self::Release => "release",
124            Self::Click => "click",
125            Self::Wheel => "scroll",
126            Self::Pan => "scroll sideways",
127            Self::DiscardPendingMoves => "discard pending moves",
128            Self::ButtonEvents => "report button events",
129            Self::MotionEvents => "report motion events",
130            Self::KeyEvents => "report key events",
131            Self::MediaEvents => "report media key events",
132            Self::LockButton => "lock or unlock a button",
133            Self::LockAxis => "lock or unlock an axis",
134            Self::LockKey => "lock or unlock a key",
135            Self::ScaleAxis => "scale an axis",
136            Self::KeyPress => "press a key",
137            Self::KeyRelease => "release a key",
138            Self::KeyTap => "tap a key",
139            Self::MediaPress => "press a media key",
140            Self::MediaRelease => "release a media key",
141            Self::MediaTap => "tap a media key",
142        })
143    }
144}
145
146/// how a box does an action
147#[derive(Clone, Debug, PartialEq, Eq)]
148#[non_exhaustive]
149pub enum Execution {
150    /// the box does it, with its own timing
151    Native,
152    /// anybox builds it on the PC out of things the box can do, so it runs on the PC's timing
153    Host,
154    Unsupported {
155        reason: Cow<'static, str>,
156    },
157}
158
159/// a way this box behaves differently that anybox can't hide, so it tells you instead of smoothing it over
160#[derive(Clone, Debug, PartialEq, Eq)]
161#[non_exhaustive]
162pub enum Quirk {
163    /// after a release the box keeps the button up for a random while, and only then does the physical button
164    /// count again
165    ForcedUpOnRelease { min: Duration, max: Duration },
166    /// the most wheel notches the box takes in one command
167    NotchesPerCommand(u16),
168    /// the box's click has its own timing and doesn't take a hold
169    FixedClickTiming,
170    /// the longest hold the box's click takes
171    MaxHold(Duration),
172    /// how long the box holds a click you don't give a hold to
173    BoxClickTiming { min: Duration, max: Duration },
174    /// the box can't time a click or tap itself, so this is what anybox does on the PC instead
175    HostClickTiming(ClickTiming),
176    /// the box presses held buttons and keys again by itself after a reconnect or a session reset, so they
177    /// stay held through it instead of being let go
178    KeepsHeld,
179    /// the most keys the box holds down at once, not counting modifiers. it can't report any past that
180    MaxKeysHeld(u8),
181    /// the box puts locks back by itself after a reconnect or a session reset
182    KeepsLocked,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
186#[non_exhaustive]
187pub struct Capability {
188    pub execution: Execution,
189    pub quirks: Vec<Quirk>,
190}
191
192impl Capability {
193    pub(crate) fn native() -> Self {
194        Self {
195            execution: Execution::Native,
196            quirks: Vec::new(),
197        }
198    }
199
200    pub(crate) fn host() -> Self {
201        Self {
202            execution: Execution::Host,
203            quirks: Vec::new(),
204        }
205    }
206
207    pub(crate) fn unsupported(reason: impl Into<Cow<'static, str>>) -> Self {
208        Self {
209            execution: Execution::Unsupported {
210                reason: reason.into(),
211            },
212            quirks: Vec::new(),
213        }
214    }
215
216    pub(crate) fn with(mut self, quirk: Quirk) -> Self {
217        self.quirks.push(quirk);
218        self
219    }
220
221    pub fn is_supported(&self) -> bool {
222        !matches!(self.execution, Execution::Unsupported { .. })
223    }
224
225    /// why it's unsupported, for an error that comes up after the capability check already passed
226    pub(crate) fn reason(&self) -> Cow<'static, str> {
227        match &self.execution {
228            Execution::Unsupported { reason } => reason.clone(),
229            _ => "the box can't do it".into(),
230        }
231    }
232}
233
234/// what this box, on this firmware, with this mouse behind it, can do
235#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct Capabilities {
237    actions: HashMap<Action, Capability>,
238    buttons: Vec<Button>,
239    axes: Vec<Axis>,
240    missing: Capability,
241}
242
243impl Capabilities {
244    pub(crate) fn new(buttons: Vec<Button>) -> Self {
245        Self {
246            actions: HashMap::new(),
247            buttons,
248            axes: Vec::new(),
249            missing: Capability::unsupported("this box doesn't have it"),
250        }
251    }
252
253    pub(crate) fn set(mut self, action: Action, capability: Capability) -> Self {
254        self.actions.insert(action, capability);
255        self
256    }
257
258    /// anything the box didn't list counts as unsupported
259    pub fn get(&self, action: Action) -> &Capability {
260        self.actions.get(&action).unwrap_or(&self.missing)
261    }
262
263    pub fn buttons(&self) -> &[Button] {
264        &self.buttons
265    }
266
267    /// the axes you can lock or scale on this box
268    pub fn axes(&self) -> &[Axis] {
269        &self.axes
270    }
271
272    pub(crate) fn with_axes(mut self, axes: Vec<Axis>) -> Self {
273        self.axes = axes;
274        self
275    }
276
277    pub fn iter(&self) -> impl Iterator<Item = (Action, &Capability)> {
278        self.actions
279            .iter()
280            .map(|(action, capability)| (*action, capability))
281    }
282
283    /// a box that can press and release but has no click or tap of its own gets one built on the PC
284    pub(crate) fn with_host_taps(mut self, timing: ClickTiming) -> Self {
285        for (tap, press, release) in [
286            (Action::Click, Action::Press, Action::Release),
287            (Action::KeyTap, Action::KeyPress, Action::KeyRelease),
288            (Action::MediaTap, Action::MediaPress, Action::MediaRelease),
289        ] {
290            let native = self.get(tap).is_supported();
291            let parts = self.get(press).is_supported() && self.get(release).is_supported();
292            if !native && parts {
293                self.actions
294                    .insert(tap, Capability::host().with(Quirk::HostClickTiming(timing)));
295            }
296        }
297        self
298    }
299}
300
301#[derive(Clone, Debug, PartialEq, Eq)]
302#[non_exhaustive]
303pub enum Firmware {
304    Version {
305        major: u16,
306        minor: u16,
307        patch: u16,
308    },
309    Build(u32),
310    /// for boxes that report a protocol number instead, like Medius
311    Protocol(u8),
312    Text(String),
313}
314
315impl fmt::Display for Firmware {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        match self {
318            Self::Version {
319                major,
320                minor,
321                patch,
322            } => write!(f, "{major}.{minor}.{patch}"),
323            Self::Build(build) => write!(f, "build {build}"),
324            Self::Protocol(protocol) => write!(f, "protocol {protocol}"),
325            Self::Text(text) => f.write_str(text),
326        }
327    }
328}
329
330#[derive(Clone, Debug, PartialEq, Eq)]
331#[non_exhaustive]
332pub enum StableId {
333    Mac([u8; 6]),
334    UsbSerial(String),
335}
336
337impl fmt::Display for StableId {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        match self {
340            Self::Mac(mac) => {
341                let hex: Vec<String> = mac.iter().map(|byte| format!("{byte:02x}")).collect();
342                write!(f, "MAC {}", hex.join(":"))
343            }
344            Self::UsbSerial(serial) => write!(f, "USB serial {serial}"),
345        }
346    }
347}
348
349#[derive(Clone, Debug, PartialEq, Eq)]
350#[non_exhaustive]
351pub struct Info {
352    /// the port the device was opened on, so you can show it or open the same box again with `Connect::port`
353    pub port: String,
354    pub family: Family,
355    pub model: Option<String>,
356    pub firmware: Option<Firmware>,
357    pub id: Option<StableId>,
358}
359
360impl Info {
361    pub(crate) fn new(family: Family) -> Self {
362        Self {
363            port: String::new(),
364            family,
365            model: None,
366            firmware: None,
367            id: None,
368        }
369    }
370}
371
372/// who the box is and what it can do, both always read from the same connection
373#[derive(Clone, Debug)]
374#[non_exhaustive]
375pub struct Snapshot {
376    pub info: Info,
377    pub capabilities: Capabilities,
378}
379
380impl Snapshot {
381    pub(crate) fn new(info: Info, capabilities: Capabilities) -> Self {
382        Self { info, capabilities }
383    }
384}