input_region_test.go RawHistoryFolder

1package wayland
2
3import (
4	"fmt"
5	"testing"
6	"time"
7
8	"github.com/bnema/go-wayland-bindings/server/fifo"
9	"github.com/bnema/go-wayland-bindings/server/pointerconstraints"
10	"github.com/bnema/go-wayland-bindings/server/wayland"
11	"github.com/bnema/go-wayland-bindings/server/xdgshell"
12	"github.com/bnema/neferwl/internal/ports"
13	"golang.org/x/sys/unix"
14)
15
16func inputChanged(t *testing.T, events <-chan ports.ClientEvent) ports.InputRegionChanged {
17	t.Helper()
18	for {
19		if v, ok := waitEvent(t, events, 2*time.Second).(ports.InputRegionChanged); ok {
20			return v
21		}
22	}
23}
24
25func TestInputRegionCommitCopySubtractAndReset(t *testing.T) {
26	s, events, _, dir := lifecycleServer(t)
27	c := protocolClient(t, s, dir)
28	w, surf, _ := surfaceMapper(t, c, events)()
29	comp := bindProtocol(t, c, "wl_compositor")
30	reg := c.AllocateID()
31	registerProtocol(t, c, reg)
32	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, reg)
33	requestProtocol(t, c, reg, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
34	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, reg)
35	requestProtocol(t, c, reg, wayland.RegionRequestSubtract, int32(0), int32(0), int32(1), int32(1))
36	requestProtocol(t, c, reg, wayland.RegionRequestDestroy)
37	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
38	if got := inputChanged(t, events); got.ID != w.ID || len(got.Rects) != 1 || got.Rects[0] != (ports.Rect{W: 1, H: 1}) {
39		t.Fatalf("copied region: %+v", got)
40	}
41	empty := c.AllocateID()
42	registerProtocol(t, c, empty)
43	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, empty)
44	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, empty)
45	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
46	if got := inputChanged(t, events); got.All || len(got.Rects) != 0 {
47		t.Fatalf("empty region: %+v", got)
48	}
49	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, uint32(0))
50	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
51	if got := inputChanged(t, events); !got.All {
52		t.Fatalf("reset region: %+v", got)
53	}
54}
55
56func TestRegionSubtractSplits(t *testing.T) {
57	g := &region{}
58	g.Add(nil, 0, 0, 10, 10)
59	g.Subtract(nil, 2, 2, 6, 6)
60	if len(g.rects) != 4 || g.boxRect() != (ports.Rect{W: 10, H: 10}) {
61		t.Fatalf("subtract: %+v", g.rects)
62	}
63}
64
65func TestSubsurfaceInputRegionOwnCommitAndOffset(t *testing.T) {
66	s, events, _, dir := lifecycleServer(t)
67	c := protocolClient(t, s, dir)
68	w, root, _ := surfaceMapper(t, c, events)()
69	comp := bindProtocol(t, c, "wl_compositor")
70	subc := bindProtocol(t, c, "wl_subcompositor")
71	child := c.AllocateID()
72	registerProtocol(t, c, child)
73	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
74	sub := c.AllocateID()
75	registerProtocol(t, c, sub)
76	requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
77	// Desync so the child's own commits apply without a parent commit.
78	requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetDesync)
79	// surfaceMapper owns a one-pixel buffer; construct a second one for the child.
80	shm := bindProtocol(t, c, "wl_shm")
81	registerProtocol(t, c, shm)
82	fd, err := unix.MemfdCreate("input-region-buffer", 0)
83	if err != nil {
84		t.Fatal(err)
85	}
86	defer unix.Close(fd)
87	if err := unix.Ftruncate(fd, 64); err != nil {
88		t.Fatal(err)
89	}
90	pool, buffer := c.AllocateID(), c.AllocateID()
91	registerProtocol(t, c, pool)
92	registerProtocol(t, c, buffer)
93	if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(64)); err != nil {
94		t.Fatal(err)
95	}
96	requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, buffer, int32(0), int32(4), int32(4), int32(16), uint32(0))
97	requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(3), int32(4))
98	requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
99	requestProtocol(t, c, child, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
100	requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
101	if err := c.Roundtrip(); err != nil {
102		t.Fatal(err)
103	}
104	got := inputChanged(t, events)
105	if len(got.Rects) == 1 {
106		got = inputChanged(t, events)
107	}
108	if got.ID != w.ID || len(got.Rects) != 2 || got.Rects[1] != (ports.Rect{X: 3, Y: 4, W: 4, H: 4}) {
109		t.Fatalf("default child input: %+v", got)
110	}
111	reg := c.AllocateID()
112	registerProtocol(t, c, reg)
113	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, reg)
114	requestProtocol(t, c, reg, wayland.RegionRequestAdd, int32(1), int32(1), int32(9), int32(9))
115	requestProtocol(t, c, child, wayland.SurfaceRequestSetInputRegion, reg)
116	requestProtocol(t, c, reg, wayland.RegionRequestDestroy)
117	if err := c.Roundtrip(); err != nil {
118		t.Fatal(err)
119	}
120	// No parent commit is needed, but the child's own commit is required.
121	requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
122	if err := c.Roundtrip(); err != nil {
123		t.Fatal(err)
124	}
125	got = inputChanged(t, events)
126	if got.ID != w.ID || len(got.Rects) != 2 || got.Rects[1] != (ports.Rect{X: 4, Y: 5, W: 3, H: 3}) {
127		t.Fatalf("committed child input: %+v", got)
128	}
129}
130
131func TestConstraintIntersectsInputRegion(t *testing.T) {
132	s, events, commands, dir := lifecycleServer(t)
133	c := protocolClient(t, s, dir)
134	seat := bindProtocol(t, c, "wl_seat")
135	registerProtocol(t, c, seat)
136	pointer := c.AllocateID()
137	registerProtocol(t, c, pointer)
138	requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
139	w, surf, _ := surfaceMapper(t, c, events)()
140	comp := bindProtocol(t, c, "wl_compositor")
141	region := c.AllocateID()
142	registerProtocol(t, c, region)
143	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
144	requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
145	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
146	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
147	if err := c.Roundtrip(); err != nil {
148		t.Fatal(err)
149	}
150	_ = inputChanged(t, events)
151	constraints := bindProtocol(t, c, "zwp_pointer_constraints_v1")
152	confine := c.AllocateID()
153	registerProtocol(t, c, confine)
154	requestProtocol(t, c, constraints, pointerconstraints.ZwpPointerConstraintsV1RequestConfinePointer, confine, surf, pointer, uint32(0), uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimePersistent))
155	commands <- ports.FocusWindow{ID: w.ID}
156	commands <- ports.PointerFocus{ID: w.ID}
157	commands <- ports.PointerMotionTo{ID: w.ID, X: 0, Y: 0}
158	if err := c.Roundtrip(); err != nil {
159		t.Fatal(err)
160	}
161	if got := constrained(t, events); got.Rect != (ports.Rect{W: 1, H: 1}) {
162		t.Fatalf("constraint intersection: %+v", got)
163	}
164}
165
166func TestInputRegionResentAfterRemap(t *testing.T) {
167	s, events, _, dir := lifecycleServer(t)
168	c := protocolClient(t, s, dir)
169	w, surf, xdg := surfaceMapper(t, c, events)()
170	comp := bindProtocol(t, c, "wl_compositor")
171	region := c.AllocateID()
172	registerProtocol(t, c, region)
173	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
174	requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
175	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
176	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
177	if err := c.Roundtrip(); err != nil {
178		t.Fatal(err)
179	}
180	if got := inputChanged(t, events); got.ID != w.ID || got.All {
181		t.Fatalf("initial input: %+v", got)
182	}
183	requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
184	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
185	if err := c.Roundtrip(); err != nil {
186		t.Fatal(err)
187	}
188	unmapped(t, events, w.ID)
189	// Remapping requires a new initial configure and an ack before attaching.
190	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
191	if err := c.Roundtrip(); err != nil {
192		t.Fatal(err)
193	}
194	var serial uint32
195	if !s.display.Do(func() {
196		for _, v := range s.surfaces {
197			if v.xdg != nil && v.xdg.window != nil && v.xdg.window.id == w.ID && len(v.xdg.serials) > 0 {
198				serial = v.xdg.serials[len(v.xdg.serials)-1]
199			}
200		}
201	}) {
202		t.Fatal("server stopped")
203	}
204	if serial == 0 {
205		t.Fatal("no remap configure")
206	}
207	requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial)
208	buffer := shmBuffer(t, c)
209	requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
210	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
211	if err := c.Roundtrip(); err != nil {
212		t.Fatal(err)
213	}
214	if got := mapped(t, events, 2*time.Second); got.ID != w.ID {
215		t.Fatalf("remapped: %+v", got)
216	}
217	if got := inputChanged(t, events); got.ID != w.ID || got.All || len(got.Rects) != 1 || got.Rects[0] != (ports.Rect{W: 1, H: 1}) {
218		t.Fatalf("remap input: %+v", got)
219	}
220}
221
222func TestRegionOverlappingAddsSubtractUnion(t *testing.T) {
223	g := &region{}
224	g.Add(nil, 0, 0, 6, 6)
225	g.Add(nil, 2, 2, 6, 6)
226	g.Subtract(nil, 3, 3, 2, 2)
227	for _, r := range g.rects {
228		if intersectRect(r, ports.Rect{X: 3, Y: 3, W: 2, H: 2}).W != 0 {
229			t.Fatalf("subtracted overlap remains: %+v", g.rects)
230		}
231	}
232	if g.boxRect() != (ports.Rect{W: 8, H: 8}) {
233		t.Fatalf("outer region changed: %+v", g.rects)
234	}
235}
236
237func TestInputRegionDeactivatesConstraint(t *testing.T) {
238	for _, kind := range []string{"confine", "lock"} {
239		for _, persistent := range []bool{false, true} {
240			t.Run(fmt.Sprintf("%s/persistent=%t", kind, persistent), func(t *testing.T) {
241				s, events, commands, dir := lifecycleServer(t)
242				c := protocolClient(t, s, dir)
243				seat := bindProtocol(t, c, "wl_seat")
244				registerProtocol(t, c, seat)
245				pointer := c.AllocateID()
246				registerProtocol(t, c, pointer)
247				requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
248				w, surf, _ := surfaceMapper(t, c, events)()
249				manager := bindProtocol(t, c, "zwp_pointer_constraints_v1")
250				id := c.AllocateID()
251				notices := logProxy(t, c, id)
252				lifetime := uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimeOneshot)
253				if persistent {
254					lifetime = uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimePersistent)
255				}
256				if kind == "lock" {
257					requestProtocol(t, c, manager, pointerconstraints.ZwpPointerConstraintsV1RequestLockPointer, id, surf, pointer, uint32(0), lifetime)
258				} else {
259					requestProtocol(t, c, manager, pointerconstraints.ZwpPointerConstraintsV1RequestConfinePointer, id, surf, pointer, uint32(0), lifetime)
260				}
261				commands <- ports.PointerFocus{ID: w.ID}
262				commands <- ports.FocusWindow{ID: w.ID}
263				if ev := next(t, c, notices); ev[0] != 0 {
264					t.Fatalf("activation: %v", ev)
265				}
266				if got := constrained(t, events); got.ID != w.ID {
267					t.Fatalf("constraint: %+v", got)
268				}
269				comp := bindProtocol(t, c, "wl_compositor")
270				region := c.AllocateID()
271				registerProtocol(t, c, region)
272				requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
273				requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
274				requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
275				if ev := next(t, c, notices); ev[0] != 1 {
276					t.Fatalf("deactivation: %v", ev)
277				}
278				if got := constrained(t, events); got.ID != 0 {
279					t.Fatalf("constraint retained: %+v", got)
280				}
281				if err := c.Roundtrip(); err != nil {
282					t.Fatal(err)
283				}
284				select {
285				case ev := <-notices:
286					t.Fatalf("unexpected reactivation: %v", ev)
287				default:
288				}
289			})
290		}
291	}
292}
293
294func TestFifoQueuedInputRegionsApplyInOrder(t *testing.T) {
295	s, events, _, dir := lifecycleServer(t)
296	c := protocolClient(t, s, dir)
297	w, surf, _ := surfaceMapper(t, c, events)()
298	manager := bindProtocol(t, c, "wp_fifo_manager_v1")
299	f := c.AllocateID()
300	registerProtocol(t, c, f)
301	requestProtocol(t, c, manager, fifo.WpFifoManagerV1RequestGetFifo, f, surf)
302	comp := bindProtocol(t, c, "wl_compositor")
303	region := c.AllocateID()
304	registerProtocol(t, c, region)
305	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
306	requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
307	// First commit sets a barrier; the next two wait behind it.
308	requestProtocol(t, c, f, fifo.WpFifoV1RequestSetBarrier)
309	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
310	requestProtocol(t, c, f, fifo.WpFifoV1RequestWaitBarrier)
311	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
312	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
313	requestProtocol(t, c, f, fifo.WpFifoV1RequestWaitBarrier)
314	empty := c.AllocateID()
315	registerProtocol(t, c, empty)
316	requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, empty)
317	requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, empty)
318	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
319	if err := c.Roundtrip(); err != nil {
320		t.Fatal(err)
321	}
322	first := inputChanged(t, events)
323	second := inputChanged(t, events)
324	if first.ID != w.ID || len(first.Rects) != 1 || first.Rects[0] != (ports.Rect{W: 1, H: 1}) || second.ID != w.ID || second.All || len(second.Rects) != 0 {
325		t.Fatalf("queued input order: first %+v, second %+v", first, second)
326	}
327}

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