1//go:build linux
2
3package client_test
4
5import (
6 "fmt"
7 "sort"
8 "testing"
9
10 "github.com/bnema/go-wayland-bindings/client/alphamodifier"
11 "github.com/bnema/go-wayland-bindings/client/colormanagement"
12 "github.com/bnema/go-wayland-bindings/client/colorrepresentation"
13 "github.com/bnema/go-wayland-bindings/client/committiming"
14 "github.com/bnema/go-wayland-bindings/client/contenttype"
15 "github.com/bnema/go-wayland-bindings/client/cursorshape"
16 "github.com/bnema/go-wayland-bindings/client/drmlease"
17 datacontrol "github.com/bnema/go-wayland-bindings/client/extdatacontrol"
18 extforeigntoplevel "github.com/bnema/go-wayland-bindings/client/extforeigntoplevellist"
19 idlenotify "github.com/bnema/go-wayland-bindings/client/extidlenotify"
20 imagecapturesource "github.com/bnema/go-wayland-bindings/client/extimagecapturesource"
21 imagecopycapture "github.com/bnema/go-wayland-bindings/client/extimagecopycapture"
22 workspace "github.com/bnema/go-wayland-bindings/client/extworkspace"
23 "github.com/bnema/go-wayland-bindings/client/fifo"
24 "github.com/bnema/go-wayland-bindings/client/fractionalscale"
25 "github.com/bnema/go-wayland-bindings/client/idleinhibit"
26 "github.com/bnema/go-wayland-bindings/client/inputmethod"
27 shortcutsinhibit "github.com/bnema/go-wayland-bindings/client/keyboardshortcutsinhibit"
28 "github.com/bnema/go-wayland-bindings/client/linuxdmabuf"
29 drmsyncobj "github.com/bnema/go-wayland-bindings/client/linuxdrmsyncobj"
30 "github.com/bnema/go-wayland-bindings/client/pointerconstraints"
31 "github.com/bnema/go-wayland-bindings/client/pointerwarp"
32 presentation "github.com/bnema/go-wayland-bindings/client/presentationtime"
33 "github.com/bnema/go-wayland-bindings/client/primaryselection"
34 "github.com/bnema/go-wayland-bindings/client/relativepointer"
35 kdeserverdecoration "github.com/bnema/go-wayland-bindings/client/serverdecoration"
36 "github.com/bnema/go-wayland-bindings/client/tablet"
37 "github.com/bnema/go-wayland-bindings/client/tearingcontrol"
38 "github.com/bnema/go-wayland-bindings/client/textinput"
39 "github.com/bnema/go-wayland-bindings/client/viewporter"
40 "github.com/bnema/go-wayland-bindings/client/virtualkeyboard"
41 "github.com/bnema/go-wayland-bindings/client/wayland"
42 foreigntoplevel "github.com/bnema/go-wayland-bindings/client/wlrforeigntoplevelmanagement"
43 layershell "github.com/bnema/go-wayland-bindings/client/wlrlayershell"
44 outputmanagement "github.com/bnema/go-wayland-bindings/client/wlroutputmanagement"
45 outputpower "github.com/bnema/go-wayland-bindings/client/wlroutputpowermanagement"
46 screencopy "github.com/bnema/go-wayland-bindings/client/wlrscreencopy"
47 "github.com/bnema/go-wayland-bindings/client/xdgactivation"
48 "github.com/bnema/go-wayland-bindings/client/xdgdecoration"
49 "github.com/bnema/go-wayland-bindings/client/xdgoutput"
50 "github.com/bnema/go-wayland-bindings/client/xdgshell"
51 "github.com/bnema/wlturbo"
52 "golang.org/x/sys/unix"
53)
54
55// headlessBinding is one generated root object and the highest version its
56// package supports (the XML interface version).
57type headlessBinding struct {
58 iface string
59 supported uint32
60 make func(*wlturbo.Context) wlturbo.Proxy
61 // optional marks globals that depend on GPU, DRM or input devices, or that
62 // this NeferWL build does not advertise headless: absence is logged, not
63 // a failure. Everything else must be announced.
64 optional bool
65}
66
67func headlessBindings() []headlessBinding {
68 return []headlessBinding{
69 {wayland.CompositorInterface, 7, func(c *wlturbo.Context) wlturbo.Proxy { return wayland.NewCompositor(c) }, false},
70 {wayland.SubcompositorInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return wayland.NewSubcompositor(c) }, false},
71 {wayland.ShmInterface, 3, func(c *wlturbo.Context) wlturbo.Proxy { return wayland.NewShm(c) }, false},
72 {wayland.SeatInterface, 11, func(c *wlturbo.Context) wlturbo.Proxy { return wayland.NewSeat(c) }, false},
73 {wayland.OutputInterface, 4, func(c *wlturbo.Context) wlturbo.Proxy { return wayland.NewOutput(c) }, false},
74 {wayland.DataDeviceManagerInterface, 4, func(c *wlturbo.Context) wlturbo.Proxy { return wayland.NewDataDeviceManager(c) }, false},
75 {xdgshell.XdgWmBaseInterface, 7, func(c *wlturbo.Context) wlturbo.Proxy { return xdgshell.NewXdgWmBase(c) }, false},
76 {linuxdmabuf.LinuxDmabufInterface, 6, func(c *wlturbo.Context) wlturbo.Proxy { return linuxdmabuf.NewLinuxDmabuf(c) }, true},
77 {drmsyncobj.WpLinuxDrmSyncobjManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return drmsyncobj.NewWpLinuxDrmSyncobjManager(c) }, true},
78 {viewporter.WpViewporterInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return viewporter.NewWpViewporter(c) }, false},
79 {fractionalscale.WpFractionalScaleManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return fractionalscale.NewWpFractionalScaleManager(c) }, false},
80 {cursorshape.WpCursorShapeManagerInterface, 2, func(c *wlturbo.Context) wlturbo.Proxy { return cursorshape.NewWpCursorShapeManager(c) }, false},
81 {tablet.TabletManagerInterface, 2, func(c *wlturbo.Context) wlturbo.Proxy { return tablet.NewTabletManager(c) }, true},
82 {textinput.TextInputManagerV3Interface, 2, func(c *wlturbo.Context) wlturbo.Proxy { return textinput.NewTextInputManagerV3(c) }, false},
83 {alphamodifier.WpAlphaModifierInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return alphamodifier.NewWpAlphaModifier(c) }, false},
84 {colormanagement.WpColorManagerInterface, 3, func(c *wlturbo.Context) wlturbo.Proxy { return colormanagement.NewWpColorManager(c) }, false},
85 {colorrepresentation.WpColorRepresentationManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return colorrepresentation.NewWpColorRepresentationManager(c) }, false},
86 {committiming.WpCommitTimingManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return committiming.NewWpCommitTimingManager(c) }, false},
87 {contenttype.WpContentTypeManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return contenttype.NewWpContentTypeManager(c) }, false},
88 {datacontrol.ExtDataControlManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return datacontrol.NewExtDataControlManager(c) }, false},
89 {drmlease.WpDrmLeaseDeviceInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return drmlease.NewWpDrmLeaseDevice(c) }, true},
90 {extforeigntoplevel.ExtForeignToplevelListInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return extforeigntoplevel.NewExtForeignToplevelList(c) }, true},
91 {fifo.WpFifoManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return fifo.NewWpFifoManager(c) }, false},
92 {foreigntoplevel.ForeignToplevelManagerInterface, 3, func(c *wlturbo.Context) wlturbo.Proxy { return foreigntoplevel.NewForeignToplevelManager(c) }, false},
93 {idleinhibit.IdleInhibitManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return idleinhibit.NewIdleInhibitManager(c) }, false},
94 {idlenotify.ExtIdleNotifierInterface, 2, func(c *wlturbo.Context) wlturbo.Proxy { return idlenotify.NewExtIdleNotifier(c) }, false},
95 {imagecapturesource.ExtOutputImageCaptureSourceManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy {
96 return imagecapturesource.NewExtOutputImageCaptureSourceManager(c)
97 }, false},
98 {imagecapturesource.ExtForeignToplevelImageCaptureSourceManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy {
99 return imagecapturesource.NewExtForeignToplevelImageCaptureSourceManager(c)
100 }, true},
101 {imagecopycapture.ExtImageCopyCaptureManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return imagecopycapture.NewExtImageCopyCaptureManager(c) }, false},
102 {inputmethod.InputMethodManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return inputmethod.NewInputMethodManager(c) }, false},
103 {kdeserverdecoration.OrgKdeKwinServerDecorationManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy {
104 return kdeserverdecoration.NewOrgKdeKwinServerDecorationManager(c)
105 }, false},
106 {layershell.LayerShellInterface, 5, func(c *wlturbo.Context) wlturbo.Proxy { return layershell.NewLayerShell(c) }, false},
107 {outputmanagement.OutputManagerInterface, 4, func(c *wlturbo.Context) wlturbo.Proxy { return outputmanagement.NewOutputManager(c) }, false},
108 {outputpower.OutputPowerManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return outputpower.NewOutputPowerManager(c) }, false},
109 {pointerconstraints.PointerConstraintsInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return pointerconstraints.NewPointerConstraints(c) }, false},
110 {pointerwarp.WpPointerWarpInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return pointerwarp.NewWpPointerWarp(c) }, false},
111 {presentation.WpPresentationInterface, 2, func(c *wlturbo.Context) wlturbo.Proxy { return presentation.NewWpPresentation(c) }, false},
112 {primaryselection.PrimarySelectionDeviceManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return primaryselection.NewPrimarySelectionDeviceManager(c) }, false},
113 {relativepointer.RelativePointerManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return relativepointer.NewRelativePointerManager(c) }, false},
114 {screencopy.ScreencopyManagerInterface, 3, func(c *wlturbo.Context) wlturbo.Proxy { return screencopy.NewScreencopyManager(c) }, false},
115 {shortcutsinhibit.KeyboardShortcutsInhibitManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return shortcutsinhibit.NewKeyboardShortcutsInhibitManager(c) }, false},
116 {tearingcontrol.WpTearingControlManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return tearingcontrol.NewWpTearingControlManager(c) }, false},
117 {virtualkeyboard.VirtualKeyboardManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return virtualkeyboard.NewVirtualKeyboardManager(c) }, false},
118 {workspace.ExtWorkspaceManagerInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return workspace.NewExtWorkspaceManager(c) }, false},
119 {xdgactivation.XdgActivationInterface, 1, func(c *wlturbo.Context) wlturbo.Proxy { return xdgactivation.NewXdgActivation(c) }, false},
120 {xdgdecoration.ZxdgDecorationManagerInterface, 2, func(c *wlturbo.Context) wlturbo.Proxy { return xdgdecoration.NewZxdgDecorationManager(c) }, false},
121 {xdgoutput.ZxdgOutputManagerInterface, 3, func(c *wlturbo.Context) wlturbo.Proxy { return xdgoutput.NewZxdgOutputManager(c) }, false},
122 }
123}
124
125// waitGlobal blocks in Dispatch until iface is announced. wl_output is added
126// by the compositor after startup, so it can follow the first roundtrip. The
127// instance context ends a wait that never completes.
128func (h *headlessInstance) waitGlobal(t *testing.T, d *wlturbo.Display, iface string) {
129 t.Helper()
130 for {
131 if _, ok := d.Registry().FindGlobal(iface); ok {
132 return
133 }
134 if e := d.Dispatch(); e != nil {
135 t.Fatalf("waiting for %s: %v (ctx %v)\n%s", iface, e, h.ctx.Err(), h.Log())
136 }
137 }
138}
139
140// bindOne binds a global at its negotiated version and fails on any error.
141func (h *headlessInstance) bindOne(t *testing.T, d *wlturbo.Display, iface string, supported uint32, p wlturbo.Proxy) uint32 {
142 t.Helper()
143 v, e := d.Registry().BindNegotiated(iface, supported, p)
144 if e != nil {
145 t.Fatalf("bind %s: %v\n%s", iface, e, h.Log())
146 }
147 return v
148}
149
150func TestHeadlessExtensions(t *testing.T) {
151 h := startHeadless(t)
152
153 t.Run("bind announced globals", func(t *testing.T) {
154 d := h.connect(t)
155 h.waitGlobal(t, d, wayland.OutputInterface)
156 for _, b := range headlessBindings() {
157 g, ok := d.Registry().FindGlobal(b.iface)
158 if !ok {
159 if b.optional {
160 t.Logf("%s absent (optional: hardware, device or build dependent)", b.iface)
161 } else {
162 t.Errorf("%s not announced", b.iface)
163 }
164 continue
165 }
166 p := b.make(d.Context())
167 v, e := d.Registry().BindNegotiated(b.iface, b.supported, p)
168 if e != nil {
169 t.Errorf("bind %s: %v", b.iface, e)
170 continue
171 }
172 want := min(g.Version, b.supported)
173 if v != want || v == 0 {
174 t.Errorf("%s negotiated %d, announced %d supported %d", b.iface, v, g.Version, b.supported)
175 }
176 if pv, ok := p.(interface{ Version() uint32 }); ok && pv.Version() != v {
177 t.Errorf("%s proxy version %d, negotiated %d", b.iface, pv.Version(), v)
178 }
179 t.Logf("%s announced=%d supported=%d negotiated=%d", b.iface, g.Version, b.supported, v)
180 }
181 // Every bind request is validated by the compositor before this returns.
182 h.sync(t, d, "bind all")
183 // Report announced globals that no generated package covers.
184 known := map[string]bool{"wl_fixes": true}
185 for _, b := range headlessBindings() {
186 known[b.iface] = true
187 }
188 var extra []string
189 for _, g := range d.Registry().GetGlobals() {
190 if !known[g.Interface] {
191 extra = append(extra, g.Interface)
192 }
193 }
194 sort.Strings(extra)
195 t.Logf("announced without a client binding: %v", extra)
196 })
197
198 t.Run("output description", func(t *testing.T) {
199 d := h.connect(t)
200 h.waitGlobal(t, d, wayland.OutputInterface)
201 out := wayland.NewOutput(d.Context())
202 h.bindOne(t, d, wayland.OutputInterface, 4, out)
203 xm := xdgoutput.NewZxdgOutputManager(d.Context())
204 h.bindOne(t, d, xdgoutput.ZxdgOutputManagerInterface, 3, xm)
205 xo, e := xm.GetXdgOutput(out)
206 noerr(t, e)
207 var lw, lh int32
208 var name string
209 var wlDone, xdgDone bool
210 var modes int
211 out.OnMode(func(flags uint32, w, h, refresh int32) { modes++ })
212 out.OnDone(func() { wlDone = true })
213 xo.OnLogicalSize(func(w, h int32) { lw, lh = w, h })
214 xo.OnName(func(n string) { name = n })
215 xo.OnDone(func() { xdgDone = true })
216 // Registered after the bind request but before any event is read.
217 h.sync(t, d, "xdg_output")
218 if lw != 640 || lh != 480 || name == "" || modes == 0 || !(wlDone || xdgDone) {
219 t.Fatalf("logical=%dx%d name=%q modes=%d wl_done=%t xdg_done=%t\n%s", lw, lh, name, modes, wlDone, xdgDone, h.Log())
220 }
221 t.Logf("output %q logical %dx%d", name, lw, lh)
222
223 // wlr-output-management lists the same output as a head with modes.
224 om := outputmanagement.NewOutputManager(d.Context())
225 h.bindOne(t, d, outputmanagement.OutputManagerInterface, 4, om)
226 // Head events arrive in any order, so each head keeps its own record.
227 type head struct {
228 name string
229 modes int
230 }
231 var heads []*head
232 done := false
233 om.OnHead(func(hd *outputmanagement.OutputHead) {
234 rec := &head{}
235 heads = append(heads, rec)
236 hd.OnName(func(s string) { rec.name = s })
237 hd.OnMode(func(*outputmanagement.OutputMode) { rec.modes++ })
238 })
239 om.OnDone(func(serial uint32) { done = true })
240 h.sync(t, d, "output manager")
241 found := false
242 for _, rec := range heads {
243 found = found || (rec.name == name && rec.modes > 0)
244 }
245 if !done || !found {
246 t.Fatalf("done=%t want head %q with modes; heads:%s\n%s", done, name, func() (s string) {
247 for _, rec := range heads {
248 s += fmt.Sprintf(" %q/%d", rec.name, rec.modes)
249 }
250 return
251 }(), h.Log())
252 }
253 noerr(t, xo.Destroy())
254 noerr(t, om.Stop())
255 h.sync(t, d, "output teardown")
256 })
257
258 t.Run("decoration negotiation", func(t *testing.T) {
259 d := h.connect(t)
260 compositor := wayland.NewCompositor(d.Context())
261 h.bindOne(t, d, wayland.CompositorInterface, 6, compositor)
262 wm := xdgshell.NewXdgWmBase(d.Context())
263 h.bindOne(t, d, xdgshell.XdgWmBaseInterface, 6, wm)
264 dm := xdgdecoration.NewZxdgDecorationManager(d.Context())
265 h.bindOne(t, d, xdgdecoration.ZxdgDecorationManagerInterface, 2, dm)
266 kde := kdeserverdecoration.NewOrgKdeKwinServerDecorationManager(d.Context())
267 var kdeDefault uint32 = 99
268 kde.OnDefaultMode(func(m uint32) { kdeDefault = m })
269 h.bindOne(t, d, kdeserverdecoration.OrgKdeKwinServerDecorationManagerInterface, 1, kde)
270
271 surface, e := compositor.CreateSurface()
272 noerr(t, e)
273 xs, e := wm.GetXdgSurface(surface)
274 noerr(t, e)
275 top, e := xs.GetToplevel()
276 noerr(t, e)
277 deco, e := dm.GetToplevelDecoration(top)
278 noerr(t, e)
279 var modes []uint32
280 deco.OnConfigure(func(m uint32) { modes = append(modes, m) })
281 // A client asking for client-side is answered with the compositor's server-side mode.
282 noerr(t, deco.SetMode(xdgdecoration.MODE_CLIENT_SIDE))
283 xs.OnConfigure(func(serial uint32) { noerr(t, xs.AckConfigure(serial)) })
284 // The initial empty commit lets the compositor send xdg_surface.configure.
285 noerr(t, surface.Commit())
286 h.sync(t, d, "decoration")
287 if len(modes) == 0 || modes[len(modes)-1] != xdgdecoration.MODE_SERVER_SIDE {
288 t.Fatalf("xdg decoration modes %v\n%s", modes, h.Log())
289 }
290 kdeDeco, e := kde.Create(surface)
291 noerr(t, e)
292 var kdeMode uint32 = 99
293 kdeDeco.OnMode(func(m uint32) { kdeMode = m })
294 h.sync(t, d, "kde decoration")
295 if kdeDefault != kdeserverdecoration.ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER || kdeMode != kdeserverdecoration.ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER {
296 t.Fatalf("kde default=%d mode=%d", kdeDefault, kdeMode)
297 }
298 noerr(t, kdeDeco.Release())
299 noerr(t, deco.Destroy())
300 noerr(t, top.Destroy())
301 noerr(t, xs.Destroy())
302 noerr(t, surface.Destroy())
303 h.sync(t, d, "decoration teardown")
304 })
305
306 t.Run("workspaces", func(t *testing.T) {
307 d := h.connect(t)
308 wsm := workspace.NewExtWorkspaceManager(d.Context())
309 groups, workspaces, done, finished := 0, 0, false, false
310 names := map[string]bool{}
311 wsm.OnWorkspaceGroup(func(*workspace.ExtWorkspaceGroupHandle) { groups++ })
312 wsm.OnWorkspace(func(w *workspace.ExtWorkspaceHandle) {
313 workspaces++
314 w.OnName(func(n string) { names[n] = true })
315 })
316 wsm.OnDone(func() { done = true })
317 wsm.OnFinished(func() { finished = true })
318 h.bindOne(t, d, workspace.ExtWorkspaceManagerInterface, 1, wsm)
319 h.sync(t, d, "workspace snapshot")
320 if !done || groups == 0 || workspaces == 0 || len(names) == 0 {
321 t.Fatalf("done=%t groups=%d workspaces=%d names=%v\n%s", done, groups, workspaces, names, h.Log())
322 }
323 t.Logf("workspaces: groups=%d workspaces=%d names=%v", groups, workspaces, names)
324 // stop is answered by finished; wait for it with the blocking dispatch.
325 noerr(t, wsm.Stop())
326 for !finished {
327 if e := d.Dispatch(); e != nil {
328 t.Fatalf("waiting for finished: %v\n%s", e, h.Log())
329 }
330 }
331 })
332
333 t.Run("color capabilities", func(t *testing.T) {
334 d := h.connect(t)
335 cm := colormanagement.NewWpColorManager(d.Context())
336 intents, features, tfs, prims := map[uint32]bool{}, map[uint32]bool{}, map[uint32]bool{}, map[uint32]bool{}
337 cmDone := false
338 cm.OnSupportedIntent(func(v uint32) { intents[v] = true })
339 cm.OnSupportedFeature(func(v uint32) { features[v] = true })
340 cm.OnSupportedTfNamed(func(v uint32) { tfs[v] = true })
341 cm.OnSupportedPrimariesNamed(func(v uint32) { prims[v] = true })
342 cm.OnDone(func() { cmDone = true })
343 version := h.bindOne(t, d, colormanagement.WpColorManagerInterface, 3, cm)
344
345 cr := colorrepresentation.NewWpColorRepresentationManager(d.Context())
346 alpha, crDone := map[uint32]bool{}, false
347 cr.OnSupportedAlphaMode(func(v uint32) { alpha[v] = true })
348 cr.OnDone(func() { crDone = true })
349 h.bindOne(t, d, colorrepresentation.WpColorRepresentationManagerInterface, 1, cr)
350 h.sync(t, d, "color capabilities")
351
352 if !cmDone || !intents[colormanagement.RENDER_INTENT_PERCEPTUAL] || !features[colormanagement.FEATURE_PARAMETRIC] ||
353 !tfs[colormanagement.TRANSFER_FUNCTION_SRGB] || !prims[colormanagement.PRIMARIES_SRGB] {
354 t.Fatalf("color manager v%d done=%t intents=%v features=%v tf=%v primaries=%v\n%s", version, cmDone, intents, features, tfs, prims, h.Log())
355 }
356 if !crDone || len(alpha) == 0 {
357 t.Fatalf("color representation done=%t alpha=%v", crDone, alpha)
358 }
359 t.Logf("color v%d intents=%v features=%v tf=%v primaries=%v alpha=%v", version, intents, features, tfs, prims, alpha)
360 })
361
362 t.Run("presentation clock", func(t *testing.T) {
363 d := h.connect(t)
364 p := presentation.NewWpPresentation(d.Context())
365 clock, got := uint32(0), false
366 p.OnClockId(func(id uint32) { clock, got = id, true })
367 h.bindOne(t, d, presentation.WpPresentationInterface, 2, p)
368 h.sync(t, d, "presentation clock")
369 // An empty commit produces no frame, so only the clock is asserted.
370 if !got || clock != unix.CLOCK_MONOTONIC {
371 t.Fatalf("clock_id got=%t %d, want CLOCK_MONOTONIC", got, clock)
372 }
373 noerr(t, p.Destroy())
374 h.sync(t, d, "presentation teardown")
375 })
376
377 t.Run("layer surface configure", func(t *testing.T) {
378 d := h.connect(t)
379 h.waitGlobal(t, d, wayland.OutputInterface)
380 compositor := wayland.NewCompositor(d.Context())
381 h.bindOne(t, d, wayland.CompositorInterface, 6, compositor)
382 shell := layershell.NewLayerShell(d.Context())
383 h.bindOne(t, d, layershell.LayerShellInterface, 5, shell)
384 surface, e := compositor.CreateSurface()
385 noerr(t, e)
386 ls, e := shell.GetLayerSurface(surface, nil, layershell.LAYER_TOP, "wlturbo-test")
387 noerr(t, e)
388 noerr(t, ls.SetAnchor(layershell.ANCHOR_TOP|layershell.ANCHOR_LEFT|layershell.ANCHOR_RIGHT))
389 noerr(t, ls.SetSize(0, 30))
390 var serial, w, hgt uint32
391 configured := false
392 ls.OnConfigure(func(s, cw, ch uint32) { serial, w, hgt, configured = s, cw, ch, true })
393 // The initial empty commit asks the compositor for the first configure.
394 noerr(t, surface.Commit())
395 h.sync(t, d, "layer configure")
396 if !configured || w != 640 || hgt != 30 {
397 t.Fatalf("configured=%t %dx%d\n%s", configured, w, hgt, h.Log())
398 }
399 noerr(t, ls.AckConfigure(serial))
400 noerr(t, ls.Destroy())
401 noerr(t, surface.Destroy())
402 h.sync(t, d, "layer teardown")
403 })
404
405 t.Run("relative pointer object", func(t *testing.T) {
406 d := h.connect(t)
407 seat := wayland.NewSeat(d.Context())
408 var caps uint32
409 seat.OnCapabilities(func(c uint32) { caps = c })
410 h.bindOne(t, d, wayland.SeatInterface, 8, seat)
411 rm := relativepointer.NewRelativePointerManager(d.Context())
412 h.bindOne(t, d, relativepointer.RelativePointerManagerInterface, 1, rm)
413 h.sync(t, d, "seat capabilities")
414 if caps&wayland.CAPABILITY_POINTER == 0 {
415 t.Skipf("seat has no pointer (caps %d); headless input capability dependent", caps)
416 }
417 ptr, e := seat.GetPointer()
418 noerr(t, e)
419 rp, e := rm.GetRelativePointer(ptr)
420 noerr(t, e)
421 // Motion needs pointer focus on a mapped surface, which an empty
422 // commit cannot provide; creation and teardown must still be valid.
423 rp.OnRelativeMotion(func(uint32, uint32, wlturbo.Fixed, wlturbo.Fixed, wlturbo.Fixed, wlturbo.Fixed) {})
424 h.sync(t, d, "relative pointer")
425 noerr(t, rp.Destroy())
426 noerr(t, ptr.Release())
427 noerr(t, rm.Destroy())
428 h.sync(t, d, "relative pointer teardown")
429 })
430}