diff --git a/toml-config/src/config/parsers/keymap.rs b/toml-config/src/config/parsers/keymap.rs index b803fd91..ea4afc66 100644 --- a/toml-config/src/config/parsers/keymap.rs +++ b/toml-config/src/config/parsers/keymap.rs @@ -3,19 +3,20 @@ use { config::{ ConfigKeymap, context::Context, - extractor::{Extractor, ExtractorError, opt, str}, + extractor::{Extractor, ExtractorError, opt, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, toml::{ - toml_span::{Span, Spanned, SpannedExt}, + toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, }, indexmap::IndexMap, jay_config::{ config_dir, - keyboard::{Keymap, parse_keymap}, + keyboard::{Keymap, keymap_from_names, parse_keymap}, }, + kbvm::xkb::rmlvo::Group, std::{io, path::PathBuf}, thiserror::Error, }; @@ -28,9 +29,11 @@ pub enum KeymapParserError { Extractor(#[from] ExtractorError), #[error("The keymap is invalid")] Invalid, - #[error("Keymap table must contain at least one of `name`, `map`")] + #[error("Keymap table must contain at least one of `name`, `map`, `path`, `rmlvo`")] MissingField, - #[error("Keymap must have both `name` and `map` fields in this context")] + #[error( + "Keymap must have both `name` and one of `map`, `path`, `rmlvo` fields in this context" + )] DefinitionRequired, #[error("Could not read {0}")] ReadFile(String, #[source] io::Error), @@ -56,14 +59,27 @@ impl Parser for KeymapParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.cx, span, table); - let (mut name_val, mut map_val, mut path) = - ext.extract((opt(str("name")), opt(str("map")), opt(str("path"))))?; - if map_val.is_some() && path.is_some() { + let (mut name_val, mut map_val, mut path, mut rmlvo) = ext.extract(( + opt(str("name")), + opt(str("map")), + opt(str("path")), + opt(val("rmlvo")), + ))?; + if map_val.is_some() as u32 + path.is_some() as u32 + rmlvo.is_some() as u32 > 1 { log::warn!( - "Both `name` and `path` are specified. Ignoring `path`: {}", - self.cx.error3(span) + "At most one of `map`, `path`, and `rmlvo` should be specified: {}", + self.cx.error3(span), ); - path = None; + let ignore_path = map_val.is_some(); + let ignore_rmlvo = map_val.is_some() || path.is_some(); + if ignore_path && path.is_some() { + log::warn!("Ignoring `path`"); + path = None; + } + if ignore_rmlvo && rmlvo.is_some() { + log::warn!("Ignoring `rmlvo`"); + rmlvo = None; + } } let file_content; if let Some(path) = path { @@ -78,10 +94,17 @@ impl Parser for KeymapParser<'_> { }; map_val = Some(file_content.as_str().spanned(path.span)); } - if self.definition && (name_val.is_none() || map_val.is_none()) { + let mut map = None; + if let Some(val) = &map_val { + map = Some(parse(val.span, val.value)?); + } + if let Some(val) = rmlvo { + map = Some(val.parse(&mut RmlvoParser(self.cx))?); + } + if self.definition && (name_val.is_none() || map.is_none()) { return Err(KeymapParserError::DefinitionRequired.spanned(span)); } - if !self.definition && map_val.is_some() { + if !self.definition && map.is_some() { if let Some(val) = name_val { log::warn!( "Cannot use both `name` and `map` in this position. Ignoring `name`: {}", @@ -101,19 +124,70 @@ impl Parser for KeymapParser<'_> { self.cx.used.borrow_mut().keymaps.push(name.into()); } } - let res = match (name_val, map_val) { - (Some(name_val), Some(map_val)) => ConfigKeymap::Defined { + let res = match (name_val, map) { + (Some(name_val), Some(map)) => ConfigKeymap::Defined { name: name_val.value.to_string(), - map: parse(map_val.span, map_val.value)?, + map, }, (Some(name_val), None) => ConfigKeymap::Named(name_val.value.to_string()), - (None, Some(map_val)) => ConfigKeymap::Literal(parse(map_val.span, map_val.value)?), + (None, Some(map)) => ConfigKeymap::Literal(map), (None, None) => return Err(KeymapParserError::MissingField.spanned(span)), }; Ok(res) } } +struct RmlvoParser<'a>(&'a Context<'a>); + +impl Parser for RmlvoParser<'_> { + type Value = Keymap; + type Error = KeymapParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (rules, model, layout, variants, options) = ext.extract(( + opt(str("rules")), + opt(str("model")), + opt(str("layout")), + opt(str("variants")), + opt(str("options")), + ))?; + let mut groups = None::>; + if layout.is_some() || variants.is_some() { + groups = Some( + Group::from_layouts_and_variants( + layout.despan().unwrap_or_default(), + variants.despan().unwrap_or_default(), + ) + .map(|g| jay_config::keyboard::Group { + layout: g.layout, + variant: g.variant, + }) + .collect(), + ); + } + let mut options_vec = None::>; + if let Some(options) = options { + options_vec = Some(options.value.split(",").collect()); + } + let map = keymap_from_names( + rules.despan(), + model.despan(), + groups.as_deref(), + options_vec.as_deref(), + ); + match map.is_valid() { + true => Ok(map), + false => Err(KeymapParserError::Invalid.spanned(span)), + } + } +} + fn parse(span: Span, string: &str) -> Result> { let map = parse_keymap(string); match map.is_valid() { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index c1b93a0a..57ba9d01 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1643,7 +1643,7 @@ "anyOf": [ { "type": "string", - "description": "Defines a keymap by its XKB representation.\n\n- Example:\n\n ```toml\n keymap = \"\"\"\n xkb_keymap {\n xkb_keycodes { include \"evdev+aliases(qwerty)\" };\n xkb_types { include \"complete\" };\n xkb_compat { include \"complete\" };\n xkb_symbols { include \"pc+us+inet(evdev)\" };\n };\n \"\"\"\n ```\n" + "description": "Defines a keymap by its XKB representation.\n\n- Example 1:\n\n ```toml\n keymap = \"\"\"\n xkb_keymap {\n xkb_keycodes { include \"evdev+aliases(qwerty)\" };\n xkb_types { include \"complete\" };\n xkb_compat { include \"complete\" };\n xkb_symbols { include \"pc+us+inet(evdev)\" };\n };\n \"\"\"\n ```\n\n- Example 2:\n\n ```toml\n keymap.rmlvo = {\n layout = \"us,de\",\n variants = \"dvorak\",\n options = \"grp:ctrl_space_toggle\",\n }\n ```\n" }, { "description": "Defines or references a keymap.\n\n- Example:\n\n ```toml\n keymap.name = \"my-keymap\"\n\n [[keymaps]]\n name = \"my-keymap\"\n path = \"./my-keymap.xkb\"\n ```\n", @@ -1655,11 +1655,15 @@ }, "map": { "type": "string", - "description": "Defines a keymap by its XKB representation.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of `map`\nand `path` has to be defined.\n" + "description": "Defines a keymap by its XKB representation.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of\n`map`, `path`, and `rmlvo` has to be defined.\n" }, "path": { "type": "string", - "description": "Loads a keymap's XKB representation from a file.\n\nIf the path is relative, it will be interpreted relative to the Jay config\ndirectory.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of `map`\nand `path` has to be defined.\n" + "description": "Loads a keymap's XKB representation from a file.\n\nIf the path is relative, it will be interpreted relative to the Jay config\ndirectory.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of\n`map`, `path`, and `rmlvo` has to be defined.\n" + }, + "rmlvo": { + "description": "Creates a keymap from RMLVO names.\n\nFor each keymap defined in the top-level `keymaps` array, exactly one of\n`map`, `path`, and `rmlvo` has to be defined.\n", + "$ref": "#/$defs/Rmlvo" } }, "required": [] @@ -1864,6 +1868,33 @@ "delay" ] }, + "Rmlvo": { + "description": "An RMLVO keymap definition.\n\nIf a parameter is not given, a value from the environment or a default is used:\n\n| name | default |\n| ---------------------- | ---------------------- |\n| `XKB_DEFAULT_RULES` | `evdev` |\n| `XKB_DEFAULT_MODEL` | `pc105` |\n| `XKB_DEFAULT_LAYOUT` | `us` |\n| `XKB_DEFAULT_VARIANTS` | |\n| `XKB_DEFAULT_OPTIONS` | |\n\n- Example:\n\n ```toml\n keymap.rmlvo = { layout = \"us,de\", variants = \"dvorak\" }\n ```\n", + "type": "object", + "properties": { + "rules": { + "type": "string", + "description": "The name of the rules file." + }, + "model": { + "type": "string", + "description": "The name of the device model." + }, + "layout": { + "type": "string", + "description": "A comma-separated list of layouts." + }, + "variants": { + "type": "string", + "description": "A comma-separated list of variants." + }, + "options": { + "type": "string", + "description": "A comma-separated list of options." + } + }, + "required": [] + }, "SimpleActionName": { "type": "string", "description": "The name of a `simple` Action.\n\nWhen used inside a window rule, the following actions apply to the matched window\ninstead fo the focused window:\n\n- `move-left`\n- `move-down`\n- `move-up`\n- `move-right`\n- `split-horizontal`\n- `split-vertical`\n- `toggle-split`\n- `tile-horizontal`\n- `tile-vertical`\n- `toggle-split`\n- `show-single`\n- `show-all`\n- `toggle-fullscreen`\n- `enter-fullscreen`\n- `exit-fullscreen`\n- `close`\n- `toggle-floating`\n- `float`\n- `tile`\n- `toggle-float-pinned`\n- `pin-float`\n- `unpin-float`\n\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 2bf15c1e..76b0e1f0 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -3557,7 +3557,7 @@ Values of this type should have one of the following forms: Defines a keymap by its XKB representation. -- Example: +- Example 1: ```toml keymap = """ @@ -3570,6 +3570,16 @@ Defines a keymap by its XKB representation. """ ``` +- Example 2: + + ```toml + keymap.rmlvo = { + layout = "us,de", + variants = "dvorak", + options = "grp:ctrl_space_toggle", + } + ``` + #### A table Defines or references a keymap. @@ -3602,8 +3612,8 @@ The table has the following fields: Defines a keymap by its XKB representation. - For each keymap defined in the top-level `keymaps` array, exactly one of `map` - and `path` has to be defined. + For each keymap defined in the top-level `keymaps` array, exactly one of + `map`, `path`, and `rmlvo` has to be defined. The value of this field should be a string. @@ -3614,11 +3624,20 @@ The table has the following fields: If the path is relative, it will be interpreted relative to the Jay config directory. - For each keymap defined in the top-level `keymaps` array, exactly one of `map` - and `path` has to be defined. + For each keymap defined in the top-level `keymaps` array, exactly one of + `map`, `path`, and `rmlvo` has to be defined. The value of this field should be a string. +- `rmlvo` (optional): + + Creates a keymap from RMLVO names. + + For each keymap defined in the top-level `keymaps` array, exactly one of + `map`, `path`, and `rmlvo` has to be defined. + + The value of this field should be a [Rmlvo](#types-Rmlvo). + ### `Libei` @@ -4099,6 +4118,62 @@ The table has the following fields: The numbers should be integers. + +### `Rmlvo` + +An RMLVO keymap definition. + +If a parameter is not given, a value from the environment or a default is used: + +| name | default | +| ---------------------- | ---------------------- | +| `XKB_DEFAULT_RULES` | `evdev` | +| `XKB_DEFAULT_MODEL` | `pc105` | +| `XKB_DEFAULT_LAYOUT` | `us` | +| `XKB_DEFAULT_VARIANTS` | | +| `XKB_DEFAULT_OPTIONS` | | + +- Example: + + ```toml + keymap.rmlvo = { layout = "us,de", variants = "dvorak" } + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `rules` (optional): + + The name of the rules file. + + The value of this field should be a string. + +- `model` (optional): + + The name of the device model. + + The value of this field should be a string. + +- `layout` (optional): + + A comma-separated list of layouts. + + The value of this field should be a string. + +- `variants` (optional): + + A comma-separated list of variants. + + The value of this field should be a string. + +- `options` (optional): + + A comma-separated list of options. + + The value of this field should be a string. + + ### `SimpleActionName` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index d4a1d12d..aba09b5e 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -7,7 +7,7 @@ Keymap: description: | Defines a keymap by its XKB representation. - - Example: + - Example 1: ```toml keymap = """ @@ -19,6 +19,16 @@ Keymap: }; """ ``` + + - Example 2: + + ```toml + keymap.rmlvo = { + layout = "us,de", + variants = "dvorak", + options = "grp:ctrl_space_toggle", + } + ``` - kind: table description: | Defines or references a keymap. @@ -50,8 +60,8 @@ Keymap: description: | Defines a keymap by its XKB representation. - For each keymap defined in the top-level `keymaps` array, exactly one of `map` - and `path` has to be defined. + For each keymap defined in the top-level `keymaps` array, exactly one of + `map`, `path`, and `rmlvo` has to be defined. path: kind: string required: false @@ -61,8 +71,59 @@ Keymap: If the path is relative, it will be interpreted relative to the Jay config directory. - For each keymap defined in the top-level `keymaps` array, exactly one of `map` - and `path` has to be defined. + For each keymap defined in the top-level `keymaps` array, exactly one of + `map`, `path`, and `rmlvo` has to be defined. + rmlvo: + ref: Rmlvo + required: false + description: | + Creates a keymap from RMLVO names. + + For each keymap defined in the top-level `keymaps` array, exactly one of + `map`, `path`, and `rmlvo` has to be defined. + + +Rmlvo: + description: | + An RMLVO keymap definition. + + If a parameter is not given, a value from the environment or a default is used: + + | name | default | + | ---------------------- | ---------------------- | + | `XKB_DEFAULT_RULES` | `evdev` | + | `XKB_DEFAULT_MODEL` | `pc105` | + | `XKB_DEFAULT_LAYOUT` | `us` | + | `XKB_DEFAULT_VARIANTS` | | + | `XKB_DEFAULT_OPTIONS` | | + + - Example: + + ```toml + keymap.rmlvo = { layout = "us,de", variants = "dvorak" } + ``` + kind: table + fields: + rules: + kind: string + required: false + description: The name of the rules file. + model: + kind: string + required: false + description: The name of the device model. + layout: + kind: string + required: false + description: A comma-separated list of layouts. + variants: + kind: string + required: false + description: A comma-separated list of variants. + options: + kind: string + required: false + description: A comma-separated list of options. Action: @@ -2998,7 +3059,7 @@ Config: required: false description: | Sets the fallback output mode. - + The default is `cursor`. - Example: