1// Package xkb provides a cgo-free libxkbcommon keymap and keyboard state.
2package xkb
3
4import (
5 "fmt"
6 "runtime"
7 "time"
8 "unsafe"
9
10 "github.com/bnema/neferwl/internal/ports"
11 "github.com/bnema/purego"
12)
13
14const (
15 modsDepressed = 1 << iota
16 modsLatched
17 modsLocked
18 modsEffective
19 layoutEffective = 1 << 7
20)
21
22type RMLVO struct{ Rules, Model, Layout, Variant, Options string }
23
24// Keymap owns its native context, keymap and state. It belongs to one input goroutine.
25type Keymap struct {
26 ctx, mapPtr, state uintptr
27 lib, libc uintptr
28 contextUnref func(uintptr)
29 keymapUnref func(uintptr)
30 stateUnref func(uintptr)
31 stateNew func(uintptr) uintptr
32 // Secure tracking belongs to the same producer goroutine as native state.
33 epoch ports.SecurityState
34 epochSet bool
35 held, quarantined map[uint32]bool
36 keymapString func(uintptr, uint32) unsafe.Pointer
37 free func(uintptr)
38 getSym func(uintptr, uint32) uint32
39 // Buffers go to C as typed pointers: they escape to the heap, where a
40 // stack growth during the call cannot move them under C.
41 symName func(uint32, *byte, uintptr) int32
42 updateKey func(uintptr, uint32, uint32) uint32
43 serializeMods func(uintptr, uint32) uint32
44 serializeLayout func(uintptr, uint32) uint32
45 modActive func(uintptr, *byte, uint32) int32
46 minCode func(uintptr) uint32
47 maxCode func(uintptr) uint32
48 symsLevel func(uintptr, uint32, uint32, uint32, *unsafe.Pointer) int32
49 fromName func(*byte, uint32) uint32
50}
51
52type ruleNames struct{ rules, model, layout, variant, options uintptr }
53
54func New(names RMLVO) (_ *Keymap, err error) {
55 k := &Keymap{}
56 k.lib, err = purego.Dlopen("libxkbcommon.so.0", purego.RTLD_NOW|purego.RTLD_LOCAL)
57 if err != nil {
58 return nil, err
59 }
60 defer func() {
61 if err != nil {
62 k.Close()
63 }
64 }()
65 k.libc, err = purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_LOCAL)
66 if err != nil {
67 return nil, err
68 }
69 var contextNew func(uint32) uintptr
70 var keymapNew func(uintptr, *ruleNames, uint32) uintptr
71 bind := func(fn any, name string) { purego.RegisterLibFunc(fn, k.lib, "xkb_"+name) }
72 bind(&contextNew, "context_new")
73 bind(&k.contextUnref, "context_unref")
74 bind(&keymapNew, "keymap_new_from_names")
75 bind(&k.keymapUnref, "keymap_unref")
76 bind(&k.stateNew, "state_new")
77 bind(&k.stateUnref, "state_unref")
78 bind(&k.keymapString, "keymap_get_as_string")
79 bind(&k.getSym, "state_key_get_one_sym")
80 bind(&k.symName, "keysym_get_name")
81 bind(&k.updateKey, "state_update_key")
82 bind(&k.serializeMods, "state_serialize_mods")
83 bind(&k.serializeLayout, "state_serialize_layout")
84 bind(&k.modActive, "state_mod_name_is_active")
85 bind(&k.minCode, "keymap_min_keycode")
86 bind(&k.maxCode, "keymap_max_keycode")
87 bind(&k.symsLevel, "keymap_key_get_syms_by_level")
88 bind(&k.fromName, "keysym_from_name")
89 purego.RegisterLibFunc(&k.free, k.libc, "free")
90 k.ctx = contextNew(0)
91 if k.ctx == 0 {
92 return nil, fmt.Errorf("xkb_context_new failed")
93 }
94 values := [...]string{names.Rules, names.Model, names.Layout, names.Variant, names.Options}
95 var buffers [5][]byte
96 var pins runtime.Pinner
97 defer pins.Unpin()
98 var pointers [5]uintptr
99 for i, v := range values {
100 if v == "" {
101 continue
102 }
103 buffers[i] = append([]byte(v), 0)
104 pins.Pin(&buffers[i][0])
105 pointers[i] = uintptr(unsafe.Pointer(&buffers[i][0]))
106 }
107 r := ruleNames{pointers[0], pointers[1], pointers[2], pointers[3], pointers[4]}
108 pins.Pin(&r)
109 k.mapPtr = keymapNew(k.ctx, &r, 0)
110 runtime.KeepAlive(buffers)
111 if k.mapPtr == 0 {
112 return nil, fmt.Errorf("xkb_keymap_new_from_names failed")
113 }
114 k.state = k.stateNew(k.mapPtr)
115 if k.state == 0 {
116 return nil, fmt.Errorf("xkb_state_new failed")
117 }
118 return k, nil
119}
120
121func (k *Keymap) String() string {
122 ptr := k.keymapString(k.mapPtr, 1)
123 if ptr == nil {
124 return ""
125 }
126 defer k.free(uintptr(ptr))
127 n := 0
128 for *(*byte)(unsafe.Add(ptr, n)) != 0 {
129 n++
130 }
131 return string(unsafe.Slice((*byte)(ptr), n))
132}
133
134func (k *Keymap) Key(evdevCode uint32, pressed bool, t time.Duration) ports.KeyEvent {
135 code := evdevCode + 8
136 sym := k.getSym(k.state, code)
137 var buf [64]byte
138 n := k.symName(sym, &buf[0], uintptr(len(buf)))
139 name := ""
140 if n > 0 && n < int32(len(buf)) {
141 name = string(buf[:n])
142 }
143 direction := uint32(0)
144 if pressed {
145 direction = 1
146 }
147 k.updateKey(k.state, code, direction)
148 event := ports.KeyEvent{Keysym: name, Pressed: pressed, Time: t, Keycode: evdevCode}
149 if pressed {
150 layout := k.serializeLayout(k.state, layoutEffective)
151 event.Base = k.levelName(code, layout, 0)
152 }
153 event.State = ports.ModState{
154 Depressed: k.serializeMods(k.state, modsDepressed), Latched: k.serializeMods(k.state, modsLatched),
155 Locked: k.serializeMods(k.state, modsLocked), Group: k.serializeLayout(k.state, layoutEffective),
156 }
157 for _, mod := range []struct {
158 name string
159 flag ports.Mods
160 }{{"Shift", ports.ModShift}, {"Control", ports.ModCtrl}, {"Mod1", ports.ModAlt}, {"Mod4", ports.ModSuper}} {
161 b := append([]byte(mod.name), 0)
162 if k.modActive(k.state, &b[0], modsEffective) > 0 {
163 event.Mods |= mod.flag
164 }
165 runtime.KeepAlive(b)
166 }
167 return event
168}
169
170// KeySecure resets native modifier/lock/layout state before translating in a
171// new security epoch. Keys physically held across a transition are suppressed
172// (including repeats and their eventual release), never replayed into XKB.
173// On reset failure no event is translated against the old native state.
174func (k *Keymap) KeySecure(code uint32, pressed bool, t time.Duration, epoch ports.SecurityState) (ports.KeyEvent, bool, error) {
175 if !k.epochSet || k.epoch != epoch {
176 next := k.stateNew(k.mapPtr)
177 if next == 0 {
178 // Even a failed translation must remember physical presses so a
179 // later successful reset cannot admit their repeats.
180 if k.held == nil {
181 k.held = make(map[uint32]bool)
182 }
183 if pressed {
184 k.held[code] = true
185 } else {
186 delete(k.held, code)
187 }
188 return ports.KeyEvent{}, false, fmt.Errorf("xkb_state_new failed during security reset")
189 }
190 old := k.state
191 k.state = next
192 k.stateUnref(old)
193 k.epoch, k.epochSet = epoch, true
194 if k.held == nil {
195 k.held = make(map[uint32]bool)
196 }
197 k.quarantined = make(map[uint32]bool, len(k.held))
198 for held := range k.held {
199 k.quarantined[held] = true
200 }
201 }
202 blocked := k.quarantined[code]
203 if pressed {
204 k.held[code] = true
205 } else {
206 delete(k.held, code)
207 delete(k.quarantined, code)
208 }
209 if blocked {
210 return ports.KeyEvent{}, false, nil
211 }
212 return k.Key(code, pressed, t), true, nil
213}
214
215// QuarantineFrom transfers physical held-key tracking when a producer replaces
216// its keymap. No old native state or key presses are replayed into the new map.
217// Call before closing previous; both maps belong to the calling goroutine.
218func (k *Keymap) QuarantineFrom(previous *Keymap) {
219 k.epoch, k.epochSet = previous.epoch, previous.epochSet
220 k.held = make(map[uint32]bool, len(previous.held))
221 k.quarantined = make(map[uint32]bool, len(previous.held))
222 for code := range previous.held {
223 k.held[code], k.quarantined[code] = true, true
224 }
225}
226
227// levelName is the first keysym name of a key at one shift level, or "".
228func (k *Keymap) levelName(code, layout, level uint32) string {
229 var ptr unsafe.Pointer
230 if k.symsLevel(k.mapPtr, code, layout, level, &ptr) < 1 {
231 return ""
232 }
233 var buf [64]byte
234 n := k.symName(*(*uint32)(ptr), &buf[0], uintptr(len(buf)))
235 if n <= 0 || n >= int32(len(buf)) {
236 return ""
237 }
238 return string(buf[:n])
239}
240
241func (k *Keymap) KeycodeFor(keysymName string) (evdev uint32, shift bool, ok bool) {
242 b := append([]byte(keysymName), 0)
243 sym := k.fromName(&b[0], 0)
244 runtime.KeepAlive(b)
245 if sym == 0 {
246 return 0, false, false
247 }
248 for code := k.minCode(k.mapPtr); code <= k.maxCode(k.mapPtr); code++ {
249 for level := uint32(0); level < 2; level++ {
250 var ptr unsafe.Pointer
251 count := k.symsLevel(k.mapPtr, code, 0, level, &ptr)
252 for i := int32(0); i < count; i++ {
253 if *(*uint32)(unsafe.Add(ptr, uintptr(i)*4)) == sym && code >= 8 {
254 return code - 8, level == 1, true
255 }
256 }
257 }
258 }
259 return 0, false, false
260}
261
262func (k *Keymap) Close() {
263 if k.state != 0 {
264 k.stateUnref(k.state)
265 k.state = 0
266 }
267 if k.mapPtr != 0 {
268 k.keymapUnref(k.mapPtr)
269 k.mapPtr = 0
270 }
271 if k.ctx != 0 {
272 k.contextUnref(k.ctx)
273 k.ctx = 0
274 }
275 if k.libc != 0 {
276 _ = purego.Dlclose(k.libc)
277 k.libc = 0
278 }
279 if k.lib != 0 {
280 _ = purego.Dlclose(k.lib)
281 k.lib = 0
282 }
283}