Skip to main content

a7n_core/
settings.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3//! Convenience settings shared across a desk (PROTOCOL.md 13).
4
5use std::collections::BTreeMap;
6
7use a7n_proto::{MachineId, ProtoError, limits, v1};
8
9pub const SYNCED_KEYS: &[&str] = &[
10    "input.speed",
11    "input.natural_scrolling",
12    "hotkeys.double_tap",
13    "hotkeys.trigger_mode",
14    "hotkeys.hold_ms",
15    "hotkeys.shake",
16    "hotkeys.home",
17    "hotkeys.lock",
18    "hotkeys.jump",
19    "feedback.style",
20    "feedback.depth",
21    "feedback.radius",
22    "feedback.speed",
23    "feedback.frequency",
24    "feedback.intensity",
25    "feedback.color",
26    "feedback.size",
27    "feedback.landing_style",
28];
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Settings {
32    pub set_at: u64,
33    pub by: MachineId,
34    pub values: BTreeMap<String, String>,
35}
36
37impl TryFrom<&v1::SettingsSync> for Settings {
38    type Error = ProtoError;
39
40    fn try_from(wire: &v1::SettingsSync) -> Result<Self, Self::Error> {
41        if wire.set_at > limits::COUNTER_MAX {
42            return Err(ProtoError::CounterTooLarge);
43        }
44        Ok(Self {
45            set_at: wire.set_at,
46            by: MachineId::try_from(wire.by.as_slice())?,
47            values: wire
48                .values
49                .iter()
50                .filter(|(key, _)| SYNCED_KEYS.contains(&key.as_str()))
51                .map(|(key, value)| (key.clone(), value.clone()))
52                .collect(),
53        })
54    }
55}
56
57impl Settings {
58    #[must_use]
59    pub fn to_proto(&self) -> v1::SettingsSync {
60        v1::SettingsSync {
61            on: true,
62            set_at: self.set_at,
63            by: self.by.as_bytes().to_vec(),
64            values: self
65                .values
66                .iter()
67                .map(|(key, value)| (key.clone(), value.clone()))
68                .collect(),
69        }
70    }
71}
72
73#[must_use]
74pub fn newer(current: Option<&Settings>, incoming: &Settings) -> bool {
75    // ponytail: clock skew decides competing intent; use logical clocks if this becomes a problem.
76    current.is_none_or(|current| (incoming.set_at, incoming.by) > (current.set_at, current.by))
77}
78
79#[cfg(test)]
80mod tests;