main f2d166b

layer.go RawHistoryFolder

1package wayland
2
3import (
4	"sort"
5
6	"github.com/bnema/go-wayland-bindings/server/wayland"
7	"github.com/bnema/go-wayland-bindings/server/wlrlayershell"
8	"github.com/bnema/go-wayland-bindings/server/xdgshell"
9	"github.com/bnema/neferwl/internal/ports"
10	"github.com/bnema/purego-libwayland/server"
11)
12
13type layerShell struct{ server *Server }
14type layerState struct {
15	width, height, anchor uint32
16	zone                  int32
17	margin                [4]int32
18	keyboard              uint32
19	layer                 ports.Layer
20}
21type layerSurface struct {
22	resource                  *wlrlayershell.ZwlrLayerSurfaceV1
23	shell                     layerShell
24	surface                   *surface
25	id                        ports.WindowID
26	namespace                 string
27	pending, current          layerState
28	configured, acked, mapped bool
29	serials                   []uint32
30	// output is the output the client asked for, else the focused one at
31	// creation. The surface is closed when it is unplugged.
32	output *output
33	closed bool // output gone: commits are ignored until destroy
34	// capture is the exclusion this surface is attached to.
35	capture    *exclusion
36	attachment *layerAttachment
37}
38
39func registerLayer(d *server.Display, s *Server) error {
40	return wlrlayershell.NewZwlrLayerShellV1Global(d, 4, func(c server.Client, v, id uint32) {
41		_, _ = wlrlayershell.NewZwlrLayerShellV1(c, int32(v), id, layerShell{s})
42	})
43}
44func (h layerShell) Destroy(*wlrlayershell.ZwlrLayerShellV1) {}
45func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32, w *wayland.Surface, wl *wayland.Output, layer uint32, namespace string) {
46	if w == nil || h.server.surfaces[w.Resource] == nil {
47		return
48	}
49	state := h.server.surfaces[w.Resource]
50	// A surface keeps its layer role once its layer surface is destroyed and
51	// may take a new one; only a live role object blocks it.
52	if state.kind != roleNone && state.kind != roleLayer || state.layer != nil || (state.next.attached && state.next.buffer != nil) || state.current != nil {
53		r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorAlreadyConstructed), "surface already constructed")
54		return
55	}
56	if layer > 3 {
57		r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorInvalidLayer), "invalid layer")
58		return
59	}
60	out := h.server.outputOf(wl)
61	gone := wl != nil && out == nil // its output was unplugged
62	if wl == nil {
63		out = h.server.outputByName("")
64	}
65	l := &layerSurface{shell: h, surface: state, id: h.server.nextWindow, namespace: namespace, output: out}
66	l.pending.layer = ports.Layer(layer)
67	resource, err := wlrlayershell.NewZwlrLayerSurfaceV1(r.Client(), r.Version(), id, l)
68	if err != nil {
69		return
70	}
71	l.resource = resource
72	h.server.nextWindow++
73	state.kind = roleLayer
74	state.layer = l
75	state.role = l.commit
76	h.server.layers[l.id] = l
77	if gone {
78		l.close()
79	} else if out != nil {
80		state.sendTreeScale()
81	}
82	resource.OnDestroy = func() {
83		l.unmap()
84		l.captureDetach()
85		delete(h.server.layers, l.id)
86		state.layer = nil
87		state.role = nil
88		state.dropQueue()
89		state.current, state.next.buffer = nil, nil
90		state.next.attached = false
91	}
92}
93func (l *layerSurface) SetSize(_ *wlrlayershell.ZwlrLayerSurfaceV1, w, h uint32) {
94	l.pending.width, l.pending.height = w, h
95}
96func (l *layerSurface) SetAnchor(r *wlrlayershell.ZwlrLayerSurfaceV1, a uint32) {
97	if a > 15 {
98		r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidAnchor), "invalid anchor")
99		return
100	}
101	l.pending.anchor = a
102}
103func (l *layerSurface) SetExclusiveZone(_ *wlrlayershell.ZwlrLayerSurfaceV1, z int32) {
104	l.pending.zone = z
105}
106func (l *layerSurface) SetMargin(_ *wlrlayershell.ZwlrLayerSurfaceV1, t, r, b, left int32) {
107	l.pending.margin = [4]int32{t, r, b, left}
108}
109
110func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfaceV1, k uint32) {
111	if k > 2 {
112		r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity), "invalid keyboard interactivity")
113		return
114	}
115	if l.capture != nil {
116		// A capture HUD never takes the keyboard, whatever it asks for.
117		k = 0
118	}
119	l.pending.keyboard = k
120}
121
122// GetPopup makes the layer surface the parent of an xdg_popup created
123// without one. Core places it relative to the layer.
124func (l *layerSurface) GetPopup(_ *wlrlayershell.ZwlrLayerSurfaceV1, r *xdgshell.Popup) {
125	if r == nil {
126		return
127	}
128	for _, w := range l.shell.server.windows {
129		if p := w.popup; p != nil && p.resource.Resource == r.Resource {
130			if p.parent == nil && !p.sent && !p.done {
131				p.layer = l
132			}
133			return
134		}
135	}
136}
137
138// surfaceResource is the layer's live wl_surface, if any.
139func (l *layerSurface) surfaceResource() *wayland.Surface {
140	for resource, state := range l.shell.server.surfaces {
141		if state == l.surface && resource.Alive() {
142			return wayland.WrapSurface(resource)
143		}
144	}
145	return nil
146}
147func (l *layerSurface) AckConfigure(r *wlrlayershell.ZwlrLayerSurfaceV1, serial uint32) {
148	for i, v := range l.serials {
149		if v == serial {
150			l.serials = l.serials[i+1:]
151			l.acked = true
152			return
153		}
154	}
155	r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "unknown configure serial")
156}
157func (*layerSurface) Destroy(*wlrlayershell.ZwlrLayerSurfaceV1) {}
158func (l *layerSurface) SetLayer(r *wlrlayershell.ZwlrLayerSurfaceV1, v uint32) {
159	if v > 3 {
160		r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "invalid layer")
161		return
162	}
163	l.pending.layer = ports.Layer(v)
164}
165
166// Exclusive edge is not implemented; layer-shell is capped at v4.
167func (*layerSurface) SetExclusiveEdge(*wlrlayershell.ZwlrLayerSurfaceV1, uint32) {}
168func (l *layerSurface) unmap() {
169	if !l.mapped {
170		return
171	}
172	if s := l.shell.server; s.seat.focused == l.id {
173		s.changeFocus(0)
174	}
175	if s := l.shell.server; s.seat.pointerFocus == l.id {
176		s.changePointerFocus(0, 0, 0)
177	}
178	l.mapped = false
179	l.surface.resetInputEmission()
180	l.configured = false
181	l.acked = false
182	l.serials = nil
183	l.shell.server.layerChanged()
184	l.shell.server.emitContent(ports.SurfaceContent{ID: l.id}, damage{full: true})
185}
186
187// close tells the client its output is gone; the surface stays unmapped
188// until the client destroys it.
189func (l *layerSurface) close() {
190	if l.closed {
191		return
192	}
193	l.unmap()
194	l.output = nil
195	l.closed = true
196	if l.resource.Resource.Alive() {
197		l.resource.SendClosed()
198	}
199}
200
201func (s *Server) layerChanged() {
202	list := make([]ports.LayerSurface, 0)
203	for _, l := range s.layers {
204		if !l.mapped {
205			continue
206		}
207		v := ports.LayerSurface{ID: l.id, Layer: l.current.layer, Anchor: l.current.anchor, ExclusiveZone: l.current.zone, Margin: l.current.margin, Namespace: l.namespace, Keyboard: l.current.keyboard}
208		if l.capture != nil {
209			// A capture HUD never takes the keyboard.
210			v.Keyboard = 0
211		}
212		if l.output != nil {
213			v.Output = l.output.name()
214		}
215		if l.surface.current != nil {
216			if b := s.buffers[l.surface.current.Resource]; b != nil {
217				v.Width, v.Height = l.surface.logicalSize(b.size())
218			}
219		}
220		list = append(list, v)
221	}
222	sort.Slice(list, func(i, j int) bool { return list[i].ID < list[j].ID })
223	s.emit(ports.LayerChanged{Layers: list})
224}
225
226// sendConfigure sizes the surface from committed state; zero means "fill",
227// the logical output size.
228func (l *layerSurface) sendConfigure() {
229	w, h := l.current.width, l.current.height
230	var ow, oh int
231	if l.output != nil {
232		ow, oh = l.output.place.Width, l.output.place.Height
233	}
234	if w == 0 {
235		w = uint32(ow)
236	}
237	if h == 0 {
238		h = uint32(oh)
239	}
240	l.shell.server.serial++
241	l.resource.SendConfigure(l.shell.server.serial, w, h)
242	l.serials = append(l.serials, l.shell.server.serial)
243	l.configured = true
244}
245
246func (l *layerSurface) commit(buffer bool) {
247	l.commitState(l.pending, buffer)
248}
249
250// commitState applies the state captured by this surface commit, not later
251// layer-shell requests still pending on the role object.
252func (l *layerSurface) commitState(p layerState, buffer bool) {
253	if l.surface.destroyed {
254		l.unmap()
255		l.captureDetach()
256		delete(l.shell.server.layers, l.id)
257		return
258	}
259	if l.closed {
260		return
261	}
262	if l.capture != nil {
263		p.keyboard = 0
264		if p.layer != ports.LayerTop && p.layer != ports.LayerOverlay {
265			// Only top and overlay layers may be attached: closing unmaps the
266			// surface (its LayerChanged is queued first), then it is detached,
267			// so core keeps it excluded until it is gone.
268			l.close()
269			l.captureDetach()
270			return
271		}
272	}
273	if (p.width == 0 && p.anchor&(ports.AnchorLeft|ports.AnchorRight) != ports.AnchorLeft|ports.AnchorRight) || (p.height == 0 && p.anchor&(ports.AnchorTop|ports.AnchorBottom) != ports.AnchorTop|ports.AnchorBottom) {
274		l.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSize), "zero size requires opposing anchors")
275		return
276	}
277	changed := p != l.current
278	sizeChanged := p.width != l.current.width || p.height != l.current.height
279	l.current = p
280	if !l.configured || sizeChanged {
281		l.sendConfigure()
282	}
283	if !buffer {
284		if l.mapped {
285			l.unmap()
286			l.configured = false
287			l.acked = false
288			l.serials = nil
289		}
290		return
291	}
292	if l.acked {
293		wasMapped := l.mapped
294		l.mapped = true
295		if changed || !wasMapped {
296			l.shell.server.layerChanged()
297		}
298	}
299}

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 26

Tags 10

↑↓ moveenter openesc close