1package neferclient
2
3import (
4 "errors"
5 "fmt"
6
7 "github.com/bnema/go-wayland-bindings/client/cursorshape"
8 "github.com/bnema/go-wayland-bindings/client/wayland"
9 "github.com/bnema/wlturbo/wl"
10 "golang.org/x/sys/unix"
11)
12
13// maxSeatVersion is the highest wl_seat version bound. Version 5 adds the
14// wl_seat.release request and wl_pointer.frame, 8 adds wl_pointer.axis_value120.
15const maxSeatVersion = 9
16
17// CursorShape is a wp_cursor_shape_v1 shape. The values are the protocol's.
18type CursorShape uint32
19
20const (
21 CursorDefault CursorShape = iota + 1
22 CursorContextMenu
23 CursorHelp
24 CursorPointer
25 CursorProgress
26 CursorWait
27 CursorCell
28 CursorCrosshair
29 CursorText
30 CursorVerticalText
31 CursorAlias
32 CursorCopy
33 CursorMove
34 CursorNoDrop
35 CursorNotAllowed
36 CursorGrab
37 CursorGrabbing
38 CursorEResize
39 CursorNResize
40 CursorNEResize
41 CursorNWResize
42 CursorSResize
43 CursorSEResize
44 CursorSWResize
45 CursorWResize
46 CursorEWResize
47 CursorNSResize
48 CursorNESWResize
49 CursorNWSEResize
50 CursorColResize
51 CursorRowResize
52 CursorAllScroll
53 CursorZoomIn
54 CursorZoomOut
55 CursorDndAsk // needs cursor-shape version 2
56 CursorAllResize // needs cursor-shape version 2
57)
58
59// PointerKind tells which pointer event a [PointerEvent] is.
60type PointerKind uint8
61
62const (
63 PointerEnter PointerKind = iota + 1
64 PointerLeave
65 PointerMotion
66 PointerButton
67 // PointerAxis is a scroll step on Axis. DX or DY carry the continuous
68 // value in logical pixels; Value120 carries the wheel value in 1/120 of a
69 // notch. A compositor can send both for one physical step: use one.
70 PointerAxis
71 // PointerAxisStop ends a scroll sequence on Axis.
72 PointerAxisStop
73)
74
75// Scroll axes of [PointerEvent.Axis].
76const (
77 AxisVertical = 0
78 AxisHorizontal = 1
79)
80
81// PointerEvent is one pointer event over a surface of the connection. X and Y
82// are surface-local logical pixels (already logical: never divide by scale)
83// and hold the latest position for every kind.
84type PointerEvent struct {
85 Surface SurfaceID
86 Kind PointerKind
87 X, Y float64
88 DX, DY float64 // PointerAxis: continuous scroll value
89 Button uint32 // PointerButton: evdev button code
90 Pressed bool // PointerButton
91 Axis uint32 // PointerAxis, PointerAxisStop: AxisVertical or AxisHorizontal
92 Value120 int32 // PointerAxis: wheel value in 1/120 notches
93}
94
95// KeyEvent is one key press, repeat or release.
96type KeyEvent struct {
97 Surface SurfaceID
98 Keycode uint32 // evdev key code; 0 when Secret is set
99 Keysym uint32 // xkb keysym in the current layout and modifier state; 0 when Secret is set
100 Modifiers Modifiers
101 Group uint32 // effective layout index
102 Pressed bool
103 Repeat bool
104 // Secret is set while a [SecretBuffer] is installed, for the press,
105 // repeats and release of a key that produced text (or is a dead or compose
106 // key): the text went into the buffer, and Keycode and Keysym are 0 so the
107 // event does not reveal which character was typed. Other keys (Enter,
108 // Escape, BackSpace, modifiers, Control combinations) keep their keysym.
109 Secret bool
110 // Text is the UTF-8 text of the key, nil for releases and keys without
111 // text. It aliases library storage that is wiped when Handler.Key
112 // returns. It is always nil while a [SecretBuffer] is installed.
113 Text []byte
114}
115
116// Seat is the connection's first wl_seat: pointer, keyboard and cursor. It
117// belongs to the owner goroutine. Without a seat global every method is a
118// no-op. The seat is bound once, at first use: a seat announced later is not
119// picked up, and a removed one is not replaced (no seat hotplug).
120type Seat struct {
121 c *Conn
122 proxy *wayland.Seat
123 name uint32 // registry name of the global
124 ver uint32
125
126 pointer *wayland.Pointer
127 ptrGen uint32 // generation of pointer; bumped on every create and drop
128 ptrSurf SurfaceID
129 px, py float64
130 pev PointerEvent
131
132 keyboard *wayland.Keyboard
133 kbGen uint32
134 kbSurf SurfaceID
135 kb *keyboard
136 kev KeyEvent
137
138 secret *SecretBuffer
139
140 shapes *cursorshape.WpCursorShapeManager
141 shapesVer uint32
142 shapeDev *cursorshape.WpCursorShapeDevice
143 enterSerial uint32 // latest pointer enter serial, 0 while outside
144 shape CursorShape
145}
146
147// Seat returns the connection's seat (nil after Close), binding wl_seat on first use. The seat
148// is also bound when the first surface is created. Input starts flowing to the
149// Handler once the compositor announces the capabilities.
150func (c *Conn) Seat() *Seat {
151 c.ensureSeat()
152 return c.seat
153}
154
155// ensureSeat binds wl_seat and the optional cursor-shape manager once. A
156// failure is reported by the next Dispatch through Handler.Error.
157func (c *Conn) ensureSeat() {
158 if c.seat != nil || c.closed {
159 return
160 }
161 s := &Seat{c: c}
162 c.seat = s
163 g, ok := c.display.Registry().FindGlobal(wayland.SeatInterface)
164 if !ok {
165 return
166 }
167 q := c.q
168 proxy, v, err := c.Bind(wayland.SeatInterface, maxSeatVersion, func(ctx *wl.Context) *wayland.Seat {
169 p := wayland.NewSeat(ctx)
170 p.OnCapabilities(func(caps uint32) {
171 e := event{kind: evSeatCaps, a: int32(caps)}
172 q.post(&e)
173 })
174 return p
175 })
176 if err != nil {
177 c.seatErr = fmt.Errorf("neferclient: bind wl_seat: %w", err)
178 return
179 }
180 s.proxy, s.name, s.ver = proxy, g.Name, v
181 m, mv, err := c.Bind(cursorshape.WpCursorShapeManagerInterface, 2, cursorshape.NewWpCursorShapeManager)
182 if err != nil {
183 if !errors.Is(err, ErrGlobalNotFound) {
184 c.seatErr = fmt.Errorf("neferclient: bind cursor shape manager: %w", err)
185 }
186 return
187 }
188 s.shapes, s.shapesVer = m, mv
189}
190
191// SetSecret installs b to receive typed text, or removes it with nil. While
192// installed, printable text goes only into b and never into KeyEvent.Text;
193// BackSpace removes one code point; every other key is still delivered as a
194// KeyEvent without text. Keys that produced text are delivered with
195// KeyEvent.Secret set and Keycode and Keysym 0, for the press, repeats and
196// release; Enter, Escape, BackSpace and Control combinations keep their
197// keysym. Installing or removing cancels held keys and repeat. The previous
198// buffer is not wiped: that is the caller's call.
199//
200// Limits: runtime/secret (GOEXPERIMENT=runtimesecret) erases Go registers,
201// stack and heap temporaries, but not the native memory of libxkbcommon, which
202// processes the key text.
203func (s *Seat) SetSecret(b *SecretBuffer) {
204 if s == nil {
205 return
206 }
207 s.secret = b
208 if s.kb != nil {
209 s.kb.setSecret(b)
210 }
211}
212
213// SetCursor asks the compositor to show shape for the pointer. It is a no-op
214// when the compositor has no cursor-shape support, the pointer is outside the
215// connection's surfaces, or shape is unchanged since the last enter.
216func (s *Seat) SetCursor(shape CursorShape) error {
217 if s == nil || s.c.closed {
218 return ErrClosed
219 }
220 if shape < CursorDefault || shape > CursorAllResize {
221 return fmt.Errorf("neferclient: invalid cursor shape %d", shape)
222 }
223 if shape >= CursorDndAsk && s.shapes != nil && s.shapesVer < 2 {
224 return &CapabilityError{Name: cursorshape.WpCursorShapeManagerInterface, Cause: fmt.Errorf("shape %d requires version 2", shape)}
225 }
226 if s.shapes == nil || s.pointer == nil || s.ptrSurf == 0 || shape == s.shape {
227 return nil
228 }
229 if s.shapeDev == nil {
230 dev, err := s.shapes.GetPointer(s.pointer)
231 if err != nil {
232 return fmt.Errorf("neferclient: cursor shape device: %w", err)
233 }
234 s.shapeDev = dev
235 }
236 if err := s.shapeDev.SetShape(s.enterSerial, uint32(shape)); err != nil {
237 return fmt.Errorf("neferclient: set cursor shape: %w", err)
238 }
239 s.shape = shape
240 return nil
241}
242
243// surfaceByObject finds the surface whose wl_surface has object id obj.
244func (c *Conn) surfaceByObject(obj uint32) *Surface {
245 for _, s := range c.surfaces {
246 if !s.closed && s.surf != nil && s.surf.ID() == obj {
247 return s
248 }
249 }
250 return nil
251}
252
253func (c *Conn) seatTimer(fd int32) bool {
254 return c.seat != nil && c.seat.kb != nil && int(fd) == c.seat.kb.tfd
255}
256
257// seatRepeat handles the repeat timer: at most one repeat per expiration.
258func (c *Conn) seatRepeat(h Handler) {
259 s := c.seat
260 if s.kb.expired() {
261 ke := &s.kev
262 *ke = KeyEvent{Surface: s.kbSurf}
263 ok, err := s.kb.repeat(ke)
264 switch {
265 case err != nil:
266 h.Error(err)
267 case ok:
268 s.deliverKey(ke, h)
269 }
270 }
271 if err := s.kb.takeTimerErr(); err != nil {
272 h.Error(err)
273 }
274 if !c.closed {
275 if err := c.armFD(s.kb.tfd, unix.EPOLL_CTL_MOD); err != nil {
276 h.Error(err)
277 }
278 }
279}
280
281func (s *Seat) deliverKey(ke *KeyEvent, h Handler) {
282 h.Key(ke)
283 clear(s.kb.scratch[:]) // ordinary text must not outlive the call
284 *ke = KeyEvent{}
285 if s.kb.changed {
286 h.SecretChanged(s.secret.Len())
287 }
288}
289
290// seatSurfaceGone forgets focus on a closing surface.
291func (c *Conn) seatSurfaceGone(sf *Surface) {
292 s := c.seat
293 if s == nil {
294 return
295 }
296 if s.ptrSurf == sf.id {
297 s.ptrSurf = 0
298 s.enterSerial, s.shape = 0, 0
299 }
300 if s.kbSurf == sf.id {
301 s.kbSurf = 0
302 if s.kb != nil {
303 s.kb.setFocus(false)
304 }
305 }
306}
307
308func (c *Conn) seatGlobalRemoved(global uint32, h Handler) {
309 if s := c.seat; s != nil && s.proxy != nil && s.name == global {
310 s.release(h)
311 }
312}
313
314// release drops the devices and the seat proxy.
315func (s *Seat) release(h Handler) {
316 s.dropPointer(h)
317 s.dropKeyboard(h)
318 if s.ver >= 5 {
319 _ = s.proxy.Release()
320 } else {
321 s.c.wlctx.Unregister(s.proxy)
322 }
323 s.proxy = nil
324}
325
326func releaseDevice(ver uint32, c *wl.Context, p interface {
327 wl.Proxy
328 Release() error
329}) {
330 if ver >= 3 {
331 _ = p.Release()
332 } else {
333 c.Unregister(p)
334 }
335}
336
337func (s *Seat) dropPointer(h Handler) {
338 if s.pointer == nil {
339 return
340 }
341 if s.shapeDev != nil {
342 _ = s.shapeDev.Destroy()
343 s.shapeDev = nil
344 }
345 s.enterSerial, s.shape = 0, 0
346 releaseDevice(s.ver, s.c.wlctx, s.pointer)
347 s.pointer = nil
348 s.ptrGen++
349 if id := s.ptrSurf; id != 0 {
350 s.ptrSurf = 0
351 pe := &s.pev
352 *pe = PointerEvent{Surface: id, Kind: PointerLeave, X: s.px, Y: s.py}
353 h.Pointer(pe)
354 }
355}
356
357func (s *Seat) dropKeyboard(h Handler) {
358 if s.keyboard == nil {
359 return
360 }
361 releaseDevice(s.ver, s.c.wlctx, s.keyboard)
362 s.keyboard = nil
363 s.kbGen++
364 if id := s.kbSurf; id != 0 {
365 s.kbSurf = 0
366 s.kb.setFocus(false)
367 h.KeyboardFocus(id, false)
368 } else if s.kb != nil {
369 s.kb.setFocus(false)
370 }
371}
372
373func (s *Seat) newPointer() error {
374 c := s.c
375 c.pause() // handlers go on after the request, before any event is read
376 defer c.resume()
377 p, err := s.proxy.GetPointer()
378 if err != nil {
379 return fmt.Errorf("neferclient: get wl_pointer: %w", err)
380 }
381 s.pointer = p
382 s.ptrGen++
383 gen, q := s.ptrGen, c.q
384 post := func(e *event) {
385 e.version = gen
386 q.post(e)
387 }
388 p.OnEnter(func(serial, surf uint32, x, y wl.Fixed) {
389 post(&event{kind: evPtrEnter, global: surf, serial: serial, a: int32(x), b: int32(y)})
390 })
391 p.OnLeave(func(_, surf uint32) { post(&event{kind: evPtrLeave, global: surf}) })
392 p.OnMotion(func(_ uint32, x, y wl.Fixed) { post(&event{kind: evPtrMotion, a: int32(x), b: int32(y)}) })
393 p.OnButton(func(serial, _, button, state uint32) {
394 post(&event{kind: evPtrButton, serial: serial, flags: button, a: int32(state)})
395 })
396 p.OnAxis(func(_, axis uint32, v wl.Fixed) { post(&event{kind: evPtrAxis, flags: axis, a: int32(v)}) })
397 p.OnAxisValue120(func(axis uint32, v int32) { post(&event{kind: evPtrAxisValue, flags: axis, a: v}) })
398 p.OnAxisStop(func(_, axis uint32) { post(&event{kind: evPtrAxisStop, flags: axis}) })
399 return nil
400}
401
402func (s *Seat) newKeyboard() error {
403 c := s.c
404 if s.kb == nil {
405 kb, err := newKeyboard(composeLocale())
406 if err != nil {
407 return fmt.Errorf("neferclient: keyboard: %w", err)
408 }
409 if err = c.armFD(kb.tfd, unix.EPOLL_CTL_ADD); err != nil {
410 kb.close()
411 return err // armFD wraps it
412 }
413 kb.secret = s.secret
414 s.kb = kb
415 }
416 c.pause()
417 defer c.resume()
418 k, err := s.proxy.GetKeyboard()
419 if err != nil {
420 return fmt.Errorf("neferclient: get wl_keyboard: %w", err)
421 }
422 s.keyboard = k
423 s.kbGen++
424 gen, q := s.kbGen, c.q
425 post := func(e *event) {
426 e.version = gen
427 q.post(e)
428 }
429 k.OnKeymap(func(format uint32, fd *wl.OwnedFD, size uint32) {
430 n, err := fd.Take()
431 if err != nil {
432 post(&event{kind: evSeatBad, a: badKeymapFD})
433 return
434 }
435 post(&event{kind: evKbKeymap, fd: int32(n), flags: size, a: int32(format)})
436 })
437 k.OnEnter(func(serial, surf uint32, _ []byte) { post(&event{kind: evKbEnter, global: surf, serial: serial}) })
438 k.OnLeave(func(_, surf uint32) { post(&event{kind: evKbLeave, global: surf}) })
439 k.OnKey(func(serial, _, key, state uint32) {
440 post(&event{kind: evKbKey, serial: serial, flags: key, a: int32(state)})
441 })
442 k.OnModifiers(func(_, depressed, latched, locked, group uint32) {
443 post(&event{kind: evKbModifiers, flags: depressed, dev: uint64(latched) | uint64(locked)<<32, a: int32(group)})
444 })
445 k.OnRepeatInfo(func(rate, delay int32) { post(&event{kind: evKbRepeat, a: rate, b: delay}) })
446 return nil
447}
448
449// Codes of evSeatBad.
450const badKeymapFD int32 = 100
451
452// applySeat handles the seat events on the owner.
453func (c *Conn) applySeat(ev *event, h Handler) {
454 defer ev.closeFD()
455 s := c.seat
456 if s == nil {
457 return
458 }
459 switch ev.kind {
460 case evSeatBad:
461 h.Error(errors.New("neferclient: keymap descriptor unavailable"))
462 case evSeatCaps:
463 s.applyCaps(uint32(ev.a), h)
464 case evPtrEnter, evPtrLeave, evPtrMotion, evPtrButton, evPtrAxis, evPtrAxisValue, evPtrAxisStop:
465 if s.pointer != nil && ev.version == s.ptrGen {
466 s.applyPointer(ev, h)
467 }
468 default:
469 if s.keyboard != nil && ev.version == s.kbGen {
470 s.applyKeyboard(ev, h)
471 if err := s.kb.takeTimerErr(); err != nil {
472 h.Error(err)
473 }
474 }
475 }
476}
477
478func (s *Seat) applyCaps(caps uint32, h Handler) {
479 if s.proxy == nil {
480 return
481 }
482 if caps&1 == 0 {
483 s.dropPointer(h)
484 }
485 if caps&2 == 0 {
486 s.dropKeyboard(h)
487 }
488 if caps&1 != 0 && s.pointer == nil {
489 if err := s.newPointer(); err != nil {
490 h.Error(err)
491 }
492 }
493 if caps&2 != 0 && s.keyboard == nil {
494 if err := s.newKeyboard(); err != nil {
495 h.Error(err)
496 }
497 }
498}
499
500func (s *Seat) applyPointer(ev *event, h Handler) {
501 pe := &s.pev
502 *pe = PointerEvent{}
503 fixed := func(v int32) float64 { return wl.Fixed(v).Float64() }
504 switch ev.kind {
505 case evPtrEnter:
506 sf := s.c.surfaceByObject(ev.global)
507 if sf == nil {
508 return
509 }
510 s.ptrSurf = sf.id
511 s.px, s.py = fixed(ev.a), fixed(ev.b)
512 s.enterSerial, s.shape = ev.serial, 0 // the compositor forgets the cursor on leave
513 pe.Kind = PointerEnter
514 case evPtrLeave:
515 if sf := s.c.surfaceByObject(ev.global); s.ptrSurf == 0 || sf == nil || sf.id != s.ptrSurf {
516 return
517 }
518 pe.Kind = PointerLeave
519 pe.Surface = s.ptrSurf
520 pe.X, pe.Y = s.px, s.py
521 s.ptrSurf = 0
522 s.enterSerial, s.shape = 0, 0
523 h.Pointer(pe)
524 return
525 default:
526 if s.ptrSurf == 0 {
527 return
528 }
529 switch ev.kind {
530 case evPtrMotion:
531 s.px, s.py = fixed(ev.a), fixed(ev.b)
532 pe.Kind = PointerMotion
533 case evPtrButton:
534 pe.Kind, pe.Button, pe.Pressed = PointerButton, ev.flags, ev.a == 1
535 case evPtrAxis:
536 pe.Kind, pe.Axis = PointerAxis, ev.flags
537 if ev.flags == AxisVertical {
538 pe.DY = fixed(ev.a)
539 } else {
540 pe.DX = fixed(ev.a)
541 }
542 case evPtrAxisValue:
543 pe.Kind, pe.Axis, pe.Value120 = PointerAxis, ev.flags, ev.a
544 case evPtrAxisStop:
545 pe.Kind, pe.Axis = PointerAxisStop, ev.flags
546 }
547 }
548 pe.Surface, pe.X, pe.Y = s.ptrSurf, s.px, s.py
549 h.Pointer(pe)
550}
551
552func (s *Seat) applyKeyboard(ev *event, h Handler) {
553 kb := s.kb
554 switch ev.kind {
555 case evKbKeymap:
556 fd := int(ev.fd)
557 ev.fd = -1 // replaceFD consumes fd; the deferred closeFD must not touch the number again
558 if ev.a == 0 { // no_keymap: the compositor sends no usable map; nothing to report
559 _ = unix.Close(fd)
560 return
561 }
562 if ev.a != 1 || ev.flags == 0 || ev.flags > keymapMax {
563 _ = unix.Close(fd)
564 h.Error(fmt.Errorf("neferclient: unsupported keymap format %d or size %d", ev.a, ev.flags))
565 return
566 }
567 if err := kb.replaceFD(fd, int(ev.flags)); err != nil {
568 h.Error(fmt.Errorf("neferclient: keymap: %w", err))
569 }
570 case evKbEnter:
571 sf := s.c.surfaceByObject(ev.global)
572 if sf == nil {
573 return
574 }
575 s.kbSurf = sf.id
576 kb.setFocus(true)
577 h.KeyboardFocus(sf.id, true)
578 case evKbLeave:
579 sf := s.c.surfaceByObject(ev.global)
580 if sf == nil || s.kbSurf != sf.id {
581 return
582 }
583 s.kbSurf = 0
584 kb.setFocus(false)
585 h.KeyboardFocus(sf.id, false)
586 case evKbKey:
587 ke := &s.kev
588 *ke = KeyEvent{Surface: s.kbSurf}
589 if ev.a == 1 {
590 ok, err := kb.press(ev.flags, ke)
591 switch {
592 case err != nil:
593 h.Error(err)
594 case ok:
595 s.deliverKey(ke, h)
596 }
597 } else if kb.release(ev.flags, ke) {
598 s.deliverKey(ke, h)
599 }
600 case evKbModifiers:
601 if err := kb.setMask(ev.flags, uint32(ev.dev), uint32(ev.dev>>32), uint32(ev.a)); err != nil {
602 h.Error(err)
603 }
604 case evKbRepeat:
605 kb.setRepeat(ev.a, ev.b)
606 }
607}
608
609func (c *Conn) closeSeat() {
610 if s := c.seat; s != nil && s.kb != nil {
611 s.kb.close()
612 s.kb = nil
613 }
614}