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