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