1package wayland
2
3import (
4 "encoding/binary"
5 "fmt"
6 "time"
7
8 "github.com/bnema/go-wayland-bindings/server/inputtimestamps"
9 "github.com/bnema/go-wayland-bindings/server/wayland"
10 "github.com/bnema/neferwl/internal/ports"
11 "github.com/bnema/purego-libwayland/server"
12 "golang.org/x/sys/unix"
13)
14
15// seatState is the wl_seat state: who has keyboard and pointer focus, the
16// bound keyboards and pointers, the keymap they carry, and the last press.
17// It belongs to the display goroutine.
18type seatState struct {
19 focused ports.WindowID
20 pointerFocus ports.WindowID
21 // pointerX, pointerY is the pointer as core placed it in pointerFocus;
22 // pointerSurface and surfacePoint turn it into surface coordinates.
23 pointerX, pointerY float64
24 // enters holds the serial of the enter event each wl_pointer received
25 // for pointerFocus; a pointer warp must name its pointer's serial.
26 enters map[*server.Resource]uint32
27 wheelRest [2]int32 // v120 not yet sent as axis_discrete, per axis
28 wheelHeld [2]float64 // axis value held back with it for pre-v8 clients
29 pointers map[server.Client][]*wayland.Pointer
30 keyboards map[server.Client][]*wayland.Keyboard
31 modState ports.ModState
32 heldKeys map[uint32]bool
33 grabKeys map[uint32]bool // pressed through the input method grab
34 keymapFD int
35 keymapSize uint32
36 keymapText string // the seat keymap, to compare virtual keymaps with
37 keymapOwner *virtualKeyboard // nil: keyboards carry the seat keymap
38 repeatRate int
39 repeatDelay int
40 // press is the serial of the last button or key press, sent to
41 // pressClient: popup grabs must come from it.
42 press uint32
43 pressClient server.Client
44 // pressAt and focusAt date the last press and keyboard focus change,
45 // for xdg-activation tokens.
46 pressAt, focusAt time.Time
47 // keyStamps and pointerStamps hold the zwp_input_timestamps_v1 objects
48 // subscribed to each device; the lists are replaced, never mutated.
49 keyStamps, pointerStamps map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1
50}
51
52// applyInput delivers a core input command to the focused client.
53func (s *Server) applyInput(cmd ports.ClientCommand) {
54 if s.security != nil && s.security.Snapshot().Protected {
55 var target ports.WindowID
56 switch c := cmd.(type) {
57 case ports.SetKeymap:
58 // Configuration updates do not authorize normal-client input.
59 case ports.PointerFocus:
60 target = c.ID
61 case ports.PointerMotionTo:
62 target = c.ID
63 case ports.PointerButtonTo:
64 target = c.ID
65 case ports.PointerAxisTo:
66 target = c.ID
67 case ports.FocusWindow:
68 target = c.ID
69 case ports.ForwardKey:
70 target = c.ID
71 }
72 if target != 0 && !s.lockInputTarget(target) {
73 return
74 }
75 }
76 switch c := cmd.(type) {
77 case ports.PointerFocus:
78 s.changePointerFocus(c.ID, c.X, c.Y)
79 case ports.PointerMotionTo:
80 if l := s.lockSurfaces[c.ID]; l != nil && s.lockInputTarget(c.ID) && c.ID == s.seat.pointerFocus {
81 s.seat.pointerX, s.seat.pointerY = c.X, c.Y
82 for _, p := range s.clientPointers(l.resource.Client()) {
83 s.stampPointer(p, c.Time)
84 p.SendMotion(wireMsec(c.Time), server.FixedFromFloat(c.X), server.FixedFromFloat(c.Y))
85 pointerFrame(p)
86 }
87 return
88 }
89 if l := s.layers[c.ID]; l != nil && c.ID == s.seat.pointerFocus {
90 s.seat.pointerX, s.seat.pointerY = c.X, c.Y
91 for _, p := range s.layerPointers(l) {
92 s.stampPointer(p, c.Time)
93 p.SendMotion(wireMsec(c.Time), server.FixedFromFloat(c.X), server.FixedFromFloat(c.Y))
94 pointerFrame(p)
95 }
96 return
97 }
98 if w := s.windows[c.ID]; w != nil && c.ID == s.seat.pointerFocus {
99 if s.relativeMotion(w, c) && s.locked(w) {
100 for _, p := range s.windowPointers(w) {
101 pointerFrame(p)
102 }
103 }
104 if s.locked(w) {
105 // Only relative motion reaches a locked pointer.
106 return
107 }
108 x, y := w.surfacePoint(c.X, c.Y)
109 s.seat.pointerX, s.seat.pointerY = c.X, c.Y
110 // A constraint waits for the pointer to enter its region.
111 if s.constraint == nil {
112 s.updateConstraint()
113 }
114 for _, p := range s.windowPointers(w) {
115 s.stampPointer(p, c.Time)
116 p.SendMotion(wireMsec(c.Time), server.FixedFromFloat(x), server.FixedFromFloat(y))
117 pointerFrame(p)
118 }
119 }
120 case ports.PointerButtonTo:
121 // Releases may target the implicit-grab window after focus has moved.
122 if client, pointers, ok := s.pointerTarget(c.ID); ok {
123 s.serial++
124 state := uint32(0)
125 if c.Pressed {
126 state = 1
127 s.seat.press, s.seat.pressClient, s.seat.pressAt = s.serial, client, time.Now()
128 }
129 for _, p := range pointers {
130 s.stampPointer(p, c.Time)
131 p.SendButton(s.serial, wireMsec(c.Time), c.Button, state)
132 pointerFrame(p)
133 }
134 }
135 case ports.PointerAxisTo:
136 // Scroll applies to the entered surface only.
137 if c.ID != s.seat.pointerFocus {
138 return
139 }
140 steps, values := s.seat.wheelSteps(c.Axis)
141 _, pointers, _ := s.pointerTarget(c.ID)
142 for _, p := range pointers {
143 s.sendAxis(p, c.Axis, steps, values)
144 }
145 case ports.SetKeymap:
146 s.setKeymap(c)
147 case ports.FocusWindow:
148 if s.seat.focused != c.ID {
149 s.changeFocus(c.ID)
150 }
151 case ports.ForwardKey:
152 if !(s.security != nil && s.security.Snapshot().Protected) && s.grabKey(c) {
153 return
154 }
155 _, keyboards := s.focusTarget(c.ID)
156 if c.ID != s.seat.focused || len(keyboards) == 0 {
157 s.log.Debug().Uint64("id", uint64(c.ID)).Msg("ignored forward key")
158 return
159 }
160 s.useKeymap(nil)
161 if s.seat.heldKeys == nil {
162 s.seat.heldKeys = make(map[uint32]bool)
163 }
164 if c.Key.Pressed {
165 s.seat.heldKeys[c.Key.Keycode] = true
166 } else {
167 delete(s.seat.heldKeys, c.Key.Keycode)
168 }
169 s.serial++
170 state := uint32(0)
171 if c.Key.Pressed {
172 state = 1
173 }
174 if c.Key.Pressed {
175 if surf, _ := s.focusTarget(c.ID); surf != nil {
176 s.seat.press, s.seat.pressClient, s.seat.pressAt = s.serial, surf.Client(), time.Now()
177 }
178 }
179 for _, k := range keyboards {
180 s.stampKeyboard(k, c.Key.Time)
181 k.SendKey(s.serial, wireMsec(c.Key.Time), c.Key.Keycode, state)
182 }
183 if c.Key.State != s.seat.modState {
184 s.seat.modState = c.Key.State
185 s.serial++
186 for _, k := range keyboards {
187 s.sendModifiers(k)
188 }
189 }
190 }
191}
192
193func (s *Server) windowKeyboards(w *window) []*wayland.Keyboard {
194 if w == nil || !w.mapped || !w.xdg.resource.Resource.Alive() {
195 return nil
196 }
197 return s.clientKeyboards(w.xdg.resource.Client())
198}
199
200// clientKeyboards returns c's wl_keyboards without allocating. The list holds
201// only live resources (OnDestroy removes a keyboard when it dies) and is
202// replaced, never mutated, so callers may iterate it while sending.
203//
204// This relies on purego-libwayland: destroyed() sets gone and runs OnDestroy
205// synchronously on the display goroutine, and PostEvent never re-enters Go, so
206// a list ranged over while sending is never changed under the caller.
207func (s *Server) clientKeyboards(c server.Client) []*wayland.Keyboard {
208 return s.seat.keyboards[c]
209}
210
211// focusTarget resolves a window or layer ID to its wl_surface and keyboards.
212func (s *Server) focusTarget(id ports.WindowID) (*wayland.Surface, []*wayland.Keyboard) {
213 if l := s.lockSurfaces[id]; l != nil && !l.closed && l.surface.wl.Alive() {
214 // Outgoing leaves also resolve a role whose OnDestroy resource is
215 // already dead. Admission is enforced before entering or delivering.
216 return l.surface.wl, s.clientKeyboards(l.surface.wl.Client())
217 }
218 if w := s.windows[id]; w != nil && w.mapped {
219 if surf := w.xdg.surfaceResource(); surf != nil {
220 return surf, s.windowKeyboards(w)
221 }
222 }
223 if l := s.layers[id]; l != nil && l.mapped && l.resource.Resource.Alive() {
224 if surf := l.surfaceResource(); surf != nil {
225 return surf, s.clientKeyboards(l.resource.Client())
226 }
227 }
228 return nil, nil
229}
230
231// pointerTarget resolves a mapped window or layer ID to its client and
232// live pointers.
233func (s *Server) pointerTarget(id ports.WindowID) (server.Client, []*wayland.Pointer, bool) {
234 if l := s.lockSurfaces[id]; l != nil && s.lockInputTarget(id) {
235 return l.resource.Client(), s.clientPointers(l.resource.Client()), true
236 }
237 if w := s.windows[id]; w != nil && w.mapped && w.xdg.resource.Resource.Alive() {
238 return w.xdg.resource.Client(), s.windowPointers(w), true
239 }
240 if l := s.layers[id]; l != nil && l.mapped && l.resource.Resource.Alive() {
241 return l.resource.Client(), s.layerPointers(l), true
242 }
243 return server.Client{}, nil, false
244}
245
246// pointerSurface is the wl_surface of a mapped window or layer and the
247// point in its surface coordinates.
248func (s *Server) pointerSurface(id ports.WindowID, x, y float64) (*wayland.Surface, []*wayland.Pointer, float64, float64) {
249 if l := s.lockSurfaces[id]; l != nil && !l.closed && l.surface.wl.Alive() {
250 return l.surface.wl, s.clientPointers(l.surface.wl.Client()), x, y
251 }
252 if w := s.windows[id]; w != nil {
253 if surf := w.xdg.surfaceResource(); surf != nil && surf.Resource.Alive() {
254 x, y := w.surfacePoint(x, y)
255 return surf, s.windowPointers(w), x, y
256 }
257 }
258 if l := s.layers[id]; l != nil && l.mapped {
259 if surf := l.surfaceResource(); surf != nil {
260 return surf, s.layerPointers(l), x, y
261 }
262 }
263 return nil, nil, 0, 0
264}
265
266// keymapFile writes a sealed memfd holding the NUL-terminated keymap.
267func keymapFile(keymap string) (int, uint32, error) {
268 if uint64(len(keymap))+1 > uint64(^uint32(0)) {
269 return -1, 0, fmt.Errorf("keymap too large")
270 }
271 fd, err := unix.MemfdCreate("neferwl-keymap", unix.MFD_CLOEXEC|unix.MFD_ALLOW_SEALING)
272 if err != nil {
273 return -1, 0, err
274 }
275 n, err := unix.Write(fd, append([]byte(keymap), 0))
276 if err == nil && n != len(keymap)+1 {
277 err = fmt.Errorf("short keymap write")
278 }
279 if err == nil {
280 _, err = unix.FcntlInt(uintptr(fd), unix.F_ADD_SEALS, unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_WRITE|unix.F_SEAL_SEAL)
281 }
282 if err != nil {
283 unix.Close(fd)
284 return -1, 0, err
285 }
286 return fd, uint32(n), nil
287}
288
289// setKeymap sends repeat info, and a new keymap when one is given, to every keyboard.
290// A new keymap resets held keys and modifiers: the focused client gets leave, then
291// enter with no keys.
292func (s *Server) setKeymap(c ports.SetKeymap) {
293 s.seat.repeatRate, s.seat.repeatDelay = c.RepeatRate, c.RepeatDelay
294 if c.Keymap == "" {
295 for _, list := range s.seat.keyboards {
296 for _, k := range list {
297 if k.Version() >= 4 {
298 k.SendRepeatInfo(int32(s.seat.repeatRate), int32(s.seat.repeatDelay))
299 }
300 }
301 }
302 s.sendGrabKeymap(false)
303 s.log.Info().Int("rate", s.seat.repeatRate).Int("delay", s.seat.repeatDelay).Msg("repeat updated")
304 return
305 }
306 fd, size, err := keymapFile(c.Keymap)
307 if err != nil {
308 s.log.Warn().Err(err).Msg("keymap update failed")
309 return
310 }
311 if s.seat.keymapFD >= 0 {
312 unix.Close(s.seat.keymapFD)
313 }
314 s.seat.keymapFD, s.seat.keymapSize, s.seat.keymapText = fd, size, c.Keymap
315 s.seat.keymapOwner = nil
316 focused := s.seat.focused
317 s.changeFocus(0)
318 s.seat.heldKeys, s.seat.grabKeys = nil, nil
319 s.seat.modState = ports.ModState{}
320 for _, list := range s.seat.keyboards {
321 for _, k := range list {
322 k.SendKeymap(uint32(wayland.KeyboardKeymapFormatXkbV1), s.seat.keymapFD, s.seat.keymapSize)
323 if k.Version() >= 4 {
324 k.SendRepeatInfo(int32(s.seat.repeatRate), int32(s.seat.repeatDelay))
325 }
326 }
327 }
328 s.sendGrabKeymap(true)
329 s.changeFocus(focused)
330 s.log.Info().Uint32("size", size).Int("rate", s.seat.repeatRate).Int("delay", s.seat.repeatDelay).Msg("keymap updated")
331}
332
333func (s *Server) sendModifiers(k *wayland.Keyboard) {
334 m := s.seat.modState
335 k.SendModifiers(s.serial, m.Depressed, m.Latched, m.Locked, m.Group)
336}
337
338func (s *Server) changeFocus(id ports.WindowID) {
339 if s.security != nil && s.security.Snapshot().Protected && id != 0 && !s.lockInputTarget(id) {
340 return
341 }
342 // Enter carries seat held keys and modifiers: they need the seat keymap.
343 s.useKeymap(nil)
344 old := s.focusClient()
345 defer func() {
346 // Tokens of a client losing the focus die with it.
347 if now := s.focusClient(); now != old {
348 s.dropTokens(old)
349 s.seat.focusAt = time.Now()
350 }
351 }()
352 if surf, keyboards := s.focusTarget(s.seat.focused); surf != nil {
353 for _, k := range keyboards {
354 s.serial++
355 k.SendLeave(s.serial, surf)
356 }
357 }
358 s.seat.focused = 0
359 if surf, keyboards := s.focusTarget(id); surf != nil {
360 s.seat.focused = id
361 s.focusSelections()
362 for _, k := range keyboards {
363 s.serial++
364 var keys []byte
365 for code := range s.seat.heldKeys {
366 keys = binary.LittleEndian.AppendUint32(keys, code)
367 }
368 k.SendEnter(s.serial, surf, keys)
369 s.sendModifiers(k)
370 }
371 }
372 s.textInputFocus()
373 s.log.Debug().Uint64("id", uint64(s.seat.focused)).Msg("keyboard focus")
374 s.updateConstraint()
375}
376
377// wheelSteps adds wheel v120 to the rest per axis and returns the whole
378// detents for axis_discrete (seat before v8). High-resolution wheels send
379// fractions of 120; a direction change drops the rest, as wlroots does.
380// wheelHeld keeps the axis value of the frames without a step; values is
381// what a pre-v8 client gets with a step: the held value plus this frame's.
382func (st *seatState) wheelSteps(a ports.PointerAxis) (steps [2]int32, values [2]float64) {
383 if a.Source != ports.AxisWheel {
384 return steps, values
385 }
386 for i, ax := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} {
387 if !ax.Set || ax.V120 == 0 {
388 continue
389 }
390 if (st.wheelRest[i] < 0) != (ax.V120 < 0) {
391 st.wheelRest[i], st.wheelHeld[i] = 0, 0
392 }
393 st.wheelRest[i] += ax.V120
394 st.wheelHeld[i] += ax.Value
395 steps[i] = st.wheelRest[i] / 120
396 st.wheelRest[i] -= steps[i] * 120
397 if steps[i] != 0 {
398 values[i], st.wheelHeld[i] = st.wheelHeld[i], 0
399 }
400 }
401 return steps, values
402}
403
404// sendAxis sends one scroll frame, each event gated by the pointer version.
405// steps are whole wheel detents for axis_discrete. A pre-v8 client gets a
406// wheel axis only with a step, carrying values (all the value since the
407// last step), so it never counts smooth and discrete scroll.
408func (s *Server) sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, values [2]float64) {
409 v := p.Version()
410 axes := [2]ports.ScrollAxis{a.Vertical, a.Horizontal}
411 for i := range axes {
412 if v < 8 && a.Source == ports.AxisWheel && axes[i].V120 != 0 && steps[i] == 0 {
413 axes[i].Set = false
414 }
415 }
416 if !axes[0].Set && !axes[1].Set {
417 return
418 }
419 if v >= 5 {
420 p.SendAxisSource(uint32(a.Source))
421 }
422 for axis, ax := range axes {
423 if !ax.Set {
424 continue
425 }
426 if ax.Stop {
427 if v >= 5 {
428 s.stampPointer(p, a.Time)
429 p.SendAxisStop(wireMsec(a.Time), uint32(axis))
430 }
431 continue
432 }
433 value := ax.Value
434 if a.Source == ports.AxisWheel && ax.V120 != 0 {
435 switch {
436 case v >= 8:
437 p.SendAxisValue120(uint32(axis), ax.V120)
438 default:
439 value = values[axis]
440 if v >= 5 {
441 p.SendAxisDiscrete(uint32(axis), steps[axis])
442 }
443 }
444 }
445 s.stampPointer(p, a.Time)
446 p.SendAxis(wireMsec(a.Time), uint32(axis), server.FixedFromFloat(value))
447 }
448 pointerFrame(p)
449}
450
451// wireMsec is a device timestamp as the 32-bit millisecond time of wl_pointer
452// and wl_keyboard events (wrapping, like the protocol).
453func wireMsec(t time.Duration) uint32 { return uint32(t / time.Millisecond) }
454
455func pointerFrame(p *wayland.Pointer) {
456 if p.Version() >= 5 {
457 p.SendFrame()
458 }
459}
460func (s *Server) windowPointers(w *window) []*wayland.Pointer {
461 if w == nil || !w.mapped || !w.xdg.resource.Resource.Alive() {
462 return nil
463 }
464 return s.clientPointers(w.xdg.resource.Client())
465}
466
467func (s *Server) layerPointers(l *layerSurface) []*wayland.Pointer {
468 if l == nil || !l.mapped || !l.resource.Resource.Alive() {
469 return nil
470 }
471 return s.clientPointers(l.resource.Client())
472}
473
474// clientPointers returns c's wl_pointers without allocating. The list holds
475// only live resources (OnDestroy removes a pointer when it dies) and is
476// replaced, never mutated, so callers may iterate it while sending. See
477// clientKeyboards for the libwayland invariant this relies on.
478func (s *Server) clientPointers(c server.Client) []*wayland.Pointer {
479 return s.seat.pointers[c]
480}
481func (s *Server) changePointerFocus(id ports.WindowID, x, y float64) {
482 if s.security != nil && s.security.Snapshot().Protected && id != 0 && !s.lockInputTarget(id) {
483 return
484 }
485 if id == s.seat.pointerFocus {
486 return
487 }
488 s.seat.wheelRest, s.seat.wheelHeld = [2]int32{}, [2]float64{}
489 // The new client sets its own cursor on enter; until then, the arrow.
490 s.cursorSurface = nil
491 s.setCursor(ports.CursorChange{})
492 if surface, pointers, _, _ := s.pointerSurface(s.seat.pointerFocus, 0, 0); surface != nil {
493 for _, p := range pointers {
494 s.serial++
495 p.SendLeave(s.serial, surface)
496 pointerFrame(p)
497 }
498 }
499 s.seat.pointerFocus = 0
500 clear(s.seat.enters)
501 if surface, pointers, sx, sy := s.pointerSurface(id, x, y); surface != nil {
502 s.seat.pointerFocus, s.seat.pointerX, s.seat.pointerY = id, x, y
503 for _, p := range pointers {
504 s.serial++
505 s.seat.enters[p.Resource] = s.serial
506 p.SendEnter(s.serial, surface, server.FixedFromFloat(sx), server.FixedFromFloat(sy))
507 pointerFrame(p)
508 }
509 }
510 s.updateConstraint()
511}