Implement Axis targets.

This commit is contained in:
Anna Rose Wiggins 2025-07-10 13:06:24 -04:00
parent ff38db6596
commit 2f7e11e8a2
6 changed files with 79 additions and 35 deletions

View file

@ -0,0 +1,15 @@
package mappingrules
import (
"golang.org/x/exp/constraints"
)
func AbsInt[T constraints.Integer](value T) T {
return max(value, -value)
}
// LerpInt linearly interpolates between two integer values using
// a float64 index value
func LerpInt[T constraints.Integer](min, max T, t float64) T {
return T((1-t)*float64(min) + t*float64(max))
}

View file

@ -1,6 +1,9 @@
package mappingrules
import (
"errors"
"fmt"
"github.com/holoplot/go-evdev"
)
@ -11,51 +14,72 @@ type RuleTargetAxis struct {
Inverted bool
DeadzoneStart int32
DeadzoneEnd int32
Sensitivity float64
Sensitivity float64 // TODO: is this even a value that makes sense?
axisSize int32
deadzoneSize int32
}
const (
MinAxisValue = int32(-32768)
MaxAxisValue = int32(32767)
)
func NewRuleTargetAxis(device_name string,
device *evdev.InputDevice,
axis evdev.EvCode,
inverted bool,
deadzone_start int32,
deadzone_end int32,
sensitivity float64) *RuleTargetAxis {
deadzoneStart int32,
deadzoneEnd int32) (*RuleTargetAxis, error) {
info, err := device.AbsInfos()
if err != nil {
return nil, err
}
if _, ok := info[axis]; !ok {
return nil, fmt.Errorf("device does not support axis %v", axis)
}
if deadzoneStart > deadzoneEnd {
return nil, errors.New("deadzone_end must be a higher value than deadzone_start")
}
deadzoneSize := AbsInt(deadzoneEnd - deadzoneStart)
// Our output range is limited to 16 bits, but we represent values internally with 32 bits.
// As a result, we shouldn't need to worry about integer overruns
axisSize := info[axis].Maximum - info[axis].Minimum - deadzoneSize
if axisSize == 0 {
return nil, errors.New("axis has size 0")
}
return &RuleTargetAxis{
DeviceName: device_name,
Device: device,
Axis: axis,
Inverted: inverted,
DeadzoneStart: deadzone_start,
DeadzoneEnd: deadzone_end,
Sensitivity: sensitivity,
}
DeadzoneStart: deadzoneStart,
DeadzoneEnd: deadzoneEnd,
deadzoneSize: deadzoneSize,
axisSize: axisSize,
}, nil
}
// TODO: lots of fixes and decisions to make here. Should we normalize all axes to the same range?
// How do we handle deadzones in light of that?
// NormalizeValue takes a raw input value and converts it to a value suitable for output.
//
// Axis inputs are normalized to the full signed int32 range to match the virtual device's axis
// characteristics.
//
// Typically this function is called after RuleTargetAxis.MatchEvent, which checks whether we are
// in the deadzone, among other things.
func (target *RuleTargetAxis) NormalizeValue(value int32) int32 {
if !target.Inverted {
return value
axisStrength := float64(value-target.deadzoneSize) / float64(target.axisSize)
if target.Inverted {
axisStrength = 1.0 - axisStrength
}
axisRange := target.DeadzoneEnd - target.DeadzoneStart
axisMid := target.DeadzoneEnd - axisRange/2
delta := value - axisMid
if delta < 0 {
delta = -delta
}
if value < axisMid {
return axisMid + delta
} else if value > axisMid {
return axisMid - delta
}
// If we reach here, we're either exactly at the midpoint or something
// strange has happened. Either way, just return the value.
return value
normalizedValue := LerpInt(MinAxisValue, MaxAxisValue, axisStrength)
return normalizedValue
}
func (target *RuleTargetAxis) CreateEvent(value int32, mode *string) *evdev.InputEvent {
@ -71,5 +95,6 @@ func (target *RuleTargetAxis) CreateEvent(value int32, mode *string) *evdev.Inpu
func (target *RuleTargetAxis) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent) bool {
return device == target.Device &&
event.Type == evdev.EV_ABS &&
event.Code == target.Axis
event.Code == target.Axis &&
(event.Value <= target.DeadzoneStart || event.Value >= target.DeadzoneEnd)
}

View file

@ -9,13 +9,13 @@ type RuleTargetButton struct {
Inverted bool
}
func NewRuleTargetButton(device_name string, device *evdev.InputDevice, code evdev.EvCode, inverted bool) *RuleTargetButton {
func NewRuleTargetButton(device_name string, device *evdev.InputDevice, code evdev.EvCode, inverted bool) (*RuleTargetButton, error) {
return &RuleTargetButton{
DeviceName: device_name,
Device: device,
Button: code,
Inverted: inverted,
}
}, nil
}
func (target *RuleTargetButton) NormalizeValue(value int32) int32 {