Rust · 9341 bytes Raw Blame History
1 //! Configuration management for HyprKVM
2
3 #![allow(dead_code)]
4
5 use std::net::SocketAddr;
6 use std::path::Path;
7
8 use hyprkvm_common::Direction;
9 use serde::{Deserialize, Serialize};
10
11 /// Main configuration structure
12 #[derive(Debug, Clone, Serialize, Deserialize)]
13 pub struct Config {
14 #[serde(default)]
15 pub daemon: DaemonConfig,
16
17 #[serde(default)]
18 pub network: NetworkConfig,
19
20 #[serde(default)]
21 pub machines: MachinesConfig,
22
23 #[serde(default)]
24 pub input: InputConfig,
25
26 #[serde(default)]
27 pub clipboard: ClipboardConfig,
28
29 #[serde(default)]
30 pub logging: LoggingConfig,
31 }
32
33 impl Default for Config {
34 fn default() -> Self {
35 Self {
36 daemon: DaemonConfig::default(),
37 network: NetworkConfig::default(),
38 machines: MachinesConfig::default(),
39 input: InputConfig::default(),
40 clipboard: ClipboardConfig::default(),
41 logging: LoggingConfig::default(),
42 }
43 }
44 }
45
46 impl Config {
47 /// Load configuration from file
48 pub fn load(path: &Path) -> Result<Self, ConfigError> {
49 let content = std::fs::read_to_string(path)
50 .map_err(|e| ConfigError::Io(e.to_string()))?;
51
52 toml::from_str(&content)
53 .map_err(|e| ConfigError::Parse(e.to_string()))
54 }
55
56 /// Save configuration to file
57 pub fn save(&self, path: &Path) -> Result<(), ConfigError> {
58 let content = toml::to_string_pretty(self)
59 .map_err(|e| ConfigError::Serialize(e.to_string()))?;
60
61 // Ensure parent directory exists
62 if let Some(parent) = path.parent() {
63 std::fs::create_dir_all(parent)
64 .map_err(|e| ConfigError::Io(e.to_string()))?;
65 }
66
67 std::fs::write(path, content)
68 .map_err(|e| ConfigError::Io(e.to_string()))
69 }
70 }
71
72 #[derive(Debug, Clone, Serialize, Deserialize)]
73 pub struct DaemonConfig {
74 /// Unix socket path for IPC
75 #[serde(default = "default_socket_path")]
76 pub socket_path: String,
77 }
78
79 fn default_socket_path() -> String {
80 std::env::var("XDG_RUNTIME_DIR")
81 .map(|dir| format!("{}/hyprkvm.sock", dir))
82 .unwrap_or_else(|_| "/tmp/hyprkvm.sock".to_string())
83 }
84
85 impl Default for DaemonConfig {
86 fn default() -> Self {
87 Self {
88 socket_path: default_socket_path(),
89 }
90 }
91 }
92
93 #[derive(Debug, Clone, Serialize, Deserialize)]
94 pub struct NetworkConfig {
95 /// Port to listen on
96 #[serde(default = "default_port")]
97 pub listen_port: u16,
98
99 /// Address to bind to
100 #[serde(default = "default_bind_address")]
101 pub bind_address: String,
102
103 /// Connection timeout in seconds
104 #[serde(default = "default_timeout")]
105 pub connect_timeout_secs: u64,
106
107 /// Ping interval in seconds
108 #[serde(default = "default_ping_interval")]
109 pub ping_interval_secs: u64,
110
111 /// Ping timeout in seconds
112 #[serde(default = "default_ping_timeout")]
113 pub ping_timeout_secs: u64,
114
115 /// TLS configuration
116 #[serde(default)]
117 pub tls: TlsConfig,
118 }
119
120 fn default_port() -> u16 { 24850 }
121 fn default_bind_address() -> String { "0.0.0.0".to_string() }
122 fn default_timeout() -> u64 { 5 }
123 fn default_ping_interval() -> u64 { 5 }
124 fn default_ping_timeout() -> u64 { 10 }
125
126 impl Default for NetworkConfig {
127 fn default() -> Self {
128 Self {
129 listen_port: default_port(),
130 bind_address: default_bind_address(),
131 connect_timeout_secs: default_timeout(),
132 ping_interval_secs: default_ping_interval(),
133 ping_timeout_secs: default_ping_timeout(),
134 tls: TlsConfig::default(),
135 }
136 }
137 }
138
139 #[derive(Debug, Clone, Serialize, Deserialize)]
140 pub struct TlsConfig {
141 /// Enable TLS encryption (default: false for backwards compatibility)
142 #[serde(default)]
143 pub enabled: bool,
144
145 /// Path to certificate file
146 #[serde(default = "default_cert_path")]
147 pub cert_path: String,
148
149 /// Path to private key file
150 #[serde(default = "default_key_path")]
151 pub key_path: String,
152
153 /// Enable TOFU (Trust On First Use) for unknown peers
154 #[serde(default = "default_true")]
155 pub tofu: bool,
156 }
157
158 fn default_cert_path() -> String {
159 dirs::config_dir()
160 .map(|d| d.join("hyprkvm").join("cert.pem"))
161 .unwrap_or_else(|| std::path::PathBuf::from("cert.pem"))
162 .to_string_lossy()
163 .to_string()
164 }
165
166 fn default_key_path() -> String {
167 dirs::config_dir()
168 .map(|d| d.join("hyprkvm").join("key.pem"))
169 .unwrap_or_else(|| std::path::PathBuf::from("key.pem"))
170 .to_string_lossy()
171 .to_string()
172 }
173
174 impl Default for TlsConfig {
175 fn default() -> Self {
176 Self {
177 enabled: false, // Default to false for backwards compatibility
178 cert_path: default_cert_path(),
179 key_path: default_key_path(),
180 tofu: true,
181 }
182 }
183 }
184
185 #[derive(Debug, Clone, Serialize, Deserialize)]
186 pub struct MachinesConfig {
187 /// This machine's name
188 #[serde(default = "default_machine_name")]
189 pub self_name: String,
190
191 /// Neighbor machines
192 #[serde(default)]
193 pub neighbors: Vec<NeighborConfig>,
194 }
195
196 fn default_machine_name() -> String {
197 hostname::get()
198 .map(|h| h.to_string_lossy().to_string())
199 .unwrap_or_else(|_| "unknown".to_string())
200 }
201
202 impl Default for MachinesConfig {
203 fn default() -> Self {
204 Self {
205 self_name: default_machine_name(),
206 neighbors: Vec::new(),
207 }
208 }
209 }
210
211 #[derive(Debug, Clone, Serialize, Deserialize)]
212 pub struct NeighborConfig {
213 /// Machine name
214 pub name: String,
215
216 /// Direction relative to this machine
217 pub direction: Direction,
218
219 /// Address (ip:port)
220 pub address: SocketAddr,
221
222 /// Pre-trusted certificate fingerprint (optional, for TLS pinning)
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub fingerprint: Option<String>,
225
226 /// Override TLS setting for this neighbor (uses global setting if None)
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub tls: Option<bool>,
229 }
230
231 #[derive(Debug, Clone, Serialize, Deserialize)]
232 pub struct InputConfig {
233 /// Escape hotkey configuration
234 #[serde(default)]
235 pub escape_hotkey: EscapeHotkeyConfig,
236 }
237
238 impl Default for InputConfig {
239 fn default() -> Self {
240 Self {
241 escape_hotkey: EscapeHotkeyConfig::default(),
242 }
243 }
244 }
245
246 #[derive(Debug, Clone, Serialize, Deserialize)]
247 pub struct EscapeHotkeyConfig {
248 /// Primary escape key (e.g., "scroll_lock", "pause")
249 #[serde(default = "default_escape_key")]
250 pub key: String,
251
252 /// Required modifiers
253 #[serde(default)]
254 pub modifiers: Vec<String>,
255
256 /// Enable triple-tap escape
257 #[serde(default = "default_true")]
258 pub triple_tap_enabled: bool,
259
260 /// Triple-tap key
261 #[serde(default = "default_triple_tap_key")]
262 pub triple_tap_key: String,
263
264 /// Triple-tap window in milliseconds
265 #[serde(default = "default_triple_tap_window")]
266 pub triple_tap_window_ms: u64,
267 }
268
269 fn default_escape_key() -> String { "scroll_lock".to_string() }
270 fn default_true() -> bool { true }
271 fn default_triple_tap_key() -> String { "shift".to_string() }
272 fn default_triple_tap_window() -> u64 { 500 }
273
274 impl Default for EscapeHotkeyConfig {
275 fn default() -> Self {
276 Self {
277 key: default_escape_key(),
278 modifiers: Vec::new(),
279 triple_tap_enabled: true,
280 triple_tap_key: default_triple_tap_key(),
281 triple_tap_window_ms: default_triple_tap_window(),
282 }
283 }
284 }
285
286 #[derive(Debug, Clone, Serialize, Deserialize)]
287 pub struct ClipboardConfig {
288 /// Enable clipboard sync
289 #[serde(default = "default_true")]
290 pub enabled: bool,
291
292 /// Sync on control transfer enter
293 #[serde(default = "default_true")]
294 pub sync_on_enter: bool,
295
296 /// Sync on control transfer leave
297 #[serde(default = "default_true")]
298 pub sync_on_leave: bool,
299
300 /// Sync text content
301 #[serde(default = "default_true")]
302 pub sync_text: bool,
303
304 /// Sync image content
305 #[serde(default = "default_true")]
306 pub sync_images: bool,
307
308 /// Maximum clipboard size in bytes
309 #[serde(default = "default_max_clipboard_size")]
310 pub max_size: u64,
311 }
312
313 fn default_max_clipboard_size() -> u64 { 10 * 1024 * 1024 } // 10MB
314
315 impl Default for ClipboardConfig {
316 fn default() -> Self {
317 Self {
318 enabled: true,
319 sync_on_enter: true,
320 sync_on_leave: true,
321 sync_text: true,
322 sync_images: true,
323 max_size: default_max_clipboard_size(),
324 }
325 }
326 }
327
328 #[derive(Debug, Clone, Serialize, Deserialize)]
329 pub struct LoggingConfig {
330 /// Log level (error, warn, info, debug, trace)
331 #[serde(default = "default_log_level")]
332 pub level: String,
333 }
334
335 fn default_log_level() -> String { "info".to_string() }
336
337 impl Default for LoggingConfig {
338 fn default() -> Self {
339 Self {
340 level: default_log_level(),
341 }
342 }
343 }
344
345 /// Configuration errors
346 #[derive(Debug, thiserror::Error)]
347 pub enum ConfigError {
348 #[error("IO error: {0}")]
349 Io(String),
350
351 #[error("Parse error: {0}")]
352 Parse(String),
353
354 #[error("Serialize error: {0}")]
355 Serialize(String),
356
357 #[error("Validation error: {0}")]
358 Validation(String),
359 }
360