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