diff --git a/television/config/keybindings.rs b/television/config/keybindings.rs index ccf9d44..36c910e 100644 --- a/television/config/keybindings.rs +++ b/television/config/keybindings.rs @@ -470,13 +470,30 @@ where where A: MapAccess<'de>, { + use serde::de::Error; + use toml::Value; + let mut bindings = FxHashMap::default(); - while let Some((key_str, action)) = - map.next_entry::()? + while let Some((key_str, raw_value)) = + map.next_entry::()? { - let key = - parse_key(&key_str).map_err(serde::de::Error::custom)?; - bindings.insert(key, action); + let key = parse_key(&key_str).map_err(Error::custom)?; + + match raw_value { + Value::Boolean(false) => { + // Explicitly unbind key + bindings.insert(key, Action::NoOp); + } + Value::Boolean(true) => { + // True means do nothing (keep current binding or ignore) + } + action => { + // Try to deserialize as Action + let action = Action::deserialize(action) + .map_err(Error::custom)?; + bindings.insert(key, action); + } + } } Ok(bindings) } @@ -738,4 +755,33 @@ mod tests { Some(&Action::SelectNextPage) ); } + + #[test] + fn test_deserialize_unbinding() { + let keybindings: KeyBindings = toml::from_str( + r#" + "esc" = "quit" + "ctrl-c" = false + "down" = "select_next_entry" + "up" = true + "#, + ) + .unwrap(); + + // Normal action binding should work + assert_eq!(keybindings.bindings.get(&Key::Esc), Some(&Action::Quit)); + assert_eq!( + keybindings.bindings.get(&Key::Down), + Some(&Action::SelectNextEntry) + ); + + // false should bind to NoOp (unbinding) + assert_eq!( + keybindings.bindings.get(&Key::Ctrl('c')), + Some(&Action::NoOp) + ); + + // true should be ignored (no binding created) + assert_eq!(keybindings.bindings.get(&Key::Up), None); + } } diff --git a/tests/cli/cli_input.rs b/tests/cli/cli_input.rs index 4a9490c..5fd9857 100644 --- a/tests/cli/cli_input.rs +++ b/tests/cli/cli_input.rs @@ -39,18 +39,16 @@ fn test_keybindings_override_default() { let mut child = tester.spawn_command_tui(tv_local_config_and_cable_with_args(&[ "--keybindings", - "a=\"quit\"", + "a=\"quit\";ctrl-c=false;esc=false", ])); - // TODO: add back when unbinding is implemented + // Test that ESC no longer quits (default behavior is overridden) + tester.send(ESC); + tester.assert_tui_running(&mut child); - // // Test that ESC no longer quits (default behavior is overridden) - // tester.send(ESC); - // tester.assert_tui_running(&mut child); - // - // // Test that Ctrl+C no longer quits (default behavior is overridden) - // tester.send(&ctrl('c')); - // tester.assert_tui_running(&mut child); + // Test that Ctrl+C no longer quits (default behavior is overridden) + tester.send(&ctrl('c')); + tester.assert_tui_running(&mut child); // Test that our custom "a" key now quits the application tester.send("'a'"); @@ -66,14 +64,12 @@ fn test_multiple_keybindings_override() { let mut child = tester.spawn_command_tui(tv_local_config_and_cable_with_args(&[ "--keybindings", - "a=\"quit\";ctrl-t=\"toggle_remote_control\"", + "a=\"quit\";ctrl-t=\"toggle_remote_control\";esc=false", ])); - // TODO: add back when unbinding is implemented - - // // Verify ESC doesn't quit (default overridden) - // tester.send(ESC); - // tester.assert_tui_running(&mut child); + // Verify ESC doesn't quit (default overridden) + tester.send(ESC); + tester.assert_tui_running(&mut child); // Test that Ctrl+T opens remote control panel (custom keybinding works) tester.send(&ctrl('t'));