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