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// msec is a libinput timestamp in wayland's millisecond clock.
515func msec(v uint64) uint32 { return uint32(v / 1000) }
516
517// translate converts one libinput event. Timestamps are the device's
518// (CLOCK_MONOTONIC), so clients measure speeds (kinetic scrolling) on
519// when events happened, not when they were read.
520// It snapshots admission before translating and queueing any input kind.
521func translate(ev uintptr, opts Options, p *pointer, in *inputState) (ports.InputEvent, error) {
522 state := securitySnapshot(opts.Security)
523 out, err := translateEvent(ev, opts, p, in, state)
524 return secureInput(out, opts.Security, state), err
525}
526
527func securitySnapshot(security ports.SessionSecurity) ports.SecurityState {
528 if security != nil {
529 return security.Snapshot()
530 }
531 return ports.SecurityState{}
532}
533
534func secureInput(ev ports.InputEvent, security ports.SessionSecurity, state ports.SecurityState) ports.InputEvent {
535 if ev == nil || security == nil {
536 return ev
537 }
538 return ports.SecurityInput{State: state, Event: ev}
539}
540
541func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state ports.SecurityState) (ports.InputEvent, error) {
542 log := opts.Log
543 switch eventType(ev) {
544 case evDeviceAdded:
545 dev := eventDevice(ev)
546 log.Info().Str("device", deviceName(dev)).Msg("input device added")
547 if in.configurable(dev) && !in.devices[dev] {
548 in.devices[deviceRef(dev)] = true
549 in.configure(dev, log)
550 }
551 case evDeviceRemoved:
552 dev := eventDevice(ev)
553 log.Info().Str("device", deviceName(dev)).Msg("input device removed")
554 if in.devices[dev] {
555 delete(in.devices, dev)
556 deviceUnref(dev)
557 }
558 // Core waits for the end of a swipe the device began.
559 return in.swipes.end(dev, true, 0), nil
560 case evSwipeBegin:
561 ge := gestureEvent(ev)
562 return in.swipes.begin(eventDevice(ev), int(gestureFingers(ge)), usec(gestureUsec(ge))), nil
563 case evSwipeUpdate:
564 ge := gestureEvent(ev)
565 return in.swipes.update(eventDevice(ev), gestureDX(ge), gestureDY(ge), usec(gestureUsec(ge))), nil
566 case evSwipeEnd:
567 ge := gestureEvent(ev)
568 return in.swipes.end(eventDevice(ev), gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil
569 case evKeyboardKey:
570 k := keyboardEvent(ev)
571 code, pressed := keyboardKey(k), keyboardState(k) == 1
572 return translateKeyboard(code, pressed, msec(keyboardUsec(k)), opts, state)
573 case evPointerMotion:
574 pe := pointerEvent(ev)
575 m := p.move(pointerDX(pe), pointerDY(pe))
576 m.UnaccelDX, m.UnaccelDY = pointerRawDX(pe), pointerRawDY(pe)
577 m.TimeUsec = pointerUsec(pe)
578 m.TimeMsec = msec(m.TimeUsec)
579 p.moved(opts.MoveCursor, true)
580 if opts.LogMotion {
581 log.Debug().Float64("x", m.X).Float64("y", m.Y).Msg("pointer")
582 }
583 return m, nil
584 case evPointerAbs:
585 // Absolute devices (tablets, VMs) map to the whole layout.
586 pe := pointerEvent(ev)
587 now := msec(pointerUsec(pe))
588 b := p.bounds()
589 if p.constraint.Mode == ports.ConstraintLock {
590 return ports.PointerMotion{X: p.x, Y: p.y, TimeMsec: now}, nil
591 }
592 x, y := p.set(float64(b.X)+pointerAbsX(pe, uint32(b.W)), float64(b.Y)+pointerAbsY(pe, uint32(b.H)))
593 p.moved(opts.MoveCursor, true)
594 return ports.PointerMotion{X: x, Y: y, TimeMsec: now}, nil
595 case evPointerButton:
596 pe := pointerEvent(ev)
597 b, pressed := pointerButton(pe), pointerBtnState(pe) == 1
598 log.Debug().Uint32("button", b).Bool("pressed", pressed).Msg("button")
599 return ports.PointerButton{Button: b, Pressed: pressed, TimeMsec: msec(pointerUsec(pe))}, nil
600 case evScrollWheel, evScrollFinger, evScrollCont:
601 pe := pointerEvent(ev)
602 // The three scroll events follow each other as the sources do.
603 source := ports.AxisSource(eventType(ev) - evScrollWheel)
604 a := ports.PointerAxis{Source: source, TimeMsec: msec(pointerUsec(pe))}
605 // libinput axis 0 is vertical, 1 horizontal, as in wl_pointer.
606 for i, s := range []*ports.ScrollAxis{&a.Vertical, &a.Horizontal} {
607 if pointerHasAxis(pe, uint32(i)) == 0 {
608 continue
609 }
610 s.Set, s.Value = true, scrollValue(pe, uint32(i))
611 if source == ports.AxisFinger {
612 // Two-finger scroll is the touchpad's only scroll source.
613 s.Value *= in.cfg.Touchpad.ScrollFactor
614 }
615 if source == ports.AxisWheel {
616 s.V120 = int32(scrollV120(pe, uint32(i)))
617 } else {
618 s.Stop = s.Value == 0
619 }
620 }
621 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")
622 return a, nil
623 }
624 return nil, nil
625}
626
627// translateKeyboard applies the production epoch before native translation.
628// Quarantined events never reach logging, reserved hotkeys or core.
629func translateKeyboard(code uint32, pressed bool, timeMsec uint32, opts Options, state ports.SecurityState) (ports.InputEvent, error) {
630 var ke ports.KeyEvent
631 if opts.Security != nil {
632 var deliver bool
633 var err error
634 ke, deliver, err = opts.Keymap.KeySecure(code, pressed, timeMsec, state)
635 if err != nil || !deliver {
636 return nil, err
637 }
638 } else {
639 ke = opts.Keymap.Key(code, pressed, timeMsec)
640 }
641 return translateKey(ke, opts, state)
642}
643
644// translateKey applies the keyboard policy without native calls. One coherent
645// snapshot governs both logging and reserved hotkeys; protected keys still go
646// to core for the locker. VT switching is allowed and does not release the lock.
647func translateKey(ke ports.KeyEvent, opts Options, security ports.SecurityState) (ports.InputEvent, error) {
648 if opts.LogKeys && !security.Protected {
649 opts.Log.Debug().Str("component", "input").Uint32("code", ke.Keycode).Str("keysym", ke.Keysym).Bool("pressed", ke.Pressed).Uint8("mods", uint8(ke.Mods)).Msg("key")
650 }
651 switch action, vt := hotkey(ke, security.Protected); action {
652 case hotkeyQuit:
653 opts.Log.Warn().Str("component", "input").Str("reason", "emergency-key").Msg("quit")
654 return nil, ErrEmergencyQuit
655 case hotkeyVT:
656 opts.Seat.SwitchVT(vt)
657 return nil, nil
658 }
659 return ke, nil
660}
661
662type hotkeyAction int
663
664const (
665 hotkeyNone hotkeyAction = iota
666 hotkeyQuit
667 hotkeyVT
668)
669
670// hotkey detects compositor-reserved keys on press: Ctrl+Alt+Backspace and
671// Ctrl+Alt+F1..F12. Protection disables emergency quit, but not VT switching.
672func hotkey(ke ports.KeyEvent, protected bool) (hotkeyAction, int) {
673 if !ke.Pressed {
674 return hotkeyNone, 0
675 }
676 if vt, ok := strings.CutPrefix(ke.Keysym, "XF86Switch_VT_"); ok {
677 var n int
678 if _, err := fmt.Sscan(vt, &n); err == nil && n >= 1 && n <= 12 {
679 return hotkeyVT, n
680 }
681 }
682 if ke.Mods&(ports.ModCtrl|ports.ModAlt) != ports.ModCtrl|ports.ModAlt {
683 return hotkeyNone, 0
684 }
685 switch {
686 case ke.Keysym == "BackSpace" || ke.Keycode == 14:
687 if !protected {
688 return hotkeyQuit, 0
689 }
690 case ke.Keycode >= 59 && ke.Keycode <= 68: // F1..F10
691 return hotkeyVT, int(ke.Keycode) - 58
692 case ke.Keycode == 87 || ke.Keycode == 88: // F11, F12
693 return hotkeyVT, int(ke.Keycode) - 76
694 }
695 return hotkeyNone, 0
696}
697
698// pointer tracks the cursor in global logical coordinates. Relative motion
699// is divided by the scale of the output under the pointer, so it moves the
700// same number of physical pixels on every output.
701type pointer struct {
702 x, y float64
703 layout ports.Layout
704 touched bool // false until the user moves it: it follows the primary output
705 constraint ports.PointerConstraint
706}
707
708// newPointer starts at the centre of the primary output, else the first.
709func newPointer(l ports.Layout) *pointer {
710 p := &pointer{}
711 p.setLayout(l)
712 return p
713}
714
715// setLayout keeps the pointer where it is, or clamps it onto an output.
716// Until it is first moved it stays centred on the primary output, which
717// may be plugged in after the others.
718func (p *pointer) setLayout(l ports.Layout) {
719 p.layout = l
720 if !p.touched && len(l) > 0 {
721 o := l[0]
722 for _, v := range l {
723 if v.Primary {
724 o = v
725 }
726 }
727 p.x, p.y = float64(o.X)+float64(o.Width)/2, float64(o.Y)+float64(o.Height)/2
728 return
729 }
730 p.x, p.y = l.Clamp(p.x, p.y, p.x, p.y)
731}
732
733func (p *pointer) scale() float64 {
734 if o, ok := p.layout.At(p.x, p.y); ok && o.Scale > 0 {
735 return o.Scale
736 }
737 return 1
738}
739
740// move applies a relative motion and returns it with the logical deltas.
741// A locked pointer stays still; a confined one stays in its rectangle.
742func (p *pointer) move(dx, dy float64) ports.PointerMotion {
743 s := p.scale()
744 dx, dy = dx/s, dy/s
745 if p.constraint.Mode != ports.ConstraintLock {
746 p.set(p.x+dx, p.y+dy)
747 }
748 return ports.PointerMotion{X: p.x, Y: p.y, DX: dx, DY: dy}
749}
750
751func (p *pointer) set(x, y float64) (float64, float64) {
752 p.touched = true
753 p.x, p.y = p.constraint.Clamp(p.layout.Clamp(p.x, p.y, x, y))
754 return p.x, p.y
755}
756
757// constrain applies a new constraint. Entering or leaving a lock resyncs
758// the pointer to core's cursor, which stood still meanwhile; a confined
759// pointer moves inside. A warp moves it to core's cursor.
760func (p *pointer) constrain(c ports.PointerConstraint) {
761 if c.Warp || c.Mode == ports.ConstraintLock || p.constraint.Mode == ports.ConstraintLock {
762 p.x, p.y = p.layout.Clamp(c.X, c.Y, c.X, c.Y)
763 }
764 p.constraint = c
765 p.x, p.y = c.Clamp(p.x, p.y)
766}
767
768// bounds is the rectangle holding every output.
769func (p *pointer) bounds() ports.Rect {
770 var r ports.Rect
771 for _, o := range p.layout {
772 r.W = max(r.W, o.X+o.Width)
773 r.H = max(r.H, o.Y+o.Height)
774 }
775 return r
776}
777
778// moved reports the target position (physical pixels after the output
779// transform) on the output under the pointer;
780// motion tells the device moved it.
781func (p *pointer) moved(move func(output string, x, y float64, motion bool), motion bool) {
782 if o, ok := p.layout.At(p.x, p.y); ok && move != nil {
783 tx, ty := o.ToTarget(p.x, p.y)
784 move(o.Info.Name, tx, ty, motion)
785 }
786}