v0.1.0 d9218d6

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

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