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}