1package neferclient
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8 "time"
9
10 xkb "github.com/bnema/purego-xkbcommon"
11 "github.com/bnema/purego-xkbcommon/raw"
12 "golang.org/x/sys/unix"
13
14 "github.com/bnema/neferclient/internal/secret"
15)
16
17// Modifiers is the set of active keyboard modifiers.
18type Modifiers uint8
19
20const (
21 ModShift Modifiers = 1 << iota
22 ModCtrl
23 ModAlt
24 ModSuper
25 ModCapsLock
26 ModNumLock
27)
28
29// modNames are the xkb modifier names behind each Modifiers bit, in order.
30var modNames = [...]string{"Shift", "Control", "Mod1", "Mod4", "Lock", "Mod2"}
31
32const (
33 maxHeldKeys = 32
34 scratchSize = 64 // longest key or compose text kept; longer yields no text
35 keymapMax = 16 << 20
36)
37
38// heldKey is a pressed key. A key that went into the SecretBuffer keeps no
39// keysym and is flagged hidden, so its release is reported without either.
40type heldKey struct {
41 evdev, sym uint32
42 hidden bool
43}
44
45type textKind uint8
46
47const (
48 kindNone textKind = iota
49 kindText // printable text went into the SecretBuffer
50 kindPending // a dead or compose key: part of a text sequence, no text yet
51 kindBackspace
52)
53
54// secretWork is the argument and result area of doSecret, which secret.Do runs
55// without arguments. translate clears it after every use.
56type secretWork struct {
57 evdev, sym uint32
58 mods Modifiers
59 repeat bool
60 kind textKind
61 changed bool
62 err error
63}
64
65// keyboard interprets wl_keyboard events with xkbcommon. It runs only on the
66// owner goroutine. It never builds a string from key text: translations go
67// through the fixed scratch array, which is wiped after every use.
68type keyboard struct {
69 ctx *xkb.Context
70 keymap *xkb.Keymap
71 state *xkb.State
72 compose *xkb.ComposeState
73
74 modBits [len(modNames)]uint32 // xkb mask per Modifiers bit; 0 when absent
75 mods Modifiers // effective modifiers and layout, refreshed when the mask changes
76 group uint32
77 lastMask [6]uint32
78 haveMask bool
79
80 held []heldKey
81 focus bool
82 rate int32 // repeats per second, 0 disables
83 delay int32 // milliseconds before the first repeat
84 repeating uint32
85 composePending bool
86 composeUsed bool // the last text() call consumed the key into a compose sequence
87
88 tfd int // repeat timerfd
89 tbuf [8]byte
90
91 secret *SecretBuffer
92 scratch [scratchSize]byte
93
94 secretFn func()
95 w secretWork
96 changed bool // the last translation edited the SecretBuffer
97 timerErr error // first repeat timer failure, reported by the seat
98}
99
100func newKeyboard(locale string) (*keyboard, error) {
101 c, err := xkb.NewContext()
102 if err != nil {
103 return nil, fmt.Errorf("xkb context: %w", err)
104 }
105 k := &keyboard{ctx: c, held: make([]heldKey, 0, maxHeldKeys), tfd: -1}
106 k.secretFn = k.doSecret
107 if t, e := c.NewComposeTable(locale); e == nil {
108 k.compose, _ = t.NewState() // compose is optional: without it, dead keys still come from xkb
109 _ = t.Close()
110 }
111 if k.tfd, err = unix.TimerfdCreate(unix.CLOCK_MONOTONIC, unix.TFD_NONBLOCK|unix.TFD_CLOEXEC); err != nil {
112 _ = c.Close()
113 return nil, fmt.Errorf("repeat timer: %w", err)
114 }
115 return k, nil
116}
117
118// composeLocale picks the locale for the compose table the way libc does.
119func composeLocale() string {
120 for _, name := range []string{"LC_ALL", "LC_CTYPE", "LANG"} {
121 if v := os.Getenv(name); v != "" {
122 return v
123 }
124 }
125 return "C"
126}
127
128func (k *keyboard) close() {
129 k.disarm()
130 if k.tfd >= 0 {
131 _ = unix.Close(k.tfd)
132 k.tfd = -1
133 }
134 if k.compose != nil {
135 _ = k.compose.Close()
136 k.compose = nil
137 }
138 if k.state != nil {
139 _ = k.state.Close()
140 k.state = nil
141 }
142 if k.keymap != nil {
143 _ = k.keymap.Close()
144 k.keymap = nil
145 }
146 if k.ctx != nil {
147 _ = k.ctx.Close()
148 k.ctx = nil
149 }
150}
151
152// replaceFD installs the keymap in fd. It consumes fd even on failure; the
153// previous keymap stays valid on failure.
154func (k *keyboard) replaceFD(fd, size int) error {
155 f := os.NewFile(uintptr(fd), "keymap")
156 if f == nil {
157 return errors.New("neferclient: invalid keymap descriptor")
158 }
159 defer f.Close()
160 km, err := k.ctx.NewKeymapFD(fd, size)
161 if err != nil {
162 return fmt.Errorf("neferclient: compile keymap: %w", err)
163 }
164 return k.replace(km)
165}
166
167func (k *keyboard) replace(km *xkb.Keymap) error {
168 s, err := km.NewState()
169 if err != nil {
170 _ = km.Close()
171 return fmt.Errorf("neferclient: xkb state: %w", err)
172 }
173 k.reset()
174 if k.state != nil {
175 _ = k.state.Close()
176 }
177 if k.keymap != nil {
178 _ = k.keymap.Close()
179 }
180 k.keymap, k.state = km, s
181 for i, name := range modNames {
182 k.modBits[i] = 0
183 if idx, e := km.ModIndex(name); e == nil && idx < 32 {
184 k.modBits[i] = 1 << idx
185 }
186 }
187 if k.haveMask { // the compositor's modifiers survive a keymap change
188 _, _ = s.UpdateMask(k.lastMask[0], k.lastMask[1], k.lastMask[2], k.lastMask[3], k.lastMask[4], k.lastMask[5])
189 }
190 k.refresh()
191 return nil
192}
193
194func (k *keyboard) setMask(depressed, latched, locked, group uint32) error {
195 mask := [6]uint32{depressed, latched, locked, 0, 0, group}
196 if k.haveMask && mask == k.lastMask {
197 return nil // compositors repeat identical masks; the state is current
198 }
199 k.lastMask = mask
200 k.haveMask = true
201 if k.state == nil {
202 return nil
203 }
204 changed, err := k.state.UpdateMask(depressed, latched, locked, 0, 0, group)
205 if err != nil {
206 return fmt.Errorf("neferclient: update xkb mask: %w", err)
207 }
208 if changed != 0 {
209 k.refresh()
210 }
211 return nil
212}
213
214// refresh recomputes the cached modifiers and layout from the xkb state. Both
215// only change when the mask does, so keys do not query them.
216func (k *keyboard) refresh() {
217 k.mods = k.queryModifiers()
218 k.group, _ = k.state.Layout()
219}
220
221// reset drops held keys, repeat and compose state. It keeps the keymap, the
222// modifier mask and the focus flag.
223func (k *keyboard) reset() {
224 clear(k.held[:cap(k.held)]) // no hidden-key record outlives the reset
225 k.held = k.held[:0]
226 k.repeating = 0
227 k.disarm()
228 k.composePending = false
229 if k.compose != nil {
230 _ = k.compose.Reset()
231 }
232}
233
234func (k *keyboard) setFocus(on bool) {
235 k.focus = on
236 k.reset()
237}
238
239func (k *keyboard) setSecret(b *SecretBuffer) {
240 k.secret = b
241 k.reset() // switching paths cancels held keys, repeat and compose
242}
243
244func (k *keyboard) setRepeat(rate, delay int32) {
245 k.rate, k.delay = max(rate, 0), max(delay, 0)
246 if k.rate == 0 {
247 k.repeating = 0
248 k.disarm()
249 }
250}
251
252// arm starts the repeat timer: first expiry after the delay, then at the rate.
253func (k *keyboard) arm() {
254 spec := unix.ItimerSpec{
255 Interval: unix.NsecToTimespec(int64(time.Second) / int64(k.rate)),
256 Value: unix.NsecToTimespec(max(int64(k.delay)*int64(time.Millisecond), 1)), // zero would disarm
257 }
258 k.setTimer(&spec)
259}
260
261func (k *keyboard) disarm() {
262 if k.tfd >= 0 {
263 var spec unix.ItimerSpec
264 k.setTimer(&spec)
265 }
266}
267
268func (k *keyboard) setTimer(spec *unix.ItimerSpec) {
269 if err := unix.TimerfdSettime(k.tfd, 0, spec, nil); err != nil && k.timerErr == nil {
270 k.timerErr = fmt.Errorf("neferclient: repeat timer: %w", err)
271 }
272}
273
274// takeTimerErr returns and clears the first timer failure.
275func (k *keyboard) takeTimerErr() error {
276 err := k.timerErr
277 k.timerErr = nil
278 return err
279}
280
281// expired consumes a timer expiration. Missed ticks are not accumulated: one
282// call yields at most one repeat.
283func (k *keyboard) expired() bool {
284 n, err := unix.Read(k.tfd, k.tbuf[:])
285 return err == nil && n == 8
286}
287
288func (k *keyboard) queryModifiers() Modifiers {
289 mods, err := k.state.Mods()
290 if err != nil {
291 return 0
292 }
293 var m Modifiers
294 for i, bit := range k.modBits {
295 if bit != 0 && mods&bit != 0 {
296 m |= 1 << i
297 }
298 }
299 return m
300}
301
302func (k *keyboard) find(evdev uint32) int {
303 for i := range k.held {
304 if k.held[i].evdev == evdev {
305 return i
306 }
307 }
308 return -1
309}
310
311func (k *keyboard) drop(i int) {
312 last := len(k.held) - 1
313 copy(k.held[i:], k.held[i+1:])
314 k.held[last] = heldKey{} // clear the vacated slot
315 k.held = k.held[:last]
316}
317
318// press translates a key press into ev. It reports false when the press must
319// not be delivered (no focus or keymap).
320func (k *keyboard) press(evdev uint32, ev *KeyEvent) (bool, error) {
321 if !k.focus || k.state == nil {
322 return false, nil
323 }
324 if i := k.find(evdev); i >= 0 {
325 k.drop(i)
326 }
327 if len(k.held) == cap(k.held) {
328 return false, nil
329 }
330 sym, err := k.state.KeySym(xkb.WaylandKeycode(evdev))
331 if err != nil {
332 return false, fmt.Errorf("neferclient: key symbol: %w", err)
333 }
334 kind, err := k.translate(evdev, sym, false, ev)
335 if err != nil {
336 return false, fmt.Errorf("neferclient: key text: %w", err)
337 }
338 if ev.Secret {
339 k.held = append(k.held, heldKey{evdev: evdev, hidden: true})
340 } else {
341 k.held = append(k.held, heldKey{evdev: evdev, sym: sym})
342 }
343 if k.secret == nil && repeatable(sym) || k.secret != nil && (kind == kindBackspace || kind == kindText && repeatable(sym)) {
344 if k.rate > 0 {
345 k.repeating = evdev
346 k.arm()
347 }
348 } else if k.secret != nil && k.repeating != 0 && repeatable(sym) {
349 // A control or rejected key stops a previous printable repeat;
350 // a modifier does not.
351 k.repeating = 0
352 k.disarm()
353 }
354 return true, nil
355}
356
357// release fills ev for a key released after a delivered press.
358func (k *keyboard) release(evdev uint32, ev *KeyEvent) bool {
359 i := k.find(evdev)
360 if i < 0 {
361 return false
362 }
363 h := k.held[i]
364 k.drop(i)
365 if k.repeating == evdev {
366 k.repeating = 0
367 k.disarm()
368 }
369 ev.Pressed, ev.Secret = false, h.hidden
370 if !h.hidden {
371 ev.Keycode, ev.Keysym = evdev, h.sym
372 }
373 k.changed = false
374 ev.Modifiers, ev.Group = k.mods, k.group
375 return true
376}
377
378// repeat produces one repeat of the held key whose timer fired.
379func (k *keyboard) repeat(ev *KeyEvent) (bool, error) {
380 if !k.focus || k.state == nil || k.rate <= 0 || k.repeating == 0 || k.find(k.repeating) < 0 {
381 return false, nil
382 }
383 evdev := k.repeating
384 stop := func() (bool, error) {
385 k.repeating = 0
386 k.disarm()
387 return false, nil
388 }
389 sym, err := k.state.KeySym(xkb.WaylandKeycode(evdev))
390 if err != nil {
391 _, _ = stop()
392 return false, fmt.Errorf("neferclient: key symbol: %w", err)
393 }
394 if k.secret == nil && !repeatable(sym) {
395 return stop()
396 }
397 kind, err := k.translate(evdev, sym, true, ev)
398 if err != nil {
399 _, _ = stop()
400 return false, fmt.Errorf("neferclient: key text: %w", err)
401 }
402 if k.secret != nil && kind != kindBackspace && !(kind == kindText && repeatable(sym)) {
403 return stop()
404 }
405 return true, nil
406}
407
408// translate fills ev for a press or repeat and returns what the key meant to
409// the SecretBuffer, if one is set. In secret mode ev.Text stays nil.
410func (k *keyboard) translate(evdev, sym uint32, repeat bool, ev *KeyEvent) (textKind, error) {
411 ev.Keycode, ev.Keysym, ev.Pressed, ev.Repeat = evdev, sym, true, repeat
412 ev.Modifiers, ev.Group = k.mods, k.group
413 k.changed = false
414 if k.secret != nil {
415 k.w = secretWork{evdev: evdev, sym: sym, mods: ev.Modifiers, repeat: repeat}
416 secret.Do(k.secretFn)
417 kind, changed, err := k.w.kind, k.w.changed, k.w.err
418 k.w = secretWork{} // the key identity must not stay in the work area
419 k.changed = changed
420 if kind == kindText || kind == kindPending {
421 // Text keys are not identified: the keycode and keysym would
422 // reveal the secret character.
423 ev.Keycode, ev.Keysym, ev.Secret = 0, 0, true
424 }
425 return kind, err
426 }
427 n, err := k.text(evdev, sym, repeat)
428 if err != nil {
429 return kindNone, err
430 }
431 if n > 0 {
432 ev.Text = k.scratch[:n:n]
433 }
434 return kindNone, nil
435}
436
437// doSecret translates one key into the SecretBuffer. It is the only code that
438// handles secret text, so it runs under runtime/secret when available.
439func (k *keyboard) doSecret() {
440 w := &k.w
441 defer clear(k.scratch[:])
442 switch {
443 case w.sym == raw.XKB_KEY_BackSpace:
444 w.kind = kindBackspace
445 k.resetCompose()
446 w.changed = k.secret.backspace()
447 case w.sym == raw.XKB_KEY_Return || w.sym == raw.XKB_KEY_KP_Enter || w.sym == raw.XKB_KEY_Escape:
448 k.resetCompose() // controls are delivered as keys only
449 case w.mods&ModCtrl != 0:
450 // Other Control combinations are neither secret text nor ours to handle.
451 default:
452 n, err := k.text(w.evdev, w.sym, w.repeat)
453 if err != nil {
454 w.err = err
455 return
456 }
457 switch {
458 case classifySecretText(k.scratch[:n]):
459 w.kind = kindText
460 w.changed = k.secret.appendText(k.scratch[:n])
461 case k.composeUsed || k.composePending || isDeadKey(w.sym):
462 w.kind = kindPending // dead and compose keys name an accent, so hide them too
463 }
464 }
465}
466
467func (k *keyboard) resetCompose() {
468 if k.compose != nil {
469 _ = k.compose.Reset()
470 }
471 k.composePending = false
472}
473
474// text writes the text of a key into scratch and returns its length. Compose
475// is fed only by physical presses: a repeat neither advances a sequence nor
476// replays composed text.
477func (k *keyboard) text(evdev, sym uint32, repeat bool) (int, error) {
478 k.composeUsed = false
479 if k.compose != nil && !repeat {
480 if _, err := k.compose.Feed(sym); err != nil {
481 return 0, err
482 }
483 status, err := k.compose.Status()
484 if err != nil {
485 return 0, err
486 }
487 k.composeUsed = status == raw.XKB_COMPOSE_COMPOSING || status == raw.XKB_COMPOSE_CANCELLED || status == raw.XKB_COMPOSE_COMPOSED
488 switch status {
489 case raw.XKB_COMPOSE_COMPOSING:
490 k.composePending = true
491 return 0, nil
492 case raw.XKB_COMPOSE_CANCELLED:
493 k.resetCompose()
494 return 0, nil
495 case raw.XKB_COMPOSE_COMPOSED:
496 n, err := k.compose.UTF8Into(k.scratch[:])
497 k.resetCompose()
498 return shortAsEmpty(n, err)
499 default:
500 k.composePending = false
501 }
502 }
503 if k.composePending {
504 return 0, nil
505 }
506 n, err := k.state.UTF8Into(xkb.WaylandKeycode(evdev), k.scratch[:])
507 return shortAsEmpty(n, err)
508}
509
510// shortAsEmpty maps a too-long text to no text; xkb already wiped the buffer.
511func shortAsEmpty(n int, err error) (int, error) {
512 if errors.Is(err, io.ErrShortBuffer) {
513 return 0, nil
514 }
515 return n, err
516}
517
518// Modifier, dead and compose keys never repeat. The compositor provides rate
519// and delay; its keymap is authoritative for text and layout.
520func repeatable(sym uint32) bool {
521 return !isModifierKey(sym) && !isDeadKey(sym)
522}
523
524func isModifierKey(sym uint32) bool {
525 switch sym {
526 case raw.XKB_KEY_Shift_L, raw.XKB_KEY_Shift_R, raw.XKB_KEY_Control_L, raw.XKB_KEY_Control_R,
527 raw.XKB_KEY_Alt_L, raw.XKB_KEY_Alt_R, raw.XKB_KEY_Super_L, raw.XKB_KEY_Super_R,
528 raw.XKB_KEY_Caps_Lock, raw.XKB_KEY_Num_Lock:
529 return true
530 }
531 return false
532}
533
534// isDeadKey reports dead keys and the compose key, which start a sequence.
535func isDeadKey(sym uint32) bool {
536 return sym == raw.XKB_KEY_Multi_key || sym >= raw.XKB_KEY_dead_grave && sym <= raw.XKB_KEY_dead_currency
537}