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/server"
7 "os"
8)
9
10func registerGlobals(d *server.Display, o Options, s *Server) error {
11 for _, register := range []func() error{
12 func() error { return registerXDG(d, s) },
13 func() error { return registerLayer(d, s) },
14 func() error { return registerXDGOutput(d, s) },
15 func() error { return registerDecoration(d, s) },
16 func() error { return registerScale(d, s) },
17 func() error { return registerCursorShape(d, s) },
18 func() error { return registerDMABuf(d, s, o.DMABuf) },
19 func() error { return registerPointerConstraints(d, s) },
20 func() error { return registerTearing(d, s) },
21 func() error { return registerPresentationConstraints(d, s) },
22 func() error { return registerPresentation(d, s) },
23 func() error { return registerSyncobj(d, s) },
24 func() error { return registerInhibit(d, s) },
25 func() error { return registerActivation(d, s) },
26 func() error { return registerVirtualKeyboard(d, s) },
27 func() error {
28 return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) })
29 },
30 func() error {
31 return wayland.NewSubcompositorGlobal(d, 1, func(c server.Client, v, id uint32) {
32 wayland.NewSubcompositor(c, int32(v), id, subcompositor{s})
33 })
34 },
35 func() error {
36 return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
37 r, e := wayland.NewShm(c, int32(v), id, shm{s})
38 if e == nil {
39 r.SendFormat(0)
40 r.SendFormat(1)
41 }
42 })
43 },
44 func() error {
45 return wayland.NewSeatGlobal(d, 8, func(c server.Client, v, id uint32) {
46 r, e := wayland.NewSeat(c, int32(v), id, seat{s})
47 if e == nil {
48 capabilities := uint32(wayland.SeatCapabilityPointer)
49 if s.keymapFD >= 0 {
50 capabilities |= uint32(wayland.SeatCapabilityKeyboard)
51 }
52 r.SendCapabilities(capabilities)
53 if v >= 2 {
54 r.SendName("seat0")
55 }
56 }
57 })
58 },
59 func() error { return registerClipboard(d, s) },
60 } {
61 if err := register(); err != nil {
62 return err
63 }
64 }
65 return nil
66}
67
68type compositor struct{ server *Server }
69
70func (c compositor) CreateSurface(r *wayland.Compositor, id uint32) {
71 state := &surface{server: c.server, bufferScale: 1}
72 if w, err := wayland.NewSurface(r.Client(), r.Version(), id, state); err == nil {
73 state.wl = w
74 c.server.surfaces[w.Resource] = state
75 state.sendScale()
76 w.OnDestroy = func() { delete(c.server.surfaces, w.Resource); state.Destroy(w) }
77 }
78}
79func (c compositor) CreateRegion(r *wayland.Compositor, id uint32) {
80 state := ®ion{}
81 if w, err := wayland.NewRegion(r.Client(), r.Version(), id, state); err == nil {
82 c.server.regions[w.Resource] = state
83 w.OnDestroy = func() { delete(c.server.regions, w.Resource) }
84 }
85}
86func (compositor) Release(*wayland.Compositor) {}
87
88type subcompositor struct{ server *Server }
89
90func (subcompositor) Destroy(*wayland.Subcompositor) {}
91func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, parent *wayland.Surface) {
92 if w == nil || parent == nil {
93 return
94 }
95 state, up := c.server.surfaces[w.Resource], c.server.surfaces[parent.Resource]
96 if state == nil || up == nil {
97 return
98 }
99 if state.kind != roleNone && state.kind != roleSubsurface || state.sub.role != nil {
100 r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role")
101 return
102 }
103 // A surface cannot be its own ancestor.
104 for p := up; p != nil; p = p.sub.parent {
105 if p == state {
106 r.PostError(uint32(wayland.SubcompositorErrorBadParent), "parent is a descendant")
107 return
108 }
109 }
110 sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state})
111 if err != nil {
112 return
113 }
114 state.kind = roleSubsurface
115 state.role = func(bool) {}
116 state.sub = subState{role: sub, parent: up, children: state.sub.children}
117 up.sub.children = append(up.sub.children, state)
118 // The child follows its root window's output and scale.
119 state.sendScale()
120 sub.OnDestroy = func() {
121 if state.sub.role != sub {
122 return
123 }
124 state.sub.role = nil
125 state.role = nil
126 state.detach()
127 }
128}
129
130// Subsurfaces are drawn as their own commits arrive (desynchronized): the
131// synchronized mode's cached state is not implemented.
132type subsurface struct{ surface *surface }
133
134func (subsurface) Destroy(*wayland.Subsurface) {}
135func (s subsurface) SetPosition(_ *wayland.Subsurface, x, y int32) {
136 s.surface.sub.pendX, s.surface.sub.pendY, s.surface.sub.moved = int(x), int(y), true
137}
138func (s subsurface) PlaceAbove(r *wayland.Subsurface, sibling *wayland.Surface) {
139 s.restack(r, sibling, true)
140}
141func (s subsurface) PlaceBelow(r *wayland.Subsurface, sibling *wayland.Surface) {
142 s.restack(r, sibling, false)
143}
144func (subsurface) SetSync(*wayland.Subsurface) {}
145func (subsurface) SetDesync(*wayland.Subsurface) {}
146
147// restack moves the surface next to a sibling or its parent. The order
148// applies at once rather than on the parent commit.
149func (s subsurface) restack(r *wayland.Subsurface, sibling *wayland.Surface, above bool) {
150 me := s.surface
151 p := me.sub.parent
152 if p == nil || sibling == nil {
153 return
154 }
155 other := me.server.surfaces[sibling.Resource]
156 if other == me || other == nil || other != p && other.sub.parent != p {
157 r.PostError(uint32(wayland.SubsurfaceErrorBadSurface), "not a sibling or the parent")
158 return
159 }
160 list := p.sub.children
161 for i, ch := range list {
162 if ch == me {
163 list = append(list[:i:i], list[i+1:]...)
164 break
165 }
166 }
167 if other == p {
168 // Relative to the parent: just above it or just below it.
169 me.sub.below = !above
170 if above {
171 list = append([]*surface{me}, list...)
172 } else {
173 list = append(list, me)
174 }
175 // Keep below children first so the flattening stays ordered.
176 sortBelowFirst(list)
177 } else {
178 me.sub.below = other.sub.below
179 at := len(list)
180 for i, ch := range list {
181 if ch == other {
182 at = i
183 if above {
184 at++
185 }
186 break
187 }
188 }
189 list = append(list[:at:at], append([]*surface{me}, list[at:]...)...)
190 }
191 p.sub.children = list
192 me.redraw()
193}
194
195// sortBelowFirst moves children below their parent ahead of those above,
196// keeping each group's order.
197func sortBelowFirst(list []*surface) {
198 below := make([]*surface, 0, len(list))
199 above := make([]*surface, 0, len(list))
200 for _, ch := range list {
201 if ch.sub.below {
202 below = append(below, ch)
203 } else {
204 above = append(above, ch)
205 }
206 }
207 copy(list, append(below, above...))
208}
209
210// region keeps the bounding box of the rectangles added: pointer
211// confinement is the only user, and subtracted areas are ignored.
212type region struct{ box ports.Rect }
213
214func (*region) Destroy(*wayland.Region) {}
215func (g *region) Add(_ *wayland.Region, x, y, w, h int32) {
216 if w <= 0 || h <= 0 {
217 return
218 }
219 r := ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)}
220 if g.box.W > 0 {
221 x0, y0 := min(g.box.X, r.X), min(g.box.Y, r.Y)
222 r = ports.Rect{X: x0, Y: y0, W: max(g.box.X+g.box.W, r.X+r.W) - x0, H: max(g.box.Y+g.box.H, r.Y+r.H) - y0}
223 }
224 g.box = r
225}
226func (*region) Subtract(*wayland.Region, int32, int32, int32, int32) {}
227
228type shm struct{ server *Server }
229
230func (h shm) CreatePool(r *wayland.Shm, id uint32, fd int, size int32) {
231 file := os.NewFile(uintptr(fd), "wl_shm_pool")
232 if size <= 0 || !fileHolds(file, int64(size)) {
233 file.Close()
234 r.PostError(uint32(wayland.ShmErrorInvalidFd), "invalid pool size")
235 return
236 }
237 h.server.nextPool++
238 state := &pool{id: h.server.nextPool, file: file, size: size, shm: r, server: h.server}
239 p, err := wayland.NewShmPool(r.Client(), 1, id, state)
240 if err != nil {
241 file.Close()
242 return
243 }
244 p.OnDestroy = func() {
245 state.destroyed = true
246 if state.refs == 0 {
247 state.file.Close()
248 }
249 }
250}
251func (shm) Release(*wayland.Shm) {}
252
253// fileHolds reports whether the file has at least size bytes.
254func fileHolds(f *os.File, size int64) bool {
255 st, err := f.Stat()
256 return err == nil && st.Size() >= size
257}
258
259// pool is a wl_shm pool. neferwl never maps it: renderers map the file
260// and read the pixels when they draw (ports.SHMBuffer).
261type pool struct {
262 id uint64
263 file *os.File
264 size int32
265 shm *wayland.Shm
266 server *Server
267 refs int
268 destroyed bool
269}
270
271func (p *pool) CreateBuffer(r *wayland.ShmPool, id uint32, offset, width, height, stride int32, format uint32) {
272 if format != uint32(wayland.ShmFormatArgb8888) && format != uint32(wayland.ShmFormatXrgb8888) {
273 p.shm.PostError(uint32(wayland.ShmErrorInvalidFormat), "unsupported format")
274 return
275 }
276 if width <= 0 || height <= 0 || int64(stride) < int64(width)*4 || offset < 0 || stride <= 0 || int64(offset)+int64(height-1)*int64(stride)+int64(width)*4 > int64(p.size) {
277 p.shm.PostError(uint32(wayland.ShmErrorInvalidStride), "invalid buffer dimensions")
278 return
279 }
280 state := &buffer{pool: p, offset: int(offset), width: int(width), height: int(height), stride: int(stride), format: format}
281 b, err := wayland.NewBuffer(r.Client(), 1, id, state)
282 if err != nil {
283 return
284 }
285 p.refs++
286 p.server.buffers[b.Resource] = state
287 b.OnDestroy = func() {
288 delete(p.server.buffers, b.Resource)
289 p.refs--
290 if p.destroyed && p.refs == 0 {
291 p.file.Close()
292 }
293 }
294}
295func (p *pool) Destroy(*wayland.ShmPool) {}
296func (p *pool) Resize(r *wayland.ShmPool, size int32) {
297 if size <= p.size {
298 p.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "pool must grow")
299 return
300 }
301 if !fileHolds(p.file, int64(size)) {
302 p.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "cannot resize pool")
303 return
304 }
305 p.size = size
306}
307
308type buffer struct {
309 pool *pool
310 offset, width, height, stride int
311 format uint32
312}
313
314func (*buffer) Destroy(*wayland.Buffer) {}
315
316func (b *buffer) size() (int, int) { return b.width, b.height }
317
318// clientBuffer is a wl_buffer's content source: wl_shm or linux-dmabuf.
319type clientBuffer interface {
320 // content returns what the renderer draws; false means the client
321 // broke its buffer (a truncated shm file).
322 content(ports.WindowID) (ports.SurfaceContent, bool)
323 size() (int, int)
324}
325
326// addBuffer tracks a buffer until the client destroys it.
327func (s *Server) addBuffer(r *wayland.Buffer, b *dmabufBuffer) {
328 s.buffers[r.Resource] = b
329 r.OnDestroy = func() {
330 delete(s.buffers, r.Resource)
331 b.close()
332 }
333}
334
335// content points the renderer at the pool; a file shrunk below the buffer
336// is refused.
337func (b *buffer) content(id ports.WindowID) (ports.SurfaceContent, bool) {
338 if !fileHolds(b.pool.file, int64(b.offset+(b.height-1)*b.stride+b.width*4)) {
339 return ports.SurfaceContent{}, false
340 }
341 shm := &ports.SHMBuffer{Pool: b.pool.id, File: b.pool.file, Offset: b.offset, Stride: b.stride}
342 return ports.SurfaceContent{ID: id, Width: b.width, Height: b.height, Opaque: b.format == uint32(wayland.ShmFormatXrgb8888), SHM: shm}, true
343}
344
345type seat struct{ server *Server }
346
347func (h seat) GetPointer(r *wayland.Seat, id uint32) {
348 p, err := wayland.NewPointer(r.Client(), r.Version(), id, pointer(h))
349 if err != nil {
350 return
351 }
352 s := h.server
353 s.pointers[r.Client()] = append(s.pointers[r.Client()], p)
354 p.OnDestroy = func() {
355 list := s.pointers[r.Client()]
356 for i, item := range list {
357 if item == p {
358 list = append(list[:i], list[i+1:]...)
359 break
360 }
361 }
362 if len(list) == 0 {
363 delete(s.pointers, r.Client())
364 } else {
365 s.pointers[r.Client()] = list
366 }
367 }
368 if s.hasPointerFocus(r.Client()) {
369 if surf, _, x, y := s.pointerSurface(s.pointerFocus, s.pointerX, s.pointerY); surf != nil {
370 s.serial++
371 p.SendEnter(s.serial, surf, server.FixedFromFloat(x), server.FixedFromFloat(y))
372 pointerFrame(p)
373 }
374 }
375}
376func (h seat) GetKeyboard(r *wayland.Seat, id uint32) {
377 k, err := wayland.NewKeyboard(r.Client(), r.Version(), id, keyboard{})
378 if err != nil {
379 return
380 }
381 s := h.server
382 k.OnDestroy = func() {
383 list := s.keyboards[r.Client()]
384 for i, item := range list {
385 if item == k {
386 list = append(list[:i], list[i+1:]...)
387 break
388 }
389 }
390 if len(list) == 0 {
391 delete(s.keyboards, r.Client())
392 } else {
393 s.keyboards[r.Client()] = list
394 }
395 }
396 if s.keymapFD < 0 {
397 return
398 }
399 s.keyboards[r.Client()] = append(s.keyboards[r.Client()], k)
400 fd, size := s.currentKeymap()
401 k.SendKeymap(uint32(wayland.KeyboardKeymapFormatXkbV1), fd, size)
402 if r.Version() >= 4 {
403 k.SendRepeatInfo(int32(s.repeatRate), int32(s.repeatDelay))
404 }
405 if surf, _ := s.focusTarget(s.focused); surf != nil && surf.Client() == r.Client() {
406 s.serial++
407 k.SendEnter(s.serial, surf, []byte{})
408 s.sendModifiers(k)
409 }
410}
411func (seat) GetTouch(r *wayland.Seat, id uint32) {
412 wayland.NewTouch(r.Client(), r.Version(), id, touch{})
413}
414func (seat) Release(*wayland.Seat) {}
415
416type keyboard struct{}
417
418func (keyboard) Release(*wayland.Keyboard) {}
419
420type touch struct{}
421
422func (touch) Release(*wayland.Touch) {}