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