detached 1a6c6c9

args_test.go RawHistoryFolder

1package e2e_test
2
3import (
4	"bytes"
5	"errors"
6	"testing"
7	"time"
8
9	"github.com/bnema/go-wayland-bindings/server/wayland"
10	"github.com/bnema/purego-libwayland/server"
11	"github.com/bnema/wlturbo"
12	"golang.org/x/sys/unix"
13)
14
15func receive[T any](t *testing.T, ch <-chan T) T {
16	t.Helper()
17	select {
18	case v := <-ch:
19		return v
20	case <-time.After(3 * time.Second):
21		t.Fatal("timed out waiting for event")
22		var zero T
23		return zero
24	}
25}
26
27type argsProxy struct {
28	wlturbo.BaseProxy
29	received chan argsEvent
30	kind     string
31}
32type argsEvent struct {
33	text  string
34	data  []byte
35	fixed wlturbo.Fixed
36	fd    int
37}
38
39func (p *argsProxy) Dispatch(e *wlturbo.Event) {
40	switch p.kind {
41	case "seat":
42		p.received <- argsEvent{text: e.String()}
43	case "keyboard":
44		if e.Opcode == uint16(wayland.KeyboardEventKeymap) {
45			_ = e.Uint32()
46			p.received <- argsEvent{fd: int(e.Fd())}
47		} else if e.Opcode == uint16(wayland.KeyboardEventEnter) {
48			_ = e.Uint32()
49			_ = e.Uint32()
50			p.received <- argsEvent{data: e.Array()}
51		}
52	case "pointer":
53		_ = e.Uint32()
54		p.received <- argsEvent{fixed: e.Fixed()}
55	}
56}
57
58type sourceHandler struct{ requests chan string }
59
60func (h sourceHandler) Offer(_ *wayland.DataSource, mimeType string) { h.requests <- mimeType }
61func (sourceHandler) Destroy(*wayland.DataSource)                    {}
62func (sourceHandler) SetActions(*wayland.DataSource, uint32)         {}
63
64type managerHandler struct {
65	requests chan string
66	t        *testing.T
67}
68
69func (h managerHandler) CreateDataSource(self *wayland.DataDeviceManager, id uint32) {
70	if _, err := wayland.NewDataSource(self.Client(), 1, id, sourceHandler{h.requests}); err != nil {
71		h.t.Error(err)
72	}
73}
74func (managerHandler) GetDataDevice(*wayland.DataDeviceManager, uint32, *wayland.Seat) {}
75func (managerHandler) Release(*wayland.DataDeviceManager)                              {}
76
77type shmHandler struct {
78	requests chan string
79	t        *testing.T
80}
81
82func (h shmHandler) CreatePool(self *wayland.Shm, id uint32, fd int, size int32) {
83	defer unix.Close(fd)
84	h.requests <- "pool"
85	if _, err := wayland.NewShmPool(self.Client(), 1, id, poolHandler{}); err != nil {
86		h.t.Error(err)
87	}
88}
89func (shmHandler) Release(*wayland.Shm) {}
90
91type poolHandler struct{}
92
93func (poolHandler) CreateBuffer(*wayland.ShmPool, uint32, int32, int32, int32, int32, uint32) {}
94func (poolHandler) Destroy(*wayland.ShmPool)                                                  {}
95func (poolHandler) Resize(self *wayland.ShmPool, size int32)                                  { self.PostError(7, "bad % request") }
96
97type seatHandler struct{ t *testing.T }
98
99type noPointerRequests struct{}
100
101func (noPointerRequests) SetCursor(*wayland.Pointer, uint32, *wayland.Surface, int32, int32) {}
102func (noPointerRequests) Release(*wayland.Pointer)                                           {}
103
104type noKeyboardRequests struct{}
105
106func (noKeyboardRequests) Release(*wayland.Keyboard) {}
107
108func (h seatHandler) GetPointer(self *wayland.Seat, id uint32) {
109	p, err := wayland.NewPointer(self.Client(), 1, id, noPointerRequests{})
110	if err != nil {
111		h.t.Error(err)
112		return
113	}
114	p.SendMotion(42, server.FixedFromFloat(-1.5), 0)
115}
116func (h seatHandler) GetKeyboard(self *wayland.Seat, id uint32) {
117	k, err := wayland.NewKeyboard(self.Client(), 1, id, noKeyboardRequests{})
118	if err != nil {
119		h.t.Error(err)
120		return
121	}
122	fds := []int{0, 0}
123	if err := unix.Pipe(fds); err != nil {
124		h.t.Error(err)
125		return
126	}
127	defer unix.Close(fds[0])
128	defer unix.Close(fds[1])
129	if _, err := unix.Write(fds[1], []byte("fd contents")); err != nil {
130		h.t.Error(err)
131		return
132	}
133	k.SendKeymap(1, fds[0], 11)
134	k.SendEnter(1, wayland.WrapSurface(self.Resource), []byte{1, 2, 3})
135}
136func (seatHandler) GetTouch(*wayland.Seat, uint32) {}
137func (seatHandler) Release(*wayland.Seat)          {}
138func TestArgumentRoundtripAndPostError(t *testing.T) {
139	socket, d, _ := startServer(t)
140	requests := make(chan string, 1)
141	stringsReceived := make(chan string, 1)
142	if !d.Do(func() {
143		if err := wayland.NewDataDeviceManagerGlobal(d, 1, func(c server.Client, v, id uint32) {
144			if _, e := wayland.NewDataDeviceManager(c, int32(v), id, managerHandler{stringsReceived, t}); e != nil {
145				t.Error(e)
146			}
147		}); err != nil {
148			t.Error(err)
149		}
150		if err := wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
151			if _, e := wayland.NewShm(c, int32(v), id, shmHandler{requests, t}); e != nil {
152				t.Error(e)
153			}
154		}); err != nil {
155			t.Error(err)
156		}
157		if err := wayland.NewSeatGlobal(d, 1, func(c server.Client, v, id uint32) {
158			s, e := wayland.NewSeat(c, int32(v), id, seatHandler{t})
159			if e != nil {
160				t.Error(e)
161				return
162			}
163			s.SendName("hello")
164		}); err != nil {
165			t.Error(err)
166		}
167	}) {
168		t.Fatal("display stopped")
169	}
170	c, err := wlturbo.Connect(socket)
171	must(t, err)
172	defer c.Close()
173	must(t, c.Roundtrip())
174	bind := func(name string) (uint32, *argsProxy) {
175		t.Helper()
176		g, ok := c.Registry().FindGlobal(name)
177		if !ok {
178			t.Fatalf("%s missing", name)
179		}
180		id, e := c.Registry().BindID(g.Name, g.Interface, 1)
181		must(t, e)
182		p := &argsProxy{received: make(chan argsEvent, 4), kind: name[3:]}
183		p.SetID(id)
184		p.SetContext(c.Context())
185		c.Context().Register(p)
186		// wlturbo rejects events without a declared signature; these hand-written
187		// proxies declare theirs explicitly.
188		if name == "wl_seat" {
189			c.RegisterEventSignature(id, uint16(wayland.SeatEventName), "string,")
190		}
191		return id, p
192	}
193	manager, _ := bind("wl_data_device_manager")
194	seat, sp := bind("wl_seat")
195	shm, _ := bind("wl_shm")
196	source := c.AllocateID()
197	must(t, c.SendRequest(manager, uint16(wayland.DataDeviceManagerRequestCreateDataSource), source))
198	must(t, c.SendRequest(source, uint16(wayland.DataSourceRequestOffer), "request text"))
199	pool := c.AllocateID()
200	fds := []int{0, 0}
201	must(t, unix.Pipe(fds))
202	defer unix.Close(fds[0])
203	defer unix.Close(fds[1])
204	must(t, c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fds[0]}, pool, uint32(0), int32(4096)))
205	keyboard := c.AllocateID()
206	kp := &argsProxy{received: make(chan argsEvent, 4), kind: "keyboard"}
207	kp.SetID(keyboard)
208	kp.SetContext(c.Context())
209	c.Context().Register(kp)
210	c.RegisterEventSignature(keyboard, uint16(wayland.KeyboardEventKeymap), "uint,fd,uint,")
211	c.RegisterEventSignature(keyboard, uint16(wayland.KeyboardEventEnter), "uint,object,array,")
212	pointer := c.AllocateID()
213	pp := &argsProxy{received: make(chan argsEvent, 4), kind: "pointer"}
214	pp.SetID(pointer)
215	pp.SetContext(c.Context())
216	c.Context().Register(pp)
217	c.RegisterEventSignature(pointer, uint16(wayland.PointerEventMotion), "uint,fixed,fixed,")
218	must(t, c.SendRequest(seat, uint16(wayland.SeatRequestGetKeyboard), keyboard))
219	must(t, c.SendRequest(seat, uint16(wayland.SeatRequestGetPointer), pointer))
220	must(t, c.Roundtrip())
221	if text := receive(t, stringsReceived); text != "request text" {
222		t.Fatalf("string request = %q", text)
223	}
224	if receive(t, requests) != "pool" {
225		t.Fatal("fd request missing")
226	}
227	if e := receive(t, sp.received); e.text != "hello" {
228		t.Fatalf("string event: %+v", e)
229	}
230	a, b := receive(t, kp.received), receive(t, kp.received)
231	if a.fd <= 0 || !bytes.Equal(b.data, []byte{1, 2, 3}) {
232		t.Fatalf("keyboard events: %+v %+v", a, b)
233	}
234	buf := make([]byte, 32)
235	n, e := unix.Read(a.fd, buf)
236	unix.Close(a.fd)
237	if e != nil || string(buf[:n]) != "fd contents" {
238		t.Fatalf("fd: %q %v", buf[:n], e)
239	}
240	if e := receive(t, pp.received); e.fixed.Float64() != -1.5 {
241		t.Fatalf("fixed: %+v", e)
242	}
243	must(t, c.SendRequest(pool, uint16(wayland.ShmPoolRequestResize), int32(8192)))
244	result := make(chan error, 1)
245	go func() {
246		for i := 0; i < 10; i++ {
247			if err := c.Dispatch(); err != nil {
248				result <- err
249				return
250			}
251		}
252		result <- nil
253	}()
254	select {
255	case err := <-result:
256		var displayErr *wlturbo.DisplayError
257		if !errors.As(err, &displayErr) || displayErr.ObjectID != pool || displayErr.Code != 7 || displayErr.Message != "bad % request" {
258			t.Fatalf("PostError result = %v", err)
259		}
260	case <-time.After(3 * time.Second):
261		t.Fatal("client did not disconnect after PostError")
262	}
263
264}

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 1

Tags 1

↑↓ moveenter openesc close