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}