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// statsEvery is how often Run logs input throughput.
241const statsEvery = 10 * time.Second
242
243// Run owns libinput and the keymap, converting device events into input events.
244func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error {
245 defer func() { opts.Keymap.Close() }()
246 if err := load(); err != nil {
247 return err
248 }
249 active = opts.Seat
250 udev := udevNew()
251 if udev == 0 {
252 return errors.New("udev_new failed")
253 }
254 defer udevUnref(udev)
255 li := createContext(unsafe.Pointer(&iface), 0, udev)
256 if li == 0 {
257 return errors.New("libinput_udev_create_context failed")
258 }
259 defer unref(li)
260 name := opts.SeatName
261 if name == "" {
262 name = "seat0"
263 }
264 if assignSeat(li, name) != 0 {
265 return fmt.Errorf("libinput_udev_assign_seat %s failed", name)
266 }
267 p := newPointer(opts.Layout)
268 p.moved(opts.MoveCursor, false)
269 in := &inputState{cfg: opts.DeviceConfig, dev: libinputDevices{}, devices: map[uintptr]bool{}}
270 defer in.release()
271 waiter, err := fdwait.New()
272 if err != nil {
273 return fmt.Errorf("libinput waiter: %w", err)
274 }
275 defer waiter.Close()
276 fd := getFD(li)
277 fwd := newForwarder(opts.Log)
278 fwdCtx, stopFwd := context.WithCancel(ctx)
279 var fwdDone sync.WaitGroup
280 fwdDone.Go(func() { fwd.run(fwdCtx, input) })
281 defer func() { stopFwd(); fwdDone.Wait() }()
282 lastStats := time.Now()
283 for ctx.Err() == nil {
284 if time.Since(lastStats) >= statsEvery {
285 lastStats = time.Now()
286 if s := fwd.take(); s.Motions > 0 || s.Sent > 0 {
287 opts.Log.Info().Int("motions", s.Motions).Int("coalesced", s.Coalesced).Int("sent", s.Sent).Dur("max_block_ms", s.MaxBlock).Msg("input stats")
288 }
289 }
290 select {
291 case on := <-opts.Active:
292 if on {
293 resume(li)
294 } else {
295 suspend(li)
296 }
297 opts.Log.Info().Bool("active", on).Msg("input")
298 case km := <-opts.Keymaps:
299 if opts.Security != nil {
300 km.QuarantineFrom(opts.Keymap)
301 }
302 opts.Keymap.Close()
303 opts.Keymap = km
304 opts.Log.Info().Msg("keymap replaced")
305 case l := <-opts.Layouts:
306 p.setLayout(l)
307 p.moved(opts.MoveCursor, false)
308 case c := <-opts.Constraints:
309 p.constrain(c)
310 p.moved(opts.MoveCursor, false)
311 case c := <-opts.DeviceConfigs:
312 in.cfg = c
313 for dev := range in.devices {
314 in.configure(dev, opts.Log)
315 }
316 t, m := c.Touchpad, c.Mouse
317 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).
318 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")
319 default:
320 }
321 fds := []unix.PollFd{{Fd: fd, Events: unix.POLLIN}}
322 n, err := waiter.Poll(fds, 100)
323 if err != nil && !errors.Is(err, unix.EINTR) {
324 return fmt.Errorf("libinput poll: %w", err)
325 }
326 if n <= 0 {
327 continue
328 }
329 if dispatch(li) != 0 {
330 opts.Log.Warn().Msg("libinput_dispatch failed")
331 }
332 for ev := getEvent(li); ev != 0; ev = getEvent(li) {
333 out, err := translate(ev, opts, p, in)
334 eventDestroy(ev)
335 if err != nil {
336 return err
337 }
338 if out != nil {
339 fwd.push(out)
340 }
341 }
342 }
343 return nil
344}
345
346// inputState is the device state Run owns: the device config, the devices
347// it configures (referenced until removed) and the gesture in progress on
348// the touchpads. dev applies the config to libinput; it is a zero-size
349// type, so storing it in the interface allocates nothing.
350type inputState struct {
351 cfg ports.InputDevicesConfig
352 dev deviceConfig
353 devices map[uintptr]bool
354 gestures gestures
355}
356
357// deviceConfig is libinput's device configuration side: what a device is
358// and the settings it takes. libinputDevices is the real one; tests use its
359// Mockery mock. It serves device-added and config reload only, never the
360// event path. A setting the device lacks is skipped without error; one it
361// refuses returns a sentinel error.
362type deviceConfig interface {
363 // IsTouchpad reports the gesture capability libinput gives touchpads.
364 IsTouchpad(dev uintptr) bool
365 // IsPointer reports the pointer capability: mice, trackballs,
366 // trackpoints and touchpads.
367 IsPointer(dev uintptr) bool
368 // Name is the device name, for logs.
369 Name(dev uintptr) string
370 SetNaturalScroll(dev uintptr, on bool) error
371 // SetAccel sets the pointer speed and, when the device offers it, the
372 // profile (ports.AccelFlat or ports.AccelAdaptive).
373 SetAccel(dev uintptr, speed float64, profile string) error
374 SetLeftHanded(dev uintptr, on bool) error
375 // SetTap enables tap to click, with one finger left, two right, three
376 // middle. Devices that cannot tap skip it.
377 SetTap(dev uintptr, on bool) error
378}
379
380// errRejected is a setting the device refuses (libinput status not success).
381var errRejected = errors.New("rejected by libinput")
382
383// libinputDevices implements deviceConfig over the libinput functions.
384type libinputDevices struct{}
385
386const (
387 // capPointer is LIBINPUT_DEVICE_CAP_POINTER.
388 capPointer = 1
389 // capGesture is LIBINPUT_DEVICE_CAP_GESTURE: libinput gives it to
390 // touchpads, with or without tapping.
391 capGesture = 5
392 // tapButtonMap is LIBINPUT_CONFIG_TAP_MAP_LRM.
393 tapButtonMap = 0
394)
395
396func (libinputDevices) IsTouchpad(dev uintptr) bool { return hasCapability(dev, capGesture) != 0 }
397func (libinputDevices) IsPointer(dev uintptr) bool { return hasCapability(dev, capPointer) != 0 }
398func (libinputDevices) Name(dev uintptr) string { return deviceName(dev) }
399
400func (libinputDevices) SetNaturalScroll(dev uintptr, on bool) error {
401 if hasNatural(dev) != 0 && setNatural(dev, flag(on)) != 0 {
402 return errRejected
403 }
404 return nil
405}
406
407func (libinputDevices) SetAccel(dev uintptr, speed float64, profile string) error {
408 if accelAvailable(dev) == 0 {
409 return nil
410 }
411 var err error
412 if setAccelSpeed(dev, speed) != 0 {
413 err = errRejected
414 }
415 if p := accelProfile(profile); accelProfiles(dev)&p != 0 && setAccelProfile(dev, p) != 0 {
416 err = errRejected
417 }
418 return err
419}
420
421func (libinputDevices) SetLeftHanded(dev uintptr, on bool) error {
422 if leftHandedAvailable(dev) != 0 && setLeftHanded(dev, flag(on)) != 0 {
423 return errRejected
424 }
425 return nil
426}
427
428func (libinputDevices) SetTap(dev uintptr, on bool) error {
429 if tapFingers(dev) <= 0 {
430 return nil
431 }
432 var err error
433 if setTap(dev, flag(on)) != 0 {
434 err = errRejected
435 }
436 if setTapMap(dev, tapButtonMap) != 0 {
437 err = errRejected
438 }
439 return err
440}
441
442// configurable reports whether the device config applies to dev: a
443// touchpad or another pointer. Keyboards, switches and the like are left
444// alone and not referenced.
445func (s *inputState) configurable(dev uintptr) bool {
446 return s.dev.IsTouchpad(dev) || s.dev.IsPointer(dev)
447}
448
449// configure applies the config to dev by type: touchpads take the touchpad
450// settings and tapping, other pointers (mice, trackballs, trackpoints) the
451// mouse settings, other devices nothing. A refused setting is logged and
452// the rest still apply. The success path logs and allocates nothing.
453func (s *inputState) configure(dev uintptr, log zerowrap.Logger) {
454 switch {
455 case s.dev.IsTouchpad(dev):
456 s.applyPointer(dev, s.cfg.Touchpad.PointerConfig, log)
457 s.check(dev, "tap to click", s.dev.SetTap(dev, s.cfg.Touchpad.Tap), log)
458 case s.dev.IsPointer(dev):
459 s.applyPointer(dev, s.cfg.Mouse, log)
460 }
461}
462
463// applyPointer applies the settings touchpads and mice share.
464func (s *inputState) applyPointer(dev uintptr, c ports.PointerConfig, log zerowrap.Logger) {
465 s.check(dev, "natural scroll", s.dev.SetNaturalScroll(dev, c.NaturalScroll), log)
466 s.check(dev, "accel", s.dev.SetAccel(dev, c.AccelSpeed, c.AccelProfile), log)
467 s.check(dev, "left handed", s.dev.SetLeftHanded(dev, c.LeftHanded), log)
468}
469
470// check logs a refused setting; the device name is only read then.
471func (s *inputState) check(dev uintptr, setting string, err error, log zerowrap.Logger) {
472 if err != nil {
473 log.Warn().Err(err).Str("device", s.dev.Name(dev)).Str("setting", setting).Msg("input device setting rejected")
474 }
475}
476
477// accelProfile is the libinput accel profile bit: LIBINPUT_CONFIG_ACCEL_
478// PROFILE_FLAT (1) or _ADAPTIVE (2).
479func accelProfile(name string) uint32 {
480 if name == ports.AccelFlat {
481 return 1
482 }
483 return 2
484}
485
486func flag(on bool) int32 {
487 if on {
488 return 1
489 }
490 return 0
491}
492
493func (s *inputState) release() {
494 for dev := range s.devices {
495 deviceUnref(dev)
496 }
497 clear(s.devices)
498}
499
500// gestures streams one touchpad gesture at a time to core: a swipe of any
501// finger count, a pinch or a hold. Core follows the fingers of a three- or
502// four-finger swipe and hands the other gestures to the window under the
503// pointer. owner is the touchpad of the gesture in progress (0: none) and
504// kind its kind; another touchpad's gesture, or another kind's event, is
505// ignored until it ends, so core never sees two streams interleaved.
506type gestures struct {
507 owner uintptr
508 kind ports.GestureKind
509}
510
511func (g *gestures) begin(dev uintptr, kind ports.GestureKind, fingers int, at time.Duration) ports.InputEvent {
512 if dev == 0 || fingers <= 0 || g.owner != 0 {
513 return nil
514 }
515 g.owner, g.kind = dev, kind
516 switch kind {
517 case ports.GesturePinch:
518 return ports.PinchBegin{Fingers: fingers, Time: at}
519 case ports.GestureHold:
520 return ports.HoldBegin{Fingers: fingers, Time: at}
521 }
522 return ports.SwipeBegin{Fingers: fingers, Time: at}
523}
524
525// running reports whether dev has a gesture of the given kind in progress.
526func (g *gestures) running(dev uintptr, kind ports.GestureKind) bool {
527 return dev != 0 && g.owner == dev && g.kind == kind
528}
529
530func (g *gestures) swipeUpdate(dev uintptr, dx, dy, accelDX, accelDY float64, at time.Duration) ports.InputEvent {
531 if !g.running(dev, ports.GestureSwipe) {
532 return nil
533 }
534 return ports.SwipeUpdate{DX: dx, DY: dy, AccelDX: accelDX, AccelDY: accelDY, Time: at}
535}
536
537func (g *gestures) pinchUpdate(dev uintptr, dx, dy, scale, rotation float64, at time.Duration) ports.InputEvent {
538 if !g.running(dev, ports.GesturePinch) {
539 return nil
540 }
541 return ports.PinchUpdate{DX: dx, DY: dy, Scale: scale, Rotation: rotation, Time: at}
542}
543
544// end finishes the gesture of the given kind on dev.
545func (g *gestures) end(dev uintptr, kind ports.GestureKind, cancelled bool, at time.Duration) ports.InputEvent {
546 if !g.running(dev, kind) {
547 return nil
548 }
549 return g.finish(cancelled, at)
550}
551
552// abort cancels whatever gesture dev has in progress (the device went).
553func (g *gestures) abort(dev uintptr) ports.InputEvent {
554 if dev == 0 || g.owner != dev {
555 return nil
556 }
557 return g.finish(true, 0)
558}
559
560func (g *gestures) finish(cancelled bool, at time.Duration) ports.InputEvent {
561 g.owner = 0
562 switch g.kind {
563 case ports.GesturePinch:
564 return ports.PinchEnd{Cancelled: cancelled, Time: at}
565 case ports.GestureHold:
566 return ports.HoldEnd{Cancelled: cancelled, Time: at}
567 }
568 return ports.SwipeEnd{Cancelled: cancelled, Time: at}
569}
570
571// usec is a libinput timestamp as a duration.
572func usec(v uint64) time.Duration { return time.Duration(v) * time.Microsecond }
573
574// translate converts one libinput event. Timestamps are the device's
575// (CLOCK_MONOTONIC), so clients measure speeds (kinetic scrolling) on
576// when events happened, not when they were read.
577// It snapshots admission before translating and queueing any input kind.
578func translate(ev uintptr, opts Options, p *pointer, in *inputState) (ports.InputEvent, error) {
579 state := securitySnapshot(opts.Security)
580 out, err := translateEvent(ev, opts, p, in, state)
581 return secureInput(out, opts.Security, state), err
582}
583
584func securitySnapshot(security ports.SessionSecurity) ports.SecurityState {
585 if security != nil {
586 return security.Snapshot()
587 }
588 return ports.SecurityState{}
589}
590
591func secureInput(ev ports.InputEvent, security ports.SessionSecurity, state ports.SecurityState) ports.InputEvent {
592 if ev == nil || security == nil {
593 return ev
594 }
595 return ports.SecurityInput{State: state, Event: ev}
596}
597
598func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state ports.SecurityState) (ports.InputEvent, error) {
599 log := opts.Log
600 switch eventType(ev) {
601 case evDeviceAdded:
602 dev := eventDevice(ev)
603 log.Info().Str("device", deviceName(dev)).Msg("input device added")
604 if in.configurable(dev) && !in.devices[dev] {
605 in.devices[deviceRef(dev)] = true
606 in.configure(dev, log)
607 }
608 case evDeviceRemoved:
609 dev := eventDevice(ev)
610 log.Info().Str("device", deviceName(dev)).Msg("input device removed")
611 if in.devices[dev] {
612 delete(in.devices, dev)
613 deviceUnref(dev)
614 }
615 // Core waits for the end of a gesture the device began.
616 return in.gestures.abort(dev), nil
617 case evSwipeBegin, evPinchBegin, evHoldBegin:
618 ge := gestureEvent(ev)
619 kind := ports.GestureSwipe
620 if eventType(ev) == evPinchBegin {
621 kind = ports.GesturePinch
622 } else if eventType(ev) == evHoldBegin {
623 kind = ports.GestureHold
624 }
625 return in.gestures.begin(eventDevice(ev), kind, int(gestureFingers(ge)), usec(gestureUsec(ge))), nil
626 case evSwipeUpdate:
627 ge := gestureEvent(ev)
628 return in.gestures.swipeUpdate(eventDevice(ev), gestureDX(ge), gestureDY(ge), gestureAccelDX(ge), gestureAccelDY(ge), usec(gestureUsec(ge))), nil
629 case evPinchUpdate:
630 ge := gestureEvent(ev)
631 return in.gestures.pinchUpdate(eventDevice(ev), gestureAccelDX(ge), gestureAccelDY(ge), gestureScale(ge), gestureAngle(ge), usec(gestureUsec(ge))), nil
632 case evSwipeEnd:
633 ge := gestureEvent(ev)
634 return in.gestures.end(eventDevice(ev), ports.GestureSwipe, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
635 case evPinchEnd:
636 ge := gestureEvent(ev)
637 return in.gestures.end(eventDevice(ev), ports.GesturePinch, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
638 case evHoldEnd:
639 ge := gestureEvent(ev)
640 return in.gestures.end(eventDevice(ev), ports.GestureHold, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
641 case evKeyboardKey:
642 k := keyboardEvent(ev)
643 code, pressed := keyboardKey(k), keyboardState(k) == 1
644 return translateKeyboard(code, pressed, usec(keyboardUsec(k)), opts, state)
645 case evPointerMotion:
646 pe := pointerEvent(ev)
647 m := p.move(pointerDX(pe), pointerDY(pe))
648 m.UnaccelDX, m.UnaccelDY = pointerRawDX(pe), pointerRawDY(pe)
649 m.Time = usec(pointerUsec(pe))
650 p.moved(opts.MoveCursor, true)
651 if opts.LogMotion {
652 log.Debug().Float64("x", m.X).Float64("y", m.Y).Msg("pointer")
653 }
654 return m, nil
655 case evPointerAbs:
656 // Absolute devices (tablets, VMs) map to the whole layout.
657 pe := pointerEvent(ev)
658 now := usec(pointerUsec(pe))
659 b := p.bounds()
660 if p.constraint.Mode == ports.ConstraintLock {
661 return ports.PointerMotion{X: p.x, Y: p.y, Time: now}, nil
662 }
663 x, y := p.set(float64(b.X)+pointerAbsX(pe, uint32(b.W)), float64(b.Y)+pointerAbsY(pe, uint32(b.H)))
664 p.moved(opts.MoveCursor, true)
665 return ports.PointerMotion{X: x, Y: y, Time: now}, nil
666 case evPointerButton:
667 pe := pointerEvent(ev)
668 b, pressed := pointerButton(pe), pointerBtnState(pe) == 1
669 log.Debug().Uint32("button", b).Bool("pressed", pressed).Msg("button")
670 return ports.PointerButton{Button: b, Pressed: pressed, Time: usec(pointerUsec(pe))}, nil
671 case evScrollWheel, evScrollFinger, evScrollCont:
672 pe := pointerEvent(ev)
673 // The three scroll events follow each other as the sources do.
674 source := ports.AxisSource(eventType(ev) - evScrollWheel)
675 a := ports.PointerAxis{Source: source, Time: usec(pointerUsec(pe))}
676 // libinput axis 0 is vertical, 1 horizontal, as in wl_pointer.
677 for i, s := range []*ports.ScrollAxis{&a.Vertical, &a.Horizontal} {
678 if pointerHasAxis(pe, uint32(i)) == 0 {
679 continue
680 }
681 s.Set, s.Value = true, scrollValue(pe, uint32(i))
682 if source == ports.AxisFinger {
683 // Two-finger scroll is the touchpad's only scroll source.
684 s.Value *= in.cfg.Touchpad.ScrollFactor
685 }
686 if source == ports.AxisWheel {
687 s.V120 = int32(scrollV120(pe, uint32(i)))
688 } else {
689 s.Stop = s.Value == 0
690 }
691 }
692 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")
693 return a, nil
694 }
695 return nil, nil
696}
697
698// translateKeyboard applies the production epoch before native translation.
699// Quarantined events never reach logging, reserved hotkeys or core.
700func translateKeyboard(code uint32, pressed bool, t time.Duration, opts Options, state ports.SecurityState) (ports.InputEvent, error) {
701 var ke ports.KeyEvent
702 if opts.Security != nil {
703 var deliver bool
704 var err error
705 ke, deliver, err = opts.Keymap.KeySecure(code, pressed, t, state)
706 if err != nil || !deliver {
707 return nil, err
708 }
709 } else {
710 ke = opts.Keymap.Key(code, pressed, t)
711 }
712 return translateKey(ke, opts, state)
713}
714
715// translateKey applies the keyboard policy without native calls. One coherent
716// snapshot governs both logging and reserved hotkeys; protected keys still go
717// to core for the locker. VT switching is allowed and does not release the lock.
718func translateKey(ke ports.KeyEvent, opts Options, security ports.SecurityState) (ports.InputEvent, error) {
719 if opts.LogKeys && !security.Protected {
720 opts.Log.Debug().Uint32("code", ke.Keycode).Str("keysym", ke.Keysym).Bool("pressed", ke.Pressed).Uint8("mods", uint8(ke.Mods)).Msg("key")
721 }
722 switch action, vt := hotkey(ke, security.Protected); action {
723 case hotkeyQuit:
724 opts.Log.Warn().Str("reason", "emergency-key").Msg("quit")
725 return nil, ErrEmergencyQuit
726 case hotkeyVT:
727 opts.Seat.SwitchVT(vt)
728 return nil, nil
729 }
730 return ke, nil
731}
732
733type hotkeyAction int
734
735const (
736 hotkeyNone hotkeyAction = iota
737 hotkeyQuit
738 hotkeyVT
739)
740
741// hotkey detects compositor-reserved keys on press: Ctrl+Alt+Backspace and
742// Ctrl+Alt+F1..F12. Protection disables emergency quit, but not VT switching.
743func hotkey(ke ports.KeyEvent, protected bool) (hotkeyAction, int) {
744 if !ke.Pressed {
745 return hotkeyNone, 0
746 }
747 if vt, ok := strings.CutPrefix(ke.Keysym, "XF86Switch_VT_"); ok {
748 var n int
749 if _, err := fmt.Sscan(vt, &n); err == nil && n >= 1 && n <= 12 {
750 return hotkeyVT, n
751 }
752 }
753 if ke.Mods&(ports.ModCtrl|ports.ModAlt) != ports.ModCtrl|ports.ModAlt {
754 return hotkeyNone, 0
755 }
756 switch {
757 case ke.Keysym == "BackSpace" || ke.Keycode == 14:
758 if !protected {
759 return hotkeyQuit, 0
760 }
761 case ke.Keycode >= 59 && ke.Keycode <= 68: // F1..F10
762 return hotkeyVT, int(ke.Keycode) - 58
763 case ke.Keycode == 87 || ke.Keycode == 88: // F11, F12
764 return hotkeyVT, int(ke.Keycode) - 76
765 }
766 return hotkeyNone, 0
767}
768
769// pointer tracks the cursor in global logical coordinates. Relative motion
770// is divided by the scale of the output under the pointer, so it moves the
771// same number of physical pixels on every output.
772type pointer struct {
773 x, y float64
774 layout ports.Layout
775 touched bool // false until the user moves it: it follows the primary output
776 constraint ports.PointerConstraint
777}
778
779// newPointer starts at the centre of the primary output, else the first.
780func newPointer(l ports.Layout) *pointer {
781 p := &pointer{}
782 p.setLayout(l)
783 return p
784}
785
786// setLayout keeps the pointer where it is, or clamps it onto an output.
787// Until it is first moved it stays centred on the primary output, which
788// may be plugged in after the others.
789func (p *pointer) setLayout(l ports.Layout) {
790 p.layout = l
791 if !p.touched && len(l) > 0 {
792 o := l[0]
793 for _, v := range l {
794 if v.Primary {
795 o = v
796 }
797 }
798 p.x, p.y = float64(o.X)+float64(o.Width)/2, float64(o.Y)+float64(o.Height)/2
799 return
800 }
801 p.x, p.y = l.Clamp(p.x, p.y, p.x, p.y)
802}
803
804func (p *pointer) scale() float64 {
805 if o, ok := p.layout.At(p.x, p.y); ok && o.Scale > 0 {
806 return o.Scale
807 }
808 return 1
809}
810
811// move applies a relative motion and returns it with the logical deltas.
812// A locked pointer stays still; a confined one stays in its rectangle.
813func (p *pointer) move(dx, dy float64) ports.PointerMotion {
814 s := p.scale()
815 dx, dy = dx/s, dy/s
816 if p.constraint.Mode != ports.ConstraintLock {
817 p.set(p.x+dx, p.y+dy)
818 }
819 return ports.PointerMotion{X: p.x, Y: p.y, DX: dx, DY: dy}
820}
821
822func (p *pointer) set(x, y float64) (float64, float64) {
823 p.touched = true
824 p.x, p.y = p.constraint.Clamp(p.layout.Clamp(p.x, p.y, x, y))
825 return p.x, p.y
826}
827
828// constrain applies a new constraint. Entering or leaving a lock resyncs
829// the pointer to core's cursor, which stood still meanwhile; a confined
830// pointer moves inside. A warp moves it to core's cursor.
831func (p *pointer) constrain(c ports.PointerConstraint) {
832 if c.Warp || c.Mode == ports.ConstraintLock || p.constraint.Mode == ports.ConstraintLock {
833 p.x, p.y = p.layout.Clamp(c.X, c.Y, c.X, c.Y)
834 }
835 p.constraint = c
836 p.x, p.y = c.Clamp(p.x, p.y)
837}
838
839// bounds is the rectangle holding every output.
840func (p *pointer) bounds() ports.Rect {
841 var r ports.Rect
842 for _, o := range p.layout {
843 r.W = max(r.W, o.X+o.Width)
844 r.H = max(r.H, o.Y+o.Height)
845 }
846 return r
847}
848
849// moved reports the target position (physical pixels after the output
850// transform) on the output under the pointer;
851// motion tells the device moved it.
852func (p *pointer) moved(move func(output string, x, y float64, motion bool), motion bool) {
853 if o, ok := p.layout.At(p.x, p.y); ok && move != nil {
854 tx, ty := o.ToTarget(p.x, p.y)
855 move(o.Info.Name, tx, ty, motion)
856 }
857}