1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8 "github.com/bnema/purego-libwayland/protocol/wayland"
9 "github.com/bnema/purego-libwayland/protocol/wlrlayershell"
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
15// outputCount counts wl_output globals the client sees.
16func outputCount(c *wlturbo.Display) int {
17 n := 0
18 for _, g := range c.Registry().GetGlobals() {
19 if g.Interface == "wl_output" {
20 n++
21 }
22 }
23 return n
24}
25
26// waitOutputs round-trips until the client sees n wl_output globals.
27func waitOutputs(t *testing.T, c *wlturbo.Display, n int) {
28 t.Helper()
29 for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
30 if err := c.Roundtrip(); err != nil {
31 t.Fatal(err)
32 }
33 if outputCount(c) == n {
34 return
35 }
36 time.Sleep(5 * time.Millisecond)
37 }
38 t.Fatalf("wl_output globals: %d, want %d", outputCount(c), n)
39}
40
41type closedProxy struct {
42 wlturbo.BaseProxy
43 configured chan [3]uint32
44 closed chan struct{}
45}
46
47func (p *closedProxy) Dispatch(e *wlturbo.Event) {
48 switch e.Opcode {
49 case uint16(wlrlayershell.ZwlrLayerSurfaceV1EventConfigure):
50 p.configured <- [3]uint32{e.Uint32(), e.Uint32(), e.Uint32()}
51 case uint16(wlrlayershell.ZwlrLayerSurfaceV1EventClosed):
52 close(p.closed)
53 }
54}
55
56func TestOutputHotplugAndLayerOutput(t *testing.T) {
57 s, _, commands, dir := lifecycleServer(t)
58 c := protocolClient(t, s, dir)
59 if outputCount(c) != 1 {
60 t.Fatal(outputCount(c))
61 }
62 commands <- ports.SetOutputs{Outputs: ports.Layout{testOutputs[0], second}, Focused: "HEADLESS-1"}
63 waitOutputs(t, c, 2)
64 // A layer surface on the second output is sized from it.
65 var name uint32
66 for n, g := range c.Registry().GetGlobals() {
67 if g.Interface == "wl_output" && (name == 0 || n > name) {
68 name = n
69 }
70 }
71 out, err := c.Registry().BindID(name, "wl_output", 4)
72 if err != nil {
73 t.Fatal(err)
74 }
75 registerProtocol(t, c, out)
76 comp := bindProtocol(t, c, "wl_compositor")
77 shell := bindProtocol(t, c, "zwlr_layer_shell_v1")
78 surf := c.AllocateID()
79 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf)
80 registerProtocol(t, c, surf)
81 layer := c.AllocateID()
82 proxy := &closedProxy{configured: make(chan [3]uint32, 2), closed: make(chan struct{})}
83 proxy.SetID(layer)
84 c.Context().Register(proxy)
85 requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, layer, surf, out, uint32(ports.LayerTop), "bar")
86 requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetAnchor, uint32(13))
87 requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetSize, uint32(0), uint32(30))
88 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
89 if err := c.Roundtrip(); err != nil {
90 t.Fatal(err)
91 }
92 select {
93 case cfg := <-proxy.configured:
94 if cfg[1] != 1280 {
95 t.Fatalf("configure %v", cfg)
96 }
97 case <-time.After(2 * time.Second):
98 t.Fatal("configure timeout")
99 }
100 // Unplugged: the global goes away and the layer surface is closed.
101 commands <- ports.SetOutputs{Outputs: ports.Layout{testOutputs[0]}, Focused: "HEADLESS-1"}
102 waitOutputs(t, c, 1)
103 select {
104 case <-proxy.closed:
105 case <-time.After(2 * time.Second):
106 t.Fatal("layer not closed")
107 }
108}