v0.8.0 446d1e3

input_test.go RawHistoryFolder

1package headlessinput
2
3import (
4	"context"
5	"strings"
6	"testing"
7
8	"github.com/bnema/neferwl/internal/adapters/logging"
9	"github.com/bnema/neferwl/internal/adapters/xkb"
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13func TestScript(t *testing.T) {
14	for _, tt := range []struct {
15		line  string
16		names []string
17		codes []uint32
18	}{
19		{"type Hi!", []string{"Shift_L", "H", "H", "Shift_L", "i", "i", "Shift_L", "exclam", "exclam", "Shift_L"}, nil},
20		{"key Shift+A", []string{"Shift_L", "A", "A", "Shift_L"}, []uint32{42, 30, 30, 42}},
21		{"key Super+Return", []string{"Super_L", "Return", "Return", "Super_L"}, []uint32{125, 28, 28, 125}},
22	} {
23		t.Run(tt.line, func(t *testing.T) {
24			km, err := xkb.New(xkb.RMLVO{Layout: "us"})
25			if err != nil {
26				if strings.Contains(err.Error(), "libxkbcommon.so.0") {
27					t.Skip(err)
28				}
29				t.Fatal(err)
30			}
31			script := make(chan string, 1)
32			script <- tt.line
33			close(script)
34			input := make(chan ports.InputEvent, 32)
35			if err := Run(context.Background(), km, nil, script, input, nil, nil, logging.For(context.Background(), "input")); err != nil {
36				t.Fatal(err)
37			}
38			if len(input) != len(tt.names) {
39				t.Fatalf("events=%d want %d", len(input), len(tt.names))
40			}
41			for i, name := range tt.names {
42				ev := (<-input).(ports.KeyEvent)
43				if ev.Keysym != name {
44					t.Errorf("event %d: %s want %s", i, ev.Keysym, name)
45				}
46				if tt.codes != nil && ev.Keycode != tt.codes[i] {
47					t.Errorf("code %d: %d", i, ev.Keycode)
48				}
49				if tt.line == "key Super+Return" && i == 1 && ev.Mods&ports.ModSuper == 0 {
50					t.Error("Return missing Super")
51				}
52				if tt.line == "key Super+Return" && i == 3 && ev.Mods&ports.ModSuper != 0 {
53					t.Error("Super not released")
54				}
55			}
56		})
57	}
58}
59
60func TestPointerScript(t *testing.T) {
61	km, err := xkb.New(xkb.RMLVO{Layout: "us"})
62	if err != nil {
63		t.Skip(err)
64	}
65	script := make(chan string, 8)
66	for _, line := range []string{"move 12.5 45", "click", "down right", "up right", "swipe sideways", "swipe left", "swipe4 up"} {
67		script <- line
68	}
69	close(script)
70	input := make(chan ports.InputEvent, 32)
71	if err := Run(context.Background(), km, nil, script, input, nil, nil, logging.For(context.Background(), "input")); err != nil {
72		t.Fatal(err)
73	}
74	if len(input) != 5+2*(swipeSteps+2) {
75		t.Fatalf("events: %d", len(input))
76	}
77	m := (<-input).(ports.PointerMotion)
78	if m.X != 12.5 || m.Y != 45 {
79		t.Fatal(m)
80	}
81	for _, want := range []ports.PointerButton{{Button: 0x110, Pressed: true}, {Button: 0x110}, {Button: 0x111, Pressed: true}, {Button: 0x111}} {
82		got := (<-input).(ports.PointerButton)
83		if got.Button != want.Button || got.Pressed != want.Pressed {
84			t.Fatalf("got %+v want %+v", got, want)
85		}
86	}
87	begin := (<-input).(ports.SwipeBegin)
88	if begin.Fingers != 3 {
89		t.Fatal(begin)
90	}
91	last := begin.Time
92	for range swipeSteps {
93		u := (<-input).(ports.SwipeUpdate)
94		if u.DX >= 0 || u.DY != 0 || u.Time <= last {
95			t.Fatalf("update %+v after %v", u, last)
96		}
97		last = u.Time
98	}
99	if e := (<-input).(ports.SwipeEnd); e.Cancelled || e.Time != last {
100		t.Fatal(e)
101	}
102	if b := (<-input).(ports.SwipeBegin); b.Fingers != 4 {
103		t.Fatal(b)
104	}
105	if u := (<-input).(ports.SwipeUpdate); u.DY >= 0 || u.DX != 0 {
106		t.Fatal(u)
107	}
108}
109
110// The cursor is placed in target pixels: after the output transform.
111func TestMoveCursorTarget(t *testing.T) {
112	km, err := xkb.New(xkb.RMLVO{Layout: "us"})
113	if err != nil {
114		t.Skip(err)
115	}
116	layouts := make(chan ports.Layout)
117	script := make(chan string)
118	input := make(chan ports.InputEvent, 4)
119	type pos struct {
120		out  string
121		x, y float64
122	}
123	moved := make(chan pos, 1)
124	done := make(chan error, 1)
125	go func() {
126		done <- Run(context.Background(), km, nil, script, input, layouts, func(o string, x, y float64) { moved <- pos{o, x, y} }, logging.For(context.Background(), "input"))
127	}()
128	// Unbuffered: the layout is taken before the move is read.
129	layouts <- ports.Layout{{Info: ports.OutputInfo{Name: "A", Width: 200, Height: 100}, Scale: 1, Transform: 1, Width: 100, Height: 200}}
130	script <- "move 10 20"
131	if got, want := <-moved, (pos{"A", 20, 90}); got != want {
132		t.Fatalf("got %+v want %+v", got, want)
133	}
134	close(script)
135	if err := <-done; err != nil {
136		t.Fatal(err)
137	}
138}

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 23

Tags 10

↑↓ moveenter openesc close