1// Package libinput reads keyboards and pointers through libinput's udev backend.
2package libinput
3
4import (
5 "context"
6 "errors"
7 "fmt"
8 "strings"
9 "sync"
10 "time"
11 "unsafe"
12
13 "github.com/bnema/neferwl/internal/adapters/fdwait"
14 "github.com/bnema/neferwl/internal/adapters/xkb"
15 "github.com/bnema/neferwl/internal/ports"
16 "github.com/bnema/purego"
17 "github.com/bnema/zerowrap"
18 "golang.org/x/sys/unix"
19)
20
21// ErrEmergencyQuit is returned when Ctrl+Alt+Backspace is pressed.
22var ErrEmergencyQuit = errors.New("emergency quit key")
23
24const (
25 evDeviceAdded = 1
26 evDeviceRemoved = 2
27 evKeyboardKey = 300
28 evPointerMotion = 400
29 evPointerAbs = 401
30 evPointerButton = 402
31 evScrollWheel = 404
32 evScrollFinger = 405
33 evScrollCont = 406
34 evSwipeBegin = 800
35 evSwipeUpdate = 801
36 evSwipeEnd = 802
37 evPinchBegin = 803
38 evPinchUpdate = 804
39 evPinchEnd = 805
40 evHoldBegin = 806
41 evHoldEnd = 807
42)
43
44var (
45 loadOnce sync.Once
46 loadErr error
47
48 udevNew func() uintptr
49 udevUnref func(uintptr) uintptr
50 createContext func(iface unsafe.Pointer, data uintptr, udev uintptr) uintptr
51 assignSeat func(li uintptr, seat string) int32
52 unref func(li uintptr) uintptr
53 getFD func(li uintptr) int32
54 dispatch func(li uintptr) int32
55 getEvent func(li uintptr) uintptr
56 eventType func(ev uintptr) int32
57 eventDestroy func(ev uintptr)
58 eventDevice func(ev uintptr) uintptr
59 deviceName func(dev uintptr) string
60 suspend func(li uintptr)
61 resume func(li uintptr) int32
62 keyboardEvent func(ev uintptr) uintptr
63 keyboardKey func(kev uintptr) uint32
64 keyboardState func(kev uintptr) int32
65 keyboardUsec func(kev uintptr) uint64
66 pointerEvent func(ev uintptr) uintptr
67 pointerDX func(pev uintptr) float64
68 pointerDY func(pev uintptr) float64
69 pointerRawDX func(pev uintptr) float64
70 pointerRawDY func(pev uintptr) float64
71 pointerUsec func(pev uintptr) uint64
72 pointerAbsX func(pev uintptr, width uint32) float64
73 pointerAbsY func(pev uintptr, height uint32) float64
74 pointerButton func(pev uintptr) uint32
75 pointerBtnState func(pev uintptr) int32
76 pointerHasAxis func(pev uintptr, axis uint32) int32
77 scrollValue func(pev uintptr, axis uint32) float64
78 scrollV120 func(pev uintptr, axis uint32) float64
79 gestureEvent func(ev uintptr) uintptr
80 gestureFingers func(gev uintptr) int32
81 gestureCanceled func(gev uintptr) int32
82 gestureDX func(gev uintptr) float64
83 gestureDY func(gev uintptr) float64
84 gestureAccelDX func(gev uintptr) float64
85 gestureAccelDY func(gev uintptr) float64
86 gestureUsec func(gev uintptr) uint64
87 gestureScale func(gev uintptr) float64
88 gestureAngle func(gev uintptr) float64
89 deviceRef func(dev uintptr) uintptr
90 deviceUnref func(dev uintptr) uintptr
91 hasNatural func(dev uintptr) int32
92 setNatural func(dev uintptr, on int32) int32
93 tapFingers func(dev uintptr) int32
94 setTap func(dev uintptr, on int32) int32
95 setTapMap func(dev uintptr, m int32) int32
96 hasCapability func(dev uintptr, capability int32) int32
97 accelAvailable func(dev uintptr) int32
98 setAccelSpeed func(dev uintptr, speed float64) int32
99 accelProfiles func(dev uintptr) uint32
100 leftHandedAvailable func(dev uintptr) int32
101 setLeftHanded func(dev uintptr, on int32) int32
102 setAccelProfile func(dev uintptr, profile uint32) int32
103 iface [2]uintptr
104 active ports.Seat
105)
106
107func load() error {
108 loadOnce.Do(func() {
109 udev, err := purego.Dlopen("libudev.so.1", purego.RTLD_NOW|purego.RTLD_LOCAL)
110 if err != nil {
111 loadErr = fmt.Errorf("open libudev.so.1: %w", err)
112 return
113 }
114 lib, err := purego.Dlopen("libinput.so.10", purego.RTLD_NOW|purego.RTLD_LOCAL)
115 if err != nil {
116 loadErr = fmt.Errorf("open libinput.so.10: %w", err)
117 return
118 }
119 defer func() {
120 if r := recover(); r != nil {
121 loadErr = fmt.Errorf("libinput: %v", r)
122 }
123 }()
124 purego.RegisterLibFunc(&udevNew, udev, "udev_new")
125 purego.RegisterLibFunc(&udevUnref, udev, "udev_unref")
126 reg := func(fn any, name string) { purego.RegisterLibFunc(fn, lib, "libinput_"+name) }
127 reg(&createContext, "udev_create_context")
128 reg(&assignSeat, "udev_assign_seat")
129 reg(&unref, "unref")
130 reg(&getFD, "get_fd")
131 reg(&dispatch, "dispatch")
132 reg(&getEvent, "get_event")
133 reg(&eventType, "event_get_type")
134 reg(&eventDestroy, "event_destroy")
135 reg(&eventDevice, "event_get_device")
136 reg(&deviceName, "device_get_name")
137 reg(&suspend, "suspend")
138 reg(&resume, "resume")
139 reg(&keyboardEvent, "event_get_keyboard_event")
140 reg(&keyboardKey, "event_keyboard_get_key")
141 reg(&keyboardState, "event_keyboard_get_key_state")
142 reg(&keyboardUsec, "event_keyboard_get_time_usec")
143 reg(&pointerEvent, "event_get_pointer_event")
144 reg(&pointerDX, "event_pointer_get_dx")
145 reg(&pointerDY, "event_pointer_get_dy")
146 reg(&pointerRawDX, "event_pointer_get_dx_unaccelerated")
147 reg(&pointerRawDY, "event_pointer_get_dy_unaccelerated")
148 reg(&pointerUsec, "event_pointer_get_time_usec")
149 reg(&pointerAbsX, "event_pointer_get_absolute_x_transformed")
150 reg(&pointerAbsY, "event_pointer_get_absolute_y_transformed")
151 reg(&pointerButton, "event_pointer_get_button")
152 reg(&pointerBtnState, "event_pointer_get_button_state")
153 reg(&pointerHasAxis, "event_pointer_has_axis")
154 reg(&scrollValue, "event_pointer_get_scroll_value")
155 reg(&scrollV120, "event_pointer_get_scroll_value_v120")
156 reg(&gestureEvent, "event_get_gesture_event")
157 reg(&gestureFingers, "event_gesture_get_finger_count")
158 reg(&gestureCanceled, "event_gesture_get_cancelled")
159 reg(&gestureDX, "event_gesture_get_dx_unaccelerated")
160 reg(&gestureDY, "event_gesture_get_dy_unaccelerated")
161 reg(&gestureAccelDX, "event_gesture_get_dx")
162 reg(&gestureAccelDY, "event_gesture_get_dy")
163 reg(&gestureUsec, "event_gesture_get_time_usec")
164 reg(&gestureScale, "event_gesture_get_scale")
165 reg(&gestureAngle, "event_gesture_get_angle_delta")
166 reg(&deviceRef, "device_ref")
167 reg(&deviceUnref, "device_unref")
168 reg(&hasNatural, "device_config_scroll_has_natural_scroll")
169 reg(&setNatural, "device_config_scroll_set_natural_scroll_enabled")
170 reg(&tapFingers, "device_config_tap_get_finger_count")
171 reg(&setTap, "device_config_tap_set_enabled")
172 reg(&setTapMap, "device_config_tap_set_button_map")
173 reg(&hasCapability, "device_has_capability")
174 reg(&accelAvailable, "device_config_accel_is_available")
175 reg(&setAccelSpeed, "device_config_accel_set_speed")
176 reg(&accelProfiles, "device_config_accel_get_profiles")
177 reg(&setAccelProfile, "device_config_accel_set_profile")
178 reg(&leftHandedAvailable, "device_config_left_handed_is_available")
179 reg(&setLeftHanded, "device_config_left_handed_set")
180 iface[0] = purego.NewCallback(func(path *byte, _, _ uintptr) uintptr {
181 fd, err := active.OpenDevice(goString(path))
182 if err != nil {
183 return uintptr(uint32(^uint32(unix.EACCES) + 1)) // -EACCES as C int
184 }
185 return uintptr(fd)
186 })
187 iface[1] = purego.NewCallback(func(fd, _ uintptr) { active.CloseDevice(int(int32(fd))) })
188 })
189 return loadErr
190}
191
192func goString(p *byte) string {
193 if p == nil {
194 return ""
195 }
196 n := 0
197 for *(*byte)(unsafe.Add(unsafe.Pointer(p), n)) != 0 {
198 n++
199 }
200 return string(unsafe.Slice(p, n))
201}
202
203// Options configures the input adapter.
204type Options struct {
205 // Security gates credential logging and emergency quit and stamps input
206 // before forwarding. Nil preserves raw unlocked behavior; core remains
207 // responsible for input admission.
208 Security ports.SessionSecurity
209 Seat ports.Seat
210 SeatName string
211 Keymap *xkb.Keymap
212 // Keymaps replaces Keymap live; Run takes ownership and closes the old one.
213 Keymaps <-chan *xkb.Keymap
214 // Layout is the initial output layout; Layouts replaces it live.
215 Layout ports.Layout
216 Layouts <-chan ports.Layout
217 // Constraints holds the pointer lock or confinement of the focused
218 // window, in global logical coordinates.
219 Constraints <-chan ports.PointerConstraint
220 // DeviceConfig is the initial config: Touchpad for touchpads, Mouse for
221 // the other pointers (mice, trackballs, trackpoints); other devices are
222 // left alone. DeviceConfigs replaces it live, on the devices already added.
223 DeviceConfig ports.InputDevicesConfig
224 DeviceConfigs <-chan ports.InputDevicesConfig
225 Active <-chan bool
226 // MoveCursor, when set, places the hardware cursor as soon as motion is
227 // read, before core sees the event: the output under the pointer and
228 // the physical position on it. motion is false when the pointer is only
229 // placed again after a layout or constraint change.
230 MoveCursor func(output string, x, y float64, motion bool)
231 Log zerowrap.Logger
232 // LogMotion logs every pointer motion (--debug=input-motion); motions
233 // are otherwise only counted in the input stats.
234 LogMotion bool
235 // LogKeys logs every key (--debug=input-keys): typed text, off by
236 // default.
237 LogKeys bool
238}
239
240// control is one control message of Options for Run to apply. keymap is the
241// replacement keymap it carries, if any: a control dropped at shutdown
242// closes it instead.
243type control struct {
244 apply func()
245 keymap *xkb.Keymap
246}
247
248func (c control) discard() {
249 if c.keymap != nil {
250 c.keymap.Close()
251 }
252}
253
254// relayControls hands the control messages of opts to Run on controls,
255// waking its poll for each, until ctx ends. It only reads the channels of
256// opts; the controls run on Run's goroutine, the only one to touch p, in, li
257// and opts.Keymap.
258func relayControls(ctx context.Context, opts *Options, p *pointer, in *inputState, li uintptr, controls chan<- control, wake func()) {
259 for {
260 var c control
261 var f func()
262 select {
263 case <-ctx.Done():
264 return
265 case on := <-opts.Active:
266 f = func() {
267 if on {
268 resume(li)
269 } else {
270 suspend(li)
271 }
272 opts.Log.Info().Bool("active", on).Msg("input")
273 }
274 case km := <-opts.Keymaps:
275 c.keymap = km
276 f = func() {
277 if opts.Security != nil {
278 km.QuarantineFrom(opts.Keymap)
279 }
280 opts.Keymap.Close()
281 opts.Keymap = km
282 opts.Log.Info().Msg("keymap replaced")
283 }
284 case l := <-opts.Layouts:
285 f = func() {
286 p.setLayout(l)
287 p.moved(opts.MoveCursor, false)
288 }
289 case pc := <-opts.Constraints:
290 f = func() {
291 p.constrain(pc)
292 p.moved(opts.MoveCursor, false)
293 }
294 case dc := <-opts.DeviceConfigs:
295 f = func() {
296 in.cfg = dc
297 for dev := range in.devices {
298 in.configure(dev, opts.Log)
299 }
300 t, m := dc.Touchpad, dc.Mouse
301 opts.Log.Info().Bool("natural_scroll", t.NaturalScroll).Bool("tap", t.Tap).Float64("accel_speed", t.AccelSpeed).Str("accel_profile", t.AccelProfile).Bool("left_handed", t.LeftHanded).Float64("scroll_factor", t.ScrollFactor).
302 Bool("mouse_natural_scroll", m.NaturalScroll).Float64("mouse_accel_speed", m.AccelSpeed).Str("mouse_accel_profile", m.AccelProfile).Bool("mouse_left_handed", m.LeftHanded).Msg("input devices reloaded")
303 }
304 }
305 c.apply = f
306 select {
307 case controls <- c:
308 wake()
309 case <-ctx.Done():
310 c.discard()
311 return
312 }
313 }
314}
315
316// statsEvery is how often Run logs input throughput.
317const statsEvery = 10 * time.Second
318
319// Run owns libinput and the keymap, converting device events into input events.
320func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error {
321 defer func() { opts.Keymap.Close() }()
322 if err := load(); err != nil {
323 return err
324 }
325 active = opts.Seat
326 udev := udevNew()
327 if udev == 0 {
328 return errors.New("udev_new failed")
329 }
330 defer udevUnref(udev)
331 li := createContext(unsafe.Pointer(&iface), 0, udev)
332 if li == 0 {
333 return errors.New("libinput_udev_create_context failed")
334 }
335 defer unref(li)
336 name := opts.SeatName
337 if name == "" {
338 name = "seat0"
339 }
340 if assignSeat(li, name) != 0 {
341 return fmt.Errorf("libinput_udev_assign_seat %s failed", name)
342 }
343 p := newPointer(opts.Layout)
344 p.moved(opts.MoveCursor, false)
345 in := &inputState{cfg: opts.DeviceConfig, dev: libinputDevices{}, devices: map[uintptr]bool{}}
346 defer in.release()
347 waiter, err := fdwait.New()
348 if err != nil {
349 return fmt.Errorf("libinput waiter: %w", err)
350 }
351 defer waiter.Close()
352 waiter.StopOn(ctx)
353 fd := getFD(li)
354 if err := waiter.Pin(fd); err != nil {
355 return fmt.Errorf("libinput waiter: %w", err)
356 }
357 fwd := newForwarder(opts.Log)
358 fwdCtx, stopFwd := context.WithCancel(ctx)
359 var fwdDone sync.WaitGroup
360 fwdDone.Go(func() { fwd.run(fwdCtx, input) })
361 // Control messages wake the poll instead of a polling timeout; they are
362 // applied on this goroutine, which owns libinput.
363 controls := make(chan control, 8)
364 fwdDone.Go(func() { relayControls(fwdCtx, &opts, p, in, li, controls, waiter.Wake) })
365 defer func() {
366 stopFwd()
367 fwdDone.Wait()
368 // Controls never applied: free what they carry.
369 for len(controls) > 0 {
370 (<-controls).discard()
371 }
372 }()
373 fds := []unix.PollFd{{Fd: fd, Events: unix.POLLIN}}
374 nextStats := time.Now().Add(statsEvery)
375 for ctx.Err() == nil {
376 if !time.Now().Before(nextStats) {
377 nextStats = time.Now().Add(statsEvery)
378 if s := fwd.take(); s.Motions > 0 || s.Sent > 0 {
379 opts.Log.Info().Int("motions", s.Motions).Int("coalesced", s.Coalesced).Int("sent", s.Sent).Dur("max_block_ms", s.MaxBlock).Msg("input stats")
380 }
381 }
382 apply:
383 for {
384 select {
385 case c := <-controls:
386 c.apply()
387 default:
388 break apply
389 }
390 }
391 n, err := waiter.Poll(fds, max(0, int(time.Until(nextStats).Milliseconds())+1))
392 if err != nil && !errors.Is(err, unix.EINTR) {
393 return fmt.Errorf("libinput poll: %w", err)
394 }
395 if n <= 0 {
396 continue
397 }
398 if dispatch(li) != 0 {
399 opts.Log.Warn().Msg("libinput_dispatch failed")
400 }
401 for ev := getEvent(li); ev != 0; ev = getEvent(li) {
402 out, err := translate(ev, opts, p, in)
403 eventDestroy(ev)
404 if err != nil {
405 return err
406 }
407 if out != nil {
408 fwd.push(out)
409 }
410 }
411 }
412 return nil
413}
414
415// inputState is the device state Run owns: the device config, the devices
416// it configures (referenced until removed) and the gesture in progress on
417// the touchpads. dev applies the config to libinput; it is a zero-size
418// type, so storing it in the interface allocates nothing.
419type inputState struct {
420 cfg ports.InputDevicesConfig
421 dev deviceConfig
422 devices map[uintptr]bool
423 gestures gestures
424}
425
426// deviceConfig is libinput's device configuration side: what a device is
427// and the settings it takes. libinputDevices is the real one; tests use its
428// Mockery mock. It serves device-added and config reload only, never the
429// event path. A setting the device lacks is skipped without error; one it
430// refuses returns a sentinel error.
431type deviceConfig interface {
432 // IsTouchpad reports the gesture capability libinput gives touchpads.
433 IsTouchpad(dev uintptr) bool
434 // IsPointer reports the pointer capability: mice, trackballs,
435 // trackpoints and touchpads.
436 IsPointer(dev uintptr) bool
437 // Name is the device name, for logs.
438 Name(dev uintptr) string
439 SetNaturalScroll(dev uintptr, on bool) error
440 // SetAccel sets the pointer speed and, when the device offers it, the
441 // profile (ports.AccelFlat or ports.AccelAdaptive).
442 SetAccel(dev uintptr, speed float64, profile string) error
443 SetLeftHanded(dev uintptr, on bool) error
444 // SetTap enables tap to click, with one finger left, two right, three
445 // middle. Devices that cannot tap skip it.
446 SetTap(dev uintptr, on bool) error
447}
448
449// errRejected is a setting the device refuses (libinput status not success).
450var errRejected = errors.New("rejected by libinput")
451
452// libinputDevices implements deviceConfig over the libinput functions.
453type libinputDevices struct{}
454
455const (
456 // capPointer is LIBINPUT_DEVICE_CAP_POINTER.
457 capPointer = 1
458 // capGesture is LIBINPUT_DEVICE_CAP_GESTURE: libinput gives it to
459 // touchpads, with or without tapping.
460 capGesture = 5
461 // tapButtonMap is LIBINPUT_CONFIG_TAP_MAP_LRM.
462 tapButtonMap = 0
463)
464
465func (libinputDevices) IsTouchpad(dev uintptr) bool { return hasCapability(dev, capGesture) != 0 }
466func (libinputDevices) IsPointer(dev uintptr) bool { return hasCapability(dev, capPointer) != 0 }
467func (libinputDevices) Name(dev uintptr) string { return deviceName(dev) }
468
469func (libinputDevices) SetNaturalScroll(dev uintptr, on bool) error {
470 if hasNatural(dev) != 0 && setNatural(dev, flag(on)) != 0 {
471 return errRejected
472 }
473 return nil
474}
475
476func (libinputDevices) SetAccel(dev uintptr, speed float64, profile string) error {
477 if accelAvailable(dev) == 0 {
478 return nil
479 }
480 var err error
481 if setAccelSpeed(dev, speed) != 0 {
482 err = errRejected
483 }
484 if p := accelProfile(profile); accelProfiles(dev)&p != 0 && setAccelProfile(dev, p) != 0 {
485 err = errRejected
486 }
487 return err
488}
489
490func (libinputDevices) SetLeftHanded(dev uintptr, on bool) error {
491 if leftHandedAvailable(dev) != 0 && setLeftHanded(dev, flag(on)) != 0 {
492 return errRejected
493 }
494 return nil
495}
496
497func (libinputDevices) SetTap(dev uintptr, on bool) error {
498 if tapFingers(dev) <= 0 {
499 return nil
500 }
501 var err error
502 if setTap(dev, flag(on)) != 0 {
503 err = errRejected
504 }
505 if setTapMap(dev, tapButtonMap) != 0 {
506 err = errRejected
507 }
508 return err
509}
510
511// configurable reports whether the device config applies to dev: a
512// touchpad or another pointer. Keyboards, switches and the like are left
513// alone and not referenced.
514func (s *inputState) configurable(dev uintptr) bool {
515 return s.dev.IsTouchpad(dev) || s.dev.IsPointer(dev)
516}
517
518// configure applies the config to dev by type: touchpads take the touchpad
519// settings and tapping, other pointers (mice, trackballs, trackpoints) the
520// mouse settings, other devices nothing. A refused setting is logged and
521// the rest still apply. The success path logs and allocates nothing.
522func (s *inputState) configure(dev uintptr, log zerowrap.Logger) {
523 switch {
524 case s.dev.IsTouchpad(dev):
525 s.applyPointer(dev, s.cfg.Touchpad.PointerConfig, log)
526 s.check(dev, "tap to click", s.dev.SetTap(dev, s.cfg.Touchpad.Tap), log)
527 case s.dev.IsPointer(dev):
528 s.applyPointer(dev, s.cfg.Mouse, log)
529 }
530}
531
532// applyPointer applies the settings touchpads and mice share.
533func (s *inputState) applyPointer(dev uintptr, c ports.PointerConfig, log zerowrap.Logger) {
534 s.check(dev, "natural scroll", s.dev.SetNaturalScroll(dev, c.NaturalScroll), log)
535 s.check(dev, "accel", s.dev.SetAccel(dev, c.AccelSpeed, c.AccelProfile), log)
536 s.check(dev, "left handed", s.dev.SetLeftHanded(dev, c.LeftHanded), log)
537}
538
539// check logs a refused setting; the device name is only read then.
540func (s *inputState) check(dev uintptr, setting string, err error, log zerowrap.Logger) {
541 if err != nil {
542 log.Warn().Err(err).Str("device", s.dev.Name(dev)).Str("setting", setting).Msg("input device setting rejected")
543 }
544}
545
546// accelProfile is the libinput accel profile bit: LIBINPUT_CONFIG_ACCEL_
547// PROFILE_FLAT (1) or _ADAPTIVE (2).
548func accelProfile(name string) uint32 {
549 if name == ports.AccelFlat {
550 return 1
551 }
552 return 2
553}
554
555func flag(on bool) int32 {
556 if on {
557 return 1
558 }
559 return 0
560}
561
562func (s *inputState) release() {
563 for dev := range s.devices {
564 deviceUnref(dev)
565 }
566 clear(s.devices)
567}
568
569// gestures streams one touchpad gesture at a time to core: a swipe of any
570// finger count, a pinch or a hold. Core follows the fingers of a three- or
571// four-finger swipe and hands the other gestures to the window under the
572// pointer. owner is the touchpad of the gesture in progress (0: none) and
573// kind its kind; another touchpad's gesture, or another kind's event, is
574// ignored until it ends, so core never sees two streams interleaved.
575type gestures struct {
576 owner uintptr
577 kind ports.GestureKind
578}
579
580func (g *gestures) begin(dev uintptr, kind ports.GestureKind, fingers int, at time.Duration) ports.InputEvent {
581 if dev == 0 || fingers <= 0 || g.owner != 0 {
582 return nil
583 }
584 g.owner, g.kind = dev, kind
585 switch kind {
586 case ports.GesturePinch:
587 return ports.PinchBegin{Fingers: fingers, Time: at}
588 case ports.GestureHold:
589 return ports.HoldBegin{Fingers: fingers, Time: at}
590 }
591 return ports.SwipeBegin{Fingers: fingers, Time: at}
592}
593
594// running reports whether dev has a gesture of the given kind in progress.
595func (g *gestures) running(dev uintptr, kind ports.GestureKind) bool {
596 return dev != 0 && g.owner == dev && g.kind == kind
597}
598
599func (g *gestures) swipeUpdate(dev uintptr, dx, dy, accelDX, accelDY float64, at time.Duration) ports.InputEvent {
600 if !g.running(dev, ports.GestureSwipe) {
601 return nil
602 }
603 return ports.SwipeUpdate{DX: dx, DY: dy, AccelDX: accelDX, AccelDY: accelDY, Time: at}
604}
605
606func (g *gestures) pinchUpdate(dev uintptr, dx, dy, scale, rotation float64, at time.Duration) ports.InputEvent {
607 if !g.running(dev, ports.GesturePinch) {
608 return nil
609 }
610 return ports.PinchUpdate{DX: dx, DY: dy, Scale: scale, Rotation: rotation, Time: at}
611}
612
613// end finishes the gesture of the given kind on dev.
614func (g *gestures) end(dev uintptr, kind ports.GestureKind, cancelled bool, at time.Duration) ports.InputEvent {
615 if !g.running(dev, kind) {
616 return nil
617 }
618 return g.finish(cancelled, at)
619}
620
621// abort cancels whatever gesture dev has in progress (the device went).
622func (g *gestures) abort(dev uintptr) ports.InputEvent {
623 if dev == 0 || g.owner != dev {
624 return nil
625 }
626 return g.finish(true, 0)
627}
628
629func (g *gestures) finish(cancelled bool, at time.Duration) ports.InputEvent {
630 g.owner = 0
631 switch g.kind {
632 case ports.GesturePinch:
633 return ports.PinchEnd{Cancelled: cancelled, Time: at}
634 case ports.GestureHold:
635 return ports.HoldEnd{Cancelled: cancelled, Time: at}
636 }
637 return ports.SwipeEnd{Cancelled: cancelled, Time: at}
638}
639
640// usec is a libinput timestamp as a duration.
641func usec(v uint64) time.Duration { return time.Duration(v) * time.Microsecond }
642
643// translate converts one libinput event. Timestamps are the device's
644// (CLOCK_MONOTONIC), so clients measure speeds (kinetic scrolling) on
645// when events happened, not when they were read.
646// It snapshots admission before translating and queueing any input kind.
647func translate(ev uintptr, opts Options, p *pointer, in *inputState) (ports.InputEvent, error) {
648 state := securitySnapshot(opts.Security)
649 out, err := translateEvent(ev, opts, p, in, state)
650 return secureInput(out, opts.Security, state), err
651}
652
653func securitySnapshot(security ports.SessionSecurity) ports.SecurityState {
654 if security != nil {
655 return security.Snapshot()
656 }
657 return ports.SecurityState{}
658}
659
660func secureInput(ev ports.InputEvent, security ports.SessionSecurity, state ports.SecurityState) ports.InputEvent {
661 if ev == nil || security == nil {
662 return ev
663 }
664 return ports.SecurityInput{State: state, Event: ev}
665}
666
667func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state ports.SecurityState) (ports.InputEvent, error) {
668 log := opts.Log
669 switch eventType(ev) {
670 case evDeviceAdded:
671 dev := eventDevice(ev)
672 log.Info().Str("device", deviceName(dev)).Msg("input device added")
673 if in.configurable(dev) && !in.devices[dev] {
674 in.devices[deviceRef(dev)] = true
675 in.configure(dev, log)
676 }
677 case evDeviceRemoved:
678 dev := eventDevice(ev)
679 log.Info().Str("device", deviceName(dev)).Msg("input device removed")
680 if in.devices[dev] {
681 delete(in.devices, dev)
682 deviceUnref(dev)
683 }
684 // Core waits for the end of a gesture the device began.
685 return in.gestures.abort(dev), nil
686 case evSwipeBegin, evPinchBegin, evHoldBegin:
687 ge := gestureEvent(ev)
688 kind := ports.GestureSwipe
689 if eventType(ev) == evPinchBegin {
690 kind = ports.GesturePinch
691 } else if eventType(ev) == evHoldBegin {
692 kind = ports.GestureHold
693 }
694 return in.gestures.begin(eventDevice(ev), kind, int(gestureFingers(ge)), usec(gestureUsec(ge))), nil
695 case evSwipeUpdate:
696 ge := gestureEvent(ev)
697 return in.gestures.swipeUpdate(eventDevice(ev), gestureDX(ge), gestureDY(ge), gestureAccelDX(ge), gestureAccelDY(ge), usec(gestureUsec(ge))), nil
698 case evPinchUpdate:
699 ge := gestureEvent(ev)
700 return in.gestures.pinchUpdate(eventDevice(ev), gestureAccelDX(ge), gestureAccelDY(ge), gestureScale(ge), gestureAngle(ge), usec(gestureUsec(ge))), nil
701 case evSwipeEnd:
702 ge := gestureEvent(ev)
703 return in.gestures.end(eventDevice(ev), ports.GestureSwipe, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
704 case evPinchEnd:
705 ge := gestureEvent(ev)
706 return in.gestures.end(eventDevice(ev), ports.GesturePinch, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
707 case evHoldEnd:
708 ge := gestureEvent(ev)
709 return in.gestures.end(eventDevice(ev), ports.GestureHold, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
710 case evKeyboardKey:
711 k := keyboardEvent(ev)
712 code, pressed := keyboardKey(k), keyboardState(k) == 1
713 return translateKeyboard(code, pressed, usec(keyboardUsec(k)), opts, state)
714 case evPointerMotion:
715 pe := pointerEvent(ev)
716 m := p.move(pointerDX(pe), pointerDY(pe))
717 m.UnaccelDX, m.UnaccelDY = pointerRawDX(pe), pointerRawDY(pe)
718 m.Time = usec(pointerUsec(pe))
719 p.moved(opts.MoveCursor, true)
720 if opts.LogMotion {
721 log.Debug().Float64("x", m.X).Float64("y", m.Y).Msg("pointer")
722 }
723 return m, nil
724 case evPointerAbs:
725 // Absolute devices (tablets, VMs) map to the whole layout.
726 pe := pointerEvent(ev)
727 now := usec(pointerUsec(pe))
728 b := p.bounds()
729 if p.constraint.Mode == ports.ConstraintLock {
730 return ports.PointerMotion{X: p.x, Y: p.y, Time: now}, nil
731 }
732 x, y := p.set(float64(b.X)+pointerAbsX(pe, uint32(b.W)), float64(b.Y)+pointerAbsY(pe, uint32(b.H)))
733 p.moved(opts.MoveCursor, true)
734 return ports.PointerMotion{X: x, Y: y, Time: now}, nil
735 case evPointerButton:
736 pe := pointerEvent(ev)
737 b, pressed := pointerButton(pe), pointerBtnState(pe) == 1
738 log.Debug().Uint32("button", b).Bool("pressed", pressed).Msg("button")
739 return ports.PointerButton{Button: b, Pressed: pressed, Time: usec(pointerUsec(pe))}, nil
740 case evScrollWheel, evScrollFinger, evScrollCont:
741 pe := pointerEvent(ev)
742 // The three scroll events follow each other as the sources do.
743 source := ports.AxisSource(eventType(ev) - evScrollWheel)
744 a := ports.PointerAxis{Source: source, Time: usec(pointerUsec(pe))}
745 // libinput axis 0 is vertical, 1 horizontal, as in wl_pointer.
746 for i, s := range []*ports.ScrollAxis{&a.Vertical, &a.Horizontal} {
747 if pointerHasAxis(pe, uint32(i)) == 0 {
748 continue
749 }
750 s.Set, s.Value = true, scrollValue(pe, uint32(i))
751 if source == ports.AxisFinger {
752 // Two-finger scroll is the touchpad's only scroll source.
753 s.Value *= in.cfg.Touchpad.ScrollFactor
754 }
755 if source == ports.AxisWheel {
756 s.V120 = int32(scrollV120(pe, uint32(i)))
757 } else {
758 s.Stop = s.Value == 0
759 }
760 }
761 log.Debug().Float64("v", a.Vertical.Value).Float64("h", a.Horizontal.Value).Int32("v120", a.Vertical.V120).Int32("hv120", a.Horizontal.V120).Uint8("source", uint8(a.Source)).Msg("scroll")
762 return a, nil
763 }
764 return nil, nil
765}
766
767// translateKeyboard applies the production epoch before native translation.
768// Quarantined events never reach logging, reserved hotkeys or core.
769func translateKeyboard(code uint32, pressed bool, t time.Duration, opts Options, state ports.SecurityState) (ports.InputEvent, error) {
770 var ke ports.KeyEvent
771 if opts.Security != nil {
772 var deliver bool
773 var err error
774 ke, deliver, err = opts.Keymap.KeySecure(code, pressed, t, state)
775 if err != nil || !deliver {
776 return nil, err
777 }
778 } else {
779 ke = opts.Keymap.Key(code, pressed, t)
780 }
781 return translateKey(ke, opts, state)
782}
783
784// translateKey applies the keyboard policy without native calls. One coherent
785// snapshot governs both logging and reserved hotkeys; protected keys still go
786// to core for the locker. VT switching is allowed and does not release the lock.
787func translateKey(ke ports.KeyEvent, opts Options, security ports.SecurityState) (ports.InputEvent, error) {
788 if opts.LogKeys && !security.Protected {
789 opts.Log.Debug().Uint32("code", ke.Keycode).Str("keysym", ke.Keysym).Bool("pressed", ke.Pressed).Uint8("mods", uint8(ke.Mods)).Msg("key")
790 }
791 switch action, vt := hotkey(ke, security.Protected); action {
792 case hotkeyQuit:
793 opts.Log.Warn().Str("reason", "emergency-key").Msg("quit")
794 return nil, ErrEmergencyQuit
795 case hotkeyVT:
796 opts.Seat.SwitchVT(vt)
797 return nil, nil
798 }
799 return ke, nil
800}
801
802type hotkeyAction int
803
804const (
805 hotkeyNone hotkeyAction = iota
806 hotkeyQuit
807 hotkeyVT
808)
809
810// hotkey detects compositor-reserved keys on press: Ctrl+Alt+Backspace and
811// Ctrl+Alt+F1..F12. Protection disables emergency quit, but not VT switching.
812func hotkey(ke ports.KeyEvent, protected bool) (hotkeyAction, int) {
813 if !ke.Pressed {
814 return hotkeyNone, 0
815 }
816 if vt, ok := strings.CutPrefix(ke.Keysym, "XF86Switch_VT_"); ok {
817 var n int
818 if _, err := fmt.Sscan(vt, &n); err == nil && n >= 1 && n <= 12 {
819 return hotkeyVT, n
820 }
821 }
822 if ke.Mods&(ports.ModCtrl|ports.ModAlt) != ports.ModCtrl|ports.ModAlt {
823 return hotkeyNone, 0
824 }
825 switch {
826 case ke.Keysym == "BackSpace" || ke.Keycode == 14:
827 if !protected {
828 return hotkeyQuit, 0
829 }
830 case ke.Keycode >= 59 && ke.Keycode <= 68: // F1..F10
831 return hotkeyVT, int(ke.Keycode) - 58
832 case ke.Keycode == 87 || ke.Keycode == 88: // F11, F12
833 return hotkeyVT, int(ke.Keycode) - 76
834 }
835 return hotkeyNone, 0
836}
837
838// pointer tracks the cursor in global logical coordinates. Relative motion
839// is divided by the scale of the output under the pointer, so it moves the
840// same number of physical pixels on every output.
841type pointer struct {
842 x, y float64
843 layout ports.Layout
844 touched bool // false until the user moves it: it follows the primary output
845 constraint ports.PointerConstraint
846}
847
848// newPointer starts at the centre of the primary output, else the first.
849func newPointer(l ports.Layout) *pointer {
850 p := &pointer{}
851 p.setLayout(l)
852 return p
853}
854
855// setLayout keeps the pointer where it is, or clamps it onto an output.
856// Until it is first moved it stays centred on the primary output, which
857// may be plugged in after the others.
858func (p *pointer) setLayout(l ports.Layout) {
859 p.layout = l
860 if !p.touched && len(l) > 0 {
861 o := l[0]
862 for _, v := range l {
863 if v.Primary {
864 o = v
865 }
866 }
867 p.x, p.y = float64(o.X)+float64(o.Width)/2, float64(o.Y)+float64(o.Height)/2
868 return
869 }
870 p.x, p.y = l.Clamp(p.x, p.y, p.x, p.y)
871}
872
873func (p *pointer) scale() float64 {
874 if o, ok := p.layout.At(p.x, p.y); ok && o.Scale > 0 {
875 return o.Scale
876 }
877 return 1
878}
879
880// move applies a relative motion and returns it with the logical deltas.
881// A locked pointer stays still; a confined one stays in its rectangle.
882func (p *pointer) move(dx, dy float64) ports.PointerMotion {
883 s := p.scale()
884 dx, dy = dx/s, dy/s
885 if p.constraint.Mode != ports.ConstraintLock {
886 p.set(p.x+dx, p.y+dy)
887 }
888 return ports.PointerMotion{X: p.x, Y: p.y, DX: dx, DY: dy}
889}
890
891func (p *pointer) set(x, y float64) (float64, float64) {
892 p.touched = true
893 p.x, p.y = p.constraint.Clamp(p.layout.Clamp(p.x, p.y, x, y))
894 return p.x, p.y
895}
896
897// constrain applies a new constraint. Entering or leaving a lock resyncs
898// the pointer to core's cursor, which stood still meanwhile; a confined
899// pointer moves inside. A warp moves it to core's cursor.
900func (p *pointer) constrain(c ports.PointerConstraint) {
901 if c.Warp || c.Mode == ports.ConstraintLock || p.constraint.Mode == ports.ConstraintLock {
902 p.x, p.y = p.layout.Clamp(c.X, c.Y, c.X, c.Y)
903 }
904 p.constraint = c
905 p.x, p.y = c.Clamp(p.x, p.y)
906}
907
908// bounds is the rectangle holding every output.
909func (p *pointer) bounds() ports.Rect {
910 var r ports.Rect
911 for _, o := range p.layout {
912 r.W = max(r.W, o.X+o.Width)
913 r.H = max(r.H, o.Y+o.Height)
914 }
915 return r
916}
917
918// moved reports the target position (physical pixels after the output
919// transform) on the output under the pointer;
920// motion tells the device moved it.
921func (p *pointer) moved(move func(output string, x, y float64, motion bool), motion bool) {
922 if o, ok := p.layout.At(p.x, p.y); ok && move != nil {
923 tx, ty := o.ToTarget(p.x, p.y)
924 move(o.Info.Name, tx, ty, motion)
925 }
926}