Add (semi-experimental) support for multiple deadzones and deadzones that emit a fixed value.

This commit is contained in:
Anna Rose Wiggins 2025-09-14 18:47:48 -04:00
parent 8a903e0703
commit 2e8c2c5404
8 changed files with 246 additions and 178 deletions

View file

@ -0,0 +1,33 @@
package configparser
type RuleTargetConfigButton struct {
Device string
Button string
Inverted bool
}
type RuleTargetConfigAxis struct {
Device string
Axis string
Inverted bool
Deadzones []DeadzoneConfig
}
type DeadzoneConfig struct {
Center int32 `yaml:"center,omitempty"`
Size int32 `yaml:"size,omitempty"`
SizePercent int32 `yaml:"size_percent,omitempty"`
Start int32 `yaml:"start,omitempty"`
End int32 `yaml:"end,omitempty"`
Emit bool `yaml:"emit,omitempty"`
Value int32 `yaml:"emit_value,omitempty"`
}
type RuleTargetConfigRelaxis struct {
Device string
Axis string
}
type RuleTargetConfigModeSelect struct {
Modes []string
}

View file

@ -65,29 +65,3 @@ type RuleConfigModeSelect struct {
Input RuleTargetConfigButton
Output RuleTargetConfigModeSelect
}
type RuleTargetConfigButton struct {
Device string
Button string
Inverted bool
}
type RuleTargetConfigAxis struct {
Device string
Axis string
DeadzoneCenter int32 `yaml:"deadzone_center,omitempty"`
DeadzoneSize int32 `yaml:"deadzone_size,omitempty"`
DeadzoneSizePercent int32 `yaml:"deadzone_size_percent,omitempty"`
DeadzoneStart int32 `yaml:"deadzone_start,omitempty"`
DeadzoneEnd int32 `yaml:"deadzone_end,omitempty"`
Inverted bool
}
type RuleTargetConfigRelaxis struct {
Device string
Axis string
}
type RuleTargetConfigModeSelect struct {
Modes []string
}

View file

@ -0,0 +1,99 @@
package mappingrules
import (
"errors"
"fmt"
"git.annabunches.net/annabunches/joyful/internal/configparser"
"github.com/holoplot/go-evdev"
)
// TODO: need tests for multiple deadzones
// TODO: need tests for emitting deadzones
type Deadzone struct {
Start int32
End int32
Size int32
Emit bool
EmitValue int32
}
// DeadzoneState indicates whether a value is in a Deadzone and, if it is, whether the deadzone
// should emit an event
type DeadzoneState int
const (
// DeadzoneClear indicates the value is *not* in the deadzone.
DeadzoneClear DeadzoneState = iota
DeadzoneEmit
DeadzoneNoEmit
)
// calculateDeadzones produces the deadzone start and end values in absolute terms
func NewDeadzoneFromConfig(dzConfig configparser.DeadzoneConfig, device Device, axis evdev.EvCode) (Deadzone, error) {
dz := Deadzone{}
dz.Emit = dzConfig.Emit
dz.EmitValue = dzConfig.Value
fmt.Printf("DEBUG: %d, %d\n", dzConfig.Value, dz.EmitValue)
var min, max int32
absInfoMap, err := device.AbsInfos()
if err != nil {
return dz, err
} else {
absInfo := absInfoMap[axis]
min = absInfo.Minimum
max = absInfo.Maximum
}
if dzConfig.Start != 0 || dzConfig.End != 0 {
dz.Start = Clamp(dzConfig.Start, min, max)
dz.End = Clamp(dzConfig.End, min, max)
if dz.Start > dz.End {
return dz, errors.New("deadzone end must be greater than deadzone start")
}
} else {
center := Clamp(dzConfig.Center, min, max)
var deadzoneSize int32
switch {
case dzConfig.Size != 0:
deadzoneSize = dzConfig.Size
case dzConfig.SizePercent != 0:
deadzoneSize = (max - min) / dzConfig.SizePercent
default:
return dz, fmt.Errorf("deadzone configured incorrectly; must define start and end or center and size")
}
dz.Start = center - deadzoneSize/2
dz.End = center + deadzoneSize/2
dz.Start, dz.End = clampAndShift(dz.Start, dz.End, min, max)
}
dz.Size = dz.End - dz.Start
return dz, nil
}
func CalculateDeadzoneSize(dzs []Deadzone) int32 {
var size int32
for _, dz := range dzs {
size += dz.Size
}
return size
}
// Match checks whether the target value is inside the deadzone.
// It returns a DeadzoneState enum and possibly an int32.
func (dz Deadzone) Match(value int32) (DeadzoneState, int32) {
if value < dz.Start || value > dz.End {
return DeadzoneClear, value
}
if dz.Emit {
return DeadzoneEmit, dz.EmitValue
}
return DeadzoneNoEmit, value
}

View file

@ -125,8 +125,12 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
t.Run("Invalid deadzone", func() {
config := configparser.RuleTargetConfigAxis{Device: "test"}
config.Axis = "x"
config.DeadzoneEnd = 100
config.DeadzoneStart = 1000
config.Deadzones = []configparser.DeadzoneConfig{
{
End: 100,
Start: 1000,
},
}
_, err := NewRuleTargetAxisFromConfig(config, t.devs)
t.NotNil(err)
})
@ -145,30 +149,21 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
for _, tc := range relDeadzoneTestCases {
t.Run(fmt.Sprintf("Relative Deadzone %d +- %d", tc.inCenter, tc.inSize), func() {
config := configparser.RuleTargetConfigAxis{
Device: "test",
Axis: "x",
DeadzoneCenter: tc.inCenter,
DeadzoneSize: tc.inSize,
Device: "test",
Axis: "x",
Deadzones: []configparser.DeadzoneConfig{{
Center: tc.inCenter,
Size: tc.inSize,
}},
}
rule, err := NewRuleTargetAxisFromConfig(config, t.devs)
t.Nil(err)
t.Equal(tc.outStart, rule.DeadzoneStart)
t.Equal(tc.outEnd, rule.DeadzoneEnd)
t.Equal(tc.outStart, rule.Deadzones[0].Start)
t.Equal(tc.outEnd, rule.Deadzones[0].End)
})
}
t.Run("Deadzone center/size invalid center", func() {
config := configparser.RuleTargetConfigAxis{
Device: "test",
Axis: "x",
DeadzoneCenter: 20000,
DeadzoneSize: 500,
}
_, err := NewRuleTargetAxisFromConfig(config, t.devs)
t.NotNil(err)
})
relDeadzonePercentTestCases := []struct {
inCenter int32
inSizePercent int32
@ -183,29 +178,20 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
for _, tc := range relDeadzonePercentTestCases {
t.Run(fmt.Sprintf("Relative percent deadzone %d +- %d%%", tc.inCenter, tc.inSizePercent), func() {
config := configparser.RuleTargetConfigAxis{
Device: "test",
Axis: "x",
DeadzoneCenter: tc.inCenter,
DeadzoneSizePercent: tc.inSizePercent,
Device: "test",
Axis: "x",
Deadzones: []configparser.DeadzoneConfig{{
Center: tc.inCenter,
SizePercent: tc.inSizePercent,
}},
}
rule, err := NewRuleTargetAxisFromConfig(config, t.devs)
t.Nil(err)
t.Equal(tc.outStart, rule.DeadzoneStart)
t.Equal(tc.outEnd, rule.DeadzoneEnd)
t.Equal(tc.outStart, rule.Deadzones[0].Start)
t.Equal(tc.outEnd, rule.Deadzones[0].End)
})
}
t.Run("Deadzone center/percent invalid center", func() {
config := configparser.RuleTargetConfigAxis{
Device: "test",
Axis: "x",
DeadzoneCenter: 20000,
DeadzoneSizePercent: 10,
}
_, err := NewRuleTargetAxisFromConfig(config, t.devs)
t.NotNil(err)
})
}
func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() {

View file

@ -28,6 +28,7 @@ func TestRunnerMappingRuleAxisCombined(t *testing.T) {
}
func (t *MappingRuleAxisCombinedTests) SetupTest() {
noDeadzone := make([]Deadzone, 0)
mode := "*"
t.mode = &mode
@ -37,13 +38,13 @@ func (t *MappingRuleAxisCombinedTests) SetupTest() {
evdev.ABS_Y: {Minimum: 0, Maximum: 10000},
}, nil)
t.inputTargetLower, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, true, 0, 0)
t.inputTargetLower, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, true, noDeadzone)
t.inputTargetLower.OutputMax = 0
t.inputTargetUpper, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_Y, false, 0, 0)
t.inputTargetUpper, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_Y, false, noDeadzone)
t.inputTargetUpper.OutputMin = 0
t.outputDevice = &evdev.InputDevice{}
t.outputTarget, _ = NewRuleTargetAxis("test-output", t.outputDevice, evdev.ABS_X, false, 0, 0)
t.outputTarget, _ = NewRuleTargetAxis("test-output", t.outputDevice, evdev.ABS_X, false, noDeadzone)
t.base = NewMappingRuleBase("", []string{"*"})
@ -67,10 +68,10 @@ func (t *MappingRuleAxisCombinedTests) TestNewMappingRuleAxisCombined() {
}, nil)
rule := &MappingRuleAxisCombined{
MappingRuleBase: t.base,
InputLower: t.inputTargetLower,
InputUpper: t.inputTargetUpper,
Output: t.outputTarget,
// MappingRuleBase: t.base,
InputLower: t.inputTargetLower,
InputUpper: t.inputTargetUpper,
// Output: t.outputTarget,
}
t.EqualValues(0, rule.InputLower.OutputMax)
t.EqualValues(0, rule.InputUpper.OutputMin)

View file

@ -67,7 +67,7 @@ func (t *MappingRuleAxisToButtonTests) SetupTest() {
Maximum: 10000,
},
}, nil)
t.inputRule, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, false, int32(0), int32(1000))
t.inputRule, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, false, []Deadzone{{Start: 0, End: 1000}})
t.outputDevice = &evdev.InputDevice{}
t.outputRule, _ = NewRuleTargetButton("test-output", t.outputDevice, evdev.ABS_X, false)
@ -113,14 +113,16 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() {
Code: evdev.ABS_X,
Value: 1001,
}, t.mode)
t.True(testRule.nextEvent > time.Duration(700*time.Millisecond))
// Allow leeway since time passes during the test
t.True(testRule.nextEvent > time.Duration(650*time.Millisecond))
testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{
Type: evdev.EV_ABS,
Code: evdev.ABS_X,
Value: 5500,
}, t.mode)
t.Equal(time.Duration(500*time.Millisecond), testRule.nextEvent)
// Allow up to 50 ms leeway since time passes during the test
t.InDelta(time.Duration(500*time.Millisecond), testRule.nextEvent, 50000000)
})
}

View file

@ -10,16 +10,15 @@ import (
)
type RuleTargetAxis struct {
DeviceName string
Device Device
Axis evdev.EvCode
Inverted bool
DeadzoneStart int32
DeadzoneEnd int32
OutputMin int32
OutputMax int32
axisSize int32
deadzoneSize int32
DeviceName string
Device Device
Axis evdev.EvCode
Inverted bool
Deadzones []Deadzone
OutputMin int32
OutputMax int32
axisSize int32
deadzoneSize int32
}
func NewRuleTargetAxisFromConfig(targetConfig configparser.RuleTargetConfigAxis, devs map[string]Device) (*RuleTargetAxis, error) {
@ -28,18 +27,18 @@ func NewRuleTargetAxisFromConfig(targetConfig configparser.RuleTargetConfigAxis,
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
}
if targetConfig.DeadzoneEnd < targetConfig.DeadzoneStart {
return nil, errors.New("deadzone_end must be greater than deadzone_start")
}
eventCode, err := eventcodes.ParseCode(targetConfig.Axis, eventcodes.CodePrefixAxis)
if err != nil {
return nil, err
}
deadzoneStart, deadzoneEnd, err := calculateDeadzones(targetConfig, device, eventCode)
if err != nil {
return nil, err
deadzones := make([]Deadzone, 0)
for _, dzConfig := range targetConfig.Deadzones {
dz, err := NewDeadzoneFromConfig(dzConfig, device, eventCode)
if err != nil {
return nil, err
}
deadzones = append(deadzones, dz)
}
return NewRuleTargetAxis(
@ -47,58 +46,15 @@ func NewRuleTargetAxisFromConfig(targetConfig configparser.RuleTargetConfigAxis,
device,
eventCode,
targetConfig.Inverted,
deadzoneStart,
deadzoneEnd,
deadzones,
)
}
// calculateDeadzones produces the deadzone start and end values in absolute terms
func calculateDeadzones(targetConfig configparser.RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) {
var deadzoneStart, deadzoneEnd int32
deadzoneStart = 0
deadzoneEnd = 0
if targetConfig.DeadzoneStart != 0 || targetConfig.DeadzoneEnd != 0 {
return targetConfig.DeadzoneStart, targetConfig.DeadzoneEnd, nil
}
var min, max int32
absInfoMap, err := device.AbsInfos()
if err != nil {
min = AxisValueMin
max = AxisValueMax
} else {
absInfo := absInfoMap[axis]
min = absInfo.Minimum
max = absInfo.Maximum
}
if targetConfig.DeadzoneCenter < min || targetConfig.DeadzoneCenter > max {
return 0, 0, fmt.Errorf("deadzone_center '%d' is out of bounds", targetConfig.DeadzoneCenter)
}
switch {
case targetConfig.DeadzoneSize != 0:
deadzoneStart = targetConfig.DeadzoneCenter - targetConfig.DeadzoneSize/2
deadzoneEnd = targetConfig.DeadzoneCenter + targetConfig.DeadzoneSize/2
case targetConfig.DeadzoneSizePercent != 0:
deadzoneSize := (max - min) / targetConfig.DeadzoneSizePercent
deadzoneStart = targetConfig.DeadzoneCenter - deadzoneSize/2
deadzoneEnd = targetConfig.DeadzoneCenter + deadzoneSize/2
}
deadzoneStart, deadzoneEnd = clampAndShift(deadzoneStart, deadzoneEnd, min, max)
return deadzoneStart, deadzoneEnd, nil
}
func NewRuleTargetAxis(device_name string,
device Device,
axis evdev.EvCode,
inverted bool,
deadzoneStart int32,
deadzoneEnd int32) (*RuleTargetAxis, error) {
deadzones []Deadzone) (*RuleTargetAxis, error) {
info, err := device.AbsInfos()
@ -117,11 +73,7 @@ func NewRuleTargetAxis(device_name string,
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)
deadzoneSize := CalculateDeadzoneSize(deadzones)
// 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
@ -132,16 +84,15 @@ func NewRuleTargetAxis(device_name string,
}
return &RuleTargetAxis{
DeviceName: device_name,
Device: device,
Axis: axis,
Inverted: inverted,
OutputMin: AxisValueMin,
OutputMax: AxisValueMax,
DeadzoneStart: deadzoneStart,
DeadzoneEnd: deadzoneEnd,
deadzoneSize: deadzoneSize,
axisSize: axisSize,
DeviceName: device_name,
Device: device,
Axis: axis,
Inverted: inverted,
OutputMin: AxisValueMin,
OutputMax: AxisValueMax,
Deadzones: deadzones,
deadzoneSize: deadzoneSize,
axisSize: axisSize,
}, nil
}
@ -150,6 +101,7 @@ func NewRuleTargetAxis(device_name string,
// Axis inputs are normalized to the full signed int32 range to match the virtual device's axis
// characteristics.
//
// If the raw value is inside the deadzone, we either emit no event, or we emit the deadzoneValue.
// 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 {
@ -178,19 +130,36 @@ func (target *RuleTargetAxis) MatchEventDeviceAndCode(device Device, event *evde
event.Code == target.Axis
}
// InDeadZone checks each deadzone for whether the target value falls within it.
// If *any* non-emitting deadzone matches, we return true.
// TODO: Add tests
func (target *RuleTargetAxis) InDeadZone(value int32) bool {
return target.deadzoneSize > 0 && value >= target.DeadzoneStart && value <= target.DeadzoneEnd
for _, dz := range target.Deadzones {
state, _ := dz.Match(value)
if state == DeadzoneNoEmit {
return true
}
}
return false
}
// 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
adjValue := value
for _, dz := range target.Deadzones {
state, dzValue := dz.Match(value)
if state == DeadzoneEmit {
adjValue = dzValue
break
}
if value > dz.End {
adjValue -= dz.Size
}
}
strength := float64(value) / float64(target.axisSize)
strength := float64(adjValue) / float64(target.axisSize)
if target.Inverted {
strength = 1.0 - strength
}

View file

@ -38,42 +38,42 @@ func (t *RuleTargetAxisTests) TearDownTest() {
}
func (t *RuleTargetAxisTests) TestNewRuleTargetAxis() {
noDeadzone := make([]Deadzone, 0)
// RuleTargets should get created
ruleTarget, err := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
ruleTarget, err := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
t.Nil(err)
t.EqualValues(10000, ruleTarget.axisSize)
ruleTarget, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, 0, 0)
ruleTarget, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, noDeadzone)
t.Nil(err)
t.EqualValues(20000, ruleTarget.axisSize)
// Creating a rule with a deadzone should work and reduce the axisSize
ruleTarget, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, -500, 500)
ruleTarget, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, []Deadzone{{Start: -500, End: 500, Size: 1000}})
t.Nil(err)
t.EqualValues(19000, ruleTarget.axisSize)
t.EqualValues(-500, ruleTarget.DeadzoneStart)
t.EqualValues(500, ruleTarget.DeadzoneEnd)
// Creating a rule with a deadzone should fail if end > start
_, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, 500, -500)
t.NotNil(err)
t.EqualValues(-500, ruleTarget.Deadzones[0].Start)
t.EqualValues(500, ruleTarget.Deadzones[0].End)
// Creating a rule on a non-existent axis should err
_, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Z, false, 0, 0)
_, err = NewRuleTargetAxis("", t.mock, evdev.ABS_Z, false, noDeadzone)
t.NotNil(err)
// If Absinfo has an error, we should create a device with permissive bounds
t.call.Unset()
t.mock.On("AbsInfos").Return(map[evdev.EvCode]evdev.AbsInfo{}, errors.New("Test Error"))
ruleTarget, err = NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
ruleTarget, err = NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
t.Nil(err)
t.Equal(AxisValueMax-AxisValueMin, ruleTarget.axisSize)
}
func (t *RuleTargetAxisTests) TestNormalizeValue() {
noDeadzone := make([]Deadzone, 0)
// Basic normalization should work
t.Run("Simple normalization", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
t.Equal(AxisValueMax, ruleTarget.NormalizeValue(int32(10000)))
t.Equal(AxisValueMin, ruleTarget.NormalizeValue(int32(0)))
t.EqualValues(0, ruleTarget.NormalizeValue(int32(5000)))
@ -81,26 +81,26 @@ func (t *RuleTargetAxisTests) TestNormalizeValue() {
// Normalization with a deadzone should work
t.Run("With Deadzone", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 5000)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, []Deadzone{{Start: 0, End: 5000, Size: 5000}})
t.Equal(AxisValueMax, ruleTarget.NormalizeValue(int32(10000)))
t.True(ruleTarget.NormalizeValue(int32(5001)) < int32(-31000))
t.InDelta(int32(-32000), ruleTarget.NormalizeValue(int32(5001)), 1000)
t.EqualValues(0, ruleTarget.NormalizeValue(int32(7500)))
})
t.Run("Inverted", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, 0, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, noDeadzone)
t.Equal(AxisValueMax, ruleTarget.NormalizeValue(int32(0)))
t.Equal(AxisValueMin, ruleTarget.NormalizeValue(int32(10000)))
})
t.Run("Out of bounds", func() { // Normalization past the stated axis bounds should clamp
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
t.Equal(AxisValueMin, ruleTarget.NormalizeValue(int32(-30000)))
t.Equal(AxisValueMax, ruleTarget.NormalizeValue(int32(30000)))
})
t.Run("With partial output range", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
ruleTarget.OutputMin = 0
ruleTarget.OutputMax = AxisValueMax
t.EqualValues(0, ruleTarget.NormalizeValue(int32(0)))
@ -110,7 +110,7 @@ func (t *RuleTargetAxisTests) TestNormalizeValue() {
}
func (t *RuleTargetAxisTests) TestMatchEvent() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, -500, 500)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_Y, false, []Deadzone{{Start: -500, End: 500}})
validEvent := &evdev.InputEvent{
Type: evdev.EV_ABS,
Code: evdev.ABS_Y,
@ -133,7 +133,9 @@ func (t *RuleTargetAxisTests) TestMatchEvent() {
}
func (t *RuleTargetAxisTests) TestCreateEvent() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
noDeadzone := make([]Deadzone, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
expected := &evdev.InputEvent{
Type: evdev.EV_ABS,
Code: evdev.ABS_X,
@ -155,43 +157,45 @@ func (t *RuleTargetAxisTests) TestCreateEvent() {
}
func (t *RuleTargetAxisTests) TestGetAxisStrength() {
noDeadzone := make([]Deadzone, 0)
t.Run("With no deadzone", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, noDeadzone)
t.Equal(0.0, ruleTarget.GetAxisStrength(0))
t.Equal(1.0, ruleTarget.GetAxisStrength(10000))
t.Equal(0.5, ruleTarget.GetAxisStrength(5000))
})
t.Run("With low deadzone", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 0, 5000)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, []Deadzone{{Start: 0, End: 5000, Size: 5000}})
t.InDelta(0.0, ruleTarget.GetAxisStrength(5001), 0.01)
t.InDelta(0.5, ruleTarget.GetAxisStrength(7500), 0.01)
t.Equal(1.0, ruleTarget.GetAxisStrength(10000))
})
t.Run("With high deadzone", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, 5000, 10000)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, false, []Deadzone{{Start: 5000, End: 10000, Size: 5000}})
t.Equal(0.0, ruleTarget.GetAxisStrength(0))
t.InDelta(0.5, ruleTarget.GetAxisStrength(2500), 0.01)
t.InDelta(1.0, ruleTarget.GetAxisStrength(4999), 0.01)
})
t.Run("Inverted", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, 0, 0)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, noDeadzone)
t.Equal(1.0, ruleTarget.GetAxisStrength(0))
t.Equal(0.5, ruleTarget.GetAxisStrength(5000))
t.Equal(0.0, ruleTarget.GetAxisStrength(10000))
})
t.Run("Inverted with low deadzone", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, 0, 5000)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, []Deadzone{{Start: 0, End: 5000, Size: 5000}})
t.InDelta(1.0, ruleTarget.GetAxisStrength(5001), 0.01)
t.InDelta(0.5, ruleTarget.GetAxisStrength(7500), 0.01)
t.Equal(0.0, ruleTarget.GetAxisStrength(10000))
})
t.Run("Inverted with high deadzone", func() {
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, 5000, 10000)
ruleTarget, _ := NewRuleTargetAxis("", t.mock, evdev.ABS_X, true, []Deadzone{{Start: 5000, End: 10000, Size: 5000}})
t.InDelta(0.0, ruleTarget.GetAxisStrength(4999), 0.01)
t.InDelta(0.5, ruleTarget.GetAxisStrength(2500), 0.01)
t.Equal(1.0, ruleTarget.GetAxisStrength(0))