v0.4.0 b6be3a8

keycodes.go RawHistoryFolder

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' }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close