use serde::Deserialize; use std::fs; #[derive(Deserialize, Debug, Clone, Default)] pub struct Config { /// Sökväg till bakgrundsbild (Kitty graphics protocol) #[serde(default)] pub background_image: Option, /// Bakgrundsfärg för virtuella terminaler. Om None → genomskinlig (Color::Reset). /// Stödjer: "#RRGGBB", "N" (indexed 0-255), färgnamn. #[serde(default)] pub terminal_bg_color: Option, /// Standard-shell för nya terminaler. Om None → $SHELL eller /bin/bash. #[serde(default)] pub default_shell: Option, #[serde(default, rename = "panel")] pub panels: Vec, #[serde(default, rename = "keybind")] pub keybinds: Vec, } /// En tangentbords-genväg definierad i config #[derive(Deserialize, Debug, Clone)] pub struct KeybindConfig { /// T.ex. "ctrl+space", "alt+f2", "ctrl+t" pub key: String, /// "global" = alltid, "wm" = bara när ingen terminal är fokuserad #[serde(default)] pub scope: KeybindScope, pub action: MenuAction, /// Visningsnamn som visas i TUI-WM-menyn (valfritt) #[serde(default)] pub label: Option, } #[derive(Deserialize, Debug, Clone, Default, PartialEq)] #[serde(rename_all = "lowercase")] pub enum KeybindScope { /// Aktiveras alltid, oavsett fokus Global, /// Aktiveras bara när ingen terminal-ruta är fokuserad #[default] Wm, } #[derive(Deserialize, Debug, Clone)] pub struct PanelConfig { pub position: PanelPosition, #[serde(default, rename = "item")] pub items: Vec, #[serde(default, rename = "status")] pub status_widgets: Vec, /// Länk till extern fil med panel-definition pub file: Option, } #[derive(Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "lowercase")] pub enum PanelPosition { Top, Bottom, Left, Right, } #[derive(Deserialize, Debug, Clone)] pub struct MenuItem { pub label: String, pub action: MenuAction, } #[derive(Deserialize, Debug, Clone)] #[serde(tag = "type", rename_all = "snake_case")] pub enum MenuAction { Exit, SpawnTerminal { shell: Option, }, RunScript { path: String, }, RunProgram { command: String, #[serde(default)] args: Vec, }, Submenu { #[serde(default, rename = "item")] items: Vec, }, /// Öppnar Tui-run kommandodialog SpawnRunDialog, /// Gör ingenting – används för display-only rader i menyer NoOp, } /// En status-widget i panelen: kör ett kommando periodiskt och renderar output #[derive(Deserialize, Debug, Clone)] pub struct StatusWidget { /// Shell-kommando som producerar en sträng (t.ex. `date '+%H:%M:%S'`) pub command: String, /// Hur ofta kommandot körs, i sekunder #[serde(default = "default_interval")] pub interval: u64, /// Hur många tecken brett blocket är pub width: u16, /// Dockas mot höger eller vänster i panelen #[serde(default)] pub align: StatusAlign, } #[derive(Deserialize, Debug, Clone, Default, PartialEq)] #[serde(rename_all = "lowercase")] pub enum StatusAlign { Left, #[default] Right, } fn default_interval() -> u64 { 10 } fn default_keybinds() -> Vec { vec![ KeybindConfig { key: "ctrl+space".to_string(), scope: KeybindScope::Global, action: MenuAction::SpawnRunDialog, label: Some("Öppna kommandodialog".to_string()), }, KeybindConfig { key: "alt+enter".to_string(), scope: KeybindScope::Global, action: MenuAction::SpawnTerminal { shell: None }, label: Some("Ny terminal".to_string()), }, KeybindConfig { key: "ctrl+q".to_string(), scope: KeybindScope::Wm, action: MenuAction::Exit, label: Some("Avsluta TUI-WM".to_string()), }, ] } fn default_panels() -> Vec { vec![PanelConfig { position: PanelPosition::Top, items: vec![ MenuItem { label: "Terminal".to_string(), action: MenuAction::SpawnTerminal { shell: Some("/bin/bash".to_string()) }, }, MenuItem { label: "Avsluta".to_string(), action: MenuAction::Exit, }, ], status_widgets: vec![], file: None, }] } impl Config { pub fn load(path: &str) -> anyhow::Result { let content = fs::read_to_string(path)?; let mut config: Config = toml::from_str(&content)?; for panel in &mut config.panels { if let Some(file) = panel.file.clone() { let c = fs::read_to_string(&file) .map_err(|e| anyhow::anyhow!("Kunde inte läsa {}: {}", file, e))?; let ext: ExternalPanelConfig = toml::from_str(&c) .map_err(|e| anyhow::anyhow!("Parse-fel i {}: {}", file, e))?; panel.items = ext.items; panel.status_widgets = ext.status_widgets; } } Ok(config) } pub fn load_or_default(path: &str) -> Config { match Config::load(path) { Ok(c) => c, Err(e) => { eprintln!("Varning: kunde inte ladda {}: {}", path, e); Config { background_image: None, terminal_bg_color: None, default_shell: None, panels: default_panels(), keybinds: default_keybinds(), } } } } /// Uppdatera background_image i config-filen (TOML) pub fn save_background(path: &str, image_path: Option<&str>) -> anyhow::Result<()> { let content = fs::read_to_string(path).unwrap_or_default(); let mut doc: toml_edit::DocumentMut = content.parse().unwrap_or_default(); match image_path { Some(p) => { doc["background_image"] = toml_edit::value(p); } None => { doc.remove("background_image"); } } fs::write(path, doc.to_string())?; Ok(()) } } #[derive(Deserialize)] struct ExternalPanelConfig { #[serde(default, rename = "item")] items: Vec, #[serde(default, rename = "status")] status_widgets: Vec, }