1package wayland
2
3import (
4 "slices"
5
6 "github.com/bnema/go-wayland-bindings/server/wayland"
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// intersectRect returns the overlap of two half-open rectangles.
11func intersectRect(a, b ports.Rect) ports.Rect {
12 x, y := max(a.X, b.X), max(a.Y, b.Y)
13 x2, y2 := min(a.X+a.W, b.X+b.W), min(a.Y+a.H, b.Y+b.H)
14 if x2 <= x || y2 <= y {
15 return ports.Rect{}
16 }
17 return ports.Rect{X: x, Y: y, W: x2 - x, H: y2 - y}
18}
19
20func (g *region) boxRect() ports.Rect {
21 if len(g.rects) == 0 {
22 return ports.Rect{}
23 }
24 x, y := g.rects[0].X, g.rects[0].Y
25 x2, y2 := x+g.rects[0].W, y+g.rects[0].H
26 for _, r := range g.rects[1:] {
27 x = min(x, r.X)
28 y = min(y, r.Y)
29 x2 = max(x2, r.X+r.W)
30 y2 = max(y2, r.Y+r.H)
31 }
32 return ports.Rect{X: x, Y: y, W: x2 - x, H: y2 - y}
33}
34
35// effectiveInput includes all committed, bounded subsurface regions.
36func (s *surface) effectiveInput() (bool, []ports.Rect) {
37 if s.inputAll && len(s.sub.children) == 0 {
38 return true, nil
39 }
40 rects := s.appendEffectiveInput(nil, 0, 0)
41 if s.xdg != nil {
42 for i := range rects {
43 rects[i].X -= s.xdg.geometry.X
44 rects[i].Y -= s.xdg.geometry.Y
45 }
46 }
47 return false, rects
48}
49
50// appendEffectiveInput walks the committed tree without a per-call recursive
51// closure. The returned slice belongs to the caller (emitInput publishes it).
52func (s *surface) appendEffectiveInput(rects []ports.Rect, x, y int) []ports.Rect {
53 if s.has {
54 bounds := ports.Rect{W: s.content.LogicalW, H: s.content.LogicalH}
55 if s.inputAll {
56 if bounds.W > 0 && bounds.H > 0 {
57 rects = append(rects, ports.Rect{X: x, Y: y, W: bounds.W, H: bounds.H})
58 }
59 } else {
60 for _, r := range s.inputRects {
61 if r = intersectRect(r, bounds); r.W > 0 {
62 r.X += x
63 r.Y += y
64 rects = append(rects, r)
65 }
66 }
67 }
68 }
69 for _, item := range s.sub.layout {
70 rects = item.child.appendEffectiveInput(rects, x+item.x, y+item.y)
71 }
72 return rects
73}
74
75// resetInputEmission makes the next mapping publish its current region again.
76func (s *surface) resetInputEmission() {
77 s.sentInput = false
78 s.lastInputAll = false
79 s.lastInputRects = nil
80}
81
82func (s *surface) emitInput() {
83 root := s.root()
84 id := root.windowID()
85 if id == 0 {
86 return
87 }
88 all, rects := root.effectiveInput()
89 if !root.sentInput && all {
90 return
91 }
92 if root.sentInput && root.lastInputAll == all && slices.Equal(root.lastInputRects, rects) {
93 return
94 }
95 root.sentInput = true
96 root.lastInputAll = all
97 root.lastInputRects = slices.Clone(rects)
98 s.server.emit(ports.InputRegionChanged{ID: id, All: all, Rects: rects})
99 if c := s.server.constraints[root]; c != nil {
100 if c.active {
101 w := s.server.windows[id]
102 if w == nil || !c.contains(w) {
103 s.server.deactivateConstraint()
104 } else {
105 s.server.emitConstraint(c)
106 }
107 } else {
108 s.server.updateConstraint()
109 }
110 }
111}
112
113func (s *surface) SetInputRegion(_ *wayland.Surface, reg *wayland.Region) {
114 s.next.inputSet = true
115 s.next.inputAll = reg == nil || reg.Resource == nil
116 s.next.inputRects = nil
117 if !s.next.inputAll {
118 if g := s.server.regions[reg.Resource]; g != nil {
119 s.next.inputRects = slices.Clone(g.rects)
120 }
121 }
122}