1package wayland
2
3import (
4 "os"
5 "slices"
6
7 "github.com/bnema/go-wayland-bindings/server/wayland"
8 "github.com/bnema/neferwl/internal/ports"
9 "github.com/bnema/purego-libwayland/server"
10 "golang.org/x/sys/unix"
11)
12
13func registerGlobals(d *server.Display, o Options, s *Server) error {
14 for _, register := range []func() error{
15 func() error { return registerXDG(d, s) },
16 func() error { return registerLayer(d, s) },
17 func() error {
18 // Advertise only with the production owner gate and authoritative
19 // backend proof/barrier channels. Standalone adapters cannot claim
20 // protection from composed-frame submission alone.
21 if s.securityController == nil || s.channels.SecurityChanges == nil || s.channels.SecurityEvents == nil {
22 return nil
23 }
24 return registerSessionLock(d, s)
25 },
26 func() error { return registerXDGOutput(d, s) },
27 func() error { return registerOutputManagement(d, s) },
28 func() error { return registerWorkspaces(d, s) },
29 func() error { return registerForeignToplevel(d, s) },
30 func() error { return registerExtForeignToplevel(d, s) },
31 func() error { return registerDecoration(d, s) },
32 func() error { return registerDialog(d, s) },
33 func() error { return registerScale(d, s) },
34 func() error { return registerCursorShape(d, s) },
35 func() error { return registerDMABuf(d, s, o.DMABuf) },
36 func() error { return registerSinglePixelBuffer(d, s) },
37 func() error { return registerPointerConstraints(d, s) },
38 func() error { return registerPointerGestures(d, s) },
39 func() error { return registerTearing(d, s) },
40 func() error { return registerColorManagement(d, s) },
41 func() error { return registerColorRepresentation(d, s) },
42 func() error { return registerPresentationConstraints(d, s) },
43 func() error { return registerPresentation(d, s) },
44 func() error { return registerSyncobj(d, s) },
45 func() error { return registerInhibit(d, s) },
46 func() error { return registerIdle(d, s) },
47 func() error { return registerCapture(d, s) },
48 func() error { return registerExclusion(d, s) },
49 func() error { return registerSecurityContext(d, s) },
50 func() error { return registerActivation(d, s) },
51 func() error { return registerForeign(d, s) },
52 func() error { return registerVirtualKeyboard(d, s) },
53 func() error { return registerTextInput(d, s) },
54 func() error { return registerInputMethod(d, s) },
55 func() error {
56 return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) })
57 },
58 func() error {
59 return wayland.NewSubcompositorGlobal(d, 1, func(c server.Client, v, id uint32) {
60 wayland.NewSubcompositor(c, int32(v), id, subcompositor{s})
61 })
62 },
63 func() error {
64 return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
65 r, e := wayland.NewShm(c, int32(v), id, shm{s})
66 if e == nil {
67 r.SendFormat(0)
68 r.SendFormat(1)
69 }
70 })
71 },
72 func() error {
73 return wayland.NewSeatGlobal(d, 8, func(c server.Client, v, id uint32) {
74 r, e := wayland.NewSeat(c, int32(v), id, seat{s})
75 if e == nil {
76 capabilities := uint32(wayland.SeatCapabilityPointer)
77 if s.seat.keymapFD >= 0 {
78 capabilities |= uint32(wayland.SeatCapabilityKeyboard)
79 }
80 r.SendCapabilities(capabilities)
81 if v >= 2 {
82 r.SendName("seat0")
83 }
84 }
85 })
86 },
87 func() error { return registerInputTimestamps(d, s) },
88 func() error { return registerClipboard(d, s) },
89 func() error { return registerFixes(d) },
90 func() error { return registerAlphaModifier(d, s) },
91 } {
92 if err := register(); err != nil {
93 return err
94 }
95 }
96 return nil
97}
98
99// registerFixes advertises wl_fixes: clients can destroy their registry,
100// and acknowledge removed globals (v2, libwayland 1.26+).
101func registerFixes(d *server.Display) error {
102 version := int32(1)
103 if server.AckGlobalRemove(nil, nil, 0) {
104 version = 2
105 }
106 return wayland.NewFixesGlobal(d, version, func(c server.Client, v, id uint32) {
107 _, _ = wayland.NewFixes(c, int32(v), id, fixes{})
108 })
109}
110
111type fixes struct{}
112
113func (fixes) Destroy(*wayland.Fixes) {}
114func (fixes) DestroyRegistry(_ *wayland.Fixes, r *wayland.Registry) {
115 if r != nil && r.Resource != nil {
116 r.Destroy()
117 }
118}
119func (fixes) AckGlobalRemove(f *wayland.Fixes, r *wayland.Registry, name uint32) {
120 if r != nil {
121 server.AckGlobalRemove(f.Resource, r.Resource, name)
122 }
123}
124
125type compositor struct{ server *Server }
126
127func (c compositor) CreateSurface(r *wayland.Compositor, id uint32) {
128 state := &surface{server: c.server, bufferScale: 1, inputAll: true}
129 if w, err := wayland.NewSurface(r.Client(), r.Version(), id, state); err == nil {
130 c.server.nextSurface++
131 state.identity = c.server.nextSurface
132 state.wl = w
133 c.server.surfaces[w.Resource] = state
134 state.sendScale()
135 w.OnDestroy = func() { delete(c.server.surfaces, w.Resource); state.Destroy(w) }
136 }
137}
138func (c compositor) CreateRegion(r *wayland.Compositor, id uint32) {
139 state := ®ion{}
140 if w, err := wayland.NewRegion(r.Client(), r.Version(), id, state); err == nil {
141 c.server.regions[w.Resource] = state
142 w.OnDestroy = func() { delete(c.server.regions, w.Resource) }
143 }
144}
145func (compositor) Release(*wayland.Compositor) {}
146
147type subcompositor struct{ server *Server }
148
149func (subcompositor) Destroy(*wayland.Subcompositor) {}
150func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, parent *wayland.Surface) {
151 if w == nil || parent == nil {
152 return
153 }
154 state, up := c.server.surfaces[w.Resource], c.server.surfaces[parent.Resource]
155 if state == nil || up == nil {
156 return
157 }
158 if state.kind != roleNone && state.kind != roleSubsurface || state.sub.role != nil {
159 r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role")
160 return
161 }
162 // A surface cannot be its own ancestor.
163 for p := up; p != nil; p = p.sub.parent {
164 if p == state {
165 r.PostError(uint32(wayland.SubcompositorErrorBadParent), "parent is a descendant")
166 return
167 }
168 }
169 sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state})
170 if err != nil {
171 return
172 }
173 state.kind = roleSubsurface
174 state.role = func(bool) {}
175 state.sub = subState{role: sub, parent: up, children: state.sub.children, layout: state.sub.layout, pendingLayout: state.sub.pendingLayout, synced: true}
176 up.sub.children = append(up.sub.children, state)
177 up.sub.pendingLayout = append(append([]childLayout(nil), up.sub.pendingLayout...), childLayout{child: state})
178 // The child follows its root window's output and scale.
179 state.sendScale()
180 up.emitInput()
181 sub.OnDestroy = func() {
182 if state.sub.role != sub {
183 return
184 }
185 state.sub.role = nil
186 state.role = nil
187 state.detach()
188 }
189}
190
191// Subsurfaces are drawn as their own commits arrive (desynchronized): the
192// synchronized mode's cached state is not implemented.
193type subsurface struct{ surface *surface }
194
195func (subsurface) Destroy(*wayland.Subsurface) {}
196func (s subsurface) SetPosition(_ *wayland.Subsurface, x, y int32) {
197 p := s.surface.sub.parent
198 if p == nil {
199 return
200 }
201 p.sub.pendingLayout = append([]childLayout(nil), p.sub.pendingLayout...)
202 for i := range p.sub.pendingLayout {
203 item := &p.sub.pendingLayout[i]
204 if item.child == s.surface {
205 item.x, item.y = int(x), int(y)
206 break
207 }
208 }
209}
210func (s subsurface) PlaceAbove(r *wayland.Subsurface, sibling *wayland.Surface) {
211 s.restack(r, sibling, true)
212}
213func (s subsurface) PlaceBelow(r *wayland.Subsurface, sibling *wayland.Surface) {
214 s.restack(r, sibling, false)
215}
216func (s subsurface) SetSync(*wayland.Subsurface) { s.surface.sub.synced = true }
217func (s subsurface) SetDesync(*wayland.Subsurface) {
218 was := s.surface.effectivelySynced()
219 s.surface.sub.synced = false
220 if was && !s.surface.effectivelySynced() {
221 s.surface.flushDesync()
222 }
223}
224
225// restack changes only the pending layout of the parent.
226func (s subsurface) restack(r *wayland.Subsurface, sibling *wayland.Surface, above bool) {
227 me := s.surface
228 p := me.sub.parent
229 if p == nil || sibling == nil {
230 return
231 }
232 other := me.server.surfaces[sibling.Resource]
233 if other == me || other == nil || other != p && other.sub.parent != p {
234 r.PostError(uint32(wayland.SubsurfaceErrorBadSurface), "not a sibling or the parent")
235 return
236 }
237 list := append([]childLayout(nil), p.sub.pendingLayout...)
238 var item childLayout
239 for _, entry := range list {
240 if entry.child == me {
241 item = entry
242 break
243 }
244 }
245 list = removeLayout(list, me)
246 at := len(list)
247 if other == p {
248 item.below = !above
249 if above {
250 for i, entry := range list {
251 if !entry.below {
252 at = i
253 break
254 }
255 }
256 }
257 } else {
258 for i, entry := range list {
259 if entry.child == other {
260 at, item.below = i, entry.below
261 if above {
262 at++
263 }
264 break
265 }
266 }
267 }
268 list = append(list, childLayout{})
269 copy(list[at+1:], list[at:])
270 list[at] = item
271 p.sub.pendingLayout = list
272}
273
274// region stores the exact union of added rectangles.
275type region struct{ rects []ports.Rect }
276
277func (*region) Destroy(*wayland.Region) {}
278func (g *region) Add(_ *wayland.Region, x, y, w, h int32) {
279 if w > 0 && h > 0 {
280 g.rects = append(g.rects, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
281 }
282}
283func (g *region) Subtract(_ *wayland.Region, x, y, w, h int32) {
284 if w <= 0 || h <= 0 {
285 return
286 }
287 cut := ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)}
288 var out []ports.Rect
289 for _, r := range g.rects {
290 i := intersectRect(r, cut)
291 if i.W == 0 {
292 out = append(out, r)
293 continue
294 }
295 for _, piece := range []ports.Rect{
296 {X: r.X, Y: r.Y, W: r.W, H: i.Y - r.Y},
297 {X: r.X, Y: i.Y + i.H, W: r.W, H: r.Y + r.H - i.Y - i.H},
298 {X: r.X, Y: i.Y, W: i.X - r.X, H: i.H},
299 {X: i.X + i.W, Y: i.Y, W: r.X + r.W - i.X - i.W, H: i.H},
300 } {
301 if piece.W > 0 && piece.H > 0 {
302 out = append(out, piece)
303 }
304 }
305 }
306 g.rects = out
307}
308
309type shm struct{ server *Server }
310
311func (h shm) CreatePool(r *wayland.Shm, id uint32, fd int, size int32) {
312 file := os.NewFile(uintptr(fd), "wl_shm_pool")
313 if size <= 0 || !fileHolds(file, int64(size)) {
314 file.Close()
315 r.PostError(uint32(wayland.ShmErrorInvalidFd), "invalid pool size")
316 return
317 }
318 h.server.nextPool++
319 state := &pool{id: h.server.nextPool, file: file, size: size, shm: r, server: h.server}
320 p, err := wayland.NewShmPool(r.Client(), 1, id, state)
321 if err != nil {
322 file.Close()
323 return
324 }
325 p.OnDestroy = func() {
326 state.destroyed = true
327 if state.refs == 0 {
328 state.file.Close()
329 }
330 }
331}
332func (shm) Release(*wayland.Shm) {}
333
334// fileHolds reports whether the file has at least size bytes.
335func fileHolds(f *os.File, size int64) bool {
336 st, err := f.Stat()
337 return err == nil && st.Size() >= size
338}
339
340// pool is a wl_shm pool. neferwl never maps it: renderers map the file
341// and read the pixels when they draw (ports.SHMBuffer).
342type pool struct {
343 id uint64
344 file *os.File
345 size int32
346 shm *wayland.Shm
347 server *Server
348 refs int
349 destroyed bool
350}
351
352func (p *pool) CreateBuffer(r *wayland.ShmPool, id uint32, offset, width, height, stride int32, format uint32) {
353 if format != uint32(wayland.ShmFormatArgb8888) && format != uint32(wayland.ShmFormatXrgb8888) {
354 p.shm.PostError(uint32(wayland.ShmErrorInvalidFormat), "unsupported format")
355 return
356 }
357 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) {
358 p.shm.PostError(uint32(wayland.ShmErrorInvalidStride), "invalid buffer dimensions")
359 return
360 }
361 state := &buffer{pool: p, offset: int(offset), width: int(width), height: int(height), stride: int(stride), format: format}
362 b, err := wayland.NewBuffer(r.Client(), 1, id, state)
363 if err != nil {
364 return
365 }
366 p.refs++
367 p.server.buffers[b.Resource] = state
368 b.OnDestroy = func() {
369 delete(p.server.buffers, b.Resource)
370 p.refs--
371 if p.destroyed && p.refs == 0 {
372 p.file.Close()
373 }
374 }
375}
376func (p *pool) Destroy(*wayland.ShmPool) {}
377func (p *pool) Resize(r *wayland.ShmPool, size int32) {
378 if size <= p.size {
379 p.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "pool must grow")
380 return
381 }
382 if !fileHolds(p.file, int64(size)) {
383 p.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "cannot resize pool")
384 return
385 }
386 p.size = size
387}
388
389type buffer struct {
390 pool *pool
391 offset, width, height, stride int
392 format uint32
393 // shm and fd are built on the first content call. The pool, offset and
394 // stride never change, so every commit sends the same read-only
395 // SHMBuffer. The pool keeps the file open while the buffer lives, which
396 // keeps fd valid.
397 shm *ports.SHMBuffer
398 fd int
399}
400
401func (*buffer) Destroy(*wayland.Buffer) {}
402
403func (b *buffer) size() (int, int) { return b.width, b.height }
404
405// clientBuffer is a wl_buffer's content source: wl_shm, linux-dmabuf or
406// single-pixel buffers.
407type clientBuffer interface {
408 // content returns what the renderer draws; false means the client
409 // broke its buffer (a truncated shm file).
410 content(ports.WindowID) (ports.SurfaceContent, bool)
411 size() (int, int)
412}
413
414// addBuffer tracks a buffer until the client destroys it.
415// A buffer that owns resources implements close() to release them.
416func (s *Server) addBuffer(r *wayland.Buffer, b clientBuffer) {
417 s.buffers[r.Resource] = b
418 r.OnDestroy = func() {
419 delete(s.buffers, r.Resource)
420 if c, ok := b.(interface{ close() }); ok {
421 c.close()
422 }
423 }
424}
425
426// content points the renderer at the pool; a file shrunk below the buffer
427// is refused. The size is checked on every call: a client may truncate the
428// file at any time, so it is never cached.
429func (b *buffer) content(id ports.WindowID) (ports.SurfaceContent, bool) {
430 if b.shm == nil {
431 b.shm = &ports.SHMBuffer{Pool: b.pool.id, File: b.pool.file, Offset: b.offset, Stride: b.stride}
432 b.fd = int(b.pool.file.Fd())
433 }
434 var st unix.Stat_t
435 if unix.Fstat(b.fd, &st) != nil || st.Size < int64(b.offset+(b.height-1)*b.stride+b.width*4) {
436 return ports.SurfaceContent{}, false
437 }
438 return ports.SurfaceContent{ID: id, Width: b.width, Height: b.height, Opaque: b.format == uint32(wayland.ShmFormatXrgb8888), SHM: b.shm}, true
439}
440
441type seat struct{ server *Server }
442
443func (h seat) GetPointer(r *wayland.Seat, id uint32) {
444 p, err := wayland.NewPointer(r.Client(), r.Version(), id, pointer(h))
445 if err != nil {
446 return
447 }
448 s := h.server
449 s.seat.pointers[r.Client()] = append(s.seat.pointers[r.Client()], p)
450 p.OnDestroy = func() {
451 delete(s.seat.enters, p.Resource)
452 // Subscribed timestamps objects become inert with the device.
453 delete(s.seat.pointerStamps, p.Resource)
454 // Copy on write: callers may be ranging over the old list (see
455 // clientPointers).
456 list := slices.DeleteFunc(slices.Clone(s.seat.pointers[r.Client()]), func(x *wayland.Pointer) bool { return x == p })
457 if len(list) == 0 {
458 delete(s.seat.pointers, r.Client())
459 } else {
460 s.seat.pointers[r.Client()] = list
461 }
462 }
463 if s.hasPointerFocus(r.Client()) {
464 if surf, _, x, y := s.pointerSurface(s.seat.pointerFocus, s.seat.pointerX, s.seat.pointerY); surf != nil {
465 s.serial++
466 s.seat.enters[p.Resource] = s.serial
467 p.SendEnter(s.serial, surf, server.FixedFromFloat(x), server.FixedFromFloat(y))
468 pointerFrame(p)
469 }
470 }
471}
472func (h seat) GetKeyboard(r *wayland.Seat, id uint32) {
473 k, err := wayland.NewKeyboard(r.Client(), r.Version(), id, keyboard{})
474 if err != nil {
475 return
476 }
477 s := h.server
478 k.OnDestroy = func() {
479 delete(s.seat.keyStamps, k.Resource)
480 // Copy on write: callers may be ranging over the old list (see
481 // clientKeyboards).
482 list := slices.DeleteFunc(slices.Clone(s.seat.keyboards[r.Client()]), func(x *wayland.Keyboard) bool { return x == k })
483 if len(list) == 0 {
484 delete(s.seat.keyboards, r.Client())
485 } else {
486 s.seat.keyboards[r.Client()] = list
487 }
488 }
489 if s.seat.keymapFD < 0 {
490 return
491 }
492 s.seat.keyboards[r.Client()] = append(s.seat.keyboards[r.Client()], k)
493 fd, size := s.currentKeymap()
494 k.SendKeymap(uint32(wayland.KeyboardKeymapFormatXkbV1), fd, size)
495 if r.Version() >= 4 {
496 k.SendRepeatInfo(int32(s.seat.repeatRate), int32(s.seat.repeatDelay))
497 }
498 if surf, _ := s.focusTarget(s.seat.focused); surf != nil && surf.Client() == r.Client() {
499 s.serial++
500 k.SendEnter(s.serial, surf, []byte{})
501 s.sendModifiers(k)
502 }
503}
504func (seat) GetTouch(r *wayland.Seat, id uint32) {
505 wayland.NewTouch(r.Client(), r.Version(), id, touch{})
506}
507func (seat) Release(*wayland.Seat) {}
508
509type keyboard struct{}
510
511func (keyboard) Release(*wayland.Keyboard) {}
512
513type touch struct{}
514
515func (touch) Release(*wayland.Touch) {}