1//go:build linux
2
3package wlturbo
4
5import (
6 "bytes"
7 "encoding/binary"
8 "errors"
9 "io"
10 "testing"
11 "time"
12)
13
14// globalEvent builds a wl_registry.global event body.
15func globalEvent(registry, name uint32, iface string, version uint32) []byte {
16 padded := (len(iface) + 1 + 3) &^ 3
17 body := make([]byte, 4+4+padded+4)
18 binary.LittleEndian.PutUint32(body, name)
19 binary.LittleEndian.PutUint32(body[4:], uint32(len(iface)+1))
20 copy(body[8:], iface)
21 binary.LittleEndian.PutUint32(body[8+padded:], version)
22 return message(registry, 0, body)
23}
24
25func u32Body(v uint32) []byte {
26 b := make([]byte, 4)
27 binary.LittleEndian.PutUint32(b, v)
28 return b
29}
30
31// readRequest reads one request the client wrote to the peer.
32func readRequest(t *testing.T, r io.Reader) (object uint32, opcode uint16, body []byte) {
33 t.Helper()
34 object, opcode, body, err := readFrame(r, 0)
35 if err != nil {
36 t.Fatalf("read request: %v", err)
37 }
38 return object, opcode, body
39}
40
41func TestRegistryBootstrapAnnouncements(t *testing.T) {
42 client, peer := unixSocketPair(t)
43 d := newDisplay(client)
44 reg := d.Registry()
45 peer.SetDeadline(time.Now().Add(2 * time.Second))
46
47 var got Global
48 reg.AddHandler("wl_compositor", func(r *Registry, name, _ uint32) { got, _ = r.FindGlobalByName(name) })
49
50 sendMessageWithFDs(t, peer, globalEvent(reg.ID(), 17, "wl_compositor", 7), nil)
51 if err := d.Dispatch(); err != nil {
52 t.Fatal(err)
53 }
54 if got.Name != 17 || got.Interface != "wl_compositor" || got.Version != 7 {
55 t.Fatalf("global: %+v", got)
56 }
57
58 sendMessageWithFDs(t, peer, message(reg.ID(), 1, u32Body(17)), nil)
59 if err := d.Dispatch(); err != nil {
60 t.Fatal(err)
61 }
62 if _, ok := reg.FindGlobalByName(17); ok {
63 t.Fatal("global not removed")
64 }
65
66 sendMessageWithFDs(t, peer, message(reg.ID(), 2, nil), nil)
67 if err := d.Dispatch(); !errors.Is(err, ErrUnknownOpcode) {
68 t.Fatalf("unknown registry opcode: %v", err)
69 }
70}
71
72func TestBindNegotiated(t *testing.T) {
73 client, peer := unixSocketPair(t)
74 d := newDisplay(client)
75 reg := d.Registry()
76 peer.SetDeadline(time.Now().Add(2 * time.Second))
77 const iface = "xdg_wm_base"
78
79 if v, err := reg.BindNegotiated(iface, 6, &BaseProxy{}); v != 0 || !errors.Is(err, ErrGlobalNotFound) {
80 t.Fatalf("missing global: %d %v", v, err)
81 }
82 if v, err := reg.BindNegotiated(iface, 0, &BaseProxy{}); v != 0 || err == nil || errors.Is(err, ErrGlobalNotFound) {
83 t.Fatalf("supported zero: %d %v", v, err)
84 }
85
86 // A global announced at version 0 is unusable.
87 sendMessageWithFDs(t, peer, globalEvent(reg.ID(), 29, iface, 0), nil)
88 if err := d.Dispatch(); err != nil {
89 t.Fatal(err)
90 }
91 if v, err := reg.BindNegotiated(iface, 6, &BaseProxy{}); v != 0 || !errors.Is(err, ErrGlobalNotFound) {
92 t.Fatalf("announced version 0: %d %v", v, err)
93 }
94 sendMessageWithFDs(t, peer, message(reg.ID(), 1, u32Body(29)), nil)
95 if err := d.Dispatch(); err != nil {
96 t.Fatal(err)
97 }
98
99 for i, announced := range []uint32{2, 9} {
100 name := uint32(30 + i)
101 sendMessageWithFDs(t, peer, globalEvent(reg.ID(), name, iface, announced), nil)
102 if err := d.Dispatch(); err != nil {
103 t.Fatal(err)
104 }
105 proxy := &BaseProxy{}
106 if v, err := reg.BindNegotiated(iface, 0, proxy); v != 0 || err == nil {
107 t.Fatalf("supported zero with global: %d %v", v, err)
108 }
109 negotiated, err := reg.BindNegotiated(iface, 6, proxy)
110 if err != nil {
111 t.Fatal(err)
112 }
113 want := min(announced, 6)
114 if negotiated != want || proxy.Version() != want {
115 t.Fatalf("announced=%d negotiated=%d proxy version=%d, want %d", announced, negotiated, proxy.Version(), want)
116 }
117 obj, op, body := readRequest(t, peer)
118 // bind: name, string (length incl. NUL, NUL, padding), version, new_id.
119 padded := (len(iface) + 1 + 3) &^ 3
120 if obj != reg.ID() || op != 0 || len(body) != 4+4+padded+4+4 ||
121 binary.LittleEndian.Uint32(body) != name ||
122 binary.LittleEndian.Uint32(body[4:]) != uint32(len(iface)+1) ||
123 string(body[8:8+len(iface)]) != iface ||
124 !bytes.Equal(body[8+len(iface):8+padded], make([]byte, padded-len(iface))) ||
125 binary.LittleEndian.Uint32(body[8+padded:]) != want ||
126 binary.LittleEndian.Uint32(body[8+padded+4:]) != proxy.ID() {
127 t.Fatalf("bind object=%d opcode=%d body=%x", obj, op, body)
128 }
129 // Remove the global so the next iteration is the only match.
130 sendMessageWithFDs(t, peer, message(reg.ID(), 1, u32Body(name)), nil)
131 if err := d.Dispatch(); err != nil {
132 t.Fatal(err)
133 }
134 }
135}