3 Keyboard
dxtr edited this page 2026-10-18 16:13:29 +02:00

Keyboard

A key press says three separate things, and cl-windowing reports each one on its own:

What Where Use it for
The physical key: where the key is event-key Controls that depend on position, such as WASD movement.
The logical key: what the layout makes of it event-symbol Shortcuts, such as Control+Z.
The text typed :text events, event-text Text fields.

Mixing them up is the usual source of keyboard bugs. On a French AZERTY keyboard the key right of Caps Lock is physically :a and types q:

Keyboard Key pressed event-key event-symbol :text
US the key right of Caps Lock :a :a "a"
French the key right of Caps Lock :a :q "q"
French the key right of Tab :q :a "a"

A game that moves left on event-key :a uses the same physical key on both keyboards. An editor that undoes on event-symbol :z uses the key labelled Z on both.

Physical keys

event-key names the key for its place on a US keyboard, whatever the layout: :a to :z, :digit-0 to :digit-9, :f1 to :f24, :return, :escape, :space, :left-shift, :up, :keypad-enter and so on. Every name is on Key Names. A key without a name is :unknown.

event-scancode is the platform's own code for the key. On Linux it is the evdev key code, such as 30 for :a; on Windows, the set 1 scancode, such as #x1E for :a, with #xE000 added for extended keys. Use it to let players rebind keys that have no name, and store it with the binding: it is stable on one machine but means nothing on another platform.

Logical keys

event-symbol is the key the layout makes of the key, in the same names. It is nil when the layout makes none of them, as for letters a US keyboard does not have, such as ö.

The symbol is computed with the modifiers held, so Shift changes it:

  • Shift+A is still :a, because upper and lower case letters share a name.
  • On a US keyboard Shift+1 types !, which has no name, so its symbol is nil. For a shortcut such as Control+Shift+1, match event-key :digit-1 and check the modifiers.
  • With Num Lock off, keypad keys are :home, :up, :page-up and so on, and with it on, :keypad-0 to :keypad-9 and :keypad-decimal.
  • AltGr is :right-alt.

Text

:text events carry what was typed, as a string, after the layout, dead keys and compose sequences have done their work:

  • A dead acute, then e, types "é" in one :text event. The dead key itself types nothing.
  • A compose sequence such as Compose, o, c types "©".
  • AltGr combinations type their character, such as AltGr+E typing "€" on many European layouts.

Dead keys and compose sequences use the compose table of the locale ($LC_ALL, $LC_CTYPE or $LANG). In a locale without one, they type nothing.

Some keys do not send :text:

  • No text is sent while Control, Alt or Super is held. Those combinations are shortcuts, so Control+S saves and does not type s.
  • Keys that type control characters send none: Return, Tab, Backspace, Delete and Escape. Handle them as :key-press events, with event-key or event-symbol.

The :text event comes right after the :key-press that typed it. Text chosen in an input method comes as :text too; see Input Methods.

Modifiers

(win:event-modifier-p event :control) => boolean

event-modifier-p says whether a modifier was in effect when a key, pointer, button or scroll event happened. The modifiers are:

Modifier Meaning
:shift Shift is held.
:control Control is held.
:alt Alt is held. AltGr is not Alt.
:super Super (the Windows or Command key) is held.
:caps-lock Caps Lock is on.
:num-lock Num Lock is on.

event-modifiers is the mask of them all, one bit each, the bit modifier-bit returns: 1 for :shift, then 2, 4, 8, 16 and 32 in the order of the table. It is for comparing sets of modifiers, and for filling an event, as a backend or a test does:

(= (win:event-modifiers event) (win:modifier-bit :control))   ; Control alone

:text events carry no modifiers.

A press of a modifier key reports itself too, as a :key-press with event-key :left-shift, :right-control and so on.

(when (and (eq (win:event-type event) :key-press)
           (win:event-modifier-p event :control)
           (not (win:event-modifier-p event :shift))
           (eq (win:event-symbol event) :z))
  (undo))

Key repeat

A held key repeats. Each repeat is a :key-press with event-repeat-p true, followed by its :text if it types any. There is no :key-release between repeats.

The delay before the first repeat and the rate afterwards are the platform's: the user's settings, as the compositor reports them, or 600 ms and 25 per second by default. A compositor that sets the rate to 0 turns repeating off. Modifier keys and others the keymap marks as not repeating do not repeat.

Repeats are made by process-events, which wakes up in time for them even when it was given a longer timeout. They come only while it is called.

Ignore repeats where they do not make sense:

(when (and (eq (win:event-type event) :key-press)
           (not (win:event-repeat-p event))
           (eq (win:event-key event) :space))
  (jump))

Focus

Keyboard events go to the window with the keyboard focus. It gains focus with :focus-in and loses it with :focus-out. A key held when the window loses focus stops repeating, and its :key-release is not reported to that window. Treat :focus-out as releasing every key you track as held.

Tracking held keys

cl-windowing reports changes, not state. To know which keys are down, as a game does every frame, keep a table:

(defvar *held* (make-hash-table))

(defun track (event)
  (case (win:event-type event)
    (:key-press (setf (gethash (win:event-key event) *held*) t))
    (:key-release (remhash (win:event-key event) *held*))
    (:focus-out (clrhash *held*))))

(defun held-p (key)
  (gethash key *held*))

On Wayland

Input comes from the compositor's first seat. The keymap is the one the compositor sends, compiled by libxkbcommon through cl-xkbcommon. It changes when the user switches layouts. If a keymap does not compile, cl-windowing warns and keeps the previous one. Until there is a keymap, event-symbol is nil and no text is typed.

Input methods, for typing Chinese, Japanese or Korean, speak text-input-v3 where the compositor offers it. See Input Methods.

On Windows

Keys are named by their scancodes, mapped to the same physical keys as on Wayland, and the layout's virtual keys give event-symbol. Text comes as Windows translates key presses, dead keys included. AltGr is not reported as Control and Alt, though Windows sends it as both: it types characters, and they arrive as :text. Input methods speak IMM32; see Input Methods and Win32 Backend.