settings/
types.rs

1// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//!
5//! Global settings data types
6//!
7//! Each global setting has a Get/Set/Subscribe method on [`crate::SettingsApi`].
8
9use std::time::Duration;
10
11use num_derive::{FromPrimitive, ToPrimitive};
12use num_traits::{FromPrimitive, ToPrimitive};
13use server::{AsScalar, FromScalar};
14
15use crate::macros::*;
16
17create_modules! {
18    SystemTheme,
19    ScreenBrightness,
20    OnboardingStatus,
21    Locale,
22    DeviceName,
23    ShowSecurityWords,
24    AutoLock,
25    EnvoyTimeSync,
26    UseStandardTimeFormat,
27    TimeZone,
28    DebugTouch,
29    AirlockMode,
30    TouchOffset,
31    MagicBackupEnabled,
32    NfcEnabled,
33    BluetoothEnabled,
34    CameraEnabled,
35    UsbEnabled,
36    DeveloperMode,
37}
38
39global_scalar! {
40    /// System-wide theme.
41    ///
42    /// This affects the appearance of all system apps on KeyOS
43    system,
44    #[derive(FromPrimitive, ToPrimitive)]
45    pub enum SystemTheme {
46        Dark,
47        Light,
48    }
49}
50
51impl FromScalar<1> for SystemTheme {
52    fn from_scalar(value: [u32; 1]) -> Self { Self::from_u32(value[0]).unwrap() }
53}
54
55impl AsScalar<1> for SystemTheme {
56    fn as_scalar(&self) -> [u32; 1] { [self.to_u32().unwrap()] }
57}
58
59global_scalar! {
60    /// Screen brightness level (0-100)
61    system,
62    pub struct ScreenBrightness(pub u8);
63}
64
65impl FromScalar<1> for ScreenBrightness {
66    fn from_scalar(value: [u32; 1]) -> Self { Self(value[0] as u8) }
67}
68
69impl AsScalar<1> for ScreenBrightness {
70    fn as_scalar(&self) -> [u32; 1] { [self.0 as u32] }
71}
72
73global_archive! {
74    /// Status of the user's onboarding.
75    encrypted,
76    #[derive(Default)]
77    pub enum OnboardingStatus {
78        #[default]
79        NotComplete,
80        Complete,
81    }
82}
83
84impl OnboardingStatus {
85    pub fn is_complete(&self) -> bool { matches!(self, Self::Complete) }
86}
87
88impl ArchivedOnboardingStatus {
89    pub fn is_complete(&self) -> bool { matches!(self, Self::Complete) }
90}
91
92global_scalar! {
93    /// The user's preferred language.
94    system,
95    #[derive(Default, FromPrimitive, ToPrimitive)]
96    pub enum Locale {
97        #[default]
98        EnglishUS,
99    }
100}
101
102impl FromScalar<1> for Locale {
103    fn from_scalar(value: [u32; 1]) -> Self { Self::from_u32(value[0]).unwrap() }
104}
105
106impl AsScalar<1> for Locale {
107    fn as_scalar(&self) -> [u32; 1] { [self.to_u32().unwrap()] }
108}
109
110impl Locale {
111    pub fn lang(&self) -> &str {
112        match self {
113            Self::EnglishUS => "en",
114        }
115    }
116}
117
118global_archive! {
119    /// The user's device name.
120    system,
121    pub struct DeviceName(pub String);
122}
123
124impl From<&str> for DeviceName {
125    fn from(value: &str) -> Self { Self(String::from(value)) }
126}
127
128impl DeviceName {
129    pub const DEFAULT: &str = "Passport Prime";
130}
131
132global_scalar! {
133    /// Whether to show the security words on the login screen.
134    system,
135    pub struct ShowSecurityWords(pub bool);
136}
137
138impl FromScalar<1> for ShowSecurityWords {
139    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
140}
141
142impl AsScalar<1> for ShowSecurityWords {
143    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
144}
145
146global_scalar! {
147    /// Time to lock the device after inactivity.
148    system,
149    pub struct AutoLock(pub Duration);
150}
151
152impl FromScalar<2> for AutoLock {
153    fn from_scalar(value: [u32; 2]) -> Self {
154        Self(Duration::from_secs(value[0] as u64) + Duration::from_millis(value[1] as u64))
155    }
156}
157
158impl AsScalar<2> for AutoLock {
159    fn as_scalar(&self) -> [u32; 2] { [self.0.as_secs() as u32, self.0.subsec_millis()] }
160}
161
162global_scalar! {
163    /// Whether to set time from envoy via QuantumLink
164    system,
165    pub struct EnvoyTimeSync(pub bool);
166}
167
168impl FromScalar<1> for EnvoyTimeSync {
169    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
170}
171
172impl AsScalar<1> for EnvoyTimeSync {
173    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
174}
175
176global_scalar! {
177    /// Whether to use the standard time format (24-hour).
178    system,
179    pub struct UseStandardTimeFormat(pub bool);
180}
181
182impl FromScalar<1> for UseStandardTimeFormat {
183    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
184}
185
186impl AsScalar<1> for UseStandardTimeFormat {
187    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
188}
189
190global_archive! {
191    /// The user's preferred timezone.
192    system,
193    pub struct TimeZone {
194         pub name: String,
195         pub data: Vec<u8>
196    }
197}
198
199impl Default for TimeZone {
200    fn default() -> Self {
201        Self {
202            name: String::from("America/New_York"),
203            data: include_bytes!("../assets/america_new_york.tzif").to_vec(),
204        }
205    }
206}
207
208impl TimeZone {
209    pub fn now(&self) -> jiff::Zoned { jiff::Timestamp::now().to_zoned(self.timezone()) }
210
211    pub fn timezone(&self) -> jiff::tz::TimeZone {
212        jiff::tz::TimeZone::tzif(&self.name, &self.data).unwrap_or_else(|_| TimeZone::default().timezone())
213    }
214
215    pub fn name(&self) -> &str { &self.name }
216
217    pub fn abbreviation(&self, timestamp: jiff::Timestamp) -> String {
218        let tz = self.timezone();
219        let info = tz.to_offset_info(timestamp);
220        info.abbreviation().into()
221    }
222}
223
224global_scalar! {
225    system,
226    pub struct DebugTouch(pub bool);
227}
228
229impl FromScalar<1> for DebugTouch {
230    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
231}
232
233impl AsScalar<1> for DebugTouch {
234    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
235}
236
237global_scalar! {
238    system,
239    #[derive(FromPrimitive, ToPrimitive)]
240    pub enum AirlockMode {
241        Disabled,
242        ReadOnly,
243        ReadWrite,
244    }
245}
246
247impl FromScalar<1> for AirlockMode {
248    fn from_scalar(value: [u32; 1]) -> Self { Self::from_u32(value[0]).unwrap() }
249}
250
251impl AsScalar<1> for AirlockMode {
252    fn as_scalar(&self) -> [u32; 1] { [self.to_u32().unwrap()] }
253}
254
255global_scalar! {
256    system,
257    pub struct TouchOffset(pub i32);
258}
259
260impl FromScalar<1> for TouchOffset {
261    fn from_scalar(value: [u32; 1]) -> Self { Self(i32::from_scalar(value)) }
262}
263
264impl AsScalar<1> for TouchOffset {
265    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
266}
267
268global_scalar! {
269    system,
270    pub struct MagicBackupEnabled(pub bool);
271}
272
273global_scalar! {
274    system,
275    pub struct NfcEnabled(pub bool);
276}
277
278global_scalar! {
279    system,
280    pub struct BluetoothEnabled(pub bool);
281}
282
283global_scalar! {
284    system,
285    pub struct CameraEnabled(pub bool);
286}
287
288global_scalar! {
289    system,
290    pub struct UsbEnabled(pub bool);
291}
292
293global_scalar! {
294    encrypted,
295    pub struct DeveloperMode(pub bool);
296}
297
298impl FromScalar<1> for MagicBackupEnabled {
299    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
300}
301
302impl AsScalar<1> for MagicBackupEnabled {
303    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
304}
305
306impl FromScalar<1> for NfcEnabled {
307    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
308}
309
310impl AsScalar<1> for NfcEnabled {
311    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
312}
313
314impl FromScalar<1> for BluetoothEnabled {
315    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
316}
317
318impl AsScalar<1> for BluetoothEnabled {
319    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
320}
321
322impl FromScalar<1> for CameraEnabled {
323    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
324}
325
326impl AsScalar<1> for CameraEnabled {
327    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
328}
329
330impl FromScalar<1> for UsbEnabled {
331    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
332}
333
334impl AsScalar<1> for UsbEnabled {
335    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
336}
337
338impl FromScalar<1> for DeveloperMode {
339    fn from_scalar(value: [u32; 1]) -> Self { Self(bool::from_scalar(value)) }
340}
341
342impl AsScalar<1> for DeveloperMode {
343    fn as_scalar(&self) -> [u32; 1] { self.0.as_scalar() }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn default_timezone() {
352        let tz = TimeZone::default();
353        assert_eq!(tz.name(), "America/New_York");
354        let timezone = tz.timezone();
355        assert_eq!(timezone.iana_name(), Some("America/New_York"));
356    }
357}