v0.1.0 d9218d6

globals.go RawHistoryFolder

1package wayland
2
3import (
4	"github.com/bnema/neferwl/internal/ports"
5	"github.com/bnema/purego-libwayland/protocol/wayland"
6	"github.com/bnema/purego-libwayland/server"
7	"os"
8)
9
10func registerGlobals(d *server.Display, o Options, s *Server) error {
11	for _, register := range []func() error{
12		func() error { return registerXDG(d, s) },
13		func() error { return registerLayer(d, s) },
14		func() error { return registerXDGOutput(d, s) },
15		func() error { return registerDecoration(d, s) },
16		func() error { return registerScale(d, s) },
17		func() error { return registerCursorShape(d, s) },
18		func() error { return registerDMABuf(d, s, o.DMABuf) },
19		func() error { return registerPointerConstraints(d, s) },
20		func() error { return registerTearing(d, s) },
21		func() error { return registerPresentationConstraints(d, s) },
22		func() error { return registerPresentation(d, s) },
23		func() error { return registerSyncobj(d, s) },
24		func() error { return registerInhibit(d, s) },
25		func() error { return registerActivation(d, s) },
26		func() error { return registerVirtualKeyboard(d, s) },
27		func() error {
28			return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) })
29		},
30		func() error {
31			return wayland.NewSubcompositorGlobal(d, 1, func(c server.Client, v, id uint32) {
32				wayland.NewSubcompositor(c, int32(v), id, subcompositor{s})
33			})
34		},
35		func() error {
36			return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
37				r, e := wayland.NewShm(c, int32(v), id, shm{s})
38				if e == nil {
39					r.SendFormat(0)
40					r.SendFormat(1)
41				}
42			})
43		},
44		func() error {
45			return wayland.NewSeatGlobal(d, 8, func(c server.Client, v, id uint32) {
46				r, e := wayland.NewSeat(c, int32(v), id, seat{s})
47				if e == nil {
48					capabilities := uint32(wayland.SeatCapabilityPointer)
49					if s.keymapFD >= 0 {
50						capabilities |= uint32(wayland.SeatCapabilityKeyboard)
51					}
52					r.SendCapabilities(capabilities)
53					if v >= 2 {
54						r.SendName("seat0")
55					}
56				}
57			})
58		},
59		func() error { return registerClipboard(d, s) },
60	} {
61		if err := register(); err != nil {
62			return err
63		}
64	}
65	return nil
66}
67
68type compositor struct{ server *Server }
69
70func (c compositor) CreateSurface(r *wayland.Compositor, id uint32) {
71	state := &surface{server: c.server, bufferScale: 1}
72	if w, err := wayland.NewSurface(r.Client(), r.Version(), id, state); err == nil {
73		state.wl = w
74		c.server.surfaces[w.Resource] = state
75		state.sendScale()
76		w.OnDestroy = func() { delete(c.server.surfaces, w.Resource); state.Destroy(w) }
77	}
78}
79func (c compositor) CreateRegion(r *wayland.Compositor, id uint32) {
80	state := &region{}
81	if w, err := wayland.NewRegion(r.Client(), r.Version(), id, state); err == nil {
82		c.server.regions[w.Resource] = state
83		w.OnDestroy = func() { delete(c.server.regions, w.Resource) }
84	}
85}
86func (compositor) Release(*wayland.Compositor) {}
87
88type subcompositor struct{ server *Server }
89
90func (subcompositor) Destroy(*wayland.Subcompositor) {}
91func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, parent *wayland.Surface) {
92	if w == nil || parent == nil {
93		return
94	}
95	state, up := c.server.surfaces[w.Resource], c.server.surfaces[parent.Resource]
96	if state == nil || up == nil {
97		return
98	}
99	if state.kind != roleNone && state.kind != roleSubsurface || state.sub.role != nil {
100		r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role")
101		return
102	}
103	// A surface cannot be its own ancestor.
104	for p := up; p != nil; p = p.sub.parent {
105		if p == state {
106			r.PostError(uint32(wayland.SubcompositorErrorBadParent), "parent is a descendant")
107			return
108		}
109	}
110	sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state})
111	if err != nil {
112		return
113	}
114	state.kind = roleSubsurface
115	state.role = func(bool) {}
116	state.sub = subState{role: sub, parent: up, children: state.sub.children}
117	up.sub.children = append(up.sub.children, state)
118	// The child follows its root window's output and scale.
119	state.sendScale()
120	sub.OnDestroy = func() {
121		if state.sub.role != sub {
122			return
123		}
124		state.sub.role = nil
125		state.role = nil
126		state.detach()
127	}
128}
129
130// Subsurfaces are drawn as their own commits arrive (desynchronized): the
131// synchronized mode's cached state is not implemented.
132type subsurface struct{ surface *surface }
133
134func (subsurface) Destroy(*wayland.Subsurface) {}
135func (s subsurface) SetPosition(_ *wayland.Subsurface, x, y int32) {
136	s.surface.sub.pendX, s.surface.sub.pendY, s.surface.sub.moved = int(x), int(y), true
137}
138func (s subsurface) PlaceAbove(r *wayland.Subsurface, sibling *wayland.Surface) {
139	s.restack(r, sibling, true)
140}
141func (s subsurface) PlaceBelow(r *wayland.Subsurface, sibling *wayland.Surface) {
142	s.restack(r, sibling, false)
143}
144func (subsurface) SetSync(*wayland.Subsurface)   {}
145func (subsurface) SetDesync(*wayland.Subsurface) {}
146
147// restack moves the surface next to a sibling or its parent. The order
148// applies at once rather than on the parent commit.
149func (s subsurface) restack(r *wayland.Subsurface, sibling *wayland.Surface, above bool) {
150	me := s.surface
151	p := me.sub.parent
152	if p == nil || sibling == nil {
153		return
154	}
155	other := me.server.surfaces[sibling.Resource]
156	if other == me || other == nil || other != p && other.sub.parent != p {
157		r.PostError(uint32(wayland.SubsurfaceErrorBadSurface), "not a sibling or the parent")
158		return
159	}
160	list := p.sub.children
161	for i, ch := range list {
162		if ch == me {
163			list = append(list[:i:i], list[i+1:]...)
164			break
165		}
166	}
167	if other == p {
168		// Relative to the parent: just above it or just below it.
169		me.sub.below = !above
170		if above {
171			list = append([]*surface{me}, list...)
172		} else {
173			list = append(list, me)
174		}
175		// Keep below children first so the flattening stays ordered.
176		sortBelowFirst(list)
177	} else {
178		me.sub.below = other.sub.below
179		at := len(list)
180		for i, ch := range list {
181			if ch == other {
182				at = i
183				if above {
184					at++
185				}
186				break
187			}
188		}
189		list = append(list[:at:at], append([]*surface{me}, list[at:]...)...)
190	}
191	p.sub.children = list
192	me.redraw()
193}
194
195// sortBelowFirst moves children below their parent ahead of those above,
196// keeping each group's order.
197func sortBelowFirst(list []*surface) {
198	below := make([]*surface, 0, len(list))
199	above := make([]*surface, 0, len(list))
200	for _, ch := range list {
201		if ch.sub.below {
202			below = append(below, ch)
203		} else {
204			above = append(above, ch)
205		}
206	}
207	copy(list, append(below, above...))
208}
209
210// region keeps the bounding box of the rectangles added: pointer
211// confinement is the only user, and subtracted areas are ignored.
212type region struct{ box ports.Rect }
213
214func (*region) Destroy(*wayland.Region) {}
215func (g *region) Add(_ *wayland.Region, x, y, w, h int32) {
216	if w <= 0 || h <= 0 {
217		return
218	}
219	r := ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)}
220	if g.box.W > 0 {
221		x0, y0 := min(g.box.X, r.X), min(g.box.Y, r.Y)
222		r = ports.Rect{X: x0, Y: y0, W: max(g.box.X+g.box.W, r.X+r.W) - x0, H: max(g.box.Y+g.box.H, r.Y+r.H) - y0}
223	}
224	g.box = r
225}
226func (*region) Subtract(*wayland.Region, int32, int32, int32, int32) {}
227
228type shm struct{ server *Server }
229
230func (h shm) CreatePool(r *wayland.Shm, id uint32, fd int, size int32) {
231	file := os.NewFile(uintptr(fd), "wl_shm_pool")
232	if size <= 0 || !fileHolds(file, int64(size)) {
233		file.Close()
234		r.PostError(uint32(wayland.ShmErrorInvalidFd), "invalid pool size")
235		return
236	}
237	h.server.nextPool++
238	state := &pool{id: h.server.nextPool, file: file, size: size, shm: r, server: h.server}
239	p, err := wayland.NewShmPool(r.Client(), 1, id, state)
240	if err != nil {
241		file.Close()
242		return
243	}
244	p.OnDestroy = func() {
245		state.destroyed = true
246		if state.refs == 0 {
247			state.file.Close()
248		}
249	}
250}
251func (shm) Release(*wayland.Shm) {}
252
253// fileHolds reports whether the file has at least size bytes.
254func fileHolds(f *os.File, size int64) bool {
255	st, err := f.Stat()
256	return err == nil && st.Size() >= size
257}
258
259// pool is a wl_shm pool. neferwl never maps it: renderers map the file
260// and read the pixels when they draw (ports.SHMBuffer).
261type pool struct {
262	id        uint64
263	file      *os.File
264	size      int32
265	shm       *wayland.Shm
266	server    *Server
267	refs      int
268	destroyed bool
269}
270
271func (p *pool) CreateBuffer(r *wayland.ShmPool, id uint32, offset, width, height, stride int32, format uint32) {
272	if format != uint32(wayland.ShmFormatArgb8888) && format != uint32(wayland.ShmFormatXrgb8888) {
273		p.shm.PostError(uint32(wayland.ShmErrorInvalidFormat), "unsupported format")
274		return
275	}
276	if width <= 0 || height <= 0 || int64(stride) < int64(width)*4 || offset < 0 || stride <= 0 || int64(offset)+int64(height-1)*int64(stride)+int64(width)*4 > int64(p.size) {
277		p.shm.PostError(uint32(wayland.ShmErrorInvalidStride), "invalid buffer dimensions")
278		return
279	}
280	state := &buffer{pool: p, offset: int(offset), width: int(width), height: int(height), stride: int(stride), format: format}
281	b, err := wayland.NewBuffer(r.Client(), 1, id, state)
282	if err != nil {
283		return
284	}
285	p.refs++
286	p.server.buffers[b.Resource] = state
287	b.OnDestroy = func() {
288		delete(p.server.buffers, b.Resource)
289		p.refs--
290		if p.destroyed && p.refs == 0 {
291			p.file.Close()
292		}
293	}
294}
295func (p *pool) Destroy(*wayland.ShmPool) {}
296func (p *pool) Resize(r *wayland.ShmPool, size int32) {
297	if size <= p.size {
298		p.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "pool must grow")
299		return
300	}
301	if !fileHolds(p.file, int64(size)) {
302		p.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "cannot resize pool")
303		return
304	}
305	p.size = size
306}
307
308type buffer struct {
309	pool                          *pool
310	offset, width, height, stride int
311	format                        uint32
312}
313
314func (*buffer) Destroy(*wayland.Buffer) {}
315
316func (b *buffer) size() (int, int) { return b.width, b.height }
317
318// clientBuffer is a wl_buffer's content source: wl_shm or linux-dmabuf.
319type clientBuffer interface {
320	// content returns what the renderer draws; false means the client
321	// broke its buffer (a truncated shm file).
322	content(ports.WindowID) (ports.SurfaceContent, bool)
323	size() (int, int)
324}
325
326// addBuffer tracks a buffer until the client destroys it.
327func (s *Server) addBuffer(r *wayland.Buffer, b *dmabufBuffer) {
328	s.buffers[r.Resource] = b
329	r.OnDestroy = func() {
330		delete(s.buffers, r.Resource)
331		b.close()
332	}
333}
334
335// content points the renderer at the pool; a file shrunk below the buffer
336// is refused.
337func (b *buffer) content(id ports.WindowID) (ports.SurfaceContent, bool) {
338	if !fileHolds(b.pool.file, int64(b.offset+(b.height-1)*b.stride+b.width*4)) {
339		return ports.SurfaceContent{}, false
340	}
341	shm := &ports.SHMBuffer{Pool: b.pool.id, File: b.pool.file, Offset: b.offset, Stride: b.stride}
342	return ports.SurfaceContent{ID: id, Width: b.width, Height: b.height, Opaque: b.format == uint32(wayland.ShmFormatXrgb8888), SHM: shm}, true
343}
344
345type seat struct{ server *Server }
346
347func (h seat) GetPointer(r *wayland.Seat, id uint32) {
348	p, err := wayland.NewPointer(r.Client(), r.Version(), id, pointer(h))
349	if err != nil {
350		return
351	}
352	s := h.server
353	s.pointers[r.Client()] = append(s.pointers[r.Client()], p)
354	p.OnDestroy = func() {
355		list := s.pointers[r.Client()]
356		for i, item := range list {
357			if item == p {
358				list = append(list[:i], list[i+1:]...)
359				break
360			}
361		}
362		if len(list) == 0 {
363			delete(s.pointers, r.Client())
364		} else {
365			s.pointers[r.Client()] = list
366		}
367	}
368	if s.hasPointerFocus(r.Client()) {
369		if surf, _, x, y := s.pointerSurface(s.pointerFocus, s.pointerX, s.pointerY); surf != nil {
370			s.serial++
371			p.SendEnter(s.serial, surf, server.FixedFromFloat(x), server.FixedFromFloat(y))
372			pointerFrame(p)
373		}
374	}
375}
376func (h seat) GetKeyboard(r *wayland.Seat, id uint32) {
377	k, err := wayland.NewKeyboard(r.Client(), r.Version(), id, keyboard{})
378	if err != nil {
379		return
380	}
381	s := h.server
382	k.OnDestroy = func() {
383		list := s.keyboards[r.Client()]
384		for i, item := range list {
385			if item == k {
386				list = append(list[:i], list[i+1:]...)
387				break
388			}
389		}
390		if len(list) == 0 {
391			delete(s.keyboards, r.Client())
392		} else {
393			s.keyboards[r.Client()] = list
394		}
395	}
396	if s.keymapFD < 0 {
397		return
398	}
399	s.keyboards[r.Client()] = append(s.keyboards[r.Client()], k)
400	fd, size := s.currentKeymap()
401	k.SendKeymap(uint32(wayland.KeyboardKeymapFormatXkbV1), fd, size)
402	if r.Version() >= 4 {
403		k.SendRepeatInfo(int32(s.repeatRate), int32(s.repeatDelay))
404	}
405	if surf, _ := s.focusTarget(s.focused); surf != nil && surf.Client() == r.Client() {
406		s.serial++
407		k.SendEnter(s.serial, surf, []byte{})
408		s.sendModifiers(k)
409	}
410}
411func (seat) GetTouch(r *wayland.Seat, id uint32) {
412	wayland.NewTouch(r.Client(), r.Version(), id, touch{})
413}
414func (seat) Release(*wayland.Seat) {}
415
416type keyboard struct{}
417
418func (keyboard) Release(*wayland.Keyboard) {}
419
420type touch struct{}
421
422func (touch) Release(*wayland.Touch) {}

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 23

Tags 10

↑↓ moveenter openesc close