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