v0.4.0 b6be3a8

outputs_test.go RawHistoryFolder

1package wayland
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/go-wayland-bindings/server/wayland"
8	"github.com/bnema/go-wayland-bindings/server/wlrlayershell"
9	"github.com/bnema/neferwl/internal/ports"
10	"github.com/bnema/wlturbo"
11)
12
13var second = ports.OutputPlacement{Info: ports.OutputInfo{Name: "HEADLESS-2", Width: 1280, Height: 1024}, X: 1920, Width: 1280, Height: 1024, Scale: 1}
14
15func TestOutputHDRConfirmedState(t *testing.T) {
16	s := &Server{}
17	const name = "DP-1"
18	if _, ok := s.hdrOutputs[name]; ok {
19		t.Fatal("output must start SDR")
20	}
21	want := ports.OutputHDR{MaxLuminance: 1000, MaxFrameAverage: 400, MinLuminance: 0.005}
22	s.setOutputHDR(ports.OutputFormats{Output: name, HDR: &want})
23	if got := s.hdrOutputs[name]; got != want {
24		t.Fatalf("HDR report: %+v", got)
25	}
26	s.setOutputHDR(ports.OutputFormats{Output: name})
27	if _, ok := s.hdrOutputs[name]; ok {
28		t.Fatal("SDR fallback must clear HDR")
29	}
30}
31
32// outputCount counts wl_output globals the client sees.
33func outputCount(c *wlturbo.Display) int {
34	n := 0
35	for _, g := range c.Registry().GetGlobals() {
36		if g.Interface == "wl_output" {
37			n++
38		}
39	}
40	return n
41}
42
43// waitOutputs round-trips until the client sees n wl_output globals.
44func waitOutputs(t *testing.T, c *wlturbo.Display, n int) {
45	t.Helper()
46	for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
47		if err := c.Roundtrip(); err != nil {
48			t.Fatal(err)
49		}
50		if outputCount(c) == n {
51			return
52		}
53		time.Sleep(5 * time.Millisecond)
54	}
55	t.Fatalf("wl_output globals: %d, want %d", outputCount(c), n)
56}
57
58type closedProxy struct {
59	wlturbo.BaseProxy
60	configured chan [3]uint32
61	closed     chan struct{}
62}
63
64func (p *closedProxy) Dispatch(e *wlturbo.Event) {
65	switch e.Opcode {
66	case uint16(wlrlayershell.ZwlrLayerSurfaceV1EventConfigure):
67		p.configured <- [3]uint32{e.Uint32(), e.Uint32(), e.Uint32()}
68	case uint16(wlrlayershell.ZwlrLayerSurfaceV1EventClosed):
69		close(p.closed)
70	}
71}
72
73// wl_fixes: a client acknowledges a removed output global and destroys its
74// registry; a bogus acknowledgement is a protocol error.
75func TestFixesAckAndDestroyRegistry(t *testing.T) {
76	s, _, commands, dir := lifecycleServer(t)
77	c := protocolClient(t, s, dir)
78	g, ok := c.Registry().FindGlobal("wl_fixes")
79	if !ok || g.Version != 2 {
80		t.Fatalf("wl_fixes %+v", g)
81	}
82	fixes := bindVersion(t, c, "wl_fixes", 2)
83	commands <- ports.SetOutputs{Outputs: ports.Layout{testOutputs[0], second}, Focused: "HEADLESS-1"}
84	waitOutputs(t, c, 2)
85	var name uint32
86	for n, g := range c.Registry().GetGlobals() {
87		if g.Interface == "wl_output" && n > name {
88			name = n
89		}
90	}
91	commands <- ports.SetOutputs{Outputs: ports.Layout{testOutputs[0]}, Focused: "HEADLESS-1"}
92	waitOutputs(t, c, 1)
93	requestProtocol(t, c, fixes, wayland.FixesRequestAckGlobalRemove, c.Registry().ID(), name)
94	requestProtocol(t, c, fixes, wayland.FixesRequestDestroyRegistry, c.Registry().ID())
95	// wl_fixes destroys another object, not the request's own proxy.
96	c.Context().UnregisterID(c.Registry().ID())
97	if err := c.Roundtrip(); err != nil {
98		t.Fatalf("valid ack and destroy: %v", err)
99	}
100
101	// A global that is still announced cannot be acknowledged.
102	c2 := protocolClient(t, s, dir)
103	fixes2 := bindVersion(t, c2, "wl_fixes", 2)
104	out, _ := c2.Registry().FindGlobal("wl_output")
105	requestProtocol(t, c2, fixes2, wayland.FixesRequestAckGlobalRemove, c2.Registry().ID(), out.Name)
106	if err := c2.Roundtrip(); err == nil {
107		t.Fatal("ack of a live global accepted")
108	}
109}
110
111func TestOutputHotplugAndLayerOutput(t *testing.T) {
112	s, _, commands, dir := lifecycleServer(t)
113	c := protocolClient(t, s, dir)
114	if outputCount(c) != 1 {
115		t.Fatal(outputCount(c))
116	}
117	commands <- ports.SetOutputs{Outputs: ports.Layout{testOutputs[0], second}, Focused: "HEADLESS-1"}
118	waitOutputs(t, c, 2)
119	// A layer surface on the second output is sized from it.
120	var name uint32
121	for n, g := range c.Registry().GetGlobals() {
122		if g.Interface == "wl_output" && (name == 0 || n > name) {
123			name = n
124		}
125	}
126	out, err := bindWireID(c, name, "wl_output", 4)
127	if err != nil {
128		t.Fatal(err)
129	}
130	registerProtocol(t, c, out)
131	comp := bindProtocol(t, c, "wl_compositor")
132	shell := bindProtocol(t, c, "zwlr_layer_shell_v1")
133	surf := c.AllocateID()
134	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf)
135	registerProtocol(t, c, surf)
136	layer := c.AllocateID()
137	proxy := &closedProxy{configured: make(chan [3]uint32, 2), closed: make(chan struct{})}
138	proxy.SetID(layer)
139	registerWireProxy(c, proxy)
140	requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, layer, surf, out, uint32(ports.LayerTop), "bar")
141	requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetAnchor, uint32(13))
142	requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetSize, uint32(0), uint32(30))
143	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
144	if err := c.Roundtrip(); err != nil {
145		t.Fatal(err)
146	}
147	select {
148	case cfg := <-proxy.configured:
149		if cfg[1] != 1280 {
150			t.Fatalf("configure %v", cfg)
151		}
152	case <-time.After(2 * time.Second):
153		t.Fatal("configure timeout")
154	}
155	// Unplugged: the global goes away and the layer surface is closed.
156	commands <- ports.SetOutputs{Outputs: ports.Layout{testOutputs[0]}, Focused: "HEADLESS-1"}
157	waitOutputs(t, c, 1)
158	select {
159	case <-proxy.closed:
160	case <-time.After(2 * time.Second):
161		t.Fatal("layer not closed")
162	}
163}

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 25

Tags 10

↑↓ moveenter openesc close