use crate::{types::socket::Request, utils::config::get_config_path}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, error::Error, fs, path::PathBuf}; #[derive(Default, Clone, Serialize, Deserialize)] #[serde(default)] pub struct DaemonConfig { pub default_input_name: Option, pub default_volume: Option, } impl DaemonConfig { pub fn save_to_file(&self) -> Result<(), Box> { let config_path = get_config_path()?.join("daemon.json"); if let Some(config_dir) = config_path.parent() { if !config_path.exists() { fs::create_dir_all(config_dir)?; } } let config_json = serde_json::to_string_pretty(self)?; fs::write(config_path, config_json.as_bytes())?; Ok(()) } pub fn load_from_file() -> Result> { let config_path = get_config_path()?.join("daemon.json"); let bytes = fs::read(config_path)?; match serde_json::from_slice::(&bytes) { Ok(config) => Ok(config), Err(_) => Ok(DaemonConfig::default()), } } } #[derive(Clone, Serialize, Deserialize)] #[serde(default)] pub struct GuiConfig { pub scale_factor: f32, pub left_panel_width: f32, pub save_volume: bool, pub save_input: bool, pub save_scale_factor: bool, pub pause_on_exit: bool, pub dirs: Vec, } impl Default for GuiConfig { fn default() -> Self { GuiConfig { scale_factor: 1.0, left_panel_width: 280.0, save_volume: false, save_input: false, save_scale_factor: false, pause_on_exit: false, dirs: vec![], } } } impl GuiConfig { pub fn save_to_file(&mut self) -> Result<(), Box> { let config_path = get_config_path()?.join("gui.json"); if let Some(config_dir) = config_path.parent() { if !config_path.exists() { fs::create_dir_all(config_dir)?; } } // Do not save scale factor if user does not want to if !self.save_scale_factor { self.scale_factor = 1.0; } let config_json = serde_json::to_string_pretty(self)?; fs::write(config_path, config_json.as_bytes())?; Ok(()) } pub fn load_from_file() -> Result> { let config_path = get_config_path()?.join("gui.json"); let bytes = fs::read(config_path)?; match serde_json::from_slice::(&bytes) { Ok(config) => Ok(config), Err(_) => Ok(GuiConfig::default()), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct HotkeySlot { pub slot: String, pub action: Request, #[serde(skip_serializing_if = "Option::is_none")] pub key_chord: Option, } #[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct HotkeyConfig { #[serde(default)] pub slots: Vec, } impl HotkeyConfig { pub fn config_path() -> Result> { Ok(get_config_path()?.join("hotkeys.json")) } pub fn load() -> Result> { let path = Self::config_path()?; if !path.exists() { return Ok(HotkeyConfig::default()); } let bytes = fs::read(&path)?; match serde_json::from_slice::(&bytes) { Ok(config) => Ok(config), Err(_) => Ok(HotkeyConfig::default()), } } pub fn save(&self) -> Result<(), Box> { let path = Self::config_path()?; if let Some(dir) = path.parent() && !dir.exists() { fs::create_dir_all(dir)?; } let json = serde_json::to_string_pretty(self)?; fs::write(path, json.as_bytes())?; Ok(()) } pub fn find_slot(&self, slot: &str) -> Option<&HotkeySlot> { self.slots.iter().find(|s| s.slot == slot) } pub fn find_slot_mut(&mut self, slot: &str) -> Option<&mut HotkeySlot> { self.slots.iter_mut().find(|s| s.slot == slot) } pub fn set_slot(&mut self, slot: String, action: Request) { if let Some(existing) = self.find_slot_mut(&slot) { existing.action = action; } else { self.slots.push(HotkeySlot { slot, action, key_chord: None, }); } } pub fn set_key_chord(&mut self, slot: &str, key_chord: Option) -> bool { if let Some(existing) = self.find_slot_mut(slot) { existing.key_chord = key_chord; true } else { false } } pub fn remove_slot(&mut self, slot: &str) -> bool { let len = self.slots.len(); self.slots.retain(|s| s.slot != slot); self.slots.len() != len } /// Returns pairs of slot names that share the same key chord. pub fn find_conflicts(&self) -> Vec<(String, String)> { let mut conflicts = vec![]; let mut chord_map: HashMap<&str, Vec<&str>> = HashMap::new(); for s in &self.slots { if let Some(chord) = &s.key_chord { chord_map.entry(chord.as_str()).or_default().push(&s.slot); } } for slots in chord_map.values() { if slots.len() > 1 { for i in 0..slots.len() { for j in (i + 1)..slots.len() { conflicts.push((slots[i].to_string(), slots[j].to_string())); } } } } conflicts } /// Find which slot(s) have the given key chord. pub fn slots_for_chord(&self, chord: &str) -> Vec<&HotkeySlot> { self.slots .iter() .filter(|s| s.key_chord.as_deref() == Some(chord)) .collect() } }