1package wayland
2
3import (
4 "strconv"
5 "strings"
6)
7
8// minKeycodeMax is the smallest keycode maximum sent to clients.
9// Xwayland (xkb/xkbUtils.c _XkbCopyClientMap, still in 24.1) clears
10// MAP_LENGTH-(max+1) entries starting at that same index of its 256-entry
11// key maps: with a maximum below 127 it writes past the end of the heap
12// block and aborts on the next realloc. Dictation tools (wtype-like)
13// send keymaps holding only the keys they type (e.g. 8..26). Keycodes up
14// to 255 are always valid, so the range is widened without changing any
15// key.
16const minKeycodeMax = 255
17
18// widenKeycodes raises the keycode maximum of a keymap's xkb_keycodes
19// section to minKeycodeMax. An explicit maximum below it is rewritten; a
20// section without one (its highest keycode is the maximum) gets one,
21// unless it includes other files or already has a keycode above it. It
22// returns the keymap and the former maximum (the highest keycode when
23// none was set), 0 when unchanged. Keywords are case-insensitive;
24// comments, strings and key names are skipped.
25func widenKeycodes(text string) (string, int) {
26 open, end := keycodesSection(text)
27 if open < 0 {
28 return text, 0
29 }
30 type span struct{ from, to int }
31 var low []span
32 maxSet, include := false, false
33 from, highest := 0, 0
34 lastName := false // the previous token was a <name>: "= N" is a keycode
35 for i := open + 1; i < end; {
36 if j := skipNoise(text, i); j != i {
37 lastName = text[i] == '<'
38 i = j
39 continue
40 }
41 c := text[i]
42 switch {
43 case isIdentStart(c):
44 j := i
45 for j < end && isIdent(text[j]) {
46 j++
47 }
48 word := text[i:j]
49 lastName = false
50 i = j
51 switch {
52 case strings.EqualFold(word, "include"), strings.EqualFold(word, "augment"), strings.EqualFold(word, "override"), strings.EqualFold(word, "replace"):
53 include = true
54 case strings.EqualFold(word, "maximum"):
55 k := skipBlank(text, j, end)
56 if k >= end || text[k] != '=' {
57 continue
58 }
59 k = skipBlank(text, k+1, end)
60 d := k
61 for d < end && text[d] >= '0' && text[d] <= '9' {
62 d++
63 }
64 n, err := strconv.Atoi(text[k:d])
65 if err != nil {
66 continue
67 }
68 maxSet = true
69 if n < minKeycodeMax {
70 low = append(low, span{k, d})
71 from = n
72 }
73 i = d
74 }
75 case c == '=' && lastName:
76 k := skipBlank(text, i+1, end)
77 d := k
78 for d < end && text[d] >= '0' && text[d] <= '9' {
79 d++
80 }
81 if n, err := strconv.Atoi(text[k:d]); err == nil {
82 highest = max(highest, n)
83 }
84 lastName = false
85 i = max(d, i+1)
86 default:
87 if c != ' ' && c != '\t' && c != '\n' && c != '\r' {
88 lastName = false
89 }
90 i++
91 }
92 }
93 if !maxSet {
94 if include || highest >= minKeycodeMax || highest == 0 {
95 return text, 0
96 }
97 return text[:open+1] + " maximum = " + strconv.Itoa(minKeycodeMax) + ";" + text[open+1:], highest
98 }
99 if len(low) == 0 {
100 return text, 0
101 }
102 var b strings.Builder
103 last := 0
104 for _, sp := range low {
105 b.WriteString(text[last:sp.from])
106 b.WriteString(strconv.Itoa(minKeycodeMax))
107 last = sp.to
108 }
109 b.WriteString(text[last:])
110 return b.String(), from
111}
112
113// keycodesSection returns the indexes of the braces of the first
114// xkb_keycodes section, -1 without one.
115func keycodesSection(text string) (open, end int) {
116 for i := 0; i < len(text); {
117 if j := skipNoise(text, i); j != i {
118 i = j
119 continue
120 }
121 if !isIdentStart(text[i]) {
122 i++
123 continue
124 }
125 j := i
126 for j < len(text) && isIdent(text[j]) {
127 j++
128 }
129 if !strings.EqualFold(text[i:j], "xkb_keycodes") {
130 i = j
131 continue
132 }
133 // The section's name may be a string; its body is the next brace.
134 for i = j; i < len(text) && text[i] != '{'; {
135 if k := skipNoise(text, i); k != i {
136 i = k
137 continue
138 }
139 if text[i] == ';' {
140 return -1, -1 // a declaration without a body
141 }
142 i++
143 }
144 if i >= len(text) {
145 return -1, -1
146 }
147 open, depth := i, 0
148 for i < len(text) {
149 if k := skipNoise(text, i); k != i {
150 i = k
151 continue
152 }
153 switch text[i] {
154 case '{':
155 depth++
156 case '}':
157 if depth--; depth == 0 {
158 return open, i
159 }
160 }
161 i++
162 }
163 return -1, -1
164 }
165 return -1, -1
166}
167
168// skipNoise returns the index after a comment (// or # to the end of the
169// line), a string or a <key name> starting at i, else i.
170func skipNoise(text string, i int) int {
171 switch {
172 case text[i] == '#', strings.HasPrefix(text[i:], "//"):
173 if n := strings.IndexByte(text[i:], '\n'); n >= 0 {
174 return i + n + 1
175 }
176 return len(text)
177 case text[i] == '"':
178 for j := i + 1; j < len(text); j++ {
179 switch text[j] {
180 case '\\':
181 j++
182 case '"':
183 return j + 1
184 }
185 }
186 return len(text)
187 case text[i] == '<':
188 if n := strings.IndexByte(text[i:], '>'); n >= 0 {
189 return i + n + 1
190 }
191 return len(text)
192 }
193 return i
194}
195
196// skipBlank skips spaces and comments from i, up to end.
197func skipBlank(text string, i, end int) int {
198 for i < end {
199 switch {
200 case text[i] == ' ' || text[i] == '\t' || text[i] == '\n' || text[i] == '\r':
201 i++
202 case text[i] == '#' || strings.HasPrefix(text[i:], "//"):
203 i = skipNoise(text, i)
204 default:
205 return i
206 }
207 }
208 return i
209}
210
211func isIdentStart(c byte) bool { return c == '_' || c|0x20 >= 'a' && c|0x20 <= 'z' }
212func isIdent(c byte) bool { return isIdentStart(c) || c >= '0' && c <= '9' }