1package wayland
2
3import (
4 "time"
5
6 "github.com/bnema/go-wayland-bindings/server/pointerconstraints"
7 "github.com/bnema/go-wayland-bindings/server/pointerwarp"
8 "github.com/bnema/go-wayland-bindings/server/relativepointer"
9 "github.com/bnema/go-wayland-bindings/server/wayland"
10 "github.com/bnema/neferwl/internal/ports"
11 "github.com/bnema/purego-libwayland/server"
12)
13
14func registerPointerConstraints(d *server.Display, s *Server) error {
15 if err := relativepointer.NewZwpRelativePointerManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
16 _, _ = relativepointer.NewZwpRelativePointerManagerV1(c, int32(v), id, relativeManager{s})
17 }); err != nil {
18 return err
19 }
20 if err := pointerwarp.NewWpPointerWarpV1Global(d, 1, func(c server.Client, v, id uint32) {
21 _, _ = pointerwarp.NewWpPointerWarpV1(c, int32(v), id, pointerWarp{s})
22 }); err != nil {
23 return err
24 }
25 return pointerconstraints.NewZwpPointerConstraintsV1Global(d, 1, func(c server.Client, v, id uint32) {
26 _, _ = pointerconstraints.NewZwpPointerConstraintsV1(c, int32(v), id, constraintManager{s})
27 })
28}
29
30type relativeManager struct{ server *Server }
31
32func (relativeManager) Destroy(*relativepointer.ZwpRelativePointerManagerV1) {}
33func (m relativeManager) GetRelativePointer(r *relativepointer.ZwpRelativePointerManagerV1, id uint32, _ *wayland.Pointer) {
34 rel, err := relativepointer.NewZwpRelativePointerV1(r.Client(), r.Version(), id, relativeHandler{})
35 if err != nil {
36 return
37 }
38 s, c := m.server, r.Client()
39 s.relatives[c] = append(s.relatives[c], rel)
40 rel.OnDestroy = func() {
41 if list := removeItem(s.relatives[c], rel); len(list) > 0 {
42 s.relatives[c] = list
43 } else {
44 delete(s.relatives, c)
45 }
46 }
47}
48
49// pointerWarp is wp_pointer_warp_v1: a client with the pointer moves it on
50// its window (Wine's SetCursorPos). Warps without the pointer, with a serial
51// other than that pointer's current enter, or outside the window are ignored.
52type pointerWarp struct{ server *Server }
53
54func (pointerWarp) Destroy(*pointerwarp.WpPointerWarpV1) {}
55func (h pointerWarp) WarpPointer(r *pointerwarp.WpPointerWarpV1, surf *wayland.Surface, p *wayland.Pointer, x, y server.Fixed, serial uint32) {
56 s := h.server
57 if s.protected() || surf == nil || p == nil || p.Client() != r.Client() {
58 return
59 }
60 if enter, ok := s.seat.enters[p.Resource]; !ok || enter != serial {
61 return
62 }
63 w := s.windows[s.seat.pointerFocus]
64 if w == nil || !w.mapped || w.xdg.resource.Client() != r.Client() || s.surfaces[surf.Resource] != w.xdg.surface {
65 return
66 }
67 // Surface-local to window-local (core's, from the geometry origin).
68 g := w.xdg.geometry
69 s.emit(ports.PointerWarp{ID: w.id, X: x.Float() - float64(g.X), Y: y.Float() - float64(g.Y)})
70}
71
72type relativeHandler struct{}
73
74func (relativeHandler) Destroy(*relativepointer.ZwpRelativePointerV1) {}
75
76// relativeMotion sends the motion deltas to the window client's relative
77// pointers. Absolute devices have no deltas and send nothing.
78// It reports whether any event was sent.
79func (s *Server) relativeMotion(w *window, c ports.PointerMotionTo) bool {
80 if s.protected() || c.DX == 0 && c.DY == 0 && c.UnaccelDX == 0 && c.UnaccelDY == 0 {
81 return false
82 }
83 if !w.mapped || !w.xdg.resource.Resource.Alive() {
84 return false
85 }
86 sent := false
87 usec := uint64(c.Time / time.Microsecond)
88 for _, rel := range s.relatives[w.xdg.resource.Client()] {
89 if rel.Resource.Alive() {
90 rel.SendRelativeMotion(uint32(usec>>32), uint32(usec), server.FixedFromFloat(c.DX), server.FixedFromFloat(c.DY), server.FixedFromFloat(c.UnaccelDX), server.FixedFromFloat(c.UnaccelDY))
91 sent = true
92 }
93 }
94 return sent
95}
96
97// constraint is a zwp_locked_pointer_v1 or zwp_confined_pointer_v1 on a
98// surface. Its region is surface-local; empty means the whole surface.
99type constraint struct {
100 surface *surface
101 lock *pointerconstraints.ZwpLockedPointerV1
102 confine *pointerconstraints.ZwpConfinedPointerV1
103 region ports.Rect
104 pending *ports.Rect // set_region waiting for the surface commit
105 persistent bool
106 active bool
107 defunct bool // a oneshot constraint that was deactivated
108}
109
110type constraintManager struct{ server *Server }
111
112func (constraintManager) Destroy(*pointerconstraints.ZwpPointerConstraintsV1) {}
113func (m constraintManager) LockPointer(r *pointerconstraints.ZwpPointerConstraintsV1, id uint32, surf *wayland.Surface, _ *wayland.Pointer, reg *wayland.Region, lifetime uint32) {
114 c := m.create(r, surf, reg, lifetime)
115 if c == nil {
116 // An unknown surface: an inert object the client can destroy.
117 _, _ = pointerconstraints.NewZwpLockedPointerV1(r.Client(), r.Version(), id, lockHandler{&constraint{}})
118 return
119 }
120 lock, err := pointerconstraints.NewZwpLockedPointerV1(r.Client(), r.Version(), id, lockHandler{c})
121 if err != nil {
122 delete(m.server.constraints, c.surface)
123 return
124 }
125 c.lock = lock
126 lock.OnDestroy = func() { m.server.dropConstraint(c) }
127 m.server.updateConstraint()
128}
129func (m constraintManager) ConfinePointer(r *pointerconstraints.ZwpPointerConstraintsV1, id uint32, surf *wayland.Surface, _ *wayland.Pointer, reg *wayland.Region, lifetime uint32) {
130 c := m.create(r, surf, reg, lifetime)
131 if c == nil {
132 _, _ = pointerconstraints.NewZwpConfinedPointerV1(r.Client(), r.Version(), id, confineHandler{&constraint{}})
133 return
134 }
135 confine, err := pointerconstraints.NewZwpConfinedPointerV1(r.Client(), r.Version(), id, confineHandler{c})
136 if err != nil {
137 delete(m.server.constraints, c.surface)
138 return
139 }
140 c.confine = confine
141 confine.OnDestroy = func() { m.server.dropConstraint(c) }
142 m.server.updateConstraint()
143}
144
145// create validates the request and registers the constraint on the surface.
146func (m constraintManager) create(r *pointerconstraints.ZwpPointerConstraintsV1, surf *wayland.Surface, reg *wayland.Region, lifetime uint32) *constraint {
147 s := m.server
148 if s.protected() || surf == nil {
149 return nil
150 }
151 state := s.surfaces[surf.Resource]
152 if state == nil {
153 return nil
154 }
155 if s.constraints[state] != nil {
156 r.PostError(uint32(pointerconstraints.ZwpPointerConstraintsV1ErrorAlreadyConstrained), "surface already constrained")
157 return nil
158 }
159 if state.kind == roleCursor {
160 return nil
161 }
162 c := &constraint{surface: state, region: s.regionBox(reg), persistent: lifetime == uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimePersistent)}
163 s.constraints[state] = c
164 return c
165}
166
167// regionBox copies a region: the protocol reads it at request time.
168func (s *Server) regionBox(reg *wayland.Region) ports.Rect {
169 if reg == nil || reg.Resource == nil {
170 return ports.Rect{}
171 }
172 if g := s.regions[reg.Resource]; g != nil {
173 return g.boxRect()
174 }
175 return ports.Rect{}
176}
177
178type lockHandler struct{ c *constraint }
179
180func (lockHandler) Destroy(*pointerconstraints.ZwpLockedPointerV1) {}
181
182// SetCursorPositionHint is ignored: the pointer stays where it locked.
183func (lockHandler) SetCursorPositionHint(*pointerconstraints.ZwpLockedPointerV1, server.Fixed, server.Fixed) {
184}
185func (h lockHandler) SetRegion(_ *pointerconstraints.ZwpLockedPointerV1, reg *wayland.Region) {
186 if h.c.surface == nil {
187 return
188 }
189 r := h.c.surface.server.regionBox(reg)
190 h.c.pending = &r
191}
192
193type confineHandler struct{ c *constraint }
194
195func (confineHandler) Destroy(*pointerconstraints.ZwpConfinedPointerV1) {}
196func (h confineHandler) SetRegion(_ *pointerconstraints.ZwpConfinedPointerV1, reg *wayland.Region) {
197 if h.c.surface == nil {
198 return
199 }
200 r := h.c.surface.server.regionBox(reg)
201 h.c.pending = &r
202}
203
204// commitConstraint applies this commit's captured region after its geometry;
205// later set_region requests remain pending. Core receives window-local state.
206func (s *surface) commitConstraint(c *constraint, region *ports.Rect, geometry bool) {
207 if c == nil || s.server.constraints[s] != c {
208 if !geometry {
209 return
210 }
211 c = s.server.constraints[s]
212 region = nil // never apply an old constraint's region to its replacement
213 }
214 if c == nil || (region == nil && !geometry) {
215 return
216 }
217 if region != nil && s.server.constraints[s] == c {
218 c.region = *region
219 }
220 if c.active {
221 s.server.emitConstraint(c)
222 } else {
223 s.server.updateConstraint()
224 }
225}
226
227// dropConstraint forgets a destroyed constraint or one on a destroyed surface.
228func (s *Server) dropConstraint(c *constraint) {
229 if s.constraints[c.surface] == c {
230 delete(s.constraints, c.surface)
231 }
232 if s.constraint == c {
233 c.active = false
234 s.constraint = nil
235 if c.lock != nil && c.lock.Resource.Alive() {
236 c.lock.SendUnlocked()
237 }
238 if c.confine != nil && c.confine.Resource.Alive() {
239 c.confine.SendUnconfined()
240 }
241 s.emit(ports.PointerConstrained{})
242 }
243 s.updateConstraint()
244}
245
246// deactivateConstraint ends the active constraint; a oneshot cannot reactivate.
247func (s *Server) deactivateConstraint() {
248 old := s.constraint
249 if old == nil {
250 return
251 }
252 old.active = false
253 if !old.persistent {
254 old.defunct = true
255 }
256 if old.lock != nil && old.lock.Resource.Alive() {
257 old.lock.SendUnlocked()
258 }
259 if old.confine != nil && old.confine.Resource.Alive() {
260 old.confine.SendUnconfined()
261 }
262 s.constraint = nil
263 s.emit(ports.PointerConstrained{})
264}
265
266// updateConstraint activates the constraint of the window holding both
267// pointer and keyboard focus, and deactivates any other.
268func (s *Server) updateConstraint() {
269 if s.protected() {
270 s.deactivateConstraint()
271 return
272 }
273 var want *constraint
274 if w := s.windows[s.seat.pointerFocus]; w != nil && w.mapped && s.seat.pointerFocus == s.seat.focused {
275 if c := s.constraints[w.xdg.surface]; c != nil && !c.defunct && (c.active || c.contains(w)) {
276 want = c
277 }
278 }
279 if want == s.constraint {
280 return
281 }
282 s.deactivateConstraint()
283 if want == nil {
284 return
285 }
286 s.constraint = want
287 want.active = true
288 if want.lock != nil {
289 want.lock.SendLocked()
290 } else {
291 want.confine.SendConfined()
292 }
293 s.emitConstraint(want)
294}
295
296// emitConstraint tells core about the active constraint, window-local.
297func (s *Server) emitConstraint(c *constraint) {
298 if s.protected() {
299 return
300 }
301 x := c.surface.xdg
302 if x == nil || x.window == nil {
303 return
304 }
305 mode := ports.ConstraintConfine
306 if c.lock != nil {
307 mode = ports.ConstraintLock
308 }
309 r := c.region
310 if r.W > 0 && r.H > 0 {
311 r.X -= x.geometry.X
312 r.Y -= x.geometry.Y
313 }
314 // Core supports one confinement rect, so this bounds the intersection;
315 // contains uses the exact input region to decide activation.
316 if mode == ports.ConstraintConfine {
317 if all, rects := c.surface.effectiveInput(); !all {
318 var box ports.Rect
319 for _, input := range rects {
320 if r.W > 0 && r.H > 0 {
321 input = intersectRect(input, r)
322 }
323 if input.W <= 0 {
324 continue
325 }
326 if box.W == 0 {
327 box = input
328 } else {
329 x0, y0 := min(box.X, input.X), min(box.Y, input.Y)
330 box = ports.Rect{X: x0, Y: y0, W: max(box.X+box.W, input.X+input.W) - x0, H: max(box.Y+box.H, input.Y+input.H) - y0}
331 }
332 }
333 r = box
334 }
335 }
336 s.emit(ports.PointerConstrained{ID: x.window.id, PointerConstraint: ports.PointerConstraint{Mode: mode, Rect: r}})
337}
338
339// contains reports whether the pointer is in the region: a constraint
340// activates only there, so it never warps the pointer.
341func (c *constraint) contains(w *window) bool {
342 r := c.region
343 x, y := w.surfacePoint(w.xdg.server.seat.pointerX, w.xdg.server.seat.pointerY)
344 all, rects := c.surface.effectiveInput()
345 if !all {
346 localX, localY := x-float64(w.xdg.geometry.X), y-float64(w.xdg.geometry.Y)
347 inside := false
348 for _, input := range rects {
349 if localX >= float64(input.X) && localX < float64(input.X+input.W) && localY >= float64(input.Y) && localY < float64(input.Y+input.H) {
350 inside = true
351 break
352 }
353 }
354 if !inside {
355 return false
356 }
357 }
358 if r.W <= 0 || r.H <= 0 {
359 return true
360 }
361 return x >= float64(r.X) && x < float64(r.X+r.W) && y >= float64(r.Y) && y < float64(r.Y+r.H)
362}
363
364// locked reports whether the window's pointer is locked.
365func (s *Server) locked(w *window) bool {
366 return !s.protected() && s.constraint != nil && s.constraint.lock != nil && s.constraint.surface == w.xdg.surface
367}