[ Guides ]
Keyboard Input Handling
Handle navigation keys (Key::Up, Key::Enter, Key::Escape, Key::BackTab), modifier combinations (ctrl, shift, alt, meta), printable Unicode characters, and pre-widget focus interceptors.
Complete working example
Section titled “Complete working example”#include <koto/koto.hpp>#include <cstdio>#include <string>
using namespace koto;
int main(){ App app; FocusManager fm; TextInputState cmd_input; ListState log_list({"System ready"});
fm.add(cmd_input).add(log_list);
// Pre-hook: intercept Ctrl+L globally even when TextInput is focused fm.interceptor([&](FocusManager&, const KeyEvent& key) { if (key.ctrl && key.ch == 'L') { log_list.set_items({"Logs cleared"}); return true; } return false; });
// Submit command when pressing Enter inside cmd_input cmd_input.on_key([&](TextInputState& state, const KeyEvent& key) { if (key.is(Key::Enter) && !state.value.empty()) { log_list.items.push_back(state.value); log_list.select_last(); state.clear(); return true; } return false; });
app.use_focus(fm);
app.on_key([&](const KeyEvent& key) { if (key.is(Key::Escape) || (key.ctrl && key.ch == 'Q')) { app.quit(); } });
app.on_render([&](Frame& frame) { auto [logs_area, input_area] = Layout::vertical(frame.area(), { Constraint::fill(), Constraint::fixed(3), });
List(log_list) .title("History") .border_style(BorderStyle::Single) .highlight_symbol("> ") .draw(frame, logs_area);
TextInput(cmd_input) .title("Command (Enter to submit, Ctrl+L to clear, Esc to quit)") .border_style(BorderStyle::Rounded) .draw(frame, input_area); });
if (auto res = app.run(); !res) { std::fprintf(stderr, "Error: %.*s\n", static_cast<int>(res.error().message().size()), res.error().message().data()); return 1; } return 0;}Step-by-step explanation
Section titled “Step-by-step explanation”- Matching functional keys with
KeyEvent::is(Key): Callkey.is(Key::Enter),key.is(Key::Escape),key.is(Key::BackTab), orkey.is(Key::CtrlRight). For plainKey::Up,Key::Down,Key::Left, andKey::Right,key.is(...)checks!shift && !ctrl && !alt(include/koto/event.hpp:66-68) soShift+Upnever triggers plainKey::Up. - Matching
Ctrl+ letter shortcuts: Both legacy ASCII control characters (1..26) and Kitty CSI-u normalizeCtrl+ letter to uppercase'A'..'Z'withkey.ctrl == true(src/event.cpp:303-306, 571-576). Checkkey.ctrl && (key.ch == 'Q' || key.ch == 'q')(orkey.ch == 'Q'). - Intercepting keys before focused widgets: When
app.use_focus(fm)is active,App::dispatch_eventpasses everyKeyEventtofm.handle_key(key)beforeapp.on_key(src/app.cpp:166-180). Usefm.interceptor(...)for global hotkeys that should run beforeTabcycling or widget handlers, or useTextInputState::on_key(...)for widget-specific overrides likeKey::Enter.
Common mistakes
Section titled “Common mistakes”- Looking for
Key::Space:Keyhas noSpaceenumerator (include/koto/event.hpp:14-40). Checkkey.ch == ' '(orkey.is_char()for general printable input). - Expecting
Ctrl+Cto sendSIGINTin raw mode:Terminal::enter_raw_mode()disablesISIG(src/terminal.cpp:246), so pressingCtrl+Carrives asKeyEvent{.ch = 'C', .ctrl = true}instead of raisingSIGINT. Handle it inon_keyorfm.interceptorand callapp.quit(). - Using bare letter hotkeys in
app.on_keyalongside aTextInputorTextArea: When a text editor state struct is focused, it consumes all unmodified printable characters (!key.ctrl && !key.alt && key.is_char()). UseKey::Escape, function keys (Key::F1..Key::F12), orctrl/altcombinations to quit or switch modes.