benotn/kkp

Emacs support for the Kitty Keyboard Protocol

Emacs Lisp

130

110 commits

updated Jul 6, 2026

See the code

README

#+TITLE: kkp.el -- Support for the Kitty Keyboard protocol in Emacs
#+AUTHOR: Benjamin Orthen
#+OPTIONS: ^:{}

[[https://melpa.org/#/kkp][file:https://melpa.org/packages/kkp-badge.svg]]

* Overview

This package provides support for the [[https://sw.kovidgoyal.net/kitty/keyboard-protocol][Kitty Keyboard Protocol]] (KKP).

KKP defines an alternative way to handle keyboard input for programs running in the terminal.
This allows, if the terminal (and intermediaries such as terminal multiplexers) support the protocol as well,
the transmission of more detailed information about a key event from the terminal to Emacs, e.g., it transmits "C-<backspace>" and "C-h" differently.

Currently, there exists another solution which solves the same problem, xterm's "modifyOtherKeys", which is already supported by Emacs (and activated by default if the terminal supports it).
KKP has the advantage of supporting more keys (e.g., "<menu>" or "<Scroll_Lock>"), more key combinations (e.g., "C-M-S-z") and more modifiers, i.e., the Hyper and Super keys.
It can also dynamically detect if a terminal supports the protocol, whereas Emacs has to deduce "modifyOtherKeys" support from the TERM variable.

** Status
This package supports the "Disambiguate escape codes" and "Report
alternate keys" enhancements. I use it on a regular basis and it has
seen some testing from other users.

** Installation
*** Melpa
You can install this package from [[https://melpa.org/#/kkp][Melpa]], by first ensuring that you have the melpa source in your package-archives.
#+begin_src emacs-lisp
  (require 'package)
  (add-to-list 'package-archives
               '("melpa" . "http://melpa.org/packages/") t)
  (package-initialize)
  (package-refresh-contents)
#+end_src

Once that is done, this package can be installed.

#+begin_src shell
  package-install kkp
#+end_src
*** use-package

#+begin_src emacs-lisp
  (use-package kkp
    :ensure t
    :hook (tty-setup . global-kkp-mode)
    :config
    ;; (setq kkp-alt-modifier 'alt) ;; use this if you want to map the Alt keyboard modifier to Alt in Emacs (and not to Meta)

    ;; For C-g aborting blocking subprocesses, see "C-g and blocking
    ;; subprocesses" in the README.
    ;; (setq kkp-restore-legacy-keys-around-subprocesses t)
    )
#+end_src

** Usage

kkp.el works out of the box by enabling =global-kkp-mode= and is customizable via the =kkp-*= customization variables.

If you want to know if your terminal supports kkp, if its activated, and if yes, which enhancements are active, use =kkp-status=.

You can control the enabled [[https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement][enhancements]] by setting =kkp-active-enhancements=.
By default, it is set to =(disambiguate-escape-codes report-alternate-keys)=.

You can run your own functions at kkp terminal setup and teardown by hooking into =kkp-terminal-setup-complete-hook= and =kkp-terminal-teardown-complete-hook=.
This can be helpful in the scenario below where only =disambiguate-escape-codes= is active for some reason.

Set =kkp-verbose= to non-nil to echo KKP state and helpful messages to the echo area (e.g. when querying the terminal, enabling/tearing down, or when the terminal does not reply). Useful for debugging slow SSH or strange terminals.

*** =C-g= and blocking subprocesses

While KKP is active, the terminal sends =C-g= as an escape sequence (e.g. =\e[103;5u=) instead of the raw =quit-char= (7) byte.
Emacs detects the quit character at the byte level, before =input-decode-map= runs, so =C-g= cannot abort a blocking, synchronous =call-process= / =process-file= (it still works at the top-level command loop).
This is why, for example, =C-g= cannot abort a running [[https://github.com/purcell/envrc][envrc]]/direnv call.

Two ways to restore the legacy single-byte encoding around such a call so =C-g= aborts it (both are no-ops when KKP is not active):

1. Advise every =call-process=: set =kkp-restore-legacy-keys-around-subprocesses= to non-nil (default nil) before enabling =global-kkp-mode=.

   #+begin_src emacs-lisp
   (setq kkp-restore-legacy-keys-around-subprocesses t)
   #+end_src

2. Advise specific commands with the =kkp-restore-legacy-keys= =:around= advice (or wrap a region with the =kkp-with-legacy-keys= macro):

   #+begin_src emacs-lisp
   (with-eval-after-load 'envrc
     (advice-add 'envrc--export :around #'kkp-restore-legacy-keys))
   #+end_src

*** No =report-alternate-keys=

Note that when you activate only =disambiguate-escape-codes= (or your terminal does only support this enhancement),
the terminal reports shifted keypresses which involve another modifier by sending the modifiers with the base layout of the key.
This means "M-S-." (Meta-Shift-.) is not translated to "M-:" (on a German keyboard) and Emacs will probably not find the proper keybinding.
=report-alternate-keys= fixes this, but if you do not want to activate it, you can remap keys by using the =key-translation-map=:

#+begin_src emacs-lisp
(define-key key-translation-map (kbd "M-S-.") (kbd "M-:"))
#+end_src

For an automated solution, see the code setup in [[https://github.com/benotn/kkp/issues/15#issue-2782693357][this issue]] (and consider using the =kkp-terminal-setup-complete-hook= and =kkp-terminal-teardown-complete-hook= hooks).

** Testing

The package includes ERT tests that mimic a strangely behaved terminal
(malformed replies, garbage bytes, wrong prefix/terminator) and slow SSH
(no reply within timeout, partial reply). Run them in batch:

#+begin_src shell
  emacs -batch -l ert -l kkp.el -l kkp-tests.el -f ert-run-tests-batch-and-exit
#+end_src

** Debugging

If you want to know if your terminal supports kkp, if its activated, and if yes, which enhancements are active, use =kkp-status=.

Set =kkp-verbose= to non-nil to echo KKP query, setup and teardown messages to the echo area.

=kkp-debug-show-terminal-state= (in =kkp-debug=) shows kkp's recorded =kkp--state= for the selected terminal alongside the terminal's live reply.
=kkp-debug-show-all-terminal-states= shows the recorded state of every terminal (no live query).

If your terminal reports KKP support and KKP is active, don't be confused that =describe-key-briefly= or similar functions sometimes do not report the key you pressed if it involved =Shift=.
If there is no keybinding for a shifted key, Emacs attempts to find a binding for the non-shifted part (controlled by the global variable =translate-upper-case-key-bindings=).
That means for example, if you hit =C-S-g= in the normal case where there exists no binding for this key sequence, 
Emacs will fall back to search for a binding for =C-g=. You can verify this yourself if evaluating =(global-set-key (kbd "C-S-g") 'revert-buffer)= and then entering =C-h c= (=describe-key-briefly=) =C-S-g=.

To determine how your keyboard input is translated, you can use the helper command =kkp-debug-describe-key-translation-chain=.
To use this debugging helper, you can:
#+begin_src emacs-lisp
  ;; either
  ;;
  ;;   M-x load-library RET kkp-debug RET
  ;;
  ;; or add (require 'kkp-debug) to your Emacs init file.
  ;;
  ;; Then execute M-x kkp-debug-describe-key-translation-chain
#+end_src


** Background

The standard xterm encoding is quite old and cannot transmit key combinations such as "C-.".

At the request of an Emacs user, xterm introduced "modifyOtherKeys" in version 216.
This feature encodes 'ordinary (i.e., "other") keys (such as "2") when
modified by Shift-, Control-, Alt- or Meta-modifiers by an escape sequence' ([[https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys][source]]).
By default, it uses a "CSI 27 ; modifier ; code ~" encoding. CSI (Control Sequence Introducer) is the bytes sequence "\e[", i.e., \x1b\x5b.

By request of Paul Leonerd Evans, xterm introduced an alternative encoding for the same keys, using a CSI-u encoding ("CSI modifier ; code u").
This is turned on by an xterm setting, [[https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:formatOtherKeys][formatOtherKeys]].
Paul Leonerd Evans documented this approach in his [[https://www.leonerd.org.uk/hacks/fixterms/][fixterms]] proposals, but does not mention if it differs from the formatOtherKeys implementation in xterm. 

Thomas Dickey documents the "modifyOtherKeys/formatOtherKeys" evolution in more detail [[https://invisible-island.net/xterm/modified-keys.html][here]].

On the basis of the fixterms proposal, Kovid Goyal devised the Kitty Keyboard Protocol.
This protocol does not deviate a lot from the fixterms proposal:
- It [[https://sw.kovidgoyal.net/kitty/keyboard-protocol/#bugs-in-fixterms][fixes]] some bugs in fixterms.
- It enables runtime opt-in and opt-out of enhancements (e.g., CSI-u encoding).
- It optionally also supports reporting event types or alternate keys.

For a complete list of enhancements, read [[https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement]].
For Emacs, other enhancements than "Disambiguate escape codes" and "Report alternate keys" do not appear to be relevant.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

emacs
kitty
terminal

Contributors

benotn

103 commits

ambirdsall

2 commits

unship

2 commits

agzam

1 commits

benotn/kkp

Emacs support for the Kitty Keyboard Protocol

Emacs Lisp

130

110 commits

updated Jul 6, 2026

See the code

README

#+TITLE: kkp.el -- Support for the Kitty Keyboard protocol in Emacs
#+AUTHOR: Benjamin Orthen
#+OPTIONS: ^:{}

[[https://melpa.org/#/kkp][file:https://melpa.org/packages/kkp-badge.svg]]

* Overview

This package provides support for the [[https://sw.kovidgoyal.net/kitty/keyboard-protocol][Kitty Keyboard Protocol]] (KKP).

KKP defines an alternative way to handle keyboard input for programs running in the terminal.
This allows, if the terminal (and intermediaries such as terminal multiplexers) support the protocol as well,
the transmission of more detailed information about a key event from the terminal to Emacs, e.g., it transmits "C-<backspace>" and "C-h" differently.

Currently, there exists another solution which solves the same problem, xterm's "modifyOtherKeys", which is already supported by Emacs (and activated by default if the terminal supports it).
KKP has the advantage of supporting more keys (e.g., "<menu>" or "<Scroll_Lock>"), more key combinations (e.g., "C-M-S-z") and more modifiers, i.e., the Hyper and Super keys.
It can also dynamically detect if a terminal supports the protocol, whereas Emacs has to deduce "modifyOtherKeys" support from the TERM variable.

** Status
This package supports the "Disambiguate escape codes" and "Report
alternate keys" enhancements. I use it on a regular basis and it has
seen some testing from other users.

** Installation
*** Melpa
You can install this package from [[https://melpa.org/#/kkp][Melpa]], by first ensuring that you have the melpa source in your package-archives.
#+begin_src emacs-lisp
  (require 'package)
  (add-to-list 'package-archives
               '("melpa" . "http://melpa.org/packages/") t)
  (package-initialize)
  (package-refresh-contents)
#+end_src

Once that is done, this package can be installed.

#+begin_src shell
  package-install kkp
#+end_src
*** use-package

#+begin_src emacs-lisp
  (use-package kkp
    :ensure t
    :hook (tty-setup . global-kkp-mode)
    :config
    ;; (setq kkp-alt-modifier 'alt) ;; use this if you want to map the Alt keyboard modifier to Alt in Emacs (and not to Meta)

    ;; For C-g aborting blocking subprocesses, see "C-g and blocking
    ;; subprocesses" in the README.
    ;; (setq kkp-restore-legacy-keys-around-subprocesses t)
    )
#+end_src

** Usage

kkp.el works out of the box by enabling =global-kkp-mode= and is customizable via the =kkp-*= customization variables.

If you want to know if your terminal supports kkp, if its activated, and if yes, which enhancements are active, use =kkp-status=.

You can control the enabled [[https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement][enhancements]] by setting =kkp-active-enhancements=.
By default, it is set to =(disambiguate-escape-codes report-alternate-keys)=.

You can run your own functions at kkp terminal setup and teardown by hooking into =kkp-terminal-setup-complete-hook= and =kkp-terminal-teardown-complete-hook=.
This can be helpful in the scenario below where only =disambiguate-escape-codes= is active for some reason.

Set =kkp-verbose= to non-nil to echo KKP state and helpful messages to the echo area (e.g. when querying the terminal, enabling/tearing down, or when the terminal does not reply). Useful for debugging slow SSH or strange terminals.

*** =C-g= and blocking subprocesses

While KKP is active, the terminal sends =C-g= as an escape sequence (e.g. =\e[103;5u=) instead of the raw =quit-char= (7) byte.
Emacs detects the quit character at the byte level, before =input-decode-map= runs, so =C-g= cannot abort a blocking, synchronous =call-process= / =process-file= (it still works at the top-level command loop).
This is why, for example, =C-g= cannot abort a running [[https://github.com/purcell/envrc][envrc]]/direnv call.

Two ways to restore the legacy single-byte encoding around such a call so =C-g= aborts it (both are no-ops when KKP is not active):

1. Advise every =call-process=: set =kkp-restore-legacy-keys-around-subprocesses= to non-nil (default nil) before enabling =global-kkp-mode=.

   #+begin_src emacs-lisp
   (setq kkp-restore-legacy-keys-around-subprocesses t)
   #+end_src

2. Advise specific commands with the =kkp-restore-legacy-keys= =:around= advice (or wrap a region with the =kkp-with-legacy-keys= macro):

   #+begin_src emacs-lisp
   (with-eval-after-load 'envrc
     (advice-add 'envrc--export :around #'kkp-restore-legacy-keys))
   #+end_src

*** No =report-alternate-keys=

Note that when you activate only =disambiguate-escape-codes= (or your terminal does only support this enhancement),
the terminal reports shifted keypresses which involve another modifier by sending the modifiers with the base layout of the key.
This means "M-S-." (Meta-Shift-.) is not translated to "M-:" (on a German keyboard) and Emacs will probably not find the proper keybinding.
=report-alternate-keys= fixes this, but if you do not want to activate it, you can remap keys by using the =key-translation-map=:

#+begin_src emacs-lisp
(define-key key-translation-map (kbd "M-S-.") (kbd "M-:"))
#+end_src

For an automated solution, see the code setup in [[https://github.com/benotn/kkp/issues/15#issue-2782693357][this issue]] (and consider using the =kkp-terminal-setup-complete-hook= and =kkp-terminal-teardown-complete-hook= hooks).

** Testing

The package includes ERT tests that mimic a strangely behaved terminal
(malformed replies, garbage bytes, wrong prefix/terminator) and slow SSH
(no reply within timeout, partial reply). Run them in batch:

#+begin_src shell
  emacs -batch -l ert -l kkp.el -l kkp-tests.el -f ert-run-tests-batch-and-exit
#+end_src

** Debugging

If you want to know if your terminal supports kkp, if its activated, and if yes, which enhancements are active, use =kkp-status=.

Set =kkp-verbose= to non-nil to echo KKP query, setup and teardown messages to the echo area.

=kkp-debug-show-terminal-state= (in =kkp-debug=) shows kkp's recorded =kkp--state= for the selected terminal alongside the terminal's live reply.
=kkp-debug-show-all-terminal-states= shows the recorded state of every terminal (no live query).

If your terminal reports KKP support and KKP is active, don't be confused that =describe-key-briefly= or similar functions sometimes do not report the key you pressed if it involved =Shift=.
If there is no keybinding for a shifted key, Emacs attempts to find a binding for the non-shifted part (controlled by the global variable =translate-upper-case-key-bindings=).
That means for example, if you hit =C-S-g= in the normal case where there exists no binding for this key sequence, 
Emacs will fall back to search for a binding for =C-g=. You can verify this yourself if evaluating =(global-set-key (kbd "C-S-g") 'revert-buffer)= and then entering =C-h c= (=describe-key-briefly=) =C-S-g=.

To determine how your keyboard input is translated, you can use the helper command =kkp-debug-describe-key-translation-chain=.
To use this debugging helper, you can:
#+begin_src emacs-lisp
  ;; either
  ;;
  ;;   M-x load-library RET kkp-debug RET
  ;;
  ;; or add (require 'kkp-debug) to your Emacs init file.
  ;;
  ;; Then execute M-x kkp-debug-describe-key-translation-chain
#+end_src


** Background

The standard xterm encoding is quite old and cannot transmit key combinations such as "C-.".

At the request of an Emacs user, xterm introduced "modifyOtherKeys" in version 216.
This feature encodes 'ordinary (i.e., "other") keys (such as "2") when
modified by Shift-, Control-, Alt- or Meta-modifiers by an escape sequence' ([[https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys][source]]).
By default, it uses a "CSI 27 ; modifier ; code ~" encoding. CSI (Control Sequence Introducer) is the bytes sequence "\e[", i.e., \x1b\x5b.

By request of Paul Leonerd Evans, xterm introduced an alternative encoding for the same keys, using a CSI-u encoding ("CSI modifier ; code u").
This is turned on by an xterm setting, [[https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:formatOtherKeys][formatOtherKeys]].
Paul Leonerd Evans documented this approach in his [[https://www.leonerd.org.uk/hacks/fixterms/][fixterms]] proposals, but does not mention if it differs from the formatOtherKeys implementation in xterm. 

Thomas Dickey documents the "modifyOtherKeys/formatOtherKeys" evolution in more detail [[https://invisible-island.net/xterm/modified-keys.html][here]].

On the basis of the fixterms proposal, Kovid Goyal devised the Kitty Keyboard Protocol.
This protocol does not deviate a lot from the fixterms proposal:
- It [[https://sw.kovidgoyal.net/kitty/keyboard-protocol/#bugs-in-fixterms][fixes]] some bugs in fixterms.
- It enables runtime opt-in and opt-out of enhancements (e.g., CSI-u encoding).
- It optionally also supports reporting event types or alternate keys.

For a complete list of enhancements, read [[https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement]].
For Emacs, other enhancements than "Disambiguate escape codes" and "Report alternate keys" do not appear to be relevant.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

emacs
kitty
terminal

Contributors

benotn

103 commits

ambirdsall

2 commits

unship

2 commits

agzam

1 commits

Languages

Emacs Lisp

100.0%