1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt;
4use std::time::Duration;
5
6use crate::click::ClickTiming;
7
8#[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 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57#[non_exhaustive]
58pub enum Axis {
59 X,
60 Y,
61 Wheel,
62 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80#[non_exhaustive]
81pub enum Direction {
82 Both,
83 Positive,
84 Negative,
85}
86
87#[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#[derive(Clone, Debug, PartialEq, Eq)]
148#[non_exhaustive]
149pub enum Execution {
150 Native,
152 Host,
154 Unsupported {
155 reason: Cow<'static, str>,
156 },
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
161#[non_exhaustive]
162pub enum Quirk {
163 ForcedUpOnRelease { min: Duration, max: Duration },
166 PhysicalOverride,
169 WholeAxisOnly,
171 NotchesPerCommand(u16),
173 FixedClickTiming,
176 MaxHold(Duration),
178 BoxClickTiming { min: Duration, max: Duration },
180 HostClickTiming(ClickTiming),
182 KeepsHeld,
185 MaxKeysHeld(u8),
187 KeepsLocked,
189}
190
191#[derive(Clone, Debug, PartialEq, Eq)]
192#[non_exhaustive]
193pub struct Capability {
194 pub execution: Execution,
195 pub quirks: Vec<Quirk>,
196}
197
198impl Capability {
199 pub(crate) fn native() -> Self {
200 Self {
201 execution: Execution::Native,
202 quirks: Vec::new(),
203 }
204 }
205
206 pub(crate) fn host() -> Self {
207 Self {
208 execution: Execution::Host,
209 quirks: Vec::new(),
210 }
211 }
212
213 pub(crate) fn unsupported(reason: impl Into<Cow<'static, str>>) -> Self {
214 Self {
215 execution: Execution::Unsupported {
216 reason: reason.into(),
217 },
218 quirks: Vec::new(),
219 }
220 }
221
222 pub(crate) fn with(mut self, quirk: Quirk) -> Self {
223 self.quirks.push(quirk);
224 self
225 }
226
227 pub fn is_supported(&self) -> bool {
228 !matches!(self.execution, Execution::Unsupported { .. })
229 }
230
231 pub(crate) fn reason(&self) -> Cow<'static, str> {
233 match &self.execution {
234 Execution::Unsupported { reason } => reason.clone(),
235 _ => "the box can't do it".into(),
236 }
237 }
238}
239
240#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct Capabilities {
243 actions: HashMap<Action, Capability>,
244 buttons: Vec<Button>,
245 axes: Vec<Axis>,
246 missing: Capability,
247}
248
249impl Capabilities {
250 pub(crate) fn new(buttons: Vec<Button>) -> Self {
251 Self {
252 actions: HashMap::new(),
253 buttons,
254 axes: Vec::new(),
255 missing: Capability::unsupported("this box doesn't have it"),
256 }
257 }
258
259 pub(crate) fn set(mut self, action: Action, capability: Capability) -> Self {
260 self.actions.insert(action, capability);
261 self
262 }
263
264 pub fn get(&self, action: Action) -> &Capability {
266 self.actions.get(&action).unwrap_or(&self.missing)
267 }
268
269 pub fn buttons(&self) -> &[Button] {
270 &self.buttons
271 }
272
273 pub fn axes(&self) -> &[Axis] {
275 &self.axes
276 }
277
278 pub(crate) fn with_axes(mut self, axes: Vec<Axis>) -> Self {
279 self.axes = axes;
280 self
281 }
282
283 pub fn iter(&self) -> impl Iterator<Item = (Action, &Capability)> {
284 self.actions
285 .iter()
286 .map(|(action, capability)| (*action, capability))
287 }
288
289 pub(crate) fn with_host_taps(mut self, timing: ClickTiming) -> Self {
291 for (tap, press, release) in [
292 (Action::Click, Action::Press, Action::Release),
293 (Action::KeyTap, Action::KeyPress, Action::KeyRelease),
294 (Action::MediaTap, Action::MediaPress, Action::MediaRelease),
295 ] {
296 let native = self.get(tap).is_supported();
297 let parts = self.get(press).is_supported() && self.get(release).is_supported();
298 if !native && parts {
299 self.actions
300 .insert(tap, Capability::host().with(Quirk::HostClickTiming(timing)));
301 }
302 }
303 self
304 }
305}
306
307#[derive(Clone, Debug, PartialEq, Eq)]
308#[non_exhaustive]
309pub enum Firmware {
310 Version {
311 major: u16,
312 minor: u16,
313 patch: u16,
314 },
315 Build(u32),
316 Protocol(u8),
319 Text(String),
320}
321
322impl fmt::Display for Firmware {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 match self {
325 Self::Version {
326 major,
327 minor,
328 patch,
329 } => write!(f, "{major}.{minor}.{patch}"),
330 Self::Build(build) => write!(f, "build {build}"),
331 Self::Protocol(protocol) => write!(f, "protocol {protocol}"),
332 Self::Text(text) => f.write_str(text),
333 }
334 }
335}
336
337#[derive(Clone, Debug, PartialEq, Eq)]
338#[non_exhaustive]
339pub enum StableId {
340 Mac([u8; 6]),
341 UsbSerial(String),
342}
343
344impl fmt::Display for StableId {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 match self {
347 Self::Mac(mac) => {
348 let hex: Vec<String> = mac.iter().map(|byte| format!("{byte:02x}")).collect();
349 write!(f, "MAC {}", hex.join(":"))
350 }
351 Self::UsbSerial(serial) => write!(f, "USB serial {serial}"),
352 }
353 }
354}
355
356#[derive(Clone, Debug, PartialEq, Eq)]
357#[non_exhaustive]
358pub struct Info {
359 pub port: String,
361 pub family: Family,
362 pub model: Option<String>,
363 pub firmware: Option<Firmware>,
364 pub id: Option<StableId>,
365 pub ferrum_api: Option<FerrumApi>,
367 pub peripheral: Option<UsbIds>,
372}
373
374impl Info {
375 pub(crate) fn new(family: Family) -> Self {
376 Self {
377 port: String::new(),
378 family,
379 model: None,
380 firmware: None,
381 id: None,
382 ferrum_api: None,
383 peripheral: None,
384 }
385 }
386}
387
388#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
390#[non_exhaustive]
391pub enum FerrumApi {
392 Software,
394 Legacy,
397}
398
399impl fmt::Display for FerrumApi {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 f.write_str(match self {
402 Self::Software => "the Ferrum app's port",
403 Self::Legacy => "the box's own legacy port",
404 })
405 }
406}
407
408#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
410#[non_exhaustive]
411pub struct UsbIds {
412 pub vid: u16,
413 pub pid: u16,
414}
415
416impl UsbIds {
417 pub fn new(vid: u16, pid: u16) -> Self {
418 Self { vid, pid }
419 }
420}
421
422#[derive(Clone, Debug, PartialEq, Eq)]
424#[non_exhaustive]
425pub struct Snapshot {
426 pub info: Info,
427 pub capabilities: Capabilities,
428}
429
430impl Snapshot {
431 pub(crate) fn new(info: Info, capabilities: Capabilities) -> Self {
432 Self { info, capabilities }
433 }
434}