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