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