use KDL instead of TOML

This commit is contained in:
2024-12-18 04:35:15 +00:00
parent 986b1039ed
commit ce5bb9f4af
4 changed files with 146 additions and 119 deletions

View File

@@ -1,4 +1,4 @@
use serde::{Deserialize, Serialize};
use kdl::KdlDocument;
use serde_json::{from_str, Value};
use std::collections::HashMap;
use std::env;
@@ -6,7 +6,6 @@ use std::process::Command;
use std::{fs, str};
/// Configuration for swayout.
#[derive(Serialize, Deserialize, Debug)]
struct Config {
/// Monitor name is independent of output name. It can be anything.
monitors: HashMap<String, Monitor>,
@@ -16,15 +15,15 @@ struct Config {
/// Available outputs that do not match a monitor in the map are disabled.
layouts: HashMap<String, HashMap<String, OutputConfig>>,
}
/// Defines a monitor by make, model, and serial.
#[derive(Serialize, Deserialize, Debug)]
struct Monitor {
make: String,
model: String,
serial: String,
}
/// Configuration for an enabled output.
#[derive(Serialize, Deserialize, Debug)]
struct OutputConfig {
/// mode. See man 5 sway-output
mode: String,
@@ -112,13 +111,13 @@ fn get_mode(output: &Value) -> String {
}
}
/// Get a `Config` from ~/.config/swayout/config.toml.
/// Get a `Config` from ~/.config/swayout/config.kdl.
/// (It really uses XDG config dirs.)
fn get_config() -> Config {
let xdg_dirs = xdg::BaseDirectories::with_prefix("swayout").unwrap();
if let Some(path) = xdg_dirs.find_config_file("config.toml") {
let toml = fs::read_to_string(path).expect("error reading file");
toml::from_str(&toml).expect("toml parse error")
if let Some(path) = xdg_dirs.find_config_file("config.kdl") {
let kdl = fs::read_to_string(path).expect("error reading file");
parse_config(kdl)
} else {
// no config files, use an empty rules
Config {
@@ -127,6 +126,46 @@ fn get_config() -> Config {
}
}
}
fn parse_config(kdl:String) -> Config {
let doc:KdlDocument = kdl.parse().expect("failed to parse config KDL");
let monitors:HashMap<String,Monitor> = doc.nodes().iter()
.filter(|node| node.name().value() == "monitor")
.map(|monitor_node| {
let name = String::from(monitor_node[0].as_string().unwrap());
let monitor = Monitor {
make : String::from(monitor_node["make"].as_string().unwrap()),
model : String::from(monitor_node["model"].as_string().unwrap()),
serial : String::from(monitor_node["serial"].as_string().unwrap()),
};
(name, monitor)
})
.collect();
let layouts:HashMap<String,HashMap<String,OutputConfig>> = doc.nodes().iter()
.filter(|node| node.name().value() == "layout")
.map(|layout_node| {
let layout_name = String::from(layout_node[0].as_string().unwrap());
let monitor_name_to_output_config:HashMap<String,OutputConfig> = layout_node
.children().unwrap().nodes().iter()
.map(|output_node| {
let output = String::from(output_node[0].as_string().unwrap());
let output_config = OutputConfig {
mode : String::from(output_node["mode"].as_string().unwrap()),
scale : String::from(output_node["scale"].as_string().unwrap()),
transform : String::from(output_node["transform"].as_string().unwrap()),
x : output_node["x"].as_i64().unwrap() as u16,
y : output_node["y"].as_i64().unwrap() as u16,
};
(output, output_config)
})
.collect();
(layout_name,monitor_name_to_output_config)
})
.collect();
Config { monitors, layouts }
}
/// Determine the available layout names.
/// Return one layout for each layout defined in the configuration file
@@ -135,12 +174,10 @@ fn get_config() -> Config {
/// and one for each available output that is not a configured monitor (for using just that output).
fn get_layouts() -> Vec<String> {
let available_outputs = get_outputs();
let rules = get_config();
let mut layout_names: Vec<String> = Vec::new();
let config = get_config();
// Get the names of monitors that are available (that match an available output)
let available_monitor_names:Vec<&String> = rules.monitors.iter()
let available_monitor_names:Vec<&String> = config.monitors.iter()
.filter(|(_monitor_name,monitor)|
available_outputs.iter().any(|output|
monitor.make == output.make
@@ -151,8 +188,10 @@ fn get_layouts() -> Vec<String> {
.map(|(monitor_name,_monitor)| monitor_name)
.collect();
let mut layout_names: Vec<String> = Vec::new();
// Add each layout defined in the config file for which all outputs are available
rules.layouts.iter().for_each(|(layout_name, layout)| {
config.layouts.iter().for_each(|(layout_name, layout)| {
if layout
.iter()
.all(|(output_name, _output)| available_monitor_names.contains(&output_name))
@@ -164,7 +203,7 @@ fn get_layouts() -> Vec<String> {
// add each individual output (by monitor name if the monitor is defined, else by output name)
available_outputs.iter().for_each(|output| {
// if there is a monitor for the output, use the monitor name, else use the output name
let monitor_opt = rules.monitors.iter()
let monitor_opt = config.monitors.iter()
.find(|(_monitor_name, monitor)| {
monitor.make == output.make
&& monitor.model == output.model