1package keyboard
2
3import (
4 "errors"
5 "time"
6 "unicode/utf8"
7
8 xkb "github.com/bnema/purego-xkbcommon"
9 "github.com/bnema/purego-xkbcommon/raw"
10)
11
12// SecretKind is the limited editing vocabulary accepted by secret consumers.
13type SecretKind uint8
14
15const (
16 SecretNone SecretKind = iota
17 SecretBytes
18 SecretBackspace
19 SecretReturn
20 SecretEscape
21 SecretClear // Control+U
22)
23
24// SecretKey contains scalar metadata only, never names or text. Bytes are
25// supplied separately and exclusively within EventSecret/TickSecret callbacks.
26type SecretKey struct {
27 Physical, Logical uint32
28 Kind SecretKind
29 Pressed, Repeat bool
30}
31
32var errSecretCallback = errors.New("keyboard: secret callback is nil")
33
34// ClearSecret cancels secret-held keys, repeat and local compose. It retains
35// the compositor's current keymap and modifier mask. No text is cached here.
36func (k *Keyboard) ClearSecret() {
37 clear(k.secretHeld)
38 clear(k.held) // fail-closed even when secret entry fails from ordinary mode
39 k.repeating = 0
40 k.next = time.Time{}
41 k.composePending = false
42 if k.compose != nil {
43 _ = k.compose.Reset()
44 }
45}
46
47// EventSecret interprets a physical event without calling any string-producing
48// XKB API or populating Key/held. scratch length must be 1..512, including space
49// for native NUL (max payload 511). The callback is synchronous and must not
50// retain bytes or convert them to strings. scratch is valid only for that call;
51// this method clears it on entry and every exit, in addition to caller wiping.
52// Enter, Escape and Control+U are initial-press controls, never repeats.
53// Release callbacks carry no bytes and Pressed=false; consumers must act on
54// controls only when Pressed=true. Switching APIs cancels the other path.
55func (k *Keyboard) EventSecret(evdev uint32, pressed bool, now time.Time, scratch []byte, consume func(SecretKey, []byte)) error {
56 clear(scratch)
57 defer clear(scratch)
58 if len(scratch) < 1 || len(scratch) > xkb.MaxUTF8Buffer {
59 k.ClearSecret()
60 return xkb.ErrUTF8Buffer
61 }
62 if consume == nil {
63 k.ClearSecret()
64 return errSecretCallback
65 }
66 if !k.secretMode {
67 k.Focus(k.focus) // discard ordinary held text and compose on mode entry
68 k.secretMode = true
69 if k.secretHeld == nil {
70 k.secretHeld = make(map[uint32]SecretKey)
71 }
72 }
73 if !pressed {
74 key, ok := k.secretHeld[evdev]
75 delete(k.secretHeld, evdev)
76 if k.repeating == evdev {
77 k.repeating = 0
78 }
79 if ok && key.Kind != SecretNone {
80 key.Pressed = false
81 consume(key, nil)
82 }
83 return nil
84 }
85 if !k.focus || k.state == nil {
86 return nil
87 }
88 _, repeat := k.secretHeld[evdev]
89 key, n, err := k.secretKey(evdev, repeat, scratch)
90 if err != nil {
91 k.ClearSecret()
92 return err
93 }
94 k.secretHeld[evdev] = key
95 if key.Kind == SecretReturn || key.Kind == SecretEscape || key.Kind == SecretClear {
96 // Clear/submit/cancel must stop a previously held printable repeat
97 // before the consumer runs; releasing Control must not resurrect it.
98 k.repeating = 0
99 k.next = time.Time{}
100 }
101 if k.rate > 0 && secretRepeatable(key) {
102 k.repeating = evdev
103 k.next = now.Add(k.delay)
104 }
105 if key.Kind != SecretNone {
106 consume(key, scratch[:n:n])
107 }
108 return nil
109}
110
111func secretRepeatable(key SecretKey) bool {
112 return key.Kind == SecretBackspace || key.Kind == SecretBytes && repeatable(key.Logical)
113}
114
115func (k *Keyboard) secretKey(evdev uint32, repeat bool, scratch []byte) (SecretKey, int, error) {
116 code := xkb.WaylandKeycode(evdev)
117 sym, err := k.state.KeySym(code)
118 if err != nil {
119 return SecretKey{}, 0, err
120 }
121 key := SecretKey{Physical: evdev, Logical: sym, Pressed: true, Repeat: repeat}
122 _, ctrl, err := k.modifiers()
123 if err != nil {
124 return SecretKey{}, 0, err
125 }
126 switch {
127 case sym == raw.XKB_KEY_BackSpace:
128 key.Kind = SecretBackspace
129 case sym == raw.XKB_KEY_Return || sym == raw.XKB_KEY_KP_Enter:
130 key.Kind = SecretReturn
131 case sym == raw.XKB_KEY_Escape:
132 key.Kind = SecretEscape
133 case ctrl && (sym == raw.XKB_KEY_u || sym == raw.XKB_KEY_U):
134 key.Kind = SecretClear
135 }
136 if key.Kind != SecretNone {
137 if k.compose != nil {
138 _ = k.compose.Reset()
139 }
140 k.composePending = false
141 if repeat && key.Kind != SecretBackspace {
142 key.Kind = SecretNone
143 }
144 return key, 0, nil
145 }
146 // Other Control combinations are not secret bytes or application shortcuts.
147 if ctrl {
148 return key, 0, nil
149 }
150 if k.compose != nil && !repeat {
151 if _, err := k.compose.Feed(sym); err != nil {
152 return key, 0, err
153 }
154 status, err := k.compose.Status()
155 if err != nil {
156 return key, 0, err
157 }
158 switch status {
159 case raw.XKB_COMPOSE_COMPOSING:
160 k.composePending = true
161 return key, 0, nil
162 case raw.XKB_COMPOSE_CANCELLED:
163 _ = k.compose.Reset()
164 k.composePending = false
165 return key, 0, nil
166 case raw.XKB_COMPOSE_COMPOSED:
167 n, err := k.compose.UTF8Into(scratch)
168 _ = k.compose.Reset()
169 k.composePending = false
170 if err != nil {
171 return key, 0, err
172 }
173 return classifySecretBytes(key, scratch, n), n, nil
174 default:
175 k.composePending = false
176 }
177 }
178 if k.composePending {
179 return key, 0, nil
180 }
181 n, err := k.state.UTF8Into(code, scratch)
182 if err != nil {
183 return key, 0, err
184 }
185 return classifySecretBytes(key, scratch, n), n, nil
186}
187
188func classifySecretBytes(key SecretKey, scratch []byte, n int) SecretKey {
189 if n == 0 || !utf8.Valid(scratch[:n]) {
190 return key
191 }
192 for rest := scratch[:n]; len(rest) > 0; {
193 r, size := utf8.DecodeRune(rest)
194 if r < 0x20 || r == 0x7f {
195 return key
196 }
197 rest = rest[size:]
198 }
199 key.Kind = SecretBytes
200 return key
201}
202
203// TickSecret resolves repeat bytes using the current mask/group, not press-time
204// plaintext. Compose is never fed by repeats. It reports whether a callback ran.
205func (k *Keyboard) TickSecret(now time.Time, scratch []byte, consume func(SecretKey, []byte)) (bool, error) {
206 clear(scratch)
207 defer clear(scratch)
208 if len(scratch) < 1 || len(scratch) > xkb.MaxUTF8Buffer {
209 k.ClearSecret()
210 return false, xkb.ErrUTF8Buffer
211 }
212 if consume == nil {
213 k.ClearSecret()
214 return false, errSecretCallback
215 }
216 if !k.secretMode || !k.focus || k.rate <= 0 || k.repeating == 0 || now.Before(k.next) {
217 return false, nil
218 }
219 if _, ok := k.secretHeld[k.repeating]; !ok {
220 return false, nil
221 }
222 key, n, err := k.secretKey(k.repeating, true, scratch)
223 if err != nil {
224 k.ClearSecret()
225 return false, err
226 }
227 if !secretRepeatable(key) {
228 k.repeating = 0
229 return false, nil
230 }
231 k.next = now.Add(time.Second / time.Duration(k.rate))
232 consume(key, scratch[:n:n])
233 return true, nil
234}