v0.3.0 cf370a0

input_test.go RawHistoryFolder

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

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