v0.1.0 47aaa0f

cursor_test.go RawHistoryFolder

1//go:build linux
2
3package wayland
4
5import (
6	"encoding/binary"
7	"io"
8	"net"
9	"testing"
10	"time"
11
12	"github.com/bnema/wlturbo"
13	"github.com/bnema/wlturbo/protocol/core"
14	"github.com/bnema/wlturbo/protocol/cursorshape"
15)
16
17// readRequest returns the object ID, opcode and body of the next request.
18// Requests can share one socket read, so each is framed by its size field.
19func readRequest(t *testing.T, p *net.UnixConn) (uint32, uint16, []byte) {
20	t.Helper()
21	_ = p.SetReadDeadline(time.Now().Add(time.Second))
22	header := make([]byte, 8)
23	if _, err := io.ReadFull(p, header); err != nil {
24		t.Fatalf("read request header: %v", err)
25	}
26	size := int(binary.LittleEndian.Uint32(header[4:]) >> 16)
27	body := make([]byte, size-8)
28	if _, err := io.ReadFull(p, body); err != nil {
29		t.Fatalf("read request body: %v", err)
30	}
31	return binary.LittleEndian.Uint32(header), uint16(binary.LittleEndian.Uint32(header[4:])), body
32}
33
34// noRequest asserts the client sent nothing.
35func noRequest(t *testing.T, p *net.UnixConn) {
36	t.Helper()
37	_ = p.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
38	b := make([]byte, 64)
39	if n, err := p.Read(b); err == nil {
40		t.Fatalf("unexpected request %x", b[:n])
41	}
42}
43
44func TestCursorShapeWire(t *testing.T) {
45	ca, server := inputPair(t)
46	d, err := wlturbo.ConnectFromConn(ca)
47	if err != nil {
48		t.Fatal(err)
49	}
50	defer d.Close()
51	inputRequest(t, server)
52	ctx := d.Context()
53	pointer := core.NewPointer(ctx)
54	pointer.SetID(d.AllocateID())
55	ctx.Register(pointer)
56	shapes := cursorshape.NewWpCursorShapeManager(ctx)
57	if err = d.Registry().Bind(1, cursorshape.WpCursorShapeManagerInterface, 1, shapes); err != nil {
58		t.Fatal(err)
59	}
60	inputRequest(t, server)
61	w := &Window{Pointer: pointer, CursorShapes: shapes, pointerGen: 1}
62
63	// Outside the surface nothing is sent.
64	if err = w.SetCursor(CursorPointer); err != nil {
65		t.Fatal(err)
66	}
67	noRequest(t, server)
68
69	// First shape after enter creates the device, then sets the shape at the
70	// enter serial.
71	w.PointerEntered(7, 1)
72	if err = w.SetCursor(CursorPointer); err != nil {
73		t.Fatal(err)
74	}
75	id, op, body := readRequest(t, server)
76	if id != shapes.ID() || op != 1 || binary.LittleEndian.Uint32(body[4:]) != pointer.ID() {
77		t.Fatalf("get_pointer id=%d op=%d %x", id, op, body)
78	}
79	device := binary.LittleEndian.Uint32(body)
80	id, op, body = readRequest(t, server)
81	if id != device || op != 1 || binary.LittleEndian.Uint32(body) != 7 || binary.LittleEndian.Uint32(body[4:]) != CursorPointer {
82		t.Fatalf("set_shape id=%d op=%d %x", id, op, body)
83	}
84
85	// An unchanged shape is not re-sent; a new one reuses the device.
86	if err = w.SetCursor(CursorPointer); err != nil {
87		t.Fatal(err)
88	}
89	noRequest(t, server)
90	if err = w.SetCursor(CursorText); err != nil {
91		t.Fatal(err)
92	}
93	if id, _, body = readRequest(t, server); id != device || binary.LittleEndian.Uint32(body[4:]) != CursorText {
94		t.Fatalf("text shape id=%d %x", id, body)
95	}
96
97	// After leave nothing is sent; re-enter re-sends even the same shape,
98	// at the new serial.
99	w.PointerLeft()
100	if err = w.SetCursor(CursorDefault); err != nil {
101		t.Fatal(err)
102	}
103	noRequest(t, server)
104	w.PointerEntered(9, 1)
105	if err = w.SetCursor(CursorText); err != nil {
106		t.Fatal(err)
107	}
108	if id, _, body = readRequest(t, server); id != device || binary.LittleEndian.Uint32(body) != 9 || binary.LittleEndian.Uint32(body[4:]) != CursorText {
109		t.Fatalf("re-enter shape id=%d %x", id, body)
110	}
111
112	// Dropping the device (pointer capability loss) destroys it.
113	w.dropCursorDevice()
114	if id, op, _ = readRequest(t, server); id != device || op != 0 {
115		t.Fatalf("destroy id=%d op=%d", id, op)
116	}
117	// The old enter serial went with the pointer: nothing is sent until the
118	// replacement pointer's own enter, and a late enter from the released
119	// pointer (older generation) is ignored.
120	if err = w.SetCursor(CursorPointer); err != nil {
121		t.Fatal(err)
122	}
123	noRequest(t, server)
124	w.pointerGen = 2
125	w.PointerEntered(11, 1)
126	if err = w.SetCursor(CursorPointer); err != nil {
127		t.Fatal(err)
128	}
129	noRequest(t, server)
130}
131
132// Without the global, SetCursor is a silent no-op.
133func TestCursorShapeUnsupported(t *testing.T) {
134	w := &Window{Pointer: &core.Pointer{}}
135	w.PointerEntered(1, 0)
136	if err := w.SetCursor(CursorPointer); err != nil {
137		t.Fatal(err)
138	}
139}

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 1

Tags 12

↑↓ moveenter openesc close