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