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