Add more tests and refactor code parsing logic.

This commit is contained in:
Anna Rose Wiggins 2025-07-16 20:45:51 -04:00
parent a05dc9126d
commit fbb26fd93a
5 changed files with 143 additions and 81 deletions

62
internal/config/codes.go Normal file
View file

@ -0,0 +1,62 @@
package config
import (
"fmt"
"strconv"
"strings"
"github.com/holoplot/go-evdev"
)
func parseCode(code, prefix string) (evdev.EvCode, error) {
code = strings.ToUpper(code)
var codeLookup map[string]evdev.EvCode
switch prefix {
case CodePrefixButton:
codeLookup = evdev.KEYFromString
case CodePrefixAxis:
codeLookup = evdev.ABSFromString
case CodePrefixRelaxis:
codeLookup = evdev.RELFromString
default:
return 0, fmt.Errorf("invalid EvCode prefix '%s'", prefix)
}
switch {
case strings.HasPrefix(code, prefix+"_"):
eventCode, ok := codeLookup[code]
if !ok {
return 0, fmt.Errorf("invalid keycode specification '%s'", code)
}
return eventCode, nil
case strings.HasPrefix(code, "0X"):
codeInt, err := strconv.ParseUint(code[2:], 16, 0)
if err != nil {
return 0, err
}
return evdev.EvCode(codeInt), nil
case prefix == CodePrefixButton && !hasError(strconv.Atoi(code)):
index, err := strconv.Atoi(code)
if err != nil {
return 0, err
}
if index >= len(ButtonFromIndex) {
return 0, fmt.Errorf("button index '%d' out of bounds", index)
}
return ButtonFromIndex[index], nil
default:
eventCode, ok := codeLookup[prefix+"_"+code]
if !ok {
return 0, fmt.Errorf("invalid keycode specification '%s'", code)
}
return eventCode, nil
}
}

View file

@ -0,0 +1,63 @@
package config
import (
"testing"
"github.com/holoplot/go-evdev"
"github.com/stretchr/testify/suite"
)
type DevicesConfigTests struct {
suite.Suite
}
func TestRunnerDevicesConfig(t *testing.T) {
suite.Run(t, new(DevicesConfigTests))
}
func (t *DevicesConfigTests) TestMakeAxes() {
t.Run("8 axes", func() {
axes := makeAxes(8)
t.Equal(8, len(axes))
t.Contains(axes, evdev.EvCode(evdev.ABS_X))
t.Contains(axes, evdev.EvCode(evdev.ABS_Y))
t.Contains(axes, evdev.EvCode(evdev.ABS_Z))
t.Contains(axes, evdev.EvCode(evdev.ABS_RX))
t.Contains(axes, evdev.EvCode(evdev.ABS_RY))
t.Contains(axes, evdev.EvCode(evdev.ABS_RZ))
t.Contains(axes, evdev.EvCode(evdev.ABS_THROTTLE))
t.Contains(axes, evdev.EvCode(evdev.ABS_RUDDER))
})
t.Run("9 axes is truncated", func() {
axes := makeAxes(9)
t.Equal(8, len(axes))
})
t.Run("3 axes", func() {
axes := makeAxes(3)
t.Equal(3, len(axes))
t.Contains(axes, evdev.EvCode(evdev.ABS_X))
t.Contains(axes, evdev.EvCode(evdev.ABS_Y))
t.Contains(axes, evdev.EvCode(evdev.ABS_Z))
})
}
func (t *DevicesConfigTests) TestMakeButtons() {
t.Run("Maximum buttons", func() {
buttons := makeButtons(VirtualDeviceMaxButtons)
t.Equal(VirtualDeviceMaxButtons, len(buttons))
})
t.Run("Truncated buttons", func() {
buttons := makeButtons(VirtualDeviceMaxButtons + 1)
t.Equal(VirtualDeviceMaxButtons, len(buttons))
})
t.Run("16 buttons", func() {
buttons := makeButtons(16)
t.Equal(16, len(buttons))
t.Contains(buttons, evdev.EvCode(evdev.BTN_DEAD))
t.NotContains(buttons, evdev.EvCode(evdev.BTN_TRIGGER_HAPPY))
})
}

View file

@ -3,8 +3,6 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"strconv"
"strings"
"git.annabunches.net/annabunches/joyful/internal/mappingrules" "git.annabunches.net/annabunches/joyful/internal/mappingrules"
"github.com/holoplot/go-evdev" "github.com/holoplot/go-evdev"
@ -16,39 +14,9 @@ func makeRuleTargetButton(targetConfig RuleTargetConfig, devs map[string]*evdev.
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
} }
var eventCode evdev.EvCode eventCode, err := parseCode(targetConfig.Button, "BTN")
buttonConfig := strings.ToUpper(targetConfig.Button) if err != nil {
switch { return nil, err
case strings.HasPrefix(buttonConfig, "BTN_"):
eventCode, ok = evdev.KEYFromString[buttonConfig]
if !ok {
return nil, fmt.Errorf("invalid button specification '%s'", buttonConfig)
}
case strings.HasPrefix(buttonConfig, "0X"):
codeInt, err := strconv.ParseUint(buttonConfig[2:], 16, 0)
if err != nil {
return nil, err
}
eventCode = evdev.EvCode(codeInt)
case !hasError(strconv.Atoi(buttonConfig)):
index, err := strconv.Atoi(buttonConfig)
if err != nil {
return nil, err
}
if index >= len(ButtonFromIndex) {
return nil, fmt.Errorf("button index '%d' out of bounds", index)
}
eventCode = ButtonFromIndex[index]
default:
eventCode, ok = evdev.KEYFromString["BTN_"+buttonConfig]
if !ok {
return nil, fmt.Errorf("invalid button specification '%s'", buttonConfig)
}
} }
return mappingrules.NewRuleTargetButton( return mappingrules.NewRuleTargetButton(
@ -69,27 +37,9 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfig, devs map[string]*evdev.In
return nil, errors.New("deadzone_end must be greater than deadzone_start") return nil, errors.New("deadzone_end must be greater than deadzone_start")
} }
var eventCode evdev.EvCode eventCode, err := parseCode(targetConfig.Axis, "ABS")
axisConfig := strings.ToUpper(targetConfig.Axis) if err != nil {
switch { return nil, err
case strings.HasPrefix(axisConfig, "ABS_"):
eventCode, ok = evdev.ABSFromString[axisConfig]
if !ok {
return nil, fmt.Errorf("invalid axis code '%s'", axisConfig)
}
case strings.HasPrefix(axisConfig, "0X"):
codeInt, err := strconv.ParseUint(axisConfig[2:], 16, 32)
if err != nil {
return nil, err
}
eventCode = evdev.EvCode(codeInt)
default:
eventCode, ok = evdev.ABSFromString["ABS_"+axisConfig]
if !ok {
return nil, fmt.Errorf("invalid axis code '%s'", axisConfig)
}
} }
return mappingrules.NewRuleTargetAxis( return mappingrules.NewRuleTargetAxis(
@ -108,28 +58,11 @@ func makeRuleTargetRelaxis(targetConfig RuleTargetConfig, devs map[string]*evdev
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
} }
var eventCode evdev.EvCode eventCode, err := parseCode(targetConfig.Axis, "REL")
axisConfig := strings.ToUpper(targetConfig.Axis) if err != nil {
switch { return nil, err
case strings.HasPrefix(axisConfig, "REL_"):
eventCode, ok = evdev.RELFromString[axisConfig]
if !ok {
return nil, fmt.Errorf("invalid axis code '%s'", axisConfig)
}
case strings.HasPrefix(axisConfig, "0X"):
codeInt, err := strconv.ParseUint(axisConfig[2:], 16, 32)
if err != nil {
return nil, err
}
eventCode = evdev.EvCode(codeInt)
default:
eventCode, ok = evdev.RELFromString["REL_"+axisConfig]
if !ok {
return nil, fmt.Errorf("invalid axis code '%s'", axisConfig)
}
} }
return mappingrules.NewRuleTargetRelaxis( return mappingrules.NewRuleTargetRelaxis(
targetConfig.Device, targetConfig.Device,
device, device,

View file

@ -12,6 +12,10 @@ type MakeRuleTargetsTests struct {
devs map[string]*evdev.InputDevice devs map[string]*evdev.InputDevice
} }
func TestRunnerMakeRuleTargets(t *testing.T) {
suite.Run(t, new(MakeRuleTargetsTests))
}
func (t *MakeRuleTargetsTests) SetupSuite() { func (t *MakeRuleTargetsTests) SetupSuite() {
t.devs = map[string]*evdev.InputDevice{ t.devs = map[string]*evdev.InputDevice{
"test": {}, "test": {},
@ -141,7 +145,3 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() {
t.NotNil(err) t.NotNil(err)
}) })
} }
func TestRunnerMakeRuleTargets(t *testing.T) {
suite.Run(t, new(MakeRuleTargetsTests))
}

View file

@ -16,6 +16,10 @@ const (
RuleTypeAxisToButton = "axis-to-button" RuleTypeAxisToButton = "axis-to-button"
RuleTypeAxisToRelaxis = "axis-to-relaxis" RuleTypeAxisToRelaxis = "axis-to-relaxis"
CodePrefixButton = "BTN"
CodePrefixAxis = "ABS"
CodePrefixRelaxis = "REL"
VirtualDeviceMaxButtons = 74 VirtualDeviceMaxButtons = 74
) )