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