detached 5004296

headless_extensions_test.go RawHistoryFolder

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}

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 1

↑↓ moveenter openesc close