diff --git a/Cargo.lock b/Cargo.lock index 37c114c..1e87332 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,7 +1074,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -1084,7 +1084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.14", ] [[package]] @@ -1272,6 +1272,15 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_with" version = "3.4.0" @@ -1529,11 +1538,26 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.21.0", +] + [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -1546,6 +1570,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.11" @@ -1867,6 +1904,7 @@ dependencies = [ "serde_with", "serde_yaml", "swayipc", + "toml 0.8.8", "wayland-client", "wayland-protocols-wlr", "x11rb", diff --git a/Cargo.toml b/Cargo.toml index d991f42..b218a11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ wayland-protocols-wlr = { version = "0.1", features = ["client"], optional = tru x11rb = { version = "0.13.0", optional = true } zbus = { version = "1.9.2", optional = true } hyprland = { version = "0.3.12", optional = true } +toml = "0.8.8" [features] gnome = ["zbus"] diff --git a/src/config/mod.rs b/src/config/mod.rs index 8af18c7..73d9b79 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -12,6 +12,7 @@ pub mod remap; mod tests; extern crate serde_yaml; +extern crate toml; use evdev::Key; use keymap::Keymap; @@ -52,14 +53,40 @@ pub struct Config { pub keymap_table: HashMap>, } +enum ConfigFiletype { + Yaml, + Toml, +} + +fn get_file_ext(filename: &PathBuf) -> ConfigFiletype { + match filename.extension() { + Some(f) => { + if f.to_str().unwrap_or("").to_lowercase() == "toml" { + ConfigFiletype::Toml + } else { + ConfigFiletype::Yaml + } + }, + _ => ConfigFiletype::Yaml, + } +} + pub fn load_configs(filenames: &Vec) -> Result> { // Assumes filenames is non-empty - let yaml = fs::read_to_string(&filenames[0])?; - let mut config: Config = serde_yaml::from_str(&yaml)?; + let config_contents = fs::read_to_string(&filenames[0])?; + let mut config: Config = match get_file_ext(&filenames[0]) { + ConfigFiletype::Yaml => serde_yaml::from_str(&config_contents)?, + ConfigFiletype::Toml => toml::from_str(&config_contents)?, + }; + for filename in &filenames[1..] { - let yaml = fs::read_to_string(&filename)?; - let c: Config = serde_yaml::from_str(&yaml)?; + let config_contents = fs::read_to_string(&filename)?; + let c: Config = match get_file_ext(&filename) { + ConfigFiletype::Yaml => serde_yaml::from_str(&config_contents)?, + ConfigFiletype::Toml => serde_yaml::from_str(&config_contents)?, + }; + config.modmap.extend(c.modmap); config.keymap.extend(c.keymap); config.virtual_modifiers.extend(c.virtual_modifiers); diff --git a/src/config/tests.rs b/src/config/tests.rs index b27558a..7fbd22e 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -1,10 +1,12 @@ use crate::Config; use indoc::indoc; -use serde_yaml::Error; + +extern crate serde_yaml; +extern crate toml; #[test] -fn test_modmap_basic() { - assert_parse(indoc! {" +fn test_yaml_modmap_basic() { + yaml_assert_parse(indoc! {" modmap: - name: Global remap: @@ -17,8 +19,8 @@ fn test_modmap_basic() { } #[test] -fn test_modmap_application() { - assert_parse(indoc! {" +fn test_yaml_modmap_application() { + yaml_assert_parse(indoc! {" modmap: - remap: Alt_L: Ctrl_L @@ -33,8 +35,8 @@ fn test_modmap_application() { } #[test] -fn test_modmap_application_regex() { - assert_parse(indoc! {r" +fn test_yaml_modmap_application_regex() { + yaml_assert_parse(indoc! {r" modmap: - remap: Alt_L: Ctrl_L @@ -51,8 +53,8 @@ fn test_modmap_application_regex() { } #[test] -fn test_modmap_multi_purpose_key() { - assert_parse(indoc! {" +fn test_yaml_modmap_multi_purpose_key() { + yaml_assert_parse(indoc! {" modmap: - remap: Space: @@ -66,8 +68,8 @@ fn test_modmap_multi_purpose_key() { "}) } #[test] -fn test_modmap_multi_purpose_key_multi_key() { - assert_parse(indoc! {" +fn test_yaml_modmap_multi_purpose_key_multi_key() { + yaml_assert_parse(indoc! {" modmap: - remap: Space: @@ -81,16 +83,16 @@ fn test_modmap_multi_purpose_key_multi_key() { "}) } #[test] -fn test_virtual_modifiers() { - assert_parse(indoc! {" +fn test_yaml_virtual_modifiers() { + yaml_assert_parse(indoc! {" virtual_modifiers: - CapsLock "}) } #[test] -fn test_modmap_press_release_key() { - assert_parse(indoc! {r#" +fn test_yaml_modmap_press_release_key() { + yaml_assert_parse(indoc! {r#" modmap: - remap: Space: @@ -100,8 +102,8 @@ fn test_modmap_press_release_key() { } #[test] -fn test_keymap_basic() { - assert_parse(indoc! {" +fn test_yaml_keymap_basic() { + yaml_assert_parse(indoc! {" keymap: - name: Global remap: @@ -112,8 +114,8 @@ fn test_keymap_basic() { } #[test] -fn test_keymap_lr_modifiers() { - assert_parse(indoc! {" +fn test_yaml_keymap_lr_modifiers() { + yaml_assert_parse(indoc! {" keymap: - name: Global remap: @@ -124,8 +126,8 @@ fn test_keymap_lr_modifiers() { } #[test] -fn test_keymap_application() { - assert_parse(indoc! {" +fn test_yaml_keymap_application() { + yaml_assert_parse(indoc! {" keymap: - remap: Alt-Enter: Ctrl-Enter @@ -140,8 +142,8 @@ fn test_keymap_application() { } #[test] -fn test_keymap_array() { - assert_parse(indoc! {" +fn test_yaml_keymap_array() { + yaml_assert_parse(indoc! {" keymap: - remap: C-w: @@ -151,8 +153,8 @@ fn test_keymap_array() { } #[test] -fn test_keymap_remap() { - assert_parse(indoc! {" +fn test_yaml_keymap_remap() { + yaml_assert_parse(indoc! {" keymap: - remap: C-x: @@ -167,8 +169,8 @@ fn test_keymap_remap() { } #[test] -fn test_keymap_launch() { - assert_parse(indoc! {r#" +fn test_yaml_keymap_launch() { + yaml_assert_parse(indoc! {r#" keymap: - remap: KEY_GRAVE: @@ -180,8 +182,8 @@ fn test_keymap_launch() { } #[test] -fn test_keymap_mode() { - assert_parse(indoc! {" +fn test_yaml_keymap_mode() { + yaml_assert_parse(indoc! {" default_mode: insert keymap: - mode: insert @@ -198,8 +200,8 @@ fn test_keymap_mode() { } #[test] -fn test_keymap_mark() { - assert_parse(indoc! {" +fn test_yaml_keymap_mark() { + yaml_assert_parse(indoc! {" keymap: - remap: C-space: { set_mark: true } @@ -210,8 +212,8 @@ fn test_keymap_mark() { } #[test] -fn test_shared_data_anchor() { - assert_parse(indoc! {" +fn test_yaml_shared_data_anchor() { + yaml_assert_parse(indoc! {" shared: terminals: &terminals - Gnome-terminal @@ -231,8 +233,8 @@ fn test_shared_data_anchor() { #[test] #[should_panic] -fn test_fail_on_data_outside_of_config_model() { - assert_parse(indoc! {" +fn test_yaml_fail_on_data_outside_of_config_model() { + yaml_assert_parse(indoc! {" terminals: &terminals - Gnome-terminal - Kitty @@ -249,8 +251,287 @@ fn test_fail_on_data_outside_of_config_model() { "}) } -fn assert_parse(yaml: &str) { - let result: Result = serde_yaml::from_str(yaml); +#[test] +fn test_toml_modmap_basic() { + toml_assert_parse(indoc! {" + [[modmap]] + name = \"Global\" + [modmap.remap] + Alt_L = \"Ctrl_L\" + + [[modmap]] + [modmap.remap] + Shift_R = \"Win_R\" + + [modmap.application] + only = \"Google-chrome\" + "}) +} + +#[test] +fn test_toml_modmap_application() { + toml_assert_parse(indoc! {" + [[modmap]] + [modmap.remap] + Alt_L = \"Ctrl_L\" + + [modmap.application] + not = [ \"Gnome-terminal\" ] + + [[modmap]] + [modmap.remap] + Shift_R = \"Win_R\" + + [modmap.application] + only = \"Google-chrome\" + + "}) +} + +#[test] +fn test_toml_modmap_application_regex() { + toml_assert_parse(indoc! {r#" + [[modmap]] + [modmap.remap] + Alt_L = "Ctrl_L" + + [modmap.application] + not = [ "/^Minecraft/", "/^Minecraft\\//", "/^Minecraft\\d/" ] + + [[modmap]] + [modmap.remap] + Shift_R = "Win_R" + + [modmap.application] + only = "/^Miencraft\\\\/" + + "#}) +} + +#[test] +fn test_toml_modmap_multi_purpose_key() { + toml_assert_parse(indoc! {" + [[modmap]] + [modmap.remap.Space] + held = [ \"Shift_L\" ] + alone = \"Space\" + + [[modmap]] + [modmap.remap.Muhenkan] + held = [ \"Alt_L\", \"Shift_L\" ] + alone = [ \"Muhenkan\" ] + alone_timeout_millis = 500 + "}) +} + +#[test] +fn test_toml_modmap_multi_purpose_key_multi_key() { + toml_assert_parse(indoc! {" + [[modmap]] + [modmap.remap.Space] + held = [ \"Shift_L\" ] + alone = [ \"Shift_L\", \"A\" ] + + [[modmap]] + [modmap.remap.Muhenkan] + held = [ \"Alt_L\", \"Shift_L\" ] + alone = [ \"Muhenkan\" ] + alone_timeout_millis = 500 + "}) +} +#[test] +fn test_toml_virtual_modifiers() { + toml_assert_parse(indoc! {" + virtual_modifiers = [ \"CapsLock\" ] + "}) +} + +#[test] +fn test_toml_modmap_press_release_key() { + toml_assert_parse(indoc! {r#" + [[modmap]] + [modmap.remap.Space.press] + launch = [ "wmctrl", "-x", "-a", "code.Code" ] + [modmap.remap.Space.release] + launch = ["wmctrl", "-x", "-a", "nocturn.Nocturn"] + "#}) +} + +#[test] +fn test_toml_keymap_basic() { + toml_assert_parse(indoc! {" + [[keymap]] + name = \"Global\" + [keymap.remap] + Alt-Enter = \"Ctrl-Enter\" + + [[keymap]] + [keymap.remap] + M-S = \"C-S\" + "}) +} + +#[test] +fn test_toml_keymap_lr_modifiers() { + toml_assert_parse(indoc! {" + [[keymap]] + name = \"Global\" + [keymap.remap] + Alt_L-Enter = \"Ctrl_L-Enter\" + + [[keymap]] + [keymap.remap] + M_R-S = \"C_L-S\" + "}) +} + +#[test] +fn test_toml_keymap_application() { + toml_assert_parse(indoc! {" + [[keymap]] + [keymap.remap] + Alt-Enter = \"Ctrl-Enter\" + [keymap.application] + not = \"Gnome-terminal\" + [[keymap]] + [keymap.remap] + Alt-S = \"Ctrl-S\" + [keymap.application] + only = \"Gnome-terminal\" + "}) +} + +#[test] +fn test_toml_keymap_array() { + toml_assert_parse(indoc! {" + [[keymap]] + [keymap.remap] + C-w = [\"Shift-C-w\", \"C-x\"] + "}) +} + +#[test] +fn test_toml_keymap_remap() { + toml_assert_parse(indoc! {" + [[keymap]] + [keymap.remap.C-x] + timeout_key = \"Down\" + timeout_millis = 1_000 + + [keymap.remap.C-x.remap] + s = \"C-w\" + + [keymap.remap.C-x.remap.C-s.remap] + x = \"C-z\" + "}) +} + +#[test] +fn test_toml_keymap_launch() { + toml_assert_parse(indoc! {r#" + [[keymap]] + [keymap.remap.KEY_GRAVE] + launch = [ "/bin/sh", "-c", "date > /tmp/hotkey_test" ] + "#}) +} + +#[test] +fn test_toml_keymap_mode() { + toml_assert_parse(indoc! {" + default_mode = \"insert\" + + [[keymap]] + mode = \"instert\" + + [keymap.remap.Esc] + set_mode = \"normal\" + + [[keymap]] + mode = \"normal\" + + [keymap.remap] + h = \"Left\" + j = \"Down\" + k = \"Up\" + l = \"Right\" + + [keymap.remap.i] + set_mode = \"insert\" + + "}) +} + +#[test] +fn test_toml_keymap_mark() { + toml_assert_parse(indoc! {" + [[keymap]] + [keymap.remap] + C-g = [ \"esc\", { set_mark = false } ] + + [keymap.remap.C-space] + set_mark = true + + [keymap.remap.C-b] + with_mark = \"left\" + + [keymap.remap.M-b] + with_mark = \"C-left\" + "}) +} + +#[test] +fn test_toml_shared_data_anchor() { + toml_assert_parse(indoc! {" + [shared] + terminals = [ \"Gnome-terminal\", \"Kitty\" ] + + [[shared.modmap]] + [shared.modmap.remap] + Alt_L = \"Ctrl_L\" + + [shared.modmap.application] + not = \"terminals\" + + [[shared.modmap]] + [shared.modmap.remap] + Shift_R = \"Win_R\" + + [shared.modmap.application] + only = \"Google-chrome\" + "}) +} + +#[test] +#[should_panic] +fn test_toml_fail_on_data_outside_of_config_model() { + toml_assert_parse(indoc! {" + terminals = [ \"Gnome-terminal\", \"Kitty\" ] + + [[modmap]] + [modmap.remap] + Alt_L = \"Ctrl_L\" + + [modmap.application] + not = [ \"Gnome-terminal\", \"Kitty\" ] + + [[modmap]] + [modmap.remap] + Shift_R = \"Win_R\" + + [modmap.application] + only = \"Google-chrome\" + "}) +} + +fn toml_assert_parse(toml: &str) { + let result: Result = toml::from_str(toml); + if let Err(e) = result { + panic!("{}", e) + } +} + +fn yaml_assert_parse(yaml: &str) { + let result: Result = serde_yaml::from_str(yaml); if let Err(e) = result { panic!("{}", e) }