3 Keyboard States and Modifiers
dxtr edited this page 2026-10-18 16:16:25 +02:00

Keyboard States and Modifiers

A keyboard-state follows one keyboard. It knows which modifiers are depressed, latched and locked, and which layout is active. From that it answers what each key produces now.

(xkb:with-keyboard-state (state keymap)
  ...)

;; or
(let ((state (xkb:make-keyboard-state keymap)))
  ...
  (xkb:destroy-keyboard-state state))

A new keyboard state has no key held, no modifier latched or locked, and layout 0 active. keyboard-state-keymap returns the keymap it was made with, so you can keep just the state:

(xkb:key-repeats-p (xkb:keyboard-state-keymap state) keycode)

Feeding a keyboard state

There are two ways to keep a keyboard state up to date. Pick the one that matches your platform, and use only that one.

With keys: update-key

Where the platform reports raw key presses and the application tracks modifiers itself, as with evdev, tell the state about every press and release:

(xkb:update-key state 50 :press)     ; left Shift down  => T
(xkb:update-key state 38 :press)     ; A down           => NIL

Feed it every key, not only modifiers. libxkbcommon handles latches, locks such as Caps Lock, and layout switching keys itself, and needs to see the other keys to clear latches.

update-key returns true if the modifiers in effect or the active layout changed, and with them what keys produce. Use it to decide whether to recompute something that depends on the modifiers, such as a cached list of active-modifiers.

With modifier masks: update-modifiers

Where the platform tracks modifiers and sends them, as a Wayland compositor does in wl_keyboard.modifiers, pass the masks on:

(xkb:update-modifiers state depressed latched locked layout)

Bit n of each mask stands for modifier n of the keymap, in the order keymap-modifiers returns. layout is the index of the active layout. It returns true if anything changed, as update-key does.

With Wayland, do not also call update-key. The compositor has already accounted for the keys in the masks it sends.

keyboard-state-masks returns the four values that update-modifiers takes. Use it to send a state to another process, or to copy it to another keyboard state of the same keymap:

(multiple-value-call #'xkb:update-modifiers other-state
  (xkb:keyboard-state-masks state))

keyboard-state-layout returns only the active layout's index.

What a key produces

(xkb:with-context (context :environment nil)
  (xkb:with-keymap (keymap context :layout "us")
    (xkb:with-keyboard-state (state keymap)
      (xkb:key-text state 38)                     ; => "a"
      (xkb:keysym-name (xkb:key-keysym state 38)) ; => "a"
      (xkb:key-text state 50)                     ; => NIL, Shift types nothing
      (xkb:update-key state 50 :press)
      (xkb:key-text state 38)                     ; => "A"
      (xkb:keysym-name (xkb:key-keysym state 38))))) ; => "A"
Function Returns
key-keysym the keysym the key produces now, or nil if it produces none, or more than one
key-text the text the key types now, without composing, or nil for keys that type nothing, such as Shift, the arrows and dead keys
key-press-text the text a press of the key types, with dead keys and compose sequences composed by a compose state

key-keysym and key-text ask "what would this key produce now?". Neither changes the state. key-press-text feeds its compose state, so call it exactly once per press: see Compose and Dead Keys.

With update-key, ask before updating the state for the same press, so that the key's own effect on the modifiers is not counted.

Asking about modifiers

active-modifiers lists the modifiers in effect, held, latched or locked:

(xkb:update-key state 37 :press)          ; Control
(xkb:update-key state 50 :press)          ; Shift
(xkb:active-modifiers state)              ; => (:SHIFT :CONTROL)
(xkb:active-modifiers state :except-consumed-by 38)
                                          ; => (:CONTROL), Shift went into the A

The keywords are those listed in Concepts, in that order. A keyword on the same real modifiers as one before it is left out, so holding Alt gives (:alt) even though most keymaps put Meta on Alt's real modifier. See Concepts.

To ask about one modifier:

(xkb:modifier-active-p state :control)   ; is Control in effect?
(xkb:modifier-active-p state "Mod3")     ; by the keymap's name for it
(xkb:modifier-active-p state :hyper)     ; NIL if the keymap has no Hyper

A keyword the keymap lacks is never in effect. A keyword that is not a modifier at all, such as :hyperr, is no modifier-keyword, so it signals a type-error that lists the keywords. A string that names no modifier of the keymap signals an xkb-error that lists the keymap's modifiers. keymap-modifier-p says whether a keymap has a modifier.

modifier-active-p reports what libxkbcommon does, so it is true for :meta while Alt is held on most keymaps.

Consumed modifiers

A modifier is consumed by a key when it went into what the key produces. With Shift held, A produces A, so the A key consumes Shift. Control and Alt usually change nothing about the keysym, so they are not consumed.

(xkb:modifier-consumed-p state 38 :shift)          ; one modifier
(xkb:active-modifiers state :except-consumed-by 38) ; the rest of them

Shortcut handling needs this: Control+Shift+A should match a shortcut bound to Control+A, not one bound to Control+Shift+a.

Shortcuts

key-shortcut returns a key's keysym and modifiers for matching shortcuts:

(xkb:update-key state 37 :press)          ; Control
(xkb:update-key state 50 :press)          ; Shift
(xkb:key-shortcut state 38)               ; => the keysym A, (:CONTROL)

Its modifiers are those of active-modifiers that the key does not consume, without the locks: Caps Lock, Num Lock and Scroll Lock. Its keysym is the one the key produces with Caps Lock off. Otherwise Caps Lock would make Control+A produce A, the same as Control+Shift+A. Num Lock still decides what keypad keys produce. See Recipes.

Layouts

With several layouts, the active one decides what keys produce:

(xkb:with-keymap (keymap context :layout "us,se")
  (xkb:with-keyboard-state (state keymap)
    (xkb:key-text state 34)               ; => "["
    (xkb:update-modifiers state 0 0 0 1)  ; switch to layout 1
    (xkb:keyboard-state-layout state)     ; => 1
    (xkb:key-text state 34)))             ; => "å"

With update-key, layouts switch through the keys that the keymap's options assign to switching. For example, :options "grp:alt_shift_toggle" switches on Alt+Shift. With Wayland, the compositor sends the active layout in wl_keyboard.modifiers.