1package wayland
2
3import (
4 "math"
5
6 "github.com/bnema/go-wayland-bindings/server/fractionalscale"
7 "github.com/bnema/go-wayland-bindings/server/viewporter"
8 "github.com/bnema/go-wayland-bindings/server/wayland"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/bnema/purego-libwayland/server"
11)
12
13// Surfaces follow the output of their window or layer (the focused output
14// until they are shown): wl_surface.enter/leave, preferred buffer scale and
15// fractional scale all come from it.
16
17// outputOfSurface is the output a surface is on: its window's configured
18// output, its layer output, or the focused one.
19func (s *Server) outputOfSurface(surf *surface) *output {
20 // Subsurfaces are on their root's output.
21 surf = surf.root()
22 // Popups are on their parent's output.
23 for surf.xdg != nil && surf.xdg.window != nil && surf.xdg.window.popup != nil && surf.xdg.window.popup.parent != nil {
24 surf = surf.xdg.window.popup.parent.xdg.surface.root()
25 }
26 name := ""
27 switch {
28 case surf.xdg != nil && surf.xdg.window != nil:
29 name = surf.xdg.window.last.Output
30 case surf.layer != nil && surf.layer.output != nil:
31 return surf.layer.output
32 case surf.lock != nil:
33 if !surf.lock.closed {
34 return surf.lock.output
35 }
36 return nil
37 }
38 return s.outputByName(name)
39}
40
41// sendTreeScale sends the scale to a surface and its subsurfaces.
42func (surf *surface) sendTreeScale() {
43 surf.sendScale()
44 for _, ch := range surf.sub.children {
45 ch.sendTreeScale()
46 }
47}
48
49// sendScale tells the surface about its output: enter/leave, the integer
50// buffer scale (wl_surface v6+) and the fractional scale.
51func (surf *surface) sendScale() {
52 s := surf.server
53 if surf.destroyed || surf.wl == nil || !surf.wl.Resource.Alive() {
54 return
55 }
56 o := s.outputOfSurface(surf)
57 if o != surf.on {
58 if surf.on != nil {
59 surf.leave(surf.on)
60 }
61 surf.on = o
62 for h := range s.colorFeedbacks {
63 if h.surf == surf {
64 h.last = o
65 h.changed(s.outputColor(o).identity)
66 }
67 }
68 if o != nil {
69 for _, r := range o.resources {
70 if r.Client() == surf.wl.Client() {
71 surf.wl.SendEnter(r)
72 }
73 }
74 }
75 }
76 scale := 1.0
77 if o != nil {
78 scale = o.place.Scale
79 }
80 // The hint lets the client render in the output's orientation (rotated
81 // outputs are always composed). Normal is the protocol default, so it is
82 // sent only on change; cursor buffers must stay untransformed.
83 var t ports.BufferTransform
84 if o != nil {
85 t = o.place.Transform
86 }
87 if surf.wl.Version() >= 6 && surf.kind != roleCursor && t != surf.prefTransform {
88 surf.prefTransform = t
89 surf.wl.SendPreferredBufferTransform(uint32(t))
90 }
91 if scale == surf.scale {
92 return
93 }
94 surf.scale = scale
95 if surf.wl.Version() >= 6 {
96 surf.wl.SendPreferredBufferScale(integerScale(scale))
97 }
98 if f := s.fractions[surf]; f != nil {
99 f.SendPreferredScale(uint32(math.Round(scale * 120)))
100 }
101}
102
103// leave sends wl_surface.leave for o when the surface is on it.
104func (surf *surface) leave(o *output) {
105 if surf.on != o || o == nil {
106 return
107 }
108 surf.on = nil
109 if surf.destroyed || !surf.wl.Resource.Alive() {
110 return
111 }
112 for _, r := range o.resources {
113 if r.Client() == surf.wl.Client() && r.Resource.Alive() {
114 surf.wl.SendLeave(r)
115 }
116 }
117}
118
119func registerScale(d *server.Display, s *Server) error {
120 if err := fractionalscale.NewWpFractionalScaleManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
121 _, _ = fractionalscale.NewWpFractionalScaleManagerV1(c, int32(v), id, fractionManager{s})
122 }); err != nil {
123 return err
124 }
125 return viewporter.NewWpViewporterGlobal(d, 1, func(c server.Client, v, id uint32) {
126 _, _ = viewporter.NewWpViewporter(c, int32(v), id, viewporterHandler{s})
127 })
128}
129
130type fractionManager struct{ server *Server }
131
132func (fractionManager) Destroy(*fractionalscale.WpFractionalScaleManagerV1) {}
133func (m fractionManager) GetFractionalScale(r *fractionalscale.WpFractionalScaleManagerV1, id uint32, w *wayland.Surface) {
134 s := m.server
135 surf := s.surfaces[w.GetResource()]
136 if surf == nil {
137 return
138 }
139 if _, dup := s.fractions[surf]; dup {
140 r.PostError(uint32(fractionalscale.WpFractionalScaleManagerV1ErrorFractionalScaleExists), "fractional scale already exists")
141 return
142 }
143 f, err := fractionalscale.NewWpFractionalScaleV1(r.Client(), r.Version(), id, fraction{})
144 if err != nil {
145 return
146 }
147 s.fractions[surf] = f
148 f.OnDestroy = func() { delete(s.fractions, surf) }
149 scale := 1.0
150 if o := s.outputOfSurface(surf); o != nil {
151 scale = o.place.Scale
152 }
153 f.SendPreferredScale(uint32(math.Round(scale * 120)))
154}
155
156type fraction struct{}
157
158func (fraction) Destroy(*fractionalscale.WpFractionalScaleV1) {}
159
160type viewporterHandler struct{ server *Server }
161
162func (viewporterHandler) Destroy(*viewporter.WpViewporter) {}
163func (h viewporterHandler) GetViewport(r *viewporter.WpViewporter, id uint32, w *wayland.Surface) {
164 surf := h.server.surfaces[w.GetResource()]
165 if surf == nil {
166 return
167 }
168 if surf.viewport != nil {
169 r.PostError(uint32(viewporter.WpViewporterErrorViewportExists), "viewport already exists")
170 return
171 }
172 vp := &viewport{surface: surf}
173 res, err := viewporter.NewWpViewport(r.Client(), r.Version(), id, vp)
174 if err != nil {
175 return
176 }
177 surf.viewport = vp
178 vp.resource = res
179 res.OnDestroy = func() {
180 // Destroying the viewport resets the size on the next commit.
181 if surf.viewport == vp {
182 surf.viewport = nil
183 }
184 }
185}
186
187// viewport holds requests for the next commit; the committed crop and
188// destination live by value on the surface so applying cannot allocate.
189type viewport struct {
190 surface *surface
191 resource *viewporter.WpViewport
192 pendingW, pendingH int32
193 pendingSet bool
194 pendingSrc [4]server.Fixed
195 pendingCrop bool
196}
197
198type viewportState struct {
199 destW, destH int32
200 dest bool
201 src [4]server.Fixed
202 crop bool
203}
204
205func (*viewport) Destroy(*viewporter.WpViewport) {}
206func (v *viewport) SetSource(r *viewporter.WpViewport, x, y, w, h server.Fixed) {
207 if v.surface.destroyed {
208 r.PostError(uint32(viewporter.WpViewportErrorNoSurface), "surface destroyed")
209 return
210 }
211 unset := x == -256 && y == -256 && w == -256 && h == -256
212 if !unset && (x < 0 || y < 0 || w <= 0 || h <= 0) {
213 r.PostError(uint32(viewporter.WpViewportErrorBadValue), "invalid source rectangle")
214 return
215 }
216 v.pendingSrc, v.pendingCrop = [4]server.Fixed{x, y, w, h}, !unset
217}
218func (v *viewport) SetDestination(r *viewporter.WpViewport, w, h int32) {
219 if v.surface.destroyed {
220 r.PostError(uint32(viewporter.WpViewportErrorNoSurface), "surface destroyed")
221 return
222 }
223 if (w == -1) != (h == -1) || (w != -1 && (w <= 0 || h <= 0)) {
224 r.PostError(uint32(viewporter.WpViewportErrorBadValue), "invalid destination size")
225 return
226 }
227 v.pendingW, v.pendingH, v.pendingSet = w, h, w != -1
228}
229
230// source returns the crop in untransformed buffer pixels, precomputed at
231// commit. The viewport source is in transformed, scaled surface
232// coordinates: its corners map back through the buffer transform.
233func (s *surface) source(bw, bh int) ([4]float32, bool) {
234 if !s.committedViewport.crop {
235 return [4]float32{}, false
236 }
237 v := s.committedViewport.src
238 scale := float64(max(s.bufferScale, 1))
239 // Transformed buffer size, in the surface's axes.
240 iw, ih := s.transformedSize(bw, bh)
241 tw, th := float64(iw), float64(ih)
242 x0, y0 := float64(v[0])*scale/256, float64(v[1])*scale/256
243 x1, y1 := x0+float64(v[2])*scale/256, y0+float64(v[3])*scale/256
244 // validateViewport tolerates a rounding overshoot: sample inside the buffer.
245 x1, y1 = min(x1, tw), min(y1, th)
246 ax, ay := s.transform.ToBuffer(x0, y0, tw, th)
247 bx, by := s.transform.ToBuffer(x1, y1, tw, th)
248 return [4]float32{float32(min(ax, bx)), float32(min(ay, by)), float32(math.Abs(bx - ax)), float32(math.Abs(by - ay))}, true
249}
250
251// transformedSize is the buffer size in the surface's axes: width and height
252// swap for a 90° or 270° buffer transform.
253func (s *surface) transformedSize(bw, bh int) (int, int) {
254 return s.transform.Size(bw, bh)
255}
256
257func (s *surface) validateViewport(bw, bh int) bool {
258 bw, bh = s.transformedSize(bw, bh)
259 v := &s.committedViewport
260 if !v.crop {
261 return true
262 }
263 if !v.dest && (v.src[2]%256 != 0 || v.src[3]%256 != 0) {
264 s.viewportError(viewporter.WpViewportErrorBadSize, "non-integer source size without destination")
265 return false
266 }
267 // int64 prevents overflow when adding signed fixed coordinates.
268 // Position and size are each rounded to 1/256 by the client, so an edge
269 // computed in floating point can land up to two units past the buffer:
270 // that rounding is tolerated, anything larger is a real overflow.
271 const rounding = 2
272 if int64(v.src[0])+int64(v.src[2]) > int64(bw)*256/int64(max(s.bufferScale, 1))+rounding ||
273 int64(v.src[1])+int64(v.src[3]) > int64(bh)*256/int64(max(s.bufferScale, 1))+rounding {
274 // The error disconnects the client: log every occurrence, with the
275 // source edges and limits in pixels to show which bound failed.
276 {
277 limitW, limitH := float64(bw)/float64(max(s.bufferScale, 1)), float64(bh)/float64(max(s.bufferScale, 1))
278 surfaceID, viewportID, bufferID := uint32(0), uint32(0), uint32(0)
279 if s.wl != nil {
280 surfaceID = s.wl.ID()
281 }
282 if s.viewport != nil && s.viewport.resource != nil {
283 viewportID = s.viewport.resource.ID()
284 }
285 if s.current != nil {
286 bufferID = s.current.ID()
287 }
288 s.server.log.Warn().Str("component", "wayland").Uint32("surface", surfaceID).Uint32("viewport", viewportID).
289 Bool("fresh", s.commitFresh).Bool("retained", !s.commitFresh).Bool("skipped_destroyed", s.commitSkipped).
290 Uint32("buffer", bufferID).Uint32("queued_buffer", s.queuedBuffer).Int("buffer_width", bw).Int("buffer_height", bh).
291 Int("committed_scale", s.bufferScale).Int("queued_scale", s.queuedScale).
292 Ints32("raw_source", []int32{int32(v.src[0]), int32(v.src[1]), int32(v.src[2]), int32(v.src[3])}).
293 Float64("source_right", float64(int64(v.src[0])+int64(v.src[2]))/256).Float64("limit_w", limitW).
294 Float64("source_bottom", float64(int64(v.src[1])+int64(v.src[3]))/256).Float64("limit_h", limitH).
295 Bool("destination_set", v.dest).Msg("viewport source exceeds buffer")
296 }
297 s.viewportError(viewporter.WpViewportErrorOutOfBuffer, "source exceeds buffer")
298 return false
299 }
300 return true
301}
302
303func (s *surface) viewportError(code viewporter.WpViewportError, message string) {
304 if s.viewport != nil && s.viewport.resource != nil {
305 s.viewport.resource.PostError(uint32(code), message)
306 }
307}
308
309// logicalSize is the surface size in logical pixels: the viewport
310// destination, else the transformed buffer divided by its buffer scale.
311func (s *surface) logicalSize(bw, bh int) (int, int) {
312 bw, bh = s.transformedSize(bw, bh)
313 if s.committedViewport.dest {
314 return int(s.committedViewport.destW), int(s.committedViewport.destH)
315 }
316 if s.committedViewport.crop {
317 return int(s.committedViewport.src[2]) / 256, int(s.committedViewport.src[3]) / 256
318 }
319 scale := max(s.bufferScale, 1)
320 return bw / scale, bh / scale
321}