v0.2.0 13c6a90

security_test.go RawHistoryFolder

1package xkb
2
3import (
4	"testing"
5
6	"github.com/bnema/neferwl/internal/ports"
7)
8
9func secureKey(t *testing.T, k *Keymap, code uint32, down bool, state ports.SecurityState) ports.KeyEvent {
10	t.Helper()
11	ev, deliver, err := k.KeySecure(code, down, 123, state)
12	if err != nil || !deliver {
13		t.Fatalf("key %d down=%v: deliver=%v err=%v", code, down, deliver, err)
14	}
15	return ev
16}
17
18func suppressedKey(t *testing.T, k *Keymap, code uint32, down bool, state ports.SecurityState) {
19	t.Helper()
20	ev, deliver, err := k.KeySecure(code, down, 123, state)
21	if err != nil || deliver || ev != (ports.KeyEvent{}) {
22		t.Fatalf("inherited key %d down=%v leaked: %+v deliver=%v err=%v", code, down, ev, deliver, err)
23	}
24}
25
26func TestSecurityEpochQuarantinesHeldKeysAcrossAcquireAndRelease(t *testing.T) {
27	k := newTest(t, "us")
28	initial := ports.SecurityState{}
29	locked := ports.SecurityState{Generation: 1, Protected: true}
30	unlocked := ports.SecurityState{Generation: 2}
31	for _, code := range []uint32{125, 42, 29, 56, 30} {
32		secureKey(t, k, code, true, initial)
33	}
34	for _, code := range []uint32{125, 42, 29, 56, 30} {
35		suppressedKey(t, k, code, true, locked) // repeats cannot reintroduce held modifiers
36	}
37	if ev := secureKey(t, k, 16, true, locked); ev.Keysym != "q" || ev.Mods != 0 || ev.State != (ports.ModState{}) {
38		t.Fatalf("password inherited native state: %+v", ev)
39	}
40	secureKey(t, k, 16, false, locked)
41	secureKey(t, k, 54, true, locked) // password-era right Shift
42	if ev := secureKey(t, k, 16, true, unlocked); ev.Keysym != "q" || ev.Mods != 0 || ev.State != (ports.ModState{}) {
43		t.Fatalf("desktop inherited password/native state: %+v", ev)
44	}
45	secureKey(t, k, 16, false, unlocked)
46	for _, code := range []uint32{30, 56, 29, 42, 125, 54} {
47		suppressedKey(t, k, code, false, unlocked)
48	}
49	secureKey(t, k, 125, true, unlocked)
50	if ev := secureKey(t, k, 16, true, unlocked); ev.Mods&ports.ModSuper == 0 || ev.State.Depressed == 0 {
51		t.Fatalf("fresh desktop Super broken: %+v", ev)
52	}
53}
54
55func TestSecurityEpochResetsLocksButFreshCapsAndNumLockWork(t *testing.T) {
56	k := newTest(t, "us")
57	initial := ports.SecurityState{}
58	locked := ports.SecurityState{Generation: 1, Protected: true}
59	for _, code := range []uint32{58, 69} {
60		secureKey(t, k, code, true, initial)
61		secureKey(t, k, code, false, initial)
62	}
63	if ev := secureKey(t, k, 30, true, initial); ev.Keysym != "A" || ev.State.Locked == 0 {
64		t.Fatalf("baseline caps: %+v", ev)
65	}
66	secureKey(t, k, 30, false, initial)
67	if ev := secureKey(t, k, 30, true, locked); ev.Keysym != "a" || ev.State.Locked != 0 {
68		t.Fatalf("old locks crossed epoch: %+v", ev)
69	}
70	secureKey(t, k, 30, false, locked)
71	secureKey(t, k, 58, true, locked)
72	secureKey(t, k, 58, false, locked)
73	if ev := secureKey(t, k, 30, true, locked); ev.Keysym != "A" || ev.State.Locked == 0 {
74		t.Fatalf("fresh protected caps broken: %+v", ev)
75	}
76	secureKey(t, k, 30, false, locked)
77	secureKey(t, k, 69, true, locked)
78	secureKey(t, k, 69, false, locked)
79	if ev := secureKey(t, k, 79, true, locked); ev.Keysym != "KP_1" || ev.State.Locked == 0 {
80		t.Fatalf("fresh protected numlock broken: %+v", ev)
81	}
82}
83
84func TestSecurityEpochUsesActualRemappedModifiers(t *testing.T) {
85	k, err := New(RMLVO{Layout: "us", Options: "ctrl:swapcaps"})
86	if err != nil {
87		t.Fatal(err)
88	}
89	defer k.Close()
90	initial, locked := ports.SecurityState{}, ports.SecurityState{Generation: 1, Protected: true}
91	secureKey(t, k, 58, true, initial) // physical Caps is Control in this map
92	if ev := secureKey(t, k, 16, true, locked); ev.Mods != 0 || ev.State != (ports.ModState{}) {
93		t.Fatalf("remapped inherited Control leaked: %+v", ev)
94	}
95	secureKey(t, k, 16, false, locked)
96	suppressedKey(t, k, 58, false, locked)
97	secureKey(t, k, 58, true, locked)
98	if ev := secureKey(t, k, 16, true, locked); ev.Mods&ports.ModCtrl == 0 || ev.State.Depressed == 0 {
99		t.Fatalf("fresh remapped Control broken: %+v", ev)
100	}
101	secureKey(t, k, 16, false, locked)
102	secureKey(t, k, 58, false, locked)
103	secureKey(t, k, 29, true, locked) // physical Control is Caps in this map
104	secureKey(t, k, 29, false, locked)
105	if ev := secureKey(t, k, 30, true, locked); ev.Keysym != "A" || ev.State.Locked == 0 {
106		t.Fatalf("fresh remapped Caps broken: %+v", ev)
107	}
108}
109
110func TestSecurityEpochAltGrAndAltReleaseOrder(t *testing.T) {
111	for _, releases := range [][]uint32{{100, 56}, {56, 100}} {
112		k := newTest(t, "fr")
113		locked := ports.SecurityState{Generation: 1, Protected: true}
114		secureKey(t, k, 100, true, locked) // ISO_Level3_Shift, not guessed Alt
115		secureKey(t, k, 56, true, locked)
116		if ev := secureKey(t, k, 11, true, locked); ev.Keysym != "at" || ev.Mods&ports.ModAlt == 0 || ev.State.Depressed == 0 {
117			t.Fatalf("fresh AltGr+Alt not mapped by XKB: %+v", ev)
118		}
119		secureKey(t, k, 11, false, locked)
120		for _, code := range releases {
121			secureKey(t, k, code, false, locked)
122		}
123		if ev := secureKey(t, k, 16, true, locked); ev.Keysym != "a" || ev.Mods != 0 || ev.State.Depressed != 0 {
124			t.Fatalf("release order left modifiers: %+v", ev)
125		}
126	}
127}
128
129func TestKeymapReplacementRetainsQuarantine(t *testing.T) {
130	previous := newTest(t, "us")
131	locked := ports.SecurityState{Generation: 1, Protected: true}
132	secureKey(t, previous, 125, true, locked)
133	next := newTest(t, "fr")
134	next.QuarantineFrom(previous)
135	suppressedKey(t, next, 125, true, locked)
136	if ev := secureKey(t, next, 16, true, locked); ev.Keysym != "a" || ev.Mods != 0 || ev.State.Depressed != 0 {
137		t.Fatalf("replacement replayed old held state: %+v", ev)
138	}
139	suppressedKey(t, next, 125, false, locked)
140	secureKey(t, next, 125, true, locked)
141	if ev := secureKey(t, next, 30, true, locked); ev.Mods&ports.ModSuper == 0 {
142		t.Fatalf("fresh key after replacement broken: %+v", ev)
143	}
144}
145
146func TestSecurityStateChangeResetsAltGrAndAltWithoutMaskInference(t *testing.T) {
147	k := newTest(t, "fr")
148	initial := ports.SecurityState{}
149	locked := ports.SecurityState{Generation: 1, Protected: true}
150	secureKey(t, k, 100, true, initial)
151	secureKey(t, k, 56, true, initial)
152	if ev := secureKey(t, k, 11, true, locked); ev.Keysym != "agrave" || ev.Mods != 0 || ev.State != (ports.ModState{}) {
153		t.Fatalf("inherited AltGr/Alt crossed acquisition: %+v", ev)
154	}
155	secureKey(t, k, 11, false, locked)
156	suppressedKey(t, k, 56, false, locked)
157	suppressedKey(t, k, 100, false, locked)
158	secureKey(t, k, 100, true, locked)
159	// Full state comparison is conservative even if a caller changes the
160	// protection bit without advancing the generation.
161	changed := locked
162	changed.Protected = false
163	if ev := secureKey(t, k, 11, true, changed); ev.Keysym != "agrave" || ev.Mods != 0 || ev.State != (ports.ModState{}) {
164		t.Fatalf("state change missed native reset: %+v", ev)
165	}
166	suppressedKey(t, k, 100, false, changed)
167}

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