Download
78 lines 2.9 KiBRustModified 2026-08-01 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
//! Persistent bar configuration: which widgets are in which zone, in what
//! order, with what options. Stored as JSON in ~/.local/config/divebar/.
//! The frontend fetches it via get_config and persists edits via
//! save_config; the file is also intended to be hand-editable.

use serde_json::{json, Value};
use std::path::PathBuf;

pub fn config_dir() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
    PathBuf::from(home).join(".local/config/divebar")
}

pub fn config_file() -> PathBuf {
    config_dir().join("config.json")
}

/// Bar-level settings (height, padding, accent, font) are deliberately
/// absent: their schema and defaults live in the frontend
/// (src/lib/bar-config.js), and absent keys mean "use the defaults".
pub fn default_config() -> Value {
    json!({
        "widgets": [
            { "widget": "workspaces", "options": {} },
            { "widget": "window-title", "options": {} },
            { "widget": "wifi", "options": {} },
            { "widget": "volume", "options": {} },
            { "widget": "brightness", "options": {} },
            { "widget": "battery", "options": {} },
            { "widget": "tray", "options": {} },
            { "widget": "date", "options": {} },
            { "widget": "time", "options": {} },
        ]
    })
}

pub fn load() -> Value {
    let path = config_file();
    match std::fs::read_to_string(&path) {
        Ok(contents) => match serde_json::from_str::<Value>(&contents) {
            Ok(v) if v.get("widgets").is_some_and(Value::is_array) => v,
            // Migrate the old zoned format: flatten left, center, right.
            Ok(v) if v.get("zones").is_some_and(Value::is_object) => {
                let zones = &v["zones"];
                let mut widgets = Vec::new();
                for zone in ["left", "center", "right"] {
                    if let Some(entries) = zones.get(zone).and_then(Value::as_array) {
                        widgets.extend(entries.iter().cloned());
                    }
                }
                let migrated = json!({ "widgets": widgets });
                let _ = save(&migrated);
                migrated
            }
            _ => {
                eprintln!(
                    "divebar: invalid config at {}, using defaults",
                    path.display()
                );
                default_config()
            }
        },
        // First run: write the defaults so the user has a file to edit.
        Err(_) => {
            let defaults = default_config();
            if let Err(err) = save(&defaults) {
                eprintln!("divebar: could not write default config: {err}");
            }
            defaults
        }
    }
}

pub fn save(config: &Value) -> Result<(), String> {
    std::fs::create_dir_all(config_dir()).map_err(|e| e.to_string())?;
    let pretty = serde_json::to_string_pretty(config).map_err(|e| e.to_string())?;
    std::fs::write(config_file(), pretty + "\n").map_err(|e| e.to_string())
}