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.