1
0
Fork 0
forked from wry/wry

Merge pull request #799 from mahkoh/jorth/toml-rmlvo

toml-config: allow specifying keymaps via RMLVO
This commit is contained in:
mahkoh 2026-03-14 21:35:42 +01:00 committed by GitHub
commit 70cb1d3ba3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 272 additions and 31 deletions

View file

@ -3,19 +3,20 @@ use {
config::{ config::{
ConfigKeymap, ConfigKeymap,
context::Context, context::Context,
extractor::{Extractor, ExtractorError, opt, str}, extractor::{Extractor, ExtractorError, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parser::{DataType, ParseResult, Parser, UnexpectedDataType},
}, },
toml::{ toml::{
toml_span::{Span, Spanned, SpannedExt}, toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value, toml_value::Value,
}, },
}, },
indexmap::IndexMap, indexmap::IndexMap,
jay_config::{ jay_config::{
config_dir, config_dir,
keyboard::{Keymap, parse_keymap}, keyboard::{Keymap, keymap_from_names, parse_keymap},
}, },
kbvm::xkb::rmlvo::Group,
std::{io, path::PathBuf}, std::{io, path::PathBuf},
thiserror::Error, thiserror::Error,
}; };
@ -28,9 +29,11 @@ pub enum KeymapParserError {
Extractor(#[from] ExtractorError), Extractor(#[from] ExtractorError),
#[error("The keymap is invalid")] #[error("The keymap is invalid")]
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, 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, DefinitionRequired,
#[error("Could not read {0}")] #[error("Could not read {0}")]
ReadFile(String, #[source] io::Error), ReadFile(String, #[source] io::Error),
@ -56,14 +59,27 @@ impl Parser for KeymapParser<'_> {
table: &IndexMap<Spanned<String>, Spanned<Value>>, table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> { ) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table); let mut ext = Extractor::new(self.cx, span, table);
let (mut name_val, mut map_val, mut path) = let (mut name_val, mut map_val, mut path, mut rmlvo) = ext.extract((
ext.extract((opt(str("name")), opt(str("map")), opt(str("path"))))?; opt(str("name")),
if map_val.is_some() && path.is_some() { 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!( log::warn!(
"Both `name` and `path` are specified. Ignoring `path`: {}", "At most one of `map`, `path`, and `rmlvo` should be specified: {}",
self.cx.error3(span) 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; let file_content;
if let Some(path) = path { if let Some(path) = path {
@ -78,10 +94,17 @@ impl Parser for KeymapParser<'_> {
}; };
map_val = Some(file_content.as_str().spanned(path.span)); 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)); 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 { if let Some(val) = name_val {
log::warn!( log::warn!(
"Cannot use both `name` and `map` in this position. Ignoring `name`: {}", "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()); self.cx.used.borrow_mut().keymaps.push(name.into());
} }
} }
let res = match (name_val, map_val) { let res = match (name_val, map) {
(Some(name_val), Some(map_val)) => ConfigKeymap::Defined { (Some(name_val), Some(map)) => ConfigKeymap::Defined {
name: name_val.value.to_string(), 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()), (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)), (None, None) => return Err(KeymapParserError::MissingField.spanned(span)),
}; };
Ok(res) 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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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::<Vec<_>>;
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::<Vec<_>>;
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<Keymap, Spanned<KeymapParserError>> { fn parse(span: Span, string: &str) -> Result<Keymap, Spanned<KeymapParserError>> {
let map = parse_keymap(string); let map = parse_keymap(string);
match map.is_valid() { match map.is_valid() {

View file

@ -1643,7 +1643,7 @@
"anyOf": [ "anyOf": [
{ {
"type": "string", "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", "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": { "map": {
"type": "string", "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": { "path": {
"type": "string", "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": [] "required": []
@ -1864,6 +1868,33 @@
"delay" "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": { "SimpleActionName": {
"type": "string", "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", "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",

View file

@ -3557,7 +3557,7 @@ Values of this type should have one of the following forms:
Defines a keymap by its XKB representation. Defines a keymap by its XKB representation.
- Example: - Example 1:
```toml ```toml
keymap = """ 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 #### A table
Defines or references a keymap. Defines or references a keymap.
@ -3602,8 +3612,8 @@ The table has the following fields:
Defines a keymap by its XKB representation. Defines a keymap by its XKB representation.
For each keymap defined in the top-level `keymaps` array, exactly one of `map` For each keymap defined in the top-level `keymaps` array, exactly one of
and `path` has to be defined. `map`, `path`, and `rmlvo` has to be defined.
The value of this field should be a string. 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 If the path is relative, it will be interpreted relative to the Jay config
directory. directory.
For each keymap defined in the top-level `keymaps` array, exactly one of `map` For each keymap defined in the top-level `keymaps` array, exactly one of
and `path` has to be defined. `map`, `path`, and `rmlvo` has to be defined.
The value of this field should be a string. 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).
<a name="types-Libei"></a> <a name="types-Libei"></a>
### `Libei` ### `Libei`
@ -4099,6 +4118,62 @@ The table has the following fields:
The numbers should be integers. The numbers should be integers.
<a name="types-Rmlvo"></a>
### `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.
<a name="types-SimpleActionName"></a> <a name="types-SimpleActionName"></a>
### `SimpleActionName` ### `SimpleActionName`

View file

@ -7,7 +7,7 @@ Keymap:
description: | description: |
Defines a keymap by its XKB representation. Defines a keymap by its XKB representation.
- Example: - Example 1:
```toml ```toml
keymap = """ keymap = """
@ -19,6 +19,16 @@ Keymap:
}; };
""" """
``` ```
- Example 2:
```toml
keymap.rmlvo = {
layout = "us,de",
variants = "dvorak",
options = "grp:ctrl_space_toggle",
}
```
- kind: table - kind: table
description: | description: |
Defines or references a keymap. Defines or references a keymap.
@ -50,8 +60,8 @@ Keymap:
description: | description: |
Defines a keymap by its XKB representation. Defines a keymap by its XKB representation.
For each keymap defined in the top-level `keymaps` array, exactly one of `map` For each keymap defined in the top-level `keymaps` array, exactly one of
and `path` has to be defined. `map`, `path`, and `rmlvo` has to be defined.
path: path:
kind: string kind: string
required: false required: false
@ -61,8 +71,59 @@ Keymap:
If the path is relative, it will be interpreted relative to the Jay config If the path is relative, it will be interpreted relative to the Jay config
directory. directory.
For each keymap defined in the top-level `keymaps` array, exactly one of `map` For each keymap defined in the top-level `keymaps` array, exactly one of
and `path` has to be defined. `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: Action: