Skip to content

Input Handling Limitations

Terminal input handling is fundamentally constrained by how terminals communicate with applications. This page documents the known limitations when using useInput() in inkx.

Keyboard Protocol Limitations

Traditional terminals use a simple protocol where each keypress is sent as a character or escape sequence. This protocol predates modern keyboard conventions and has fundamental ambiguities.

Indistinguishable Keys

Several key combinations produce identical byte sequences:

KeysBoth SendReason
Ctrl+I / Tab0x09Tab is ASCII character 9, same as Ctrl+I
Ctrl+M / Enter0x0DCarriage return is ASCII 13, same as Ctrl+M
Ctrl+[ / Escape0x1BEscape is ASCII 27, same as Ctrl+[
Ctrl+H / Backspace0x08Backspace is ASCII 8, same as Ctrl+H
Shift+Enter / Enter\rMost terminals don't distinguish

This means your useInput handler cannot tell these apart:

tsx
useInput((input, key) => {
  // These are IDENTICAL - you cannot distinguish them
  if (key.tab) {
    // Could be Tab OR Ctrl+I
  }
  if (key.return) {
    // Could be Enter OR Ctrl+M
  }
})

Kitty Keyboard Protocol

The Kitty keyboard protocol solves these ambiguities by encoding modifier state explicitly. However:

  • Only newer terminals support it (Kitty, WezTerm, foot, Ghostty)
  • Applications must opt-in by sending an escape sequence
  • inkx does not currently implement this protocol

When Kitty protocol support is added, these limitations will be resolved for supported terminals.

Undetectable Key Combinations

Some key combinations cannot be detected at all in traditional terminal mode:

  • Ctrl+Shift+<letter> - Shift state is lost
  • Ctrl+<number> - Most produce no output
  • Ctrl+, / Ctrl+. / Ctrl+; - No assigned control codes
  • Cmd/Super+<key> - Usually intercepted by the OS/window manager

CJK and IME Input

Input Method Editors (IMEs) for Chinese, Japanese, and Korean present challenges for terminal applications.

IME Composition Window

When typing with an IME, a composition window shows candidate characters. In terminal applications:

  • The composition window may flicker during rapid input
  • Positioning of the composition window varies by terminal
  • Some terminals overlay it at the cursor, others use a separate position

Synchronized Update Mode

inkx uses Synchronized Update Mode (SUM) to reduce flicker:

\x1b[?2026h  // Begin synchronized update
... render output ...
\x1b[?2026l  // End synchronized update

This helps with IME flicker, but:

  • Not all terminals support SUM (macOS Terminal does not)
  • Terminal multiplexers may have partial support

Recommendations for CJK Input

  1. Use a terminal with good IME support (iTerm2, Kitty, WezTerm)
  2. Avoid rapid re-renders during composition
  3. Test your app with actual IME input, not just ASCII

Terminal-Specific Behavior

Different terminals send different escape sequences for the same keys. inkx handles the most common variants, but edge cases exist.

Function Key Variations

Function keys F1-F12 have multiple encodings:

Terminal StyleF1F5
xterm (O-style)\x1bOP-
xterm ([~style)\x1b[11~\x1b[15~
Cygwin/libuv\x1b[[A\x1b[[E

inkx recognizes all these variants, but some obscure terminals may use others.

Home/End keys also vary:

TerminalHomeEnd
xterm (standard)\x1b[H\x1b[F
xterm (alternate)\x1b[1~\x1b[4~
rxvt\x1b[7~\x1b[8~

Terminal Comparison

FeaturemacOS TerminaliTerm2KittyWezTerm
Synchronized UpdateNoYesYesYes
Kitty ProtocolNoNoYesYes
Function keys F1-F12YesYesYesYes
Function keys F13-F24PartialYesYesYes
Meta/Alt keyOption+EscConfigurableYesYes
IME supportBasicGoodGoodGood

Modifier Key Handling

Meta/Alt Key

The Meta (Alt on PC, Option on Mac) key behavior varies:

  • macOS Terminal: Option key types special characters by default
  • iTerm2: Configurable - can send Esc+<key> or special characters
  • Linux terminals: Usually send Esc+<key>

inkx detects meta when it receives \x1b followed by a character:

tsx
useInput((input, key) => {
  if (key.meta && input === "a") {
    // Alt+A or Option+A (when configured to send escape)
  }
})

Ctrl+Shift Combinations

Most terminals cannot distinguish Ctrl+Shift+Letter from Ctrl+Letter:

tsx
useInput((input, key) => {
  // key.shift may be false even if Shift was held with Ctrl
  if (key.ctrl && input === "a") {
    // Could be Ctrl+A OR Ctrl+Shift+A
  }
})

Shift Detection for Letters

Shift is reliably detected for regular letter input (uppercase vs lowercase):

tsx
useInput((input, key) => {
  if (input === "A" && key.shift) {
    // Shift+A - this works reliably
  }
})

Terminal Multiplexers

Using tmux, screen, or similar multiplexers adds another layer:

Additional Limitations in tmux

  • Some escape sequences are intercepted by tmux
  • Passthrough mode may be required for certain sequences
  • Synchronized Update support varies by version
  • Function keys may need special configuration

Recommendations

  1. Test your app both inside and outside tmux
  2. Document any tmux-specific configuration needed
  3. Use simpler key bindings that work universally

Keys That Work Universally

Despite these limitations, many keys work reliably everywhere:

KeyReliability
Arrow keysExcellent
Enter/ReturnExcellent
EscapeExcellent
TabExcellent (but indistinguishable from Ctrl+I)
BackspaceExcellent
DeleteGood
Home/EndGood
Page Up/DownGood
F1-F12Good
Ctrl+A through Ctrl+ZGood (except Ctrl+I, Ctrl+M, Ctrl+[)
Shift+TabGood
Letters and numbersExcellent
Common punctuationExcellent

Best Practices

Design for Compatibility

tsx
// Good: Use keys that work everywhere
useInput((input, key) => {
  if (key.upArrow || input === "k") moveUp()
  if (key.downArrow || input === "j") moveDown()
  if (key.return) select()
  if (key.escape || input === "q") quit()
})

// Risky: Relies on Ctrl combinations that may conflict
useInput((input, key) => {
  if (key.ctrl && input === "i") {
    // User pressing Tab will trigger this too!
  }
})

Provide Alternative Bindings

When using keys with known limitations, offer alternatives:

tsx
useInput((input, key) => {
  // Multiple ways to trigger the same action
  if (input === "?" || input === "h" || (key.ctrl && input === "h")) {
    showHelp()
  }
})

Document Your Keybindings

Be explicit about which keys your app uses and any known limitations:

tsx
function HelpScreen() {
  return (
    <Box flexDirection="column">
      <Text bold>Keybindings:</Text>
      <Text>j/Down - Move down</Text>
      <Text>k/Up - Move up</Text>
      <Text>Enter - Select</Text>
      <Text>Escape - Back</Text>
      <Text dimColor>Note: Ctrl+M is the same as Enter</Text>
    </Box>
  )
}

Future Improvements

inkx may add support for:

  1. Kitty keyboard protocol - Enables full modifier detection in supported terminals
  2. Bracketed paste mode - Already partially supported, distinguishes pasted text from typed input
  3. Mouse input - Click and scroll events
  4. Focus events - Detect when terminal gains/loses focus

These improvements will be opt-in and gracefully degrade in unsupported terminals.

Released under the MIT License.