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