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