1package e2e_test
2
3import (
4 "bytes"
5 "errors"
6 "testing"
7 "time"
8
9 "github.com/bnema/go-wayland-bindings/server/wayland"
10 "github.com/bnema/purego-libwayland/server"
11 "github.com/bnema/wlturbo"
12 "golang.org/x/sys/unix"
13)
14
15func receive[T any](t *testing.T, ch <-chan T) T {
16 t.Helper()
17 select {
18 case v := <-ch:
19 return v
20 case <-time.After(3 * time.Second):
21 t.Fatal("timed out waiting for event")
22 var zero T
23 return zero
24 }
25}
26
27type argsProxy struct {
28 wlturbo.BaseProxy
29 received chan argsEvent
30 kind string
31}
32type argsEvent struct {
33 text string
34 data []byte
35 fixed wlturbo.Fixed
36 fd int
37}
38
39func (p *argsProxy) Dispatch(e *wlturbo.Event) {
40 switch p.kind {
41 case "seat":
42 p.received <- argsEvent{text: e.String()}
43 case "keyboard":
44 if e.Opcode == uint16(wayland.KeyboardEventKeymap) {
45 _ = e.Uint32()
46 p.received <- argsEvent{fd: int(e.Fd())}
47 } else if e.Opcode == uint16(wayland.KeyboardEventEnter) {
48 _ = e.Uint32()
49 _ = e.Uint32()
50 p.received <- argsEvent{data: e.Array()}
51 }
52 case "pointer":
53 _ = e.Uint32()
54 p.received <- argsEvent{fixed: e.Fixed()}
55 }
56}
57
58type sourceHandler struct{ requests chan string }
59
60func (h sourceHandler) Offer(_ *wayland.DataSource, mimeType string) { h.requests <- mimeType }
61func (sourceHandler) Destroy(*wayland.DataSource) {}
62func (sourceHandler) SetActions(*wayland.DataSource, uint32) {}
63
64type managerHandler struct {
65 requests chan string
66 t *testing.T
67}
68
69func (h managerHandler) CreateDataSource(self *wayland.DataDeviceManager, id uint32) {
70 if _, err := wayland.NewDataSource(self.Client(), 1, id, sourceHandler{h.requests}); err != nil {
71 h.t.Error(err)
72 }
73}
74func (managerHandler) GetDataDevice(*wayland.DataDeviceManager, uint32, *wayland.Seat) {}
75func (managerHandler) Release(*wayland.DataDeviceManager) {}
76
77type shmHandler struct {
78 requests chan string
79 t *testing.T
80}
81
82func (h shmHandler) CreatePool(self *wayland.Shm, id uint32, fd int, size int32) {
83 defer unix.Close(fd)
84 h.requests <- "pool"
85 if _, err := wayland.NewShmPool(self.Client(), 1, id, poolHandler{}); err != nil {
86 h.t.Error(err)
87 }
88}
89func (shmHandler) Release(*wayland.Shm) {}
90
91type poolHandler struct{}
92
93func (poolHandler) CreateBuffer(*wayland.ShmPool, uint32, int32, int32, int32, int32, uint32) {}
94func (poolHandler) Destroy(*wayland.ShmPool) {}
95func (poolHandler) Resize(self *wayland.ShmPool, size int32) { self.PostError(7, "bad % request") }
96
97type seatHandler struct{ t *testing.T }
98
99type noPointerRequests struct{}
100
101func (noPointerRequests) SetCursor(*wayland.Pointer, uint32, *wayland.Surface, int32, int32) {}
102func (noPointerRequests) Release(*wayland.Pointer) {}
103
104type noKeyboardRequests struct{}
105
106func (noKeyboardRequests) Release(*wayland.Keyboard) {}
107
108func (h seatHandler) GetPointer(self *wayland.Seat, id uint32) {
109 p, err := wayland.NewPointer(self.Client(), 1, id, noPointerRequests{})
110 if err != nil {
111 h.t.Error(err)
112 return
113 }
114 p.SendMotion(42, server.FixedFromFloat(-1.5), 0)
115}
116func (h seatHandler) GetKeyboard(self *wayland.Seat, id uint32) {
117 k, err := wayland.NewKeyboard(self.Client(), 1, id, noKeyboardRequests{})
118 if err != nil {
119 h.t.Error(err)
120 return
121 }
122 fds := []int{0, 0}
123 if err := unix.Pipe(fds); err != nil {
124 h.t.Error(err)
125 return
126 }
127 defer unix.Close(fds[0])
128 defer unix.Close(fds[1])
129 if _, err := unix.Write(fds[1], []byte("fd contents")); err != nil {
130 h.t.Error(err)
131 return
132 }
133 k.SendKeymap(1, fds[0], 11)
134 k.SendEnter(1, wayland.WrapSurface(self.Resource), []byte{1, 2, 3})
135}
136func (seatHandler) GetTouch(*wayland.Seat, uint32) {}
137func (seatHandler) Release(*wayland.Seat) {}
138func TestArgumentRoundtripAndPostError(t *testing.T) {
139 socket, d, _ := startServer(t)
140 requests := make(chan string, 1)
141 stringsReceived := make(chan string, 1)
142 if !d.Do(func() {
143 if err := wayland.NewDataDeviceManagerGlobal(d, 1, func(c server.Client, v, id uint32) {
144 if _, e := wayland.NewDataDeviceManager(c, int32(v), id, managerHandler{stringsReceived, t}); e != nil {
145 t.Error(e)
146 }
147 }); err != nil {
148 t.Error(err)
149 }
150 if err := wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
151 if _, e := wayland.NewShm(c, int32(v), id, shmHandler{requests, t}); e != nil {
152 t.Error(e)
153 }
154 }); err != nil {
155 t.Error(err)
156 }
157 if err := wayland.NewSeatGlobal(d, 1, func(c server.Client, v, id uint32) {
158 s, e := wayland.NewSeat(c, int32(v), id, seatHandler{t})
159 if e != nil {
160 t.Error(e)
161 return
162 }
163 s.SendName("hello")
164 }); err != nil {
165 t.Error(err)
166 }
167 }) {
168 t.Fatal("display stopped")
169 }
170 c, err := wlturbo.Connect(socket)
171 must(t, err)
172 defer c.Close()
173 must(t, c.Roundtrip())
174 bind := func(name string) (uint32, *argsProxy) {
175 t.Helper()
176 g, ok := c.Registry().FindGlobal(name)
177 if !ok {
178 t.Fatalf("%s missing", name)
179 }
180 id, e := c.Registry().BindID(g.Name, g.Interface, 1)
181 must(t, e)
182 p := &argsProxy{received: make(chan argsEvent, 4), kind: name[3:]}
183 p.SetID(id)
184 p.SetContext(c.Context())
185 c.Context().Register(p)
186 // wlturbo rejects events without a declared signature; these hand-written
187 // proxies declare theirs explicitly.
188 if name == "wl_seat" {
189 c.RegisterEventSignature(id, uint16(wayland.SeatEventName), "string,")
190 }
191 return id, p
192 }
193 manager, _ := bind("wl_data_device_manager")
194 seat, sp := bind("wl_seat")
195 shm, _ := bind("wl_shm")
196 source := c.AllocateID()
197 must(t, c.SendRequest(manager, uint16(wayland.DataDeviceManagerRequestCreateDataSource), source))
198 must(t, c.SendRequest(source, uint16(wayland.DataSourceRequestOffer), "request text"))
199 pool := c.AllocateID()
200 fds := []int{0, 0}
201 must(t, unix.Pipe(fds))
202 defer unix.Close(fds[0])
203 defer unix.Close(fds[1])
204 must(t, c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fds[0]}, pool, uint32(0), int32(4096)))
205 keyboard := c.AllocateID()
206 kp := &argsProxy{received: make(chan argsEvent, 4), kind: "keyboard"}
207 kp.SetID(keyboard)
208 kp.SetContext(c.Context())
209 c.Context().Register(kp)
210 c.RegisterEventSignature(keyboard, uint16(wayland.KeyboardEventKeymap), "uint,fd,uint,")
211 c.RegisterEventSignature(keyboard, uint16(wayland.KeyboardEventEnter), "uint,object,array,")
212 pointer := c.AllocateID()
213 pp := &argsProxy{received: make(chan argsEvent, 4), kind: "pointer"}
214 pp.SetID(pointer)
215 pp.SetContext(c.Context())
216 c.Context().Register(pp)
217 c.RegisterEventSignature(pointer, uint16(wayland.PointerEventMotion), "uint,fixed,fixed,")
218 must(t, c.SendRequest(seat, uint16(wayland.SeatRequestGetKeyboard), keyboard))
219 must(t, c.SendRequest(seat, uint16(wayland.SeatRequestGetPointer), pointer))
220 must(t, c.Roundtrip())
221 if text := receive(t, stringsReceived); text != "request text" {
222 t.Fatalf("string request = %q", text)
223 }
224 if receive(t, requests) != "pool" {
225 t.Fatal("fd request missing")
226 }
227 if e := receive(t, sp.received); e.text != "hello" {
228 t.Fatalf("string event: %+v", e)
229 }
230 a, b := receive(t, kp.received), receive(t, kp.received)
231 if a.fd <= 0 || !bytes.Equal(b.data, []byte{1, 2, 3}) {
232 t.Fatalf("keyboard events: %+v %+v", a, b)
233 }
234 buf := make([]byte, 32)
235 n, e := unix.Read(a.fd, buf)
236 unix.Close(a.fd)
237 if e != nil || string(buf[:n]) != "fd contents" {
238 t.Fatalf("fd: %q %v", buf[:n], e)
239 }
240 if e := receive(t, pp.received); e.fixed.Float64() != -1.5 {
241 t.Fatalf("fixed: %+v", e)
242 }
243 must(t, c.SendRequest(pool, uint16(wayland.ShmPoolRequestResize), int32(8192)))
244 result := make(chan error, 1)
245 go func() {
246 for i := 0; i < 10; i++ {
247 if err := c.Dispatch(); err != nil {
248 result <- err
249 return
250 }
251 }
252 result <- nil
253 }()
254 select {
255 case err := <-result:
256 var displayErr *wlturbo.DisplayError
257 if !errors.As(err, &displayErr) || displayErr.ObjectID != pool || displayErr.Code != 7 || displayErr.Message != "bad % request" {
258 t.Fatalf("PostError result = %v", err)
259 }
260 case <-time.After(3 * time.Second):
261 t.Fatal("client did not disconnect after PostError")
262 }
263
264}