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