gardesk/garlock / 7e11bac

Browse files

Add configuration module with TOML support

- Config structs for general, background, ring, indicator, font
- Auto-create default config on first run
- Load from ~/.config/garlock/config.toml
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
7e11bac08f089c02a4f9e283ef118d1a6e7a8cf2
Parents
9b88a72
Tree
145606c

1 changed file

StatusFile+-
A garlock/src/config.rs 325 0
garlock/src/config.rsadded
@@ -0,0 +1,325 @@
1
+//! Configuration loading and management for garlock
2
+//!
3
+//! Loads configuration from TOML file with sensible defaults.
4
+
5
+use anyhow::{Context, Result};
6
+use serde::{Deserialize, Serialize};
7
+use std::path::Path;
8
+
9
+/// Main configuration structure
10
+#[derive(Debug, Clone, Serialize, Deserialize)]
11
+#[serde(default)]
12
+pub struct Config {
13
+    /// General settings
14
+    pub general: GeneralConfig,
15
+
16
+    /// Background settings
17
+    pub background: BackgroundConfig,
18
+
19
+    /// Ring indicator settings
20
+    pub ring: RingConfig,
21
+
22
+    /// Indicator settings (caps lock, attempts, etc.)
23
+    pub indicator: IndicatorConfig,
24
+
25
+    /// Font settings
26
+    pub font: FontConfig,
27
+}
28
+
29
+impl Default for Config {
30
+    fn default() -> Self {
31
+        Self {
32
+            general: GeneralConfig::default(),
33
+            background: BackgroundConfig::default(),
34
+            ring: RingConfig::default(),
35
+            indicator: IndicatorConfig::default(),
36
+            font: FontConfig::default(),
37
+        }
38
+    }
39
+}
40
+
41
+impl Config {
42
+    /// Load configuration from file or use defaults
43
+    ///
44
+    /// If no config file exists, creates one with default values.
45
+    pub fn load(path: Option<&Path>) -> Result<Self> {
46
+        let config_path = path
47
+            .map(|p| p.to_path_buf())
48
+            .or_else(|| dirs::config_dir().map(|d| d.join("garlock/config.toml")));
49
+
50
+        if let Some(path) = &config_path {
51
+            if path.exists() {
52
+                let content = std::fs::read_to_string(path)
53
+                    .with_context(|| format!("Failed to read config file: {:?}", path))?;
54
+                let config: Config = toml::from_str(&content)
55
+                    .with_context(|| format!("Failed to parse config file: {:?}", path))?;
56
+                tracing::info!(?path, "Loaded configuration");
57
+                return Ok(config);
58
+            }
59
+        }
60
+
61
+        // Create default config file if it doesn't exist
62
+        let config = Config::default();
63
+        if let Some(path) = config_path {
64
+            if let Err(e) = config.write_default(&path) {
65
+                tracing::warn!(?path, "Failed to create default config: {}", e);
66
+            }
67
+        }
68
+
69
+        tracing::debug!("Using default configuration");
70
+        Ok(config)
71
+    }
72
+
73
+    /// Write the default configuration file with comments
74
+    fn write_default(&self, path: &Path) -> Result<()> {
75
+        // Create parent directory if needed
76
+        if let Some(parent) = path.parent() {
77
+            std::fs::create_dir_all(parent)
78
+                .with_context(|| format!("Failed to create config directory: {:?}", parent))?;
79
+        }
80
+
81
+        let content = Self::default_config_content();
82
+        std::fs::write(path, content)
83
+            .with_context(|| format!("Failed to write config file: {:?}", path))?;
84
+
85
+        tracing::info!(?path, "Created default configuration file");
86
+        Ok(())
87
+    }
88
+
89
+    /// Generate default config file content with documentation comments
90
+    fn default_config_content() -> String {
91
+        r##"# garlock configuration
92
+# Screen locker for the gar desktop suite
93
+#
94
+# This file is auto-generated with default values.
95
+# Uncomment and modify options as needed.
96
+
97
+[general]
98
+# Grace period in seconds before password is required (0 to disable)
99
+grace_period = 0
100
+
101
+# PAM service name (must have corresponding /etc/pam.d/garlock file)
102
+pam_service = "garlock"
103
+
104
+# Maximum failed attempts before cooldown kicks in
105
+max_attempts = 3
106
+
107
+# Cooldown duration in seconds (multiplied by attempts over max)
108
+cooldown_seconds = 5
109
+
110
+[background]
111
+# Gaussian blur radius (higher = more blur, 0 to disable)
112
+blur_radius = 25.0
113
+
114
+# Brightness adjustment (0.0 = black, 1.0 = original brightness)
115
+brightness = 0.6
116
+
117
+# Fallback solid color if screenshot capture fails (hex format)
118
+fallback_color = "#1a1a2e"
119
+
120
+[ring]
121
+# Ring geometry
122
+radius_outer = 90.0
123
+radius_inner = 75.0
124
+line_width = 6.0
125
+
126
+# State colors (hex format with optional alpha: #RRGGBB or #RRGGBBAA)
127
+# Idle: waiting for input
128
+color_idle = "#1e90ffcc"
129
+
130
+# Typing: receiving password input
131
+color_typing = "#00ff00cc"
132
+
133
+# Verifying: checking password with PAM
134
+color_verifying = "#ffa500cc"
135
+
136
+# Wrong: authentication failed
137
+color_wrong = "#ff0000cc"
138
+
139
+# Clear: backspace pressed / clearing input
140
+color_clear = "#ffff00cc"
141
+
142
+# Inner circle fill color
143
+color_inside = "#00000088"
144
+
145
+# Ring track background color
146
+color_ring_bg = "#00000055"
147
+
148
+[indicator]
149
+# Show Caps Lock warning when active
150
+show_caps_lock = true
151
+caps_lock_text = "Caps Lock"
152
+
153
+# Show number of failed attempts
154
+show_failed_attempts = true
155
+
156
+# Show current time on lock screen
157
+show_time = false
158
+time_format = "%H:%M"
159
+
160
+[font]
161
+# Font family for text elements
162
+family = "Sans"
163
+
164
+# Font size in points
165
+size = 14
166
+"##
167
+        .to_string()
168
+    }
169
+}
170
+
171
+/// General settings
172
+#[derive(Debug, Clone, Serialize, Deserialize)]
173
+#[serde(default)]
174
+pub struct GeneralConfig {
175
+    /// Grace period in seconds before password is required (0 to disable)
176
+    pub grace_period: u32,
177
+
178
+    /// PAM service name
179
+    pub pam_service: String,
180
+
181
+    /// Maximum failed attempts before cooldown
182
+    pub max_attempts: u32,
183
+
184
+    /// Cooldown duration in seconds per attempt over max
185
+    pub cooldown_seconds: u32,
186
+}
187
+
188
+impl Default for GeneralConfig {
189
+    fn default() -> Self {
190
+        Self {
191
+            grace_period: 0,
192
+            pam_service: "garlock".to_string(),
193
+            max_attempts: 3,
194
+            cooldown_seconds: 5,
195
+        }
196
+    }
197
+}
198
+
199
+/// Background settings
200
+#[derive(Debug, Clone, Serialize, Deserialize)]
201
+#[serde(default)]
202
+pub struct BackgroundConfig {
203
+    /// Blur radius (0 to disable)
204
+    pub blur_radius: f32,
205
+
206
+    /// Brightness adjustment (0.0-1.0, lower is darker)
207
+    pub brightness: f32,
208
+
209
+    /// Fallback color if screenshot fails (hex format)
210
+    pub fallback_color: String,
211
+}
212
+
213
+impl Default for BackgroundConfig {
214
+    fn default() -> Self {
215
+        Self {
216
+            blur_radius: 25.0,
217
+            brightness: 0.6,
218
+            fallback_color: "#1a1a2e".to_string(),
219
+        }
220
+    }
221
+}
222
+
223
+/// Ring indicator settings
224
+#[derive(Debug, Clone, Serialize, Deserialize)]
225
+#[serde(default)]
226
+pub struct RingConfig {
227
+    /// Outer ring radius
228
+    pub radius_outer: f64,
229
+
230
+    /// Inner ring radius
231
+    pub radius_inner: f64,
232
+
233
+    /// Ring line width
234
+    pub line_width: f64,
235
+
236
+    /// Color when idle (hex format with alpha)
237
+    pub color_idle: String,
238
+
239
+    /// Color when typing (hex format with alpha)
240
+    pub color_typing: String,
241
+
242
+    /// Color when verifying (hex format with alpha)
243
+    pub color_verifying: String,
244
+
245
+    /// Color when wrong password (hex format with alpha)
246
+    pub color_wrong: String,
247
+
248
+    /// Color when cleared (hex format with alpha)
249
+    pub color_clear: String,
250
+
251
+    /// Inner circle color (hex format with alpha)
252
+    pub color_inside: String,
253
+
254
+    /// Ring background color (hex format with alpha)
255
+    pub color_ring_bg: String,
256
+}
257
+
258
+impl Default for RingConfig {
259
+    fn default() -> Self {
260
+        Self {
261
+            radius_outer: 90.0,
262
+            radius_inner: 75.0,
263
+            line_width: 6.0,
264
+            color_idle: "#1e90ffcc".to_string(),
265
+            color_typing: "#00ff00cc".to_string(),
266
+            color_verifying: "#ffa500cc".to_string(),
267
+            color_wrong: "#ff0000cc".to_string(),
268
+            color_clear: "#ffff00cc".to_string(),
269
+            color_inside: "#00000088".to_string(),
270
+            color_ring_bg: "#00000055".to_string(),
271
+        }
272
+    }
273
+}
274
+
275
+/// Indicator settings
276
+#[derive(Debug, Clone, Serialize, Deserialize)]
277
+#[serde(default)]
278
+pub struct IndicatorConfig {
279
+    /// Show Caps Lock warning
280
+    pub show_caps_lock: bool,
281
+
282
+    /// Caps Lock warning text
283
+    pub caps_lock_text: String,
284
+
285
+    /// Show failed attempt count
286
+    pub show_failed_attempts: bool,
287
+
288
+    /// Show time on lock screen
289
+    pub show_time: bool,
290
+
291
+    /// Time format (strftime)
292
+    pub time_format: String,
293
+}
294
+
295
+impl Default for IndicatorConfig {
296
+    fn default() -> Self {
297
+        Self {
298
+            show_caps_lock: true,
299
+            caps_lock_text: "Caps Lock".to_string(),
300
+            show_failed_attempts: true,
301
+            show_time: false,
302
+            time_format: "%H:%M".to_string(),
303
+        }
304
+    }
305
+}
306
+
307
+/// Font settings
308
+#[derive(Debug, Clone, Serialize, Deserialize)]
309
+#[serde(default)]
310
+pub struct FontConfig {
311
+    /// Font family
312
+    pub family: String,
313
+
314
+    /// Font size
315
+    pub size: u32,
316
+}
317
+
318
+impl Default for FontConfig {
319
+    fn default() -> Self {
320
+        Self {
321
+            family: "Sans".to_string(),
322
+            size: 14,
323
+        }
324
+    }
325
+}