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}