Implement axis targets, axis -> button and axis -> relative axis mappings. (#1)
Co-authored-by: Anna Rose Wiggins <annabunches@gmail.com> Co-committed-by: Anna Rose Wiggins <annabunches@gmail.com>
This commit is contained in:
parent
ff38db6596
commit
e617a6eda6
25 changed files with 903 additions and 130 deletions
|
@ -1,66 +1,87 @@
|
|||
package mappingrules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
type RuleTargetAxis struct {
|
||||
DeviceName string
|
||||
Device *evdev.InputDevice
|
||||
Device RuleTargetDevice
|
||||
Axis evdev.EvCode
|
||||
Inverted bool
|
||||
DeadzoneStart int32
|
||||
DeadzoneEnd int32
|
||||
Sensitivity float64
|
||||
axisSize int32
|
||||
deadzoneSize int32
|
||||
}
|
||||
|
||||
func NewRuleTargetAxis(device_name string,
|
||||
device *evdev.InputDevice,
|
||||
device RuleTargetDevice,
|
||||
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 {
|
||||
// If we can't get AbsInfo (for example, we're a virtual device)
|
||||
// we set the bounds to the maximum allowable
|
||||
info = map[evdev.EvCode]evdev.AbsInfo{
|
||||
axis: {
|
||||
Minimum: AxisValueMin,
|
||||
Maximum: AxisValueMax,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 := Abs(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
|
||||
}
|
||||
|
||||
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
|
||||
axisStrength := target.GetAxisStrength(value)
|
||||
return LerpInt(AxisValueMin, AxisValueMax, axisStrength)
|
||||
}
|
||||
|
||||
func (target *RuleTargetAxis) CreateEvent(value int32, mode *string) *evdev.InputEvent {
|
||||
// TODO: we can use the axis begin/end to decide whether to emit the event
|
||||
// TODO: oh no we need center deadzones actually...
|
||||
value = Clamp(value, AxisValueMin, AxisValueMax)
|
||||
return &evdev.InputEvent{
|
||||
Type: evdev.EV_ABS,
|
||||
Code: target.Axis,
|
||||
|
@ -68,8 +89,33 @@ func (target *RuleTargetAxis) CreateEvent(value int32, mode *string) *evdev.Inpu
|
|||
}
|
||||
}
|
||||
|
||||
func (target *RuleTargetAxis) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent) bool {
|
||||
func (target *RuleTargetAxis) MatchEvent(device RuleTargetDevice, event *evdev.InputEvent) bool {
|
||||
return target.MatchEventDeviceAndCode(device, event) &&
|
||||
!target.InDeadZone(event.Value)
|
||||
}
|
||||
|
||||
// TODO: Add tests
|
||||
func (target *RuleTargetAxis) MatchEventDeviceAndCode(device RuleTargetDevice, event *evdev.InputEvent) bool {
|
||||
return device == target.Device &&
|
||||
event.Type == evdev.EV_ABS &&
|
||||
event.Code == target.Axis
|
||||
}
|
||||
|
||||
// TODO: Add tests
|
||||
func (target *RuleTargetAxis) InDeadZone(value int32) bool {
|
||||
return value >= target.DeadzoneStart && value <= target.DeadzoneEnd
|
||||
}
|
||||
|
||||
// GetAxisStrength returns a float between 0.0 and 1.0, representing the proportional
|
||||
// position along the axis' full range. (after factoring in deadzones)
|
||||
// Calling this function with `value` inside the deadzone range will produce undefined behavior
|
||||
func (target *RuleTargetAxis) GetAxisStrength(value int32) float64 {
|
||||
if value > target.DeadzoneEnd {
|
||||
value -= target.deadzoneSize
|
||||
}
|
||||
strength := float64(value) / float64(target.axisSize)
|
||||
if target.Inverted {
|
||||
strength = 1.0 - strength
|
||||
}
|
||||
return strength
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue