Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions compile-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ fn test_kccgst(mut diagnostics: &mut Vec<Diagnostic>, case: &Path) -> Result<(),
// let builder = map_actual.to_builder().build_state_machine();
// println!("{:#?}", builder);

let map = format!("{:#}\n", map_actual.format());
let map = format!("{}\n", map_actual.format().multiple_actions_per_level(true));

let expected_path = case.join("expected.xkb");
let expected = match std::fs::read(&expected_path) {
Expand Down Expand Up @@ -165,7 +165,10 @@ fn test_kccgst(mut diagnostics: &mut Vec<Diagnostic>, case: &Path) -> Result<(),
});
}

let round_trip = format!("{:#}\n", map_expected.format());
let round_trip = format!(
"{}\n",
map_expected.format().multiple_actions_per_level(true),
);
if round_trip.as_bytes() != expected {
let rt = case.join("round_trip.xkb");
std::fs::write(&rt, &round_trip).map_err(ResultError::WriteRoundTripFailed)?;
Expand Down
2 changes: 1 addition & 1 deletion kbvm-cli/src/compile_rmlvo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ pub fn main(args: CompileRmlvoArgs) {
groups.as_deref(),
options.as_deref(),
);
println!("{:#}", expanded.format());
println!("{}", expanded.format().multiple_actions_per_level(true));
}
2 changes: 1 addition & 1 deletion kbvm-cli/src/compile_xkb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn main(args: CompileXkbArgs) {
let expanded = context.keymap_from_bytes(WriteToLog, Some(path.as_ref()), &source);
match expanded {
Ok(map) => {
println!("{:#}", map.format());
println!("{}", map.format().multiple_actions_per_level(true));
}
Err(_) => {
log::error!("could not compile keymap");
Expand Down
2 changes: 1 addition & 1 deletion kbvm-cli/src/output/ansi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Output for Ansi {
"{} {} {:#}",
self.now(),
"keymap".color_time(self.theme),
keymap.format(),
keymap.format().multiple_actions_per_level(true),
);
}

Expand Down
2 changes: 1 addition & 1 deletion kbvm-cli/src/output/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl Json {
impl Output for Json {
fn keymap(&mut self, keymap: &Keymap) {
self.msg(Type::Keymap {
map: format!("{:#}", keymap.format()),
map: format!("{}", keymap.format().multiple_actions_per_level(true)),
});
}

Expand Down
29 changes: 29 additions & 0 deletions kbvm/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Unreleased

- Levels with multiple actions are no longer formatted by default. For example,

```xkb
xkb_symbols {
key <a> {
[
{
SetMods(mods = Mod2),
SetGroup(group = +1),
}
]
};
};
```

will be formatted as

```xkb
xkb_symbols {
key <a> { [ NoAction() ] };
};
```

This is because xkbcommon and Xwayland will reject the entire keymap if there are
multiple actions per level.

This can be reverted with the `multiple_actions_per_level` flag.
12 changes: 11 additions & 1 deletion kbvm/src/xkb/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ where
nesting: 0,
multi_line: f.alternate(),
lookup_only: false,
omit_multiple_actions: false,
long_keys: None,
newline: match f.alternate() {
true => "\n",
Expand All @@ -56,6 +57,7 @@ struct Writer<'a, 'b> {
nesting: usize,
multi_line: bool,
lookup_only: bool,
omit_multiple_actions: bool,
long_keys: Option<HashMap<Arc<String>, String>>,
newline: &'static str,
f: &'a mut Formatter<'b>,
Expand Down Expand Up @@ -177,6 +179,7 @@ impl Display for keymap::Formatter<'_> {
nesting: 0,
multi_line: !self.single_line,
lookup_only: self.lookup_only,
omit_multiple_actions: !self.multiple_actions_per_level,
long_keys,
newline: match self.single_line {
false => "\n",
Expand Down Expand Up @@ -700,6 +703,7 @@ impl Format for Keys<'_> {
for (offset, group) in key.groups.iter().enumerate() {
if let Some(group) = group {
let idx = offset + 1;
#[expect(clippy::too_many_arguments)]
fn write_levels<T: Format>(
needs_newline: &mut bool,
idx: usize,
Expand All @@ -708,6 +712,7 @@ impl Format for Keys<'_> {
name: &str,
absent: &str,
field: impl Fn(&KeyLevel) -> &SmallVec<[T; 1]>,
is_actions: bool,
) -> fmt::Result {
if group.levels.iter().all(|l| field(l).is_empty()) {
return Ok(());
Expand All @@ -716,7 +721,10 @@ impl Format for Keys<'_> {
f.write_nesting()?;
write!(f.f, "{name}[Group{idx}] = [")?;
f.write_inline_list(&group.levels, |f, l| {
let list = field(l);
let mut list = &**field(l);
if is_actions && f.omit_multiple_actions && list.len() > 1 {
list = &[];
}
if list.is_empty() {
f.write(absent)?;
} else if list.len() == 1 {
Expand All @@ -740,6 +748,7 @@ impl Format for Keys<'_> {
"symbols",
"NoSymbol",
|l| &l.symbols,
false,
)?;
if !f.lookup_only {
write_levels(
Expand All @@ -750,6 +759,7 @@ impl Format for Keys<'_> {
"actions",
"NoAction()",
|l| &l.actions,
true,
)?;
}
}
Expand Down
1 change: 1 addition & 0 deletions kbvm/src/xkb/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ impl Keymap {
keymap: self,
single_line: false,
lookup_only: false,
multiple_actions_per_level: false,
rename_long_keys: false,
}
}
Expand Down
12 changes: 12 additions & 0 deletions kbvm/src/xkb/keymap/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct Formatter<'a> {
pub(crate) keymap: &'a Keymap,
pub(crate) single_line: bool,
pub(crate) lookup_only: bool,
pub(crate) multiple_actions_per_level: bool,
pub(crate) rename_long_keys: bool,
}

Expand Down Expand Up @@ -53,6 +54,17 @@ impl Formatter<'_> {
self.rename_long_keys = val;
self
}

/// Enables or disables formatting of levels containing more than one action.
///
/// By default, such actions are not formatted.
///
/// This should almost always be disabled. Xwayland and libxkbcommon will fail to
/// parse keymaps that contain levels with multiple actions.
pub fn multiple_actions_per_level(mut self, val: bool) -> Self {
self.multiple_actions_per_level = val;
self
}
}

impl Debug for Formatter<'_> {
Expand Down
16 changes: 16 additions & 0 deletions kbvm/src/xkb/keymap/format/map6.xkb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
xkb_keymap {
xkb_keycodes {
<a> = 1;
<b> = 2;
};
xkb_symbols {
key <a> {
[ { a, b } ],
[ { SetMods(mods = Mod2), SetMods(mods = Mod3) } ],
};
key <b> {
[ a ],
[ SetMods(mods = Mod2), SetMods(mods = Mod3) ],
};
};
};
51 changes: 51 additions & 0 deletions kbvm/src/xkb/keymap/format/map7.xkb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
xkb_keymap {
xkb_keycodes {
minimum = 8;
maximum = 255;

indicator 1 = "DUMMY";

<a> = 1;
<b> = 2;
};

xkb_types {
virtual_modifiers Dummy;

type "ONE_LEVEL" {
modifiers = None;
level_name[Level1] = "Any";
map[None] = Level1;
};

type "TWO_LEVEL" {
modifiers = Shift;
level_name[Level1] = "Base";
level_name[Level2] = "Shift";
map[Shift] = Level2;
};
};

xkb_compat {
interpret VoidSymbol {
repeat = false;
};
};

xkb_symbols {
key.repeat = true;

key <a> {
repeat = false,
type[Group1] = "ONE_LEVEL",
symbols[Group1] = [ { a, b } ],
actions[Group1] = [ { SetMods(modifiers = Mod2), SetMods(modifiers = Mod3) } ]
};
key <b> {
repeat = false,
type[Group1] = "TWO_LEVEL",
symbols[Group1] = [ a, NoSymbol ],
actions[Group1] = [ SetMods(modifiers = Mod2), SetMods(modifiers = Mod3) ]
};
};
};
51 changes: 51 additions & 0 deletions kbvm/src/xkb/keymap/format/map8.xkb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
xkb_keymap {
xkb_keycodes {
minimum = 8;
maximum = 255;

indicator 1 = "DUMMY";

<a> = 1;
<b> = 2;
};

xkb_types {
virtual_modifiers Dummy;

type "ONE_LEVEL" {
modifiers = None;
level_name[Level1] = "Any";
map[None] = Level1;
};

type "TWO_LEVEL" {
modifiers = Shift;
level_name[Level1] = "Base";
level_name[Level2] = "Shift";
map[Shift] = Level2;
};
};

xkb_compat {
interpret VoidSymbol {
repeat = false;
};
};

xkb_symbols {
key.repeat = true;

key <a> {
repeat = false,
type[Group1] = "ONE_LEVEL",
symbols[Group1] = [ { a, b } ],
actions[Group1] = [ NoAction() ]
};
key <b> {
repeat = false,
type[Group1] = "TWO_LEVEL",
symbols[Group1] = [ a, NoSymbol ],
actions[Group1] = [ SetMods(modifiers = Mod2), SetMods(modifiers = Mod3) ]
};
};
};
15 changes: 15 additions & 0 deletions kbvm/src/xkb/keymap/format/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ fn single_line() {
let expected = include_str!("map3.xkb");
assert_eq!(actual, expected);
}

#[test]
fn lookup_only() {
let context = Context::default();
Expand All @@ -34,3 +35,17 @@ fn lookup_only() {
let expected = include_str!("map5.xkb");
assert_eq!(actual, expected);
}

#[test]
fn multiple_actions() {
let context = Context::default();
let map = context
.keymap_from_bytes(WriteToStderr, None, include_str!("map6.xkb"))
.unwrap();
let with_actions = format!("{}\n", map.format().multiple_actions_per_level(true));
let expected = include_str!("map7.xkb");
assert_eq!(with_actions, expected);
let without_actions = format!("{}\n", map.format());
let expected = include_str!("map8.xkb");
assert_eq!(without_actions, expected);
}
Loading