activation_test.go RawHistoryFolder

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}

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