This post was written by Claude (Anthropic's Opus 4.6 model, running in Claude Code) at Jesse's request. We were implementing multiline editing in a terminal-based coding agent and hit the problem firsthand.


If you're building an interactive terminal app -- a coding agent, a TUI editor, a chat interface -- you will eventually want to do something like "Shift+Enter inserts a newline, Enter submits." You will discover that you can't. Your terminal sends the exact same byte for both.

This isn't a bug in your code. It's a limitation baked into how terminals encode keyboard input, and it has been there since 1978. There is now a fix, but adopting it requires understanding what's broken and why.

This post covers the problem, the solution (the Kitty keyboard protocol), and exactly how to configure every major terminal, multiplexer, and TUI framework to use it.

The Problem: 48 Years of Broken Keyboard Input #

How Terminals Encode Keys #

When you press a key in a terminal, the terminal emulator translates it into a sequence of bytes and sends those bytes to the application over a pseudo-terminal (pty). For regular characters, the byte is the ASCII code: a sends 0x61, Z sends 0x5A.

For modifier keys, the encoding depends on which modifier:

Ctrl works by clearing bits 5 and 6 of the character code. Pressing Ctrl+A sends 0x01 (65 AND 31 = 1). This is an electrical trick from the ASR-33 teletype (1963) -- the Ctrl key physically zeroed the top two bits of the 7-bit character code. No lookup table. Pure circuitry.

Shift changes the character (a becomes A), then the shifted character's ASCII code is sent. The modifier itself is invisible -- the application sees A (0x41), not "Shift was held while pressing a."

Alt/Meta prefixes the key with an Escape byte (0x1B). Alt+a sends 0x1B 0x61.

The Collisions #

The Ctrl bit-masking creates collisions where physically different keys produce identical bytes:

You Press Byte Sent Also Sent By
Tab 0x09 Ctrl+I
Enter 0x0D Ctrl+M
Backspace 0x08 or 0x7F Ctrl+H or Ctrl+?
Escape 0x1B Ctrl+[

These aren't "the same key." Tab and Ctrl+I are different physical actions with different semantic intent. But because 73 AND 31 = 9 and the Tab key also sends 9, the application receives the same byte and cannot distinguish them.

It gets worse. Modifier combinations on these keys are completely lost:

You Press Byte Sent Identical To
Enter 0x0D Enter
Shift+Enter 0x0D Enter
Ctrl+Enter 0x0D Enter
Alt+Enter 0x1B 0x0D Escape then Enter

Every Enter variant sends the same \r. Your app cannot tell them apart.

The Escape Ambiguity #

The Alt prefix creates a timing-dependent ambiguity. When your app receives 0x1B, it could mean:

  1. The user pressed Escape (and nothing else is coming)
  2. The user pressed Alt+something (and the next byte is about to arrive)
  3. A function key was pressed (and more bytes for a CSI sequence are coming)

The only way to distinguish these is to wait and see if more bytes arrive -- typically 50-100ms. This is why pressing Escape in Vim or Emacs in a terminal has a perceptible delay. The application is guessing.

Over slow SSH connections, this heuristic fails. Bytes from a single Alt+a keypress might arrive with enough delay between them to look like separate Escape and a presses.

The Full Picture #

Here's what a modern GUI application can detect that a traditional terminal application cannot:

  • Shift+Enter, Ctrl+Enter, Alt+Enter (all indistinguishable from Enter)
  • Ctrl+Shift+anything (Shift is invisible on Ctrl combos)
  • Ctrl+comma, Ctrl+period, Ctrl+semicolon (no ASCII control codes for punctuation)
  • Key release events (terminals only report press)
  • Key repeat events (distinct from rapid pressing)
  • Which physical key was pressed on non-US keyboard layouts

This isn't a minor inconvenience. It means terminal applications have a fundamentally smaller input vocabulary than GUI applications. Every coding agent, terminal editor, and TUI app is forced to work within the keyboard vocabulary available on a VT100 in 1978.

A Brief History #

1963: ASCII standardized. The 32 control codes (0-31) are assigned. The Ctrl key's bit-masking behavior is defined.

1978: DEC VT100 released. Introduces CSI (Control Sequence Introducer) escape sequences (ESC [ followed by parameters). Becomes the universal standard.

1983: DEC VT220 adds function keys F6-F20 with CSI number ~ encoding. The gaps in the numbering (F11 is 23~, not 22~) are because some codes were reserved for editing keys.

2006: Thomas Dickey adds modifyOtherKeys to xterm (patch #214). First mechanism for encoding modifier state on keys that lack ASCII control codes. Uses CSI 27 ; modifier ; keycode ~ format.

2008: Paul "LeoNerd" Evans proposes an alternative encoding: CSI keycode ; modifier u. The "u" terminator. Dickey adds formatOtherKeys to xterm (patch #235) to support Evans's format alongside his own. Evans publishes the fixterms specification and writes libtermkey, which Neovim later adopts.

2021: Kovid Goyal (author of Kitty terminal) publishes the Kitty keyboard protocol, extending Evans's CSI u encoding with progressive enhancement flags, a push/pop stack, key release events, and a detection mechanism. GitHub RFC #3248.

2021-2026: Adoption wave. Kitty, foot, WezTerm, Ghostty, Alacritty, iTerm2, VS Code terminal, Warp adopt the protocol. Neovim, Vim, Helix, fish shell, nushell implement client support. Framework libraries (crossterm, bubbletea, tcell, Ink) add APIs.

The Solution: Kitty Keyboard Protocol #

The protocol works by the application sending an activation escape sequence. The terminal then encodes all key events using a structured, unambiguous format.

Encoding Format #

CSI keycode ; modifiers u

Where:

  • keycode is the Unicode code point of the key (Enter = 13, Tab = 9, a = 97)
  • modifiers is a bitmask + 1 (Shift = 2, Alt = 3, Ctrl = 5, Ctrl+Shift = 6)

Examples:

Key Combination Escape Sequence Breakdown
Enter ESC[13u keycode 13, no modifier
Shift+Enter ESC[13;2u keycode 13, Shift (1+1)
Ctrl+Enter ESC[13;5u keycode 13, Ctrl (1+4)
Alt+Enter ESC[13;3u keycode 13, Alt (1+2)
Tab ESC[9u keycode 9, no modifier
Ctrl+I ESC[105;5u keycode 105 ('i'), Ctrl
Escape ESC[27u keycode 27, no modifier

Tab and Ctrl+I are now distinct. Enter and Shift+Enter are now distinct. Escape is unambiguous. The 48-year-old collisions are resolved.

Modifier Bitmask #

The modifier value in the escape sequence is 1 + sum of active modifier bits:

Modifier Bit Value
Shift 1
Alt 2
Ctrl 4
Super (Cmd/Win) 8
Hyper 16
Meta 32
Caps Lock 64
Num Lock 128

So Ctrl+Shift = 1 + 4 + 1 = 6. Alt+Ctrl = 1 + 2 + 4 = 7.

Progressive Enhancement #

Applications request specific capabilities via flags:

Flag Value What It Adds
Disambiguate escape codes 1 Resolves all legacy collisions
Report event types 2 Press, repeat, and release events
Report alternate keys 4 Shifted key variant + base layout key
Report all keys as escape codes 8 Even plain letters get CSI encoding
Report associated text 16 Unicode codepoints embedded in sequence

Most apps only need flag 1 (disambiguate). The other flags are for specialized use cases like games (need key release) or international keyboard support (need alternate keys).

Activation and Cleanup #

CSI > flags u     Push flags onto stack (activate)
CSI < number u    Pop entries from stack (restore previous state)
CSI ? u           Query current flags

The push/pop stack is important. If your app activates the protocol and then crashes, the terminal is left in a bad state. The stack lets nested applications (an editor inside a shell inside tmux) each push their own flags and pop them on exit without interfering with each other.

Detection #

Send CSI ? u followed by CSI c (device attributes query). If the terminal supports the protocol, it responds with CSI ? flags u before the device attributes response. If it doesn't support the protocol, you only get the device attributes response. All terminals respond to CSI c, so it acts as a sentinel.

Terminal Support #

Full Kitty Protocol Support #

These terminals support the complete protocol -- push/pop/query, all enhancement flags. Applications opt in at runtime; no user configuration needed unless noted.

Terminal Since Version Configuration Notes
Kitty 0.20.0 (Apr 2021) None Reference implementation
foot ~1.11 (Dec 2021) None One of the most complete implementations
WezTerm 20220624 enable_kitty_keyboard = true in wezterm.lua Must be explicitly enabled
Alacritty 0.13.0 (Dec 2023) None
Ghostty 1.0 (Dec 2024) None Supported from first release
iTerm2 ~3.5+ None (apps request it at runtime) Also has legacy CSI u checkbox; not needed
Rio 0.0.17 (Aug 2023) use-kitty-keyboard-protocol = true (default)
Warp Feb 2026 None Recent addition
Contour 0.4.0 (Dec 2023) None Significant conformance fixes in 0.6.3

Partial Support (modifyOtherKeys / CSI u format only) #

These terminals can send CSI u encoded key events but do NOT support the Kitty protocol's push/pop/query stack, progressive enhancement flags, or event type reporting.

Terminal Mechanism Configuration
xterm modifyOtherKeys mode 2 + formatOtherKeys=1 In ~/.Xresources: *vt100.modifyOtherKeys: 2 and *vt100.formatOtherKeys: 1. Run xrdb -merge ~/.Xresources. Apps can also enable at runtime with CSI > 4 ; 2 m.
mintty (Git Bash) modifyOtherKeys (on by default) Already enabled. Apps request via standard xterm escape sequences.

IDE Terminals #

Terminal Support Configuration
VS Code (xterm.js) Full Kitty protocol "terminal.integrated.enableKittyKeyboardProtocol": true in settings. Available since VS Code 1.109 (Jan 2026).
JetBrains IDEs No evidence of support JediTerm-based. No Kitty protocol or modifyOtherKeys.
Emacs vterm/term/eat No Open feature request for Eat (Codeberg #157). The kkp package lets Emacs running inside a capable terminal use the protocol, but Emacs's own terminal emulators don't support it.

No Support #

Terminal Status Workaround
macOS Terminal.app No CSI u, no modifyOtherKeys, no known plans. Enable "Use Option as Meta Key" for Option+Enter. See Per-Terminal Configuration.
PuTTY No support (as of v0.83, Feb 2025). No known plans. AutoHotkey on Windows. Ctrl+J everywhere.
GNOME Terminal (VTE) Patches under review (Dec 2025). Not in any released version. None. Ctrl+J only.
Konsole (KDE) Feature requested (Nov 2025). No implementation started. Custom .keytab file can map Shift+Enter to CSI u. See below.
Windows Terminal PR #19817 merged Feb 2026, but not yet in any released version. Targeted for v1.25. sendInput action can bind Shift+Enter to CSI u today. See below.
conhost.exe (Windows legacy) No, and likely never will. None. Switch to Windows Terminal or WezTerm.
GNU Screen No. Will not forward protocol sequences. Maintenance-mode project. None. Use tmux or Zellij instead.
mosh No. Architectural limitation -- mosh runs a server-side terminal emulator that must understand every sequence, and it doesn't understand CSI u. None. Use SSH directly.

Terminal Multiplexer Configuration #

tmux #

tmux supports forwarding CSI u encoded key sequences, but does not implement the full Kitty protocol (no push/pop/query). This is sufficient for most use cases.

Required version: 3.2+ (3.5+ recommended)

Add to ~/.tmux.conf:

# Forward extended key sequences to applications
set -s extended-keys on

# Tell tmux your outer terminal supports extended keys
# Adjust the glob to match your terminal's $TERM value
set -as terminal-features 'xterm*:extkeys'

# Use CSI u encoding format
set -s extended-keys-format csi-u

Then reload: tmux source ~/.tmux.conf

on vs always:

  • on (shown above): Only forwards extended sequences when the inner application explicitly requests them. Safe default -- won't break apps that don't understand CSI u.
  • always: Unconditionally forwards extended sequences to all panes. Simpler, but apps that don't expect CSI u may misbehave.

The terminal-features line is critical. tmux needs to know the outer terminal supports extended keys. Without it, tmux won't request them from the outer terminal, so it has nothing to forward. The glob must match your $TERM value:

# Common patterns:
set -as terminal-features 'xterm*:extkeys'       # xterm, most defaults
set -as terminal-features ',*alacritty*:extkeys'  # Alacritty
set -as terminal-features ',*ghostty*:extkeys'    # Ghostty
set -as terminal-features ',xterm-kitty:extkeys'  # Kitty

Known issues:

  • tmux 3.5+ defaults to modifyOtherKeys mode 2 when extended-keys is on. Mode 2 encodes Ctrl+C, Ctrl+Z, and Ctrl+L differently, which can break shell signal handling.
  • $TERM inside tmux is tmux-256color, not your outer terminal. Apps that auto-detect by $TERM name won't recognize tmux as capable. This is why extended-keys on (request-based) is better than relying on auto-detection.

Zellij #

Required version: 0.41.0+

// In config.kdl (enabled by default in 0.41.0+)
support_kitty_keyboard_protocol true

Zellij supports progressive enhancement level 1 (disambiguate escape codes). Known issues with fish 4.0 compatibility and the push/pop stack.

GNU Screen #

No support. Screen does not forward CSI u sequences. If you need extended keyboard input, switch to tmux or Zellij.

mosh #

No support. mosh runs a server-side terminal emulator and drops sequences it doesn't understand. CSI u sequences are silently eaten. Use SSH directly if you need the protocol.

SSH #

Transparent. SSH is a byte-stream transport -- it forwards everything without interpretation. The Kitty protocol works perfectly over SSH. The only issue is $TERM on the remote host: if your terminal sets TERM=xterm-kitty, the remote may not have that terminfo entry. Solutions:

  • Kitty: use kitty +kitten ssh (copies terminfo automatically)
  • Others: TERM=xterm-256color on the remote (loses some features but universally available)
  • Manual: infocmp -a xterm-kitty | ssh remote tic -x -o ~/.terminfo /dev/stdin

Framework Implementation Guide #

Ink (React for CLI) -- TypeScript/Node.js #

Supported since v6.7.0 (PR #855).

import { render } from 'ink';

render(<App />, {
  kittyKeyboard: { mode: 'enabled' }
});

Use 'enabled', not 'auto'. Auto-detection checks $TERM_PROGRAM and fails inside tmux (which reports xterm-256color).

import { useInput } from 'ink';

useInput((input, key) => {
  if (key.return && key.shift) {
    // Shift+Enter -- insert newline
  } else if (key.return && key.ctrl) {
    // Ctrl+Enter
  } else if (key.return) {
    // Enter -- submit
  }
});

With the protocol enabled, useInput provides key.shift, key.ctrl, key.meta on all keys, plus key.super, key.hyper, key.capsLock, key.numLock, and key.eventType ('press', 'repeat', 'release').

crossterm -- Rust #

use crossterm::event::{
    KeyboardEnhancementFlags,
    PushKeyboardEnhancementFlags,
    PopKeyboardEnhancementFlags,
    KeyCode, KeyModifiers, KeyEventKind,
};
use crossterm::execute;
use std::io::stdout;

// Enable on startup
execute!(
    stdout(),
    PushKeyboardEnhancementFlags(
        KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
        | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
    )
)?;

// Handle keys
if let Event::Key(key_event) = event::read()? {
    if key_event.kind == KeyEventKind::Press
        && key_event.code == KeyCode::Enter
        && key_event.modifiers.contains(KeyModifiers::SHIFT)
    {
        // Shift+Enter
    }
}

// Disable on exit (always do this, including in signal handlers)
execute!(stdout(), PopKeyboardEnhancementFlags)?;

ratatui uses crossterm as its backend -- same API applies.

Bubble Tea -- Go #

Supported since v2.0.0 (Aug 2024).

p := tea.NewProgram(
    model{},
    tea.WithKeyboardEnhancements(tea.WithKeyReleases),
)

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        if msg.String() == "shift+enter" {
            // Shift+Enter
        }
    }
    return m, nil
}

tcell -- Go #

Auto-detected. tcell queries the terminal on init and enables the protocol automatically if supported.

switch ev := screen.PollEvent().(type) {
case *tcell.EventKey:
    if ev.Key() == tcell.KeyEnter && ev.Modifiers()&tcell.ModShift != 0 {
        // Shift+Enter
    }
}

Textual -- Python #

Textual's parser can decode CSI u sequences, but it's unclear whether it actively enables the protocol. If your terminal sends CSI u (because the user enabled it or another app activated it), Textual will parse it.

class MyApp(App):
    BINDINGS = [("shift+enter", "newline", "New line")]

    def action_newline(self):
        # Handle Shift+Enter
        pass

prompt_toolkit -- Python #

No Kitty protocol support. No CSI u parsing. The common workaround is configuring your terminal to send a custom escape sequence for Shift+Enter and registering it manually -- fragile and terminal-specific.

Rolling Your Own #

If your framework doesn't support the protocol, here's the minimal implementation:

1. Activate:

stdout.write('\x1b[>1u')    // Push flag 1 (disambiguate)

2. Parse incoming bytes. Match the regex:

\x1b\[(\d+)(?::(\d+))?(?:;(\d+)(?::(\d+))?)?u

Group 1 is the keycode, group 3 is the modifier field (subtract 1, then check bits).

3. Deactivate on exit:

stdout.write('\x1b[<u')     // Pop from stack

Always deactivate in cleanup and signal handlers. Leaving the terminal in enhanced mode after your app exits will confuse the shell.

Practical Recommendations #

For Terminal App Developers #

  1. Enable the protocol with mode: 'enabled' (or equivalent), not auto-detect. Auto-detection fails inside tmux, which is where many of your users are.

  2. Always provide a fallback. Not everyone has a capable terminal. Ctrl+J (which sends 0x0A, line feed -- distinct from Enter's 0x0D) works everywhere as a newline insertion key. Make it your universal fallback.

  3. Pop on exit. Register cleanup in signal handlers (SIGINT, SIGTERM) and normal exit paths. A crashed app that leaves the terminal in enhanced mode is a terrible user experience.

  4. Tell users how to set up their terminal. The #1 support request you will get is "Shift+Enter doesn't work." The answer is almost always tmux configuration or an unsupported terminal. Consider adding a setup/diagnostic command.

Per-Terminal Configuration #

Step-by-step setup for every major terminal. If your terminal is listed under "Full Kitty Protocol Support" above with "None" in the Configuration column (Kitty, Ghostty, foot, Alacritty, iTerm2, Warp), apps that request the protocol get it automatically -- no user action needed. The terminals below need explicit configuration or have important details.

A note on workarounds: For terminals that lack native Kitty protocol support, I've included the best workarounds I could find through research. I have not personally tested every workaround on every terminal. If you find that something described here doesn't work as documented, I'd appreciate a correction -- see the end of this post for contact info.

iTerm2 (macOS) #

iTerm2 supports the full Kitty protocol. Apps that request it at runtime get it automatically -- no checkbox needed. But iTerm2 also has a legacy CSI u mode that predates the Kitty protocol:

  • Kitty protocol (recommended): Works automatically. No configuration. Apps send CSI > flags u and iTerm2 responds.
  • Legacy CSI u mode: Preferences > Profiles > Keys > General > "Report modifiers using CSI u". This is a global toggle that changes key encoding for ALL apps in that profile, including shells that don't understand CSI u. Don't enable this -- use the Kitty protocol path instead (apps opt in themselves).

If you need Hyper/Super/Meta modifier keys (rare), go to Preferences > Profiles > Keys and remap Left Control, Right Control, Left Command, or Right Command to the desired modifier. This only works when a Kitty-protocol-aware app is running.

WezTerm #

Must be explicitly enabled. Add to ~/.wezterm.lua:

local config = wezterm.config_builder()
config.enable_kitty_keyboard = true
return config

Do NOT also enable enable_csi_u_key_encoding -- that's a separate, older option and the two conflict.

xterm #

xterm doesn't support the Kitty protocol, but its modifyOtherKeys mode with CSI u format encoding covers the most important use case (disambiguating modified keys).

Add to ~/.Xresources:

*vt100.modifyOtherKeys: 2
*vt100.formatOtherKeys: 1

Then run:

xrdb -merge ~/.Xresources

Open a new xterm. Shift+Enter will now send ESC[13;2u instead of \r.

Apps can also request this at runtime by sending CSI > 4 ; 2 m (enable modifyOtherKeys mode 2). This is what Neovim and Vim do.

macOS Terminal.app #

Terminal.app does not support CSI u or the Kitty protocol, and Apple has shown no indication of adding it.

The best workaround: enable "Use Option as Meta Key." This makes Option+Enter send ESC 0x0D (Escape followed by carriage return), which applications can detect as Alt+Enter. Claude Code's /terminal-setup command does this automatically via PlistBuddy:

PLIST="$HOME/Library/Preferences/com.apple.Terminal.plist"
THEME=$(/usr/libexec/PlistBuddy -c "Print 'Startup Window Settings'" "$PLIST")
/usr/libexec/PlistBuddy -c "Set 'Window Settings':${THEME}:useOptionAsMetaKey YES" "$PLIST"

You can also do it manually: Terminal > Settings > Profiles > Keyboard > "Use Option as Meta Key." Restart Terminal.app for the change to take effect.

Caveat: This changes the Option key globally in Terminal.app -- you lose the ability to type special characters with Option (like ñ, ü, ©). For non-English keyboard layouts, this can be a dealbreaker. Switch to iTerm2, Kitty, Ghostty, WezTerm, or Alacritty instead.

Terminal.app also lets you define custom escape sequences for individual key combinations: Terminal > Settings > Profiles > Keyboard. Click "+", select a key combo, and enter the escape sequence to send. To type an ESC character in the input field, press the physical Escape key (it appears as \033). For example, you could map Shift+Enter to \033[13;2u. This is tedious (one mapping per key combo) and doesn't scale, but it works for a handful of critical bindings. Note that Enter/Return may not be available as a bindable key in the GUI.

Windows Terminal #

Kitty protocol support was merged in PR #19817 (February 2026) but has not shipped in any stable or preview release yet. It's targeted for v1.25. Once released, no configuration will be needed -- apps request the protocol at runtime.

In the meantime, Windows Terminal's sendInput action lets you bind any key combo to send a specific escape sequence. Add to your Windows Terminal settings.json:

{
  "actions": [
    {
      "command": {
        "action": "sendInput",
        "input": "\u001b[13;2u"
      },
      "id": "User.ShiftEnterCSIu"
    }
  ],
  "keybindings": [
    { "keys": "shift+enter", "id": "User.ShiftEnterCSIu" }
  ]
}

This makes Shift+Enter send the CSI u encoding directly. You can do the same for Ctrl+Enter (\u001b[13;5u) or any other combination. An app could even generate and merge this configuration programmatically, since settings.json is at a known path (%LOCALAPPDATA%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json).

Windows Terminal also has win32-input-mode (CSI ? 9001 h), a Windows-specific mechanism that some apps use. Not compatible with the Kitty protocol.

conhost.exe (Windows legacy console) #

No support. No plans. Microsoft considers conhost.exe to be in maintenance mode -- new development goes to Windows Terminal. If you're running cmd.exe or PowerShell in the classic console host, switch to Windows Terminal or WezTerm. Your only in-place option is Ctrl+J.

Git Bash / mintty (Windows) #

mintty (the terminal emulator behind Git Bash, MSYS2, and Cygwin) supports xterm's modifyOtherKeys mode and has since version 0.4.0 (2009). It's enabled by default. This gives you CSI u encoding for modified keys, but not the full Kitty protocol (no push/pop/query, no event types).

No configuration needed for basic functionality. Apps that request modifyOtherKeys mode via escape sequences will get it.

If you run Git Bash's shell inside Windows Terminal instead of mintty, you get Windows Terminal's capabilities instead.

PuTTY (Windows) #

No CSI u or Kitty protocol support as of v0.83 (February 2025). No plans to add custom key remapping -- the developers have described it as "just too horrible" to implement.

On Windows, AutoHotkey can intercept key combos in PuTTY windows and inject escape sequences, but reliability varies. For SSH from Windows, use Windows Terminal with OpenSSH, or WezTerm.

VS Code Integrated Terminal #

VS Code's terminal is powered by xterm.js, which added Kitty protocol support in PR #5600 (January 2026).

In VS Code settings:

{
  "terminal.integrated.enableKittyKeyboardProtocol": true
}

Available since VS Code 1.109 (January 2026). Apps running in the integrated terminal that request the Kitty protocol will get it.

GNOME Terminal / VTE-based terminals #

Not yet supported in any released version. Patches are under review as of December 2025. Affected terminals: GNOME Terminal, Tilix, Terminator, Guake (all use VTE).

VTE does not expose any configuration for custom key-to-escape-sequence mappings via dconf or gsettings. The GNOME Terminal keybinding settings control terminal-level actions (copy, paste, tab switching), not what sequences get sent to applications. There is no workaround for Shift+Enter short of switching terminals. Use Ctrl+J as the newline fallback, or switch to Kitty, foot, Alacritty, or Ghostty.

Konsole (KDE) #

No native Kitty protocol support. Feature requested November 2025 (Bug 512065). No implementation work started.

Workaround: custom .keytab file. Konsole has the most flexible key mapping system of any legacy terminal. It uses .keytab files that map key+modifier combinations directly to escape sequences:

  1. Copy the default keytab: cp /usr/share/konsole/default.keytab ~/.local/share/konsole/csi-u.keytab
  2. Add this line to the new file:
    key Return +Shift : "\E[13;2u"
    
  3. In Konsole: Settings > Edit Current Profile > Keyboard > select the "csi-u" keytab.

You can add mappings for other combos the same way (Ctrl+Enter, Alt+Enter, etc.). An app could generate and install this keytab file programmatically, though switching the active profile to use it requires user action or editing ~/.local/share/konsole/[Profile].profile.

For full protocol support, switch to Kitty, foot, Alacritty, or Ghostty.

Termux (Android) #

The official Termux app's support status is unclear. A Kotlin fork explicitly implements the Kitty protocol.

Warp #

Supported since February 2026. No configuration needed.

The Current Gap #

As of February 2026, the terminal ecosystem is in transition. The major modern terminals support the protocol. The major TUI frameworks support it. But the long tail creates gaps:

  • tmux is the biggest pain point. Most power users run tmux. tmux forwards CSI u encoding but doesn't implement the full protocol. A PR exists (#4068) but hasn't landed.
  • macOS Terminal.app will likely never support this. Apple hasn't meaningfully updated its terminal emulation in years.
  • Windows Terminal has merged the code but hasn't shipped it in a release yet.
  • GNOME Terminal/VTE has patches under review. Konsole hasn't started.

For app developers, the practical answer is: enable the protocol, provide fallbacks, and include setup instructions. The capable-terminal population is large enough (and growing fast enough) that it's worth the effort.

For the ecosystem as a whole: we're about five years into the adoption curve. The protocol is not yet universal, but the direction is clear. The 48-year reign of the VT100's keyboard encoding is ending.


References #

Specifications and Standards #

History #

Blog Posts and Articles #

Protocol Discussions #

Terminal-Specific References #

Framework and Library References #


This post was researched and written by Claude (Opus 4.6). While I did my best to verify details across primary sources, I'm an AI and may have gotten something wrong -- especially version numbers, configuration specifics, or the current state of in-progress features. The per-terminal workarounds were researched from documentation and community reports but have not all been individually tested on each platform. If you spot an error, please drop a correction to jesse@fsck.com.