1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/xdgactivation"
8 "github.com/bnema/neferwl/internal/ports"
9 "github.com/bnema/wlturbo"
10)
11
12// tokenProxy receives an xdg_activation_token_v1.done token.
13type tokenProxy struct {
14 wlturbo.BaseProxy
15 tokens chan string
16}
17
18func (p *tokenProxy) Dispatch(e *wlturbo.Event) { p.tokens <- e.String() }
19
20// newToken commits a token request, with a serial and seat when given,
21// and returns the token.
22func newToken(t *testing.T, c *wlturbo.Display, manager uint32, serialSeat ...uint32) string {
23 t.Helper()
24 id := c.AllocateID()
25 p := &tokenProxy{tokens: make(chan string, 1)}
26 p.SetID(id)
27 registerWireProxy(c, p)
28 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestGetActivationToken, id)
29 if len(serialSeat) == 2 {
30 requestProtocol(t, c, id, xdgactivation.ActivationTokenV1RequestSetSerial, serialSeat[0], serialSeat[1])
31 }
32 requestProtocol(t, c, id, xdgactivation.ActivationTokenV1RequestCommit)
33 for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
34 if err := c.Roundtrip(); err != nil {
35 t.Fatal(err)
36 }
37 select {
38 case tok := <-p.tokens:
39 return tok
40 default:
41 }
42 }
43 t.Fatal("no token")
44 return ""
45}
46
47func activated(t *testing.T, events <-chan ports.ClientEvent, want ports.WindowID) {
48 t.Helper()
49 deadline := time.After(2 * time.Second)
50 for {
51 select {
52 case ev := <-events:
53 if a, ok := ev.(ports.WindowActivate); ok {
54 if a.ID != want {
55 t.Fatalf("activated %d, want %d", a.ID, want)
56 }
57 return
58 }
59 case <-deadline:
60 t.Fatal("no activation")
61 }
62 }
63}
64
65func noActivation(t *testing.T, c *wlturbo.Display, events <-chan ports.ClientEvent) {
66 t.Helper()
67 if err := c.Roundtrip(); err != nil {
68 t.Fatal(err)
69 }
70 deadline := time.After(100 * time.Millisecond)
71 for {
72 select {
73 case ev := <-events:
74 if _, ok := ev.(ports.WindowActivate); ok {
75 t.Fatal("activated with an invalid token")
76 }
77 case <-deadline:
78 return
79 }
80 }
81}
82
83func TestActivationNeedsFocusedRequester(t *testing.T) {
84 s, events, commands, dir := lifecycleServer(t)
85 c := protocolClient(t, s, dir)
86 mapWindow := surfaceMapper(t, c, events)
87 first, _, _ := mapWindow()
88 second, surf, _ := mapWindow()
89 manager := bindProtocol(t, c, "xdg_activation_v1")
90
91 // Without focus the token is inert.
92 token := newToken(t, c, manager)
93 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, token, surf)
94 noActivation(t, c, events)
95
96 // The focused client's token activates, once.
97 commands <- ports.FocusWindow{ID: first.ID}
98 waitFocus(t, s, first.ID)
99 token = newToken(t, c, manager)
100 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, token, surf)
101 activated(t, events, second.ID)
102 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, token, surf)
103 noActivation(t, c, events)
104
105 // Unknown tokens are ignored.
106 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, "not-a-token", surf)
107 noActivation(t, c, events)
108}
109
110// Another client's token is inert while one client has the focus; the
111// focused client may hand its token to activate another client's window.
112func TestActivationAcrossClients(t *testing.T) {
113 s, events, commands, dir := lifecycleServer(t)
114 a := protocolClient(t, s, dir)
115 winA, _, _ := surfaceMapper(t, a, events)()
116 b := protocolClient(t, s, dir)
117 winB, surfB, _ := surfaceMapper(t, b, events)()
118 managerA := bindProtocol(t, a, "xdg_activation_v1")
119 managerB := bindProtocol(t, b, "xdg_activation_v1")
120 commands <- ports.FocusWindow{ID: winA.ID}
121 waitFocus(t, s, winA.ID)
122 token := newToken(t, b, managerB)
123 requestProtocol(t, b, managerB, xdgactivation.ActivationV1RequestActivate, token, surfB)
124 noActivation(t, b, events)
125 token = newToken(t, a, managerA)
126 requestProtocol(t, b, managerB, xdgactivation.ActivationV1RequestActivate, token, surfB)
127 activated(t, events, winB.ID)
128
129 // A token dies when the focus leaves its client.
130 token = newToken(t, a, managerA)
131 commands <- ports.FocusWindow{ID: winB.ID}
132 waitFocus(t, s, winB.ID)
133 requestProtocol(t, b, managerB, xdgactivation.ActivationV1RequestActivate, token, surfB)
134 noActivation(t, b, events)
135}
136
137// A press serial earns a token only while the press is the latest user
138// action: after a focus change it is stale.
139func TestActivationPressSerial(t *testing.T) {
140 s, events, commands, dir := lifecycleServer(t)
141 c := protocolClient(t, s, dir)
142 w, surf, _ := surfaceMapper(t, c, events)()
143 manager := bindProtocol(t, c, "xdg_activation_v1")
144 seat := bindProtocol(t, c, "wl_seat")
145 registerProtocol(t, c, seat)
146 press := func() uint32 {
147 var serial uint32
148 done := make(chan struct{})
149 s.display.Do(func() {
150 s.serial++
151 s.seat.press, s.seat.pressClient, s.seat.pressAt = s.serial, s.windows[w.ID].xdg.resource.Client(), time.Now()
152 serial = s.serial
153 close(done)
154 })
155 <-done
156 return serial
157 }
158 serial := press()
159 token := newToken(t, c, manager, serial, seat)
160 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, token, surf)
161 activated(t, events, w.ID)
162 // A wrong serial earns nothing.
163 token = newToken(t, c, manager, serial+100, seat)
164 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, token, surf)
165 noActivation(t, c, events)
166 // The focus moved after the press: stale.
167 commands <- ports.FocusWindow{ID: w.ID}
168 commands <- ports.FocusWindow{}
169 waitFocus(t, s, 0)
170 token = newToken(t, c, manager, serial, seat)
171 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestActivate, token, surf)
172 noActivation(t, c, events)
173}
174
175func TestActivationTokenCommittedTwice(t *testing.T) {
176 s, _, _, dir := lifecycleServer(t)
177 c := protocolClient(t, s, dir)
178 manager := bindProtocol(t, c, "xdg_activation_v1")
179 id := c.AllocateID()
180 registerProtocol(t, c, id)
181 requestProtocol(t, c, manager, xdgactivation.ActivationV1RequestGetActivationToken, id)
182 requestProtocol(t, c, id, xdgactivation.ActivationTokenV1RequestCommit)
183 requestProtocol(t, c, id, xdgactivation.ActivationTokenV1RequestCommit)
184 expectProtocolError(t, c, id, uint32(xdgactivation.ActivationTokenV1ErrorAlreadyUsed))
185}
186
187// waitFocus waits until the display goroutine has applied a focus change.
188func waitFocus(t *testing.T, s *Server, id ports.WindowID) {
189 t.Helper()
190 for deadline := time.Now().Add(time.Second); time.Now().Before(deadline); time.Sleep(time.Millisecond) {
191 done := make(chan ports.WindowID, 1)
192 s.display.Do(func() { done <- s.seat.focused })
193 if <-done == id {
194 return
195 }
196 }
197 t.Fatal("focus not applied", id)
198}