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