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 NotchesPerCommand(u16),
168 FixedClickTiming,
170 MaxHold(Duration),
172 BoxClickTiming { min: Duration, max: Duration },
174 HostClickTiming(ClickTiming),
176 KeepsHeld,
179 MaxKeysHeld(u8),
181 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 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#[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 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 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 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 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 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#[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}