v0.3.0 cf370a0

cursor_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"testing"
6	"time"
7
8	"github.com/bnema/go-wayland-bindings/server/cursorshape"
9	"github.com/bnema/go-wayland-bindings/server/wayland"
10	"github.com/bnema/neferwl/internal/logging"
11	"github.com/bnema/neferwl/internal/ports"
12	"golang.org/x/sys/unix"
13)
14
15func cursorServer(t *testing.T) (*Server, chan ports.ClientEvent, chan ports.ClientCommand, chan ports.CursorChange, string) {
16	t.Helper()
17	dir := t.TempDir()
18	events := make(chan ports.ClientEvent, 16)
19	commands := make(chan ports.ClientCommand, 16)
20	cursors := make(chan ports.CursorChange, 16)
21	s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs}, Channels{Events: events, Commands: commands, Cursors: cursors}, logging.For(context.Background(), "wayland"))
22	if err != nil {
23		t.Fatal(err)
24	}
25	ctx, cancel := context.WithCancel(context.Background())
26	done := make(chan error, 1)
27	go func() { done <- s.Run(ctx) }()
28	t.Cleanup(func() { cancel(); <-done })
29	return s, events, commands, cursors, dir
30}
31
32// nextCursor returns the latest cursor change sent within a short wait.
33func nextCursor(t *testing.T, cursors <-chan ports.CursorChange) ports.CursorChange {
34	t.Helper()
35	var c ports.CursorChange
36	select {
37	case c = <-cursors:
38	case <-time.After(2 * time.Second):
39		t.Fatal("no cursor change")
40	}
41	for {
42		select {
43		case c = <-cursors:
44		case <-time.After(50 * time.Millisecond):
45			return c
46		}
47	}
48}
49
50func TestCursorShapeFollowsPointerFocus(t *testing.T) {
51	s, events, commands, cursors, dir := cursorServer(t)
52	c := protocolClient(t, s, dir)
53	seat := bindProtocol(t, c, "wl_seat")
54	registerProtocol(t, c, seat)
55	pointer := c.AllocateID()
56	registerProtocol(t, c, pointer)
57	requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
58	manager := bindProtocol(t, c, "wp_cursor_shape_manager_v1")
59	device := c.AllocateID()
60	registerProtocol(t, c, device)
61	requestProtocol(t, c, manager, cursorshape.WpCursorShapeManagerV1RequestGetPointer, device, pointer)
62	w := toplevelMapper(t, c, events)()
63
64	// Without pointer focus the request is ignored.
65	requestProtocol(t, c, device, cursorshape.WpCursorShapeDeviceV1RequestSetShape, uint32(1), uint32(cursorshape.WpCursorShapeDeviceV1ShapeText))
66	if err := c.Roundtrip(); err != nil {
67		t.Fatal(err)
68	}
69	select {
70	case got := <-cursors:
71		t.Fatalf("cursor change without focus: %+v", got)
72	case <-time.After(50 * time.Millisecond):
73	}
74
75	commands <- ports.PointerFocus{ID: w.ID, X: 0, Y: 0}
76	if got := nextCursor(t, cursors); got != (ports.CursorChange{}) {
77		t.Fatalf("enter: %+v, want the arrow", got)
78	}
79	requestProtocol(t, c, device, cursorshape.WpCursorShapeDeviceV1RequestSetShape, uint32(2), uint32(cursorshape.WpCursorShapeDeviceV1ShapePointer))
80	if err := c.Roundtrip(); err != nil {
81		t.Fatal(err)
82	}
83	if got := nextCursor(t, cursors); got.Shape != "pointer" {
84		t.Fatalf("shape: %+v", got)
85	}
86	commands <- ports.PointerFocus{ID: 0}
87	if got := nextCursor(t, cursors); got != (ports.CursorChange{}) {
88		t.Fatalf("leave: %+v, want the arrow", got)
89	}
90}
91
92func TestCursorShapeNames(t *testing.T) {
93	for shape, want := range map[cursorshape.WpCursorShapeDeviceV1Shape]string{
94		cursorshape.WpCursorShapeDeviceV1ShapeDefault:      "default",
95		cursorshape.WpCursorShapeDeviceV1ShapeNwseResize:   "nwse-resize",
96		cursorshape.WpCursorShapeDeviceV1ShapeZoomOut:      "zoom-out",
97		cursorshape.WpCursorShapeDeviceV1ShapeAllResize:    "all-resize",
98		cursorshape.WpCursorShapeDeviceV1ShapeVerticalText: "vertical-text",
99	} {
100		if got := cursorShapes[shape]; got != want {
101			t.Errorf("shape %d = %q, want %q", shape, got, want)
102		}
103	}
104}
105
106func TestCursorShapeInvalid(t *testing.T) {
107	s, _, _, _, dir := cursorServer(t)
108	c := protocolClient(t, s, dir)
109	seat := bindProtocol(t, c, "wl_seat")
110	registerProtocol(t, c, seat)
111	pointer := c.AllocateID()
112	registerProtocol(t, c, pointer)
113	requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
114	manager := bindProtocol(t, c, "wp_cursor_shape_manager_v1")
115	device := c.AllocateID()
116	registerProtocol(t, c, device)
117	requestProtocol(t, c, manager, cursorshape.WpCursorShapeManagerV1RequestGetPointer, device, pointer)
118	// Version 1 ends at zoom_out.
119	requestProtocol(t, c, device, cursorshape.WpCursorShapeDeviceV1RequestSetShape, uint32(1), uint32(cursorshape.WpCursorShapeDeviceV1ShapeDndAsk))
120	expectProtocolError(t, c, device, uint32(cursorshape.WpCursorShapeDeviceV1ErrorInvalidShape))
121}
122
123func TestSetCursorSurface(t *testing.T) {
124	s, events, commands, cursors, dir := cursorServer(t)
125	c := protocolClient(t, s, dir)
126	seat := bindProtocol(t, c, "wl_seat")
127	registerProtocol(t, c, seat)
128	pointer := c.AllocateID()
129	registerProtocol(t, c, pointer)
130	requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
131	w := toplevelMapper(t, c, events)()
132	commands <- ports.PointerFocus{ID: w.ID, X: 0, Y: 0}
133	nextCursor(t, cursors)
134
135	comp := bindProtocol(t, c, "wl_compositor")
136	shm := bindProtocol(t, c, "wl_shm")
137	registerProtocol(t, c, shm)
138	fd, err := unix.MemfdCreate("cursor", unix.MFD_CLOEXEC)
139	if err != nil {
140		t.Fatal(err)
141	}
142	defer unix.Close(fd)
143	// 2x1 ARGB: opaque blue, then transparent; stride padded to 12.
144	if _, err := unix.Pwrite(fd, []byte{255, 0, 0, 255, 0, 0, 0, 0, 9, 9, 9, 9}, 0); err != nil {
145		t.Fatal(err)
146	}
147	pool, buf := c.AllocateID(), c.AllocateID()
148	registerProtocol(t, c, pool)
149	registerProtocol(t, c, buf)
150	if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(12)); err != nil {
151		t.Fatal(err)
152	}
153	requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, buf, int32(0), int32(2), int32(1), int32(12), uint32(wayland.ShmFormatArgb8888))
154	surf := c.AllocateID()
155	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf)
156	registerProtocol(t, c, surf)
157
158	// A cursor surface without a buffer hides the cursor.
159	requestProtocol(t, c, pointer, wayland.PointerRequestSetCursor, uint32(1), surf, int32(1), int32(0))
160	if err := c.Roundtrip(); err != nil {
161		t.Fatal(err)
162	}
163	if got := nextCursor(t, cursors); !got.Hidden {
164		t.Fatalf("empty surface: %+v", got)
165	}
166	// A commit updates the image.
167	requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buf, int32(0), int32(0))
168	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
169	if err := c.Roundtrip(); err != nil {
170		t.Fatal(err)
171	}
172	got := nextCursor(t, cursors)
173	if got.Image == nil || got.Scale != 1 || got.Image.W != 2 || got.Image.H != 1 || got.Image.HotX != 1 || string(got.Image.Pixels) != string([]byte{255, 0, 0, 255, 0, 0, 0, 0}) {
174		t.Fatalf("image: %+v %+v", got, got.Image)
175	}
176	// A null surface hides the cursor.
177	requestProtocol(t, c, pointer, wayland.PointerRequestSetCursor, uint32(1), uint32(0), int32(0), int32(0))
178	if err := c.Roundtrip(); err != nil {
179		t.Fatal(err)
180	}
181	if got := nextCursor(t, cursors); !got.Hidden {
182		t.Fatalf("null surface: %+v", got)
183	}
184}
185
186func TestSetCursorRoleError(t *testing.T) {
187	s, events, _, _, dir := cursorServer(t)
188	c := protocolClient(t, s, dir)
189	seat := bindProtocol(t, c, "wl_seat")
190	registerProtocol(t, c, seat)
191	pointer := c.AllocateID()
192	registerProtocol(t, c, pointer)
193	requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
194	toplevelMapper(t, c, events)()
195	// Surface 3 of the mapper's connection is the toplevel's wl_surface:
196	// find it through the server instead of guessing ids.
197	var surf uint32
198	s.display.Do(func() {
199		for _, st := range s.surfaces {
200			if st.kind == roleXDG {
201				surf = st.wl.ID()
202			}
203		}
204	})
205	requestProtocol(t, c, pointer, wayland.PointerRequestSetCursor, uint32(1), surf, int32(0), int32(0))
206	expectProtocolError(t, c, pointer, uint32(wayland.PointerErrorRole))
207}

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