Skip to content

CyberShadow/term-keys

Repository files navigation

term-keys - lossless keyboard input for Emacs

This package allows configuring Emacs and a supported terminal emulator to handle keyboard input involving any combination of keys and modifiers.

Table of Contents

Introduction

Generally, terminal emulators and applications running in a terminal cannot reliably transmit and receive certain keystrokes (keys in combination with modifiers). For some key combinations, there is no consensus on how these events should be encoded on the wire (F-keys and movement keys plus modifiers); some other key combinations cannot be represented at all (such as Ctrl1 or CtrlShiftA). This can be an impediment for Emacs users, especially when you've already configured your Emacs in an environment unrestricted by these limitations (X11) and now wish to use it in the terminal as well.

The term-keys package allows solving this problem by giving these key combinations a custom, unique encoding, overcoming the limitations of the protocol implemented by the terminal emulators.

Installation

To install term-keys, first add my ELPA package archive. Add this to your init.el and evaluate it:

(require 'package) ; you may already have this line
(add-to-list 'package-archives
             '("cselpa" . "https://elpa.thecybershadow.net/packages/"))

Then, install it like any ELPA package (M-xpackage-installRETterm-keys).

Setup

Setting up term-keys is a three-part process:

  1. Configure Emacs;
  2. Configure term-keys (optional);
  3. Configure your terminal emulator.

See the sections below for more information.

Configuring Emacs

Setting up term-keys is as easy as:

(require 'term-keys)
(term-keys-mode t)

This will automatically set up current and new TTY frames to decode term-keys key sequences. If you prefer to enable it for each frame manually, you can do so by invoking (term-keys/init).

Configuring term-keys

term-keys is very configurable. Most things can be changed via Emacs' customize interface - use M-xcustomize-groupRETterm-keys to access it.

Some of the supplementary code for terminal emulator configuration can be configured as well. Because it's not loaded by default (as it's only necessary for initial configuration), you need to load it explicitly before configuring it, e.g. using M-:(require 'term-keys-konsole), and only then invoking Emacs' customize interface. See the terminal's section in this file for more information.

The only part of term-keys which cannot be configured via the customize interface is the set of key combinations to support, as (counting all possible keys plus combinations of modifier keys) their total number is very large. For this purpose, term-keys allows specifying the name of a function to invoke, which shall implement this logic. See the documentation of term-keys/want-key-p-func and term-keys/want-key-p-def (as well as the definition of the latter) for more information.

Terminal Emulators

Each terminal emulator has its own method of configuration. Consult the section corresponding to your terminal emulator of choice below.

Note that you will need to update the terminal emulator configuration every time you change the term-keys configuration.

urxvt (rxvt-unicode)

There's three ways to configure urxvt with term-keys: via command-line parameters, via X resources, or by invoking a term-keys function from Emacs.

Command-line parameters

Command-line configuration consists in adding the key bindings to the urxvt invocation directly. You can use term-keys/urxvt-script to create a shell script in this manner:

(require 'term-keys-urxvt)
(with-temp-buffer
  (insert (term-keys/urxvt-script))
  (write-region (point-min) (point-max) "~/launch-urxvt-with-term-keys.sh"))

Afterwards, you can run e.g.:

$ sh ~/launch-urxvt-with-term-keys.sh -e emacs -nw

This will launch Emacs under an urxvt instance configured for term-keys.

X resources

X resource configuration consists in adding the term-keys configuration to the X resources. The X resources are global (per X session), and will apply to all newly-started urxvt instances. You can use term-keys/urxvt-xresources to create the necessary configuration in this manner:

(require 'term-keys-urxvt)
(with-temp-buffer
  (insert (term-keys/urxvt-xresources))
  (append-to-file (point-min) (point-max) "~/.Xresources"))

Then use xrdb to load the file into memory:

$ xrdb -merge ~/.Xresources
Direct invocation

In addition to generating a static configuration as a shell script or X resources, you can ask term-keys to invoke urxvt directly. This has the benefit that it will always use the up-to-date term-keys configuration, but the downside that it must be done by invoking emacs or emacsclient. For example:

$ emacsclient --eval '(term-keys/urxvt-run-emacs)'

See the term-keys/urxvt-* definitions for more urxvt-specific help code.

You may also want to disable some default urxvt shortcuts which may interfere with using Emacs. Consider adding something like this to your ~/.Xresources:

URxvt*iso14755: 0
URxvt.keysym.Shift-Insert: builtin-string:
URxvt.keysym.M-S: builtin-string:

xterm

xterm is configured nearly identically as urxvt; thus, this section will be very similar to the urxvt section above.

Note: xterm supports an extended input mode; see modifyOtherKeys et al in the xterm(1) man page. You may find it sufficient for your use case even without term-keys.

As with urxvt, there's two ways to configure xterm: via command-line parameters or X resources. Command-line configuration consists in adding the key bindings to the xterm invocation directly. You can use term-keys/xterm-script to create a shell script in this manner:

(require 'term-keys-xterm)
(with-temp-buffer
  (insert (term-keys/xterm-script))
  (write-region (point-min) (point-max) "~/launch-xterm-with-term-keys.sh"))

Afterwards, you can run e.g.:

$ sh ~/launch-xterm-with-term-keys.sh -e emacs -nw

This will launch Emacs under an xterm instance configured for term-keys.

X resource configuration consists in adding the term-keys configuration to the X resources. The X resources are global (per X session), and will apply to all newly-started xterm instances. You can use term-keys/xterm-xresources to create the necessary configuration in this manner:

(require 'term-keys-xterm)
(with-temp-buffer
  (insert (term-keys/xterm-xresources))
  (append-to-file (point-min) (point-max) "~/.Xresources"))

Then use xrdb to load the file into memory:

$ xrdb -merge ~/.Xresources

In addition to generating a static configuration as a shell script or X resources, you can ask term-keys to invoke xterm directly. This has the benefit that it will always use the up-to-date term-keys configuration, but the downside that it must be done by invoking emacs or emacsclient. For example:

$ emacsclient --eval '(term-keys/xterm-run-emacs)'

See the term-keys/xterm-* definitions for more xterm-specific help code.

You may also want to disable the eightBitInput xterm option, e.g. with -xrm 'XTerm*eightBitInput: false'.

kitty

kitty is configured via its kitty.conf configuration file.

To configure kitty for term-keys, use term-keys/kitty-conf to generate a kitty.conf fragment:

(require 'term-keys-kitty)
(with-temp-buffer
  (insert (term-keys/kitty-conf))
  (write-region (point-min) (point-max) "~/kitty-for-term-keys.conf"))

Then, add the output to your main kitty.conf file.

You can customize kitty's mapping of GLFW modifiers to Emacs modifiers in the generated configuration using the respective customize group, i.e.: M-:(progn (require 'term-keys-kitty) (customize-group 'term-keys/glfw))

wezterm

wezterm is configured via its wezterm.lua configuration file.

To configure wezterm for term-keys, use term-keys/wezterm-conf to generate a wezterm.lua fragment:

(require 'term-keys-wezterm)
(with-temp-buffer
  (insert (term-keys/wezterm-conf))
  (write-region (point-min) (point-max) "~/wezterm-for-term-keys.lua"))

Then, add the output to your main wezterm.lua file.

You can customize wezterm's mapping of GLFW modifiers to Emacs modifiers in the generated configuration using the respective customize group, i.e.: M-:(progn (require 'term-keys-wezterm) (customize-group 'term-keys/glfw))

Alacritty

Alacritty is configured via its alacritty.yml configuration file.

To configure alacritty for term-keys, use term-keys/alacritty-config to generate a alacritty.yml fragment:

(require 'term-keys-alacritty)
(with-temp-buffer
  (insert (term-keys/alacritty-config))
  (write-region (point-min) (point-max) "~/alacritty-for-term-keys.yml"))

Then, add the output to your main alacritty.yml file.

You can customize Alacritty's mapping of its supported modifiers to Emacs modifiers in the generated configuration using the respective customize group, i.e.: M-:(progn (require 'term-keys-alacritty) (customize-group 'term-keys/alacritty))

Konsole

Konsole provides an interface for adding new and editing existing escape sequences for key combinations (Settings → Edit current profile... → Keyboard → Edit). term-keys can generate a Konsole keyboard profile according to its settings.

To do so:

  1. Create a new keyboard profile, based on the default one.
    (Select "Default (XFree 4)", and click "New...".)
    Name it e.g. Emacs.

  2. Append the output of term-keys/konsole-keytab to the newly created .keytab file, e.g.:

    (require 'term-keys-konsole)
    (with-temp-buffer
      (insert (term-keys/konsole-keytab))
      (append-to-file (point-min) (point-max) "~/.local/share/konsole/Emacs.keytab"))
  3. Assign the keyboard profile to a new or existing Konsole profile.

You can customize the mapping of Konsole (Qt) modifiers to Emacs modifiers using the respective customize group, i.e.: M-:(progn (require 'term-keys-konsole) (customize-group 'term-keys/konsole))

You may also want to disable some default Konsole shortcuts (Settings → Configure Shortcuts...), as they may interfere with standard Emacs commands.

Yakuake

Yakuake seems to share much with Konsole, and can be configured in the same way. See the section above for details.

Linux console

The Linux console can be customized using .keymap files and the loadkeys program. You can configure it for term-keys as follows:

  1. Use term-keys/linux-keymap to create a .keymap file:

    (require 'term-keys-linux)
    (with-temp-buffer
      (insert (term-keys/linux-keymap))
      (write-region (point-min) (point-max) "~/term-keys.keymap"))
  2. Load the created .keymap file:

    $ sudo loadkeys ~/term-keys.keymap

To reset the layout to the default one, run sudo loadkeys -d.

You will need to invoke loadkeys after every boot; alternatively, on systemd distributions, you can add a KEYMAP= line to /etc/vconsole.conf.

You can customize some settings affecting the generated .keymap files using the respective customize group, i.e.: M-:(progn (require 'term-keys-linux) (customize-group 'term-keys/linux))

Note that the .keymap files generated by term-keys/linux-keymap assume a QWERTY keyboard layout. If you use another layout (AZERTY, Dvorak, Colemak...), you will need to customize term-keys/mapping and set the alphanumeric keynumbers accordingly.

Note also that Linux has a limitation on the number of customized keys. Custom key strings need to be assigned to "function keys", for which there are 256 slots by default (which includes the default strings for the F-keys and some other keys like Insert/Delete). This leaves about 234 slots for term-keys, which is sufficient for the default configuration, but may not be sufficient for a custom one, especially if you enable support for additional modifiers.

st

st is configured by editing its config.h and recompiling. The key sequences can be configured in this way as well.

You can configure st for term-keys as follows:

  1. Generate the initial config.h, e.g. by building st once.

  2. Use the term-keys/st-config-* functions to create header files:

    (require 'term-keys-st)
    ;; Assuming st is checked out in ~/st
    (with-temp-buffer
      (insert (term-keys/st-config-key))
      (write-region (point-min) (point-max) "~/st/config-term-keys-key.h"))
    (with-temp-buffer
      (insert (term-keys/st-config-mappedkeys))
      (write-region (point-min) (point-max) "~/st/config-term-keys-mappedkeys.h"))
  3. Update the definition of mappedkeys in config.h as follows:

    static KeySym mappedkeys[] = {
    #include "config-term-keys-mappedkeys.h"
        -1
    };
  4. Update the definition of key in config.h as follows:

    static Key key[] = {
    #include "config-term-keys-key.h"
        /* keysym           mask            string      appkey appcursor crlf */
        /* ( ... original definitions follow ... ) */
  5. Rebuild st.

You can customize st's mapping of X11 modifiers to Emacs modifiers in the generated configuration using the respective customize group, i.e.: M-:(progn (require 'term-keys-st) (customize-group 'term-keys/x11))

macOS Terminal

The standard macOS Terminal.app allows, to a certain extent, customizing escape sequences sent by key combinations (Terminal → Preferences... → Profiles → (select a profile) → Keyboard). term-keys can generate a configuration XML file, which can be injected into Terminal.app's preferences plist file.

To do so:

  1. Create a new profile. Name it e.g. Emacs.

  2. Enable the "Use Option as Meta key" option on the Keyboard tab.

  3. Export the term-keys keymap XML file:

    (require 'term-keys-terminal-app)
    (with-temp-buffer
      (insert (term-keys/terminal-app-keymap-xml))
      (append-to-file (point-min) (point-max) "~/term-keys.xml"))
  4. Import the keymap into Terminal.app's preferences file:

    $ plutil -replace 'Window Settings.Emacs.keyMapBoundKeys' \
        -xml "$(cat ~/term-keys.xml)" \
        ~/Library/Preferences/com.apple.Terminal.plist

    (If you named your profile something other than Emacs, substitute its name in the command above.)

  5. Restart Terminal.app.

Note that the application's settings UI only allows configuring a small set of keys. This limitation does not carry over to the underlying configuration file format, and thus does not apply to term-keys - the generated keymap file will contain definitions for all keys that generate a Unicode key code on macOS. These entries will appear (displayed with the key's hex code instead of the key name) in the terminal emulator's configuration, but cannot be edited there.

You can customize the mapping of Terminal.app modifiers to Emacs modifiers using the respective customize group, i.e.: M-:(progn (require 'term-keys-terminal-app) (customize-group 'term-keys/terminal-app))

Unsupported terminals

These terminals don't (directly) support customizing key bindings, and thus cannot be used with term-keys:

  • PuTTY - Custom key mappings are a known frequently-requested feature.
  • GNOME Terminal - Uses the Gnome VTE widget, which doesn't seem to support key binding customization.
  • tilix, Guake, tilda - Use the same widget as GNOME Terminal.
  • GNOME Terminator - Uses the same widget as GNOME Terminal, however has a plugin system. Perhaps writing a plugin is possible?
  • termite - Uses the same widget as GNOME Terminal, however, it has a modify_other_keys option, similar to xterm's modifyOtherKeys.
  • mintty - No support for customizing key bindings, however, it already has some support for xterm's modifyOtherKeys protocol extensions.
  • Bitvise SSH Client - No support for customizing key bindings. Supports a custom extended protocol (bvterm), which seems to allow lossless keyboard input (including key-up events), however it is full of Windows-isms (much of the protocol uses the same data types as used by the Windows API), and getting Emacs to speak it would be a non-trivial task that's not likely to be achievable using just Emacs Lisp code.
  • Terminology - No support for customizing key bindings at runtime (via a configuration file), however, this terminal generates its key/string translation tables using another terminal emulator (i.e. by sending keyboard events with xdotool to another terminal emulator and recording the resulting strings). Therefore, it may be possible to configure Terminology with term-keys by running its configuration script through another terminal pre-configured with term-keys (e.g. xterm), then rebuilding Terminology using the resulting configuration.

Similar projects

  • xterm-keybinder is an Emacs package similar to this one. term-keys improves upon xterm-keybinder by supporting more terminals, better documentation, customizability, and extensibility, and improved wire efficiency.

  • notty is an experimental terminal emulator which also aims at achieving lossless keyboard input by extending the ANSI protocol. term-keys does not use notty's protocol because its escape sequence prefix (^[{) conflicts with a default Emacs command binding (backward-paragraph).

  • Fix Keyboard Input on Terminals is a proposal to improve input in terminals, which is to some extent implemented in xterm and Emacs. Unfortunately, the proposed protocol is vague and incomplete, as it does not cover modifier keys and many PC keyboard keys.

  • The kitty terminal emulator (the one written in Python, not the PuTTY fork) implements its own protocol extensions for keyboard handling (though it can also be simply configured with term-keys as described above).