1package core
2
3import (
4 "context"
5 "reflect"
6 "slices"
7 "testing"
8
9 "github.com/bnema/neferwl/internal/ports"
10)
11
12// While an exclusion is live, the set of layers kept over a fullscreen window
13// is built once per change of the attached set and shared by every screen: a
14// publish allocates nothing for it.
15func TestExclusionKeepIsSharedAndBuiltOncePerChange(t *testing.T) {
16 c, _, _ := hiddenCaptureCommands(t)
17 c.addScreen(ports.OutputInfo{Name: "B", Width: 100, Height: 100})
18 c.captureOpen(ports.CaptureSessionOpen{ID: 1, Output: "A"})
19 c.captureExclusionBegin(ports.CaptureExclusionBegin{Session: 1})
20 c.captureExclusionLayer(ports.CaptureExclusionLayer{Session: 1, Layer: 10, Attached: true})
21 c.captureExclusionLayer(ports.CaptureExclusionLayer{Session: 1, Layer: 11, Attached: true})
22 e := c.capt.excl
23 keep := c.exclusionKeep(e)
24 if len(keep) != 2 || !keep[10] || !keep[11] {
25 t.Fatalf("keep %v", keep)
26 }
27 if n := testing.AllocsPerRun(100, func() { _ = c.exclusionKeep(e) }); n != 0 {
28 t.Fatalf("%v allocations for an unchanged exclusion", n)
29 }
30 if err := c.publish(context.Background()); err != nil {
31 t.Fatal(err)
32 }
33 for _, sc := range c.screens {
34 if reflect.ValueOf(sc.capture).Pointer() != reflect.ValueOf(keep).Pointer() {
35 t.Fatalf("screen %s has its own keep set", sc.name())
36 }
37 }
38 // A change of the attached set builds a new one, once.
39 c.captureExclusionLayer(ports.CaptureExclusionLayer{Session: 1, Layer: 11})
40 if k := c.exclusionKeep(e); len(k) != 1 || !k[10] {
41 t.Fatalf("keep after detach %v", k)
42 }
43 // An ended exclusion keeps nothing over a fullscreen window.
44 c.screens[0].layers = []ports.LayerSurface{{ID: 10}}
45 c.captureExclusionEnd(1)
46 if c.capt.excl == nil || !c.capt.excl.ended || !slices.Equal(c.capt.excl.retained, []WindowID{10}) {
47 t.Fatalf("ended exclusion %+v", c.capt.excl)
48 }
49 if k := c.exclusionKeep(c.capt.excl); k != nil {
50 t.Fatalf("keep of an ended exclusion %v", k)
51 }
52 // With no layer listed any more it is gone.
53 c.screens[0].layers = nil
54 c.pruneRetained()
55 if c.capt.excl != nil {
56 t.Fatalf("exclusion survived its last listed layer: %+v", c.capt.excl)
57 }
58}
59
60// sameCaptureState compares every field of the session state, by hand. A new
61// field must be compared there, or a change of it is never sent to wayland.
62func TestSameCaptureStateComparesEveryField(t *testing.T) {
63 const compared = 10 // ID Output Rect Workspace Hidden Active Reason Exclusion Revision Layers
64 if n := reflect.TypeFor[ports.CaptureSessionState]().NumField(); n != compared {
65 t.Fatalf("ports.CaptureSessionState has %d fields, sameCaptureState compares %d: compare the new field in sameCaptureState (capture.go), then update this count", n, compared)
66 }
67 base := ports.CaptureSessionState{}
68 for _, v := range []ports.CaptureSessionState{
69 {ID: 1}, {Output: "A"}, {Rect: Rect{W: 1}}, {Workspace: 1}, {Hidden: true}, {Active: true},
70 {Reason: ports.CaptureReasonOutputGone}, {Exclusion: true}, {Revision: 1}, {Layers: []WindowID{1}},
71 } {
72 if sameCaptureState(base, v) {
73 t.Fatalf("a change of %+v is not seen", v)
74 }
75 }
76}