1// Package keyboard interprets Wayland evdev key events on the UI loop.
2package keyboard
3
4import (
5 "errors"
6 "os"
7 "time"
8
9 xkb "github.com/bnema/purego-xkbcommon"
10 "github.com/bnema/purego-xkbcommon/raw"
11)
12
13type Key struct {
14 Physical uint32
15 Logical uint32
16 Name, Text string
17 Pressed, Repeat bool
18 Shift, Ctrl bool
19 Group uint32
20}
21type Keyboard struct {
22 context *xkb.Context
23 keymap *xkb.Keymap
24 state *xkb.State
25 compose *xkb.ComposeState
26 held map[uint32]Key
27 secretHeld map[uint32]SecretKey
28 secretMode bool
29 rate int
30 delay time.Duration
31 next time.Time
32 repeating uint32
33 focus bool
34 composePending bool
35}
36
37func New(locale string) (*Keyboard, error) {
38 c, err := xkb.NewContext()
39 if err != nil {
40 return nil, err
41 }
42 k := &Keyboard{context: c, held: make(map[uint32]Key), focus: true}
43 if locale != "" {
44 t, e := c.NewComposeTable(locale)
45 if e != nil {
46 c.Close()
47 return nil, e
48 }
49 k.compose, e = t.NewState()
50 t.Close()
51 if e != nil {
52 c.Close()
53 return nil, e
54 }
55 }
56 return k, nil
57}
58func (k *Keyboard) Close() {
59 k.Focus(false)
60 if k.compose != nil {
61 k.compose.Close()
62 }
63 if k.state != nil {
64 k.state.Close()
65 }
66 if k.keymap != nil {
67 k.keymap.Close()
68 }
69 if k.context != nil {
70 k.context.Close()
71 }
72}
73
74// ReplaceFD consumes the received FD even on failure; the previous keymap remains valid on failure.
75func (k *Keyboard) ReplaceFD(fd, size int) error {
76 if fd < 0 {
77 return errors.New("keyboard: invalid fd")
78 }
79 file := os.NewFile(uintptr(fd), "keymap")
80 if file == nil {
81 return errors.New("keyboard: invalid fd")
82 }
83 defer file.Close()
84 km, err := k.context.NewKeymapFD(fd, size)
85 if err != nil {
86 return err
87 }
88 return k.replace(km)
89}
90func (k *Keyboard) Rules(layout, options string) error {
91 km, err := k.context.NewKeymapRules("evdev", "pc105", layout, "", options)
92 if err != nil {
93 return err
94 }
95 return k.replace(km)
96}
97func (k *Keyboard) replace(km *xkb.Keymap) error {
98 s, err := km.NewState()
99 if err != nil {
100 km.Close()
101 return err
102 }
103 k.Focus(false)
104 if k.state != nil {
105 k.state.Close()
106 }
107 if k.keymap != nil {
108 k.keymap.Close()
109 }
110 k.keymap = km
111 k.state = s
112 k.Focus(true)
113 return nil
114}
115func (k *Keyboard) Mask(depressed, latched, locked, groupDepressed, groupLatched, groupLocked uint32) error {
116 if k.state == nil {
117 return errors.New("keyboard: no keymap")
118 }
119 _, err := k.state.UpdateMask(depressed, latched, locked, groupDepressed, groupLatched, groupLocked)
120 return err
121}
122func (k *Keyboard) Focus(on bool) {
123 k.focus = on
124 k.held = make(map[uint32]Key)
125 clear(k.secretHeld)
126 k.secretMode = false
127 k.repeating = 0
128 k.next = time.Time{}
129 if k.compose != nil {
130 k.compose.Reset()
131 }
132 k.composePending = false
133}
134func (k *Keyboard) RepeatInfo(rate int, delay time.Duration) {
135 k.rate = rate
136 k.delay = delay
137 if rate <= 0 {
138 k.repeating = 0
139 }
140}
141func (k *Keyboard) Event(evdev uint32, pressed bool, now time.Time) (Key, error) {
142 if k.secretMode {
143 k.Focus(k.focus)
144 }
145 if !pressed {
146 key := k.held[evdev]
147 delete(k.held, evdev)
148 if k.repeating == evdev {
149 k.repeating = 0
150 }
151 key.Pressed = false
152 key.Text = ""
153 return key, nil
154 }
155 if !k.focus || k.state == nil {
156 return Key{}, nil
157 }
158 code := xkb.WaylandKeycode(evdev)
159 sym, err := k.state.KeySym(code)
160 if err != nil {
161 return Key{}, err
162 }
163 text, err := k.state.UTF8(code)
164 if err != nil {
165 return Key{}, err
166 }
167 name, _ := xkb.KeysymName(sym)
168 group, _ := k.state.Layout()
169 key := Key{Physical: evdev, Logical: sym, Name: name, Text: text, Pressed: true, Group: group}
170 if key.Shift, key.Ctrl, err = k.modifiers(); err != nil {
171 return Key{}, err
172 }
173 if k.compose != nil {
174 if _, err = k.compose.Feed(sym); err != nil {
175 return Key{}, err
176 }
177 status, _ := k.compose.Status()
178 switch status {
179 case raw.XKB_COMPOSE_COMPOSING, raw.XKB_COMPOSE_CANCELLED:
180 key.Text = ""
181 k.composePending = status == raw.XKB_COMPOSE_COMPOSING
182 if status == raw.XKB_COMPOSE_CANCELLED {
183 k.compose.Reset()
184 }
185 case raw.XKB_COMPOSE_COMPOSED:
186 key.Text, err = k.compose.UTF8()
187 k.compose.Reset()
188 k.composePending = false
189 if err != nil {
190 return Key{}, err
191 }
192 default:
193 k.composePending = false
194 }
195 }
196 k.held[evdev] = key
197 if k.rate > 0 && repeatable(sym) {
198 k.repeating = evdev
199 k.next = now.Add(k.delay)
200 }
201 return key, nil
202}
203
204// Modifier, dead and compose keys never repeat. The compositor provides rate/delay;
205// its keymap is authoritative for text and layout.
206func repeatable(sym uint32) bool {
207 switch sym {
208 case raw.XKB_KEY_Shift_L, raw.XKB_KEY_Shift_R, raw.XKB_KEY_Control_L, raw.XKB_KEY_Control_R,
209 raw.XKB_KEY_Alt_L, raw.XKB_KEY_Alt_R, raw.XKB_KEY_Super_L, raw.XKB_KEY_Super_R,
210 raw.XKB_KEY_Caps_Lock, raw.XKB_KEY_Num_Lock, raw.XKB_KEY_Multi_key:
211 return false
212 }
213 return sym < raw.XKB_KEY_dead_grave || sym > raw.XKB_KEY_dead_currency
214}
215
216// Tick returns at most one repeat for a due deadline, without accumulating missed ticks.
217// It resolves the held physical key against the current modifier/group mask.
218// Compose is fed only by physical presses: synthetic repeats neither advance a
219// compose sequence nor replay the press-time composed text.
220func (k *Keyboard) Tick(now time.Time) (Key, bool) {
221 if k.secretMode || !k.focus || k.rate <= 0 || k.repeating == 0 || now.Before(k.next) {
222 return Key{}, false
223 }
224 physical := k.repeating
225 if _, ok := k.held[physical]; !ok {
226 return Key{}, false
227 }
228 code := xkb.WaylandKeycode(physical)
229 sym, err := k.state.KeySym(code)
230 if err != nil || !repeatable(sym) {
231 k.repeating = 0
232 return Key{}, false
233 }
234 text, err := k.state.UTF8(code)
235 if err != nil {
236 k.repeating = 0
237 return Key{}, false
238 }
239 group, err := k.state.Layout()
240 if err != nil {
241 k.repeating = 0
242 return Key{}, false
243 }
244 name, _ := xkb.KeysymName(sym)
245 if k.composePending {
246 text = ""
247 }
248 key := Key{Physical: physical, Logical: sym, Name: name, Text: text, Pressed: true, Repeat: true, Group: group}
249 // Repeats carry the current modifiers so e.g. held Shift+arrow keeps extending.
250 if key.Shift, key.Ctrl, err = k.modifiers(); err != nil {
251 k.repeating = 0
252 return Key{}, false
253 }
254 k.next = now.Add(time.Second / time.Duration(k.rate))
255 return key, true
256}
257
258// modifiers reports whether Shift and Control are active in the current state.
259func (k *Keyboard) modifiers() (shift, ctrl bool, err error) {
260 mods, err := k.state.Mods()
261 if err != nil {
262 return false, false, err
263 }
264 if idx, e := k.keymap.ModIndex("Shift"); e == nil && idx < 32 {
265 shift = mods&(1<<idx) != 0
266 }
267 if idx, e := k.keymap.ModIndex("Control"); e == nil && idx < 32 {
268 ctrl = mods&(1<<idx) != 0
269 }
270 return shift, ctrl, nil
271}
272func (k *Keyboard) NextRepeat() (time.Time, bool) { return k.next, k.repeating != 0 }