(WIP) Move rule initialization into rule package.
This commit is contained in:
parent
727985f91c
commit
9e4062ba30
21 changed files with 366 additions and 489 deletions
|
@ -1,15 +1,3 @@
|
|||
// The ConfigParser is the main structure you'll interact with when using this package.
|
||||
//
|
||||
// Example usage:
|
||||
// config := &config.ConfigParser{}
|
||||
// config.Parse(<some directory containing YAML files>)
|
||||
// virtualDevices := config.CreateVirtualDevices()
|
||||
// physicalDevices := config.ConnectVirtualDevices()
|
||||
// modes := config.GetModes()
|
||||
// rules := config.BuildRules(physicalDevices, virtualDevices, modes)
|
||||
//
|
||||
// nb: there are methods defined on ConfigParser in other files in this package!
|
||||
|
||||
package configparser
|
||||
|
||||
import (
|
||||
|
@ -22,60 +10,6 @@ import (
|
|||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
type ConfigParser struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// Parse all the config files and store the config data for further use
|
||||
func (parser *ConfigParser) Parse(directory string) error {
|
||||
parser.config = Config{}
|
||||
|
||||
// Find the config files in the directory
|
||||
dirEntries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
err = os.Mkdir(directory, 0755)
|
||||
if err != nil {
|
||||
return errors.New("Failed to create config directory at " + directory)
|
||||
}
|
||||
}
|
||||
|
||||
// Open each yaml file and add its contents to the global config
|
||||
for _, file := range dirEntries {
|
||||
name := file.Name()
|
||||
if file.IsDir() || !(strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml")) {
|
||||
continue
|
||||
}
|
||||
|
||||
filePath := filepath.Join(directory, name)
|
||||
if strings.HasSuffix(filePath, ".yaml") || strings.HasSuffix(filePath, ".yml") {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
logger.LogError(err, "Error while opening config file")
|
||||
continue
|
||||
}
|
||||
newConfig := Config{}
|
||||
err = yaml.Unmarshal(data, &newConfig)
|
||||
logger.LogIfError(err, "Error parsing YAML")
|
||||
parser.config.Rules = append(parser.config.Rules, newConfig.Rules...)
|
||||
parser.config.Devices = append(parser.config.Devices, newConfig.Devices...)
|
||||
parser.config.Modes = append(parser.config.Modes, newConfig.Modes...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parser.config.Devices) == 0 {
|
||||
return errors.New("Found no devices in configuration. Please add configuration at " + directory)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (parser *ConfigParser) GetModes() []string {
|
||||
if len(parser.config.Modes) == 0 {
|
||||
return []string{"*"}
|
||||
}
|
||||
return parser.config.Modes
|
||||
}
|
||||
|
||||
func ParseConfig(directory string) (*Config, error) {
|
||||
config := new(Config)
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
// InitPhysicalDevices will create InputDevices corresponding to any registered
|
||||
// devices with type = physical.
|
||||
//
|
||||
// This function assumes Parse() has been called.
|
||||
//
|
||||
// This function should only be called once.
|
||||
func (parser *ConfigParser) InitPhysicalDevices() map[string]*evdev.InputDevice {
|
||||
deviceMap := make(map[string]*evdev.InputDevice)
|
||||
|
||||
for _, deviceConfig := range parser.config.Devices {
|
||||
if strings.ToLower(deviceConfig.Type) != DeviceTypePhysical {
|
||||
continue
|
||||
}
|
||||
|
||||
deviceConfig := deviceConfig.Config.(DeviceConfigPhysical)
|
||||
|
||||
var infoName string
|
||||
var device *evdev.InputDevice
|
||||
var err error
|
||||
|
||||
if deviceConfig.DevicePath != "" {
|
||||
infoName = deviceConfig.DevicePath
|
||||
device, err = evdev.Open(deviceConfig.DevicePath)
|
||||
} else {
|
||||
infoName = deviceConfig.DeviceName
|
||||
device, err = evdev.OpenByName(deviceConfig.DeviceName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to open physical device, skipping. Confirm the device name or path with 'evinfo'")
|
||||
continue
|
||||
}
|
||||
|
||||
if deviceConfig.Lock {
|
||||
logger.LogDebugf("Locking device '%s'", infoName)
|
||||
err := device.Grab()
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to grab device for exclusive access")
|
||||
}
|
||||
}
|
||||
|
||||
logger.Log(fmt.Sprintf("Connected to '%s' as '%s'", infoName, deviceConfig.Name))
|
||||
deviceMap[deviceConfig.Name] = device
|
||||
}
|
||||
|
||||
return deviceMap
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
|
||||
type Device interface {
|
||||
AbsInfos() (map[evdev.EvCode]evdev.AbsInfo, error)
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/eventcodes"
|
||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
func makeRuleTargetButton(targetConfig RuleTargetConfigButton, devs map[string]Device) (*mappingrules.RuleTargetButton, error) {
|
||||
device, ok := devs[targetConfig.Device]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
||||
}
|
||||
|
||||
eventCode, err := eventcodes.ParseCodeButton(targetConfig.Button)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetButton(
|
||||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
targetConfig.Inverted,
|
||||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Device) (*mappingrules.RuleTargetAxis, error) {
|
||||
device, ok := devs[targetConfig.Device]
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetAxis(
|
||||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
targetConfig.Inverted,
|
||||
deadzoneStart,
|
||||
deadzoneEnd,
|
||||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetRelaxis(targetConfig RuleTargetConfigRelaxis, devs map[string]Device) (*mappingrules.RuleTargetRelaxis, error) {
|
||||
device, ok := devs[targetConfig.Device]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
||||
}
|
||||
|
||||
eventCode, err := eventcodes.ParseCode(targetConfig.Axis, eventcodes.CodePrefixRelaxis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetRelaxis(
|
||||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetModeSelect(targetConfig RuleTargetConfigModeSelect, allModes []string) (*mappingrules.RuleTargetModeSelect, error) {
|
||||
if ok := validateModes(targetConfig.Modes, allModes); !ok {
|
||||
return nil, errors.New("undefined mode in mode select list")
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetModeSelect(targetConfig.Modes)
|
||||
}
|
||||
|
||||
// calculateDeadzones produces the deadzone start and end values in absolute terms
|
||||
// TODO: on the one hand, this logic feels betten encapsulated in mappingrules. On the other hand,
|
||||
// passing even more parameters to NewRuleTargetAxis feels terrible
|
||||
func calculateDeadzones(targetConfig 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 = mappingrules.AxisValueMin
|
||||
max = mappingrules.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 clampAndShift(start, end, min, max int32) (int32, int32) {
|
||||
if start < min {
|
||||
end += min - start
|
||||
start = min
|
||||
}
|
||||
if end > max {
|
||||
start -= end - max
|
||||
end = max
|
||||
}
|
||||
|
||||
return start, end
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/holoplot/go-evdev"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type MakeRuleTargetsTests struct {
|
||||
suite.Suite
|
||||
devs map[string]Device
|
||||
deviceMock *DeviceMock
|
||||
}
|
||||
|
||||
type DeviceMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *DeviceMock) AbsInfos() (map[evdev.EvCode]evdev.AbsInfo, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).(map[evdev.EvCode]evdev.AbsInfo), args.Error(1)
|
||||
}
|
||||
|
||||
func TestRunnerMakeRuleTargets(t *testing.T) {
|
||||
suite.Run(t, new(MakeRuleTargetsTests))
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) SetupSuite() {
|
||||
t.deviceMock = new(DeviceMock)
|
||||
t.deviceMock.On("AbsInfos").Return(
|
||||
map[evdev.EvCode]evdev.AbsInfo{
|
||||
evdev.ABS_X: {
|
||||
Minimum: 0,
|
||||
Maximum: 10000,
|
||||
},
|
||||
evdev.ABS_Y: {
|
||||
Minimum: 0,
|
||||
Maximum: 10000,
|
||||
},
|
||||
}, nil,
|
||||
)
|
||||
t.devs = map[string]Device{
|
||||
"test": t.deviceMock,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetButton() {
|
||||
config := RuleTargetConfigButton{Device: "test"}
|
||||
|
||||
t.Run("Standard keycode", func() {
|
||||
config.Button = "BTN_TRIGGER"
|
||||
rule, err := makeRuleTargetButton(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.BTN_TRIGGER, rule.Button)
|
||||
})
|
||||
|
||||
t.Run("Hex code", func() {
|
||||
config.Button = "0x2fd"
|
||||
rule, err := makeRuleTargetButton(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.EvCode(0x2fd), rule.Button)
|
||||
})
|
||||
|
||||
t.Run("Index", func() {
|
||||
config.Button = "3"
|
||||
rule, err := makeRuleTargetButton(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.BTN_TOP, rule.Button)
|
||||
})
|
||||
|
||||
t.Run("Index too high", func() {
|
||||
config.Button = "74"
|
||||
_, err := makeRuleTargetButton(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Un-prefixed keycode", func() {
|
||||
config.Button = "pinkie"
|
||||
rule, err := makeRuleTargetButton(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.BTN_PINKIE, rule.Button)
|
||||
})
|
||||
|
||||
t.Run("Invalid keycode", func() {
|
||||
config.Button = "foo"
|
||||
_, err := makeRuleTargetButton(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||
codeTestCases := []struct {
|
||||
input string
|
||||
output evdev.EvCode
|
||||
}{
|
||||
{"ABS_X", evdev.ABS_X},
|
||||
{"0x01", evdev.ABS_Y},
|
||||
{"x", evdev.ABS_X},
|
||||
}
|
||||
|
||||
for _, tc := range codeTestCases {
|
||||
t.Run(fmt.Sprintf("KeyCode %s", tc.input), func() {
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = tc.input
|
||||
rule, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(tc.output, rule.Axis)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Invalid code", func() {
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = "foo"
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Invalid deadzone", func() {
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = "x"
|
||||
config.DeadzoneEnd = 100
|
||||
config.DeadzoneStart = 1000
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
relDeadzoneTestCases := []struct {
|
||||
inCenter int32
|
||||
inSize int32
|
||||
outStart int32
|
||||
outEnd int32
|
||||
}{
|
||||
{5000, 1000, 4500, 5500},
|
||||
{0, 500, 0, 500},
|
||||
{10000, 500, 9500, 10000},
|
||||
}
|
||||
|
||||
for _, tc := range relDeadzoneTestCases {
|
||||
t.Run(fmt.Sprintf("Relative Deadzone %d +- %d", tc.inCenter, tc.inSize), func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: tc.inCenter,
|
||||
DeadzoneSize: tc.inSize,
|
||||
}
|
||||
rule, err := makeRuleTargetAxis(config, t.devs)
|
||||
|
||||
t.Nil(err)
|
||||
t.Equal(tc.outStart, rule.DeadzoneStart)
|
||||
t.Equal(tc.outEnd, rule.DeadzoneEnd)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Deadzone center/size invalid center", func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: 20000,
|
||||
DeadzoneSize: 500,
|
||||
}
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
relDeadzonePercentTestCases := []struct {
|
||||
inCenter int32
|
||||
inSizePercent int32
|
||||
outStart int32
|
||||
outEnd int32
|
||||
}{
|
||||
{5000, 10, 4500, 5500},
|
||||
{0, 10, 0, 1000},
|
||||
{10000, 10, 9000, 10000},
|
||||
}
|
||||
|
||||
for _, tc := range relDeadzonePercentTestCases {
|
||||
t.Run(fmt.Sprintf("Relative percent deadzone %d +- %d%%", tc.inCenter, tc.inSizePercent), func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: tc.inCenter,
|
||||
DeadzoneSizePercent: tc.inSizePercent,
|
||||
}
|
||||
rule, err := makeRuleTargetAxis(config, t.devs)
|
||||
|
||||
t.Nil(err)
|
||||
t.Equal(tc.outStart, rule.DeadzoneStart)
|
||||
t.Equal(tc.outEnd, rule.DeadzoneEnd)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Deadzone center/percent invalid center", func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: 20000,
|
||||
DeadzoneSizePercent: 10,
|
||||
}
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() {
|
||||
config := RuleTargetConfigRelaxis{Device: "test"}
|
||||
|
||||
t.Run("Standard keycode", func() {
|
||||
config.Axis = "REL_WHEEL"
|
||||
rule, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.REL_WHEEL, rule.Axis)
|
||||
})
|
||||
|
||||
t.Run("Hex keycode", func() {
|
||||
config.Axis = "0x00"
|
||||
rule, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.REL_X, rule.Axis)
|
||||
})
|
||||
|
||||
t.Run("Un-prefixed keycode", func() {
|
||||
config.Axis = "wheel"
|
||||
rule, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.REL_WHEEL, rule.Axis)
|
||||
})
|
||||
|
||||
t.Run("Invalid keycode", func() {
|
||||
config.Axis = "foo"
|
||||
_, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Incorrect axis type", func() {
|
||||
config.Axis = "ABS_X"
|
||||
_, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
// TODO: At some point it would *very likely* make sense to map each rule to all of the physical devices that can
|
||||
// trigger it, and return that instead. Something like a map[Device][]mappingrule.MappingRule.
|
||||
// This would speed up rule matching by only checking relevant rules for a given input event.
|
||||
// We could take this further and make it a map[<struct of *inputdevice, type, and code>][]rule
|
||||
// For very large rule-bases this may be helpful for staying performant.
|
||||
func InitRules(config []RuleConfig, pInputDevs map[string]*evdev.InputDevice, vInputDevs map[string]*evdev.InputDevice, modes []string) []mappingrules.MappingRule {
|
||||
rules := make([]mappingrules.MappingRule, 0)
|
||||
|
||||
// Golang can't inspect the concrete map type to determine interface conformance,
|
||||
// so we handle that here.
|
||||
pDevs := make(map[string]Device)
|
||||
for name, dev := range pInputDevs {
|
||||
pDevs[name] = dev
|
||||
}
|
||||
vDevs := make(map[string]Device)
|
||||
for name, dev := range vInputDevs {
|
||||
vDevs[name] = dev
|
||||
}
|
||||
|
||||
for _, ruleConfig := range config {
|
||||
var newRule mappingrules.MappingRule
|
||||
var err error
|
||||
|
||||
if ok := validateModes(ruleConfig.Modes, modes); !ok {
|
||||
logger.Logf("Skipping rule '%s', mode list specifies undefined mode.", ruleConfig.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
base := mappingrules.NewMappingRuleBase(ruleConfig.Name, ruleConfig.Modes)
|
||||
|
||||
switch strings.ToLower(ruleConfig.Type) {
|
||||
case RuleTypeButton:
|
||||
newRule, err = makeMappingRuleButton(ruleConfig.Config.(RuleConfigButton), pDevs, vDevs, base)
|
||||
case RuleTypeButtonCombo:
|
||||
newRule, err = makeMappingRuleCombo(ruleConfig.Config.(RuleConfigButtonCombo), pDevs, vDevs, base)
|
||||
case RuleTypeButtonLatched:
|
||||
newRule, err = makeMappingRuleLatched(ruleConfig.Config.(RuleConfigButtonLatched), pDevs, vDevs, base)
|
||||
case RuleTypeAxis:
|
||||
newRule, err = makeMappingRuleAxis(ruleConfig.Config.(RuleConfigAxis), pDevs, vDevs, base)
|
||||
case RuleTypeAxisCombined:
|
||||
newRule, err = makeMappingRuleAxisCombined(ruleConfig.Config.(RuleConfigAxisCombined), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToButton:
|
||||
newRule, err = makeMappingRuleAxisToButton(ruleConfig.Config.(RuleConfigAxisToButton), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToRelaxis:
|
||||
newRule, err = makeMappingRuleAxisToRelaxis(ruleConfig.Config.(RuleConfigAxisToRelaxis), pDevs, vDevs, base)
|
||||
case RuleTypeModeSelect:
|
||||
newRule, err = makeMappingRuleModeSelect(ruleConfig.Config.(RuleConfigModeSelect), pDevs, modes, base)
|
||||
default:
|
||||
err = fmt.Errorf("bad rule type '%s' for rule '%s'", ruleConfig.Type, ruleConfig.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.LogErrorf(err, "Failed to build rule '%s'", ruleConfig.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, newRule)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
// TODO: how much of these functions could we fold into the unmarshaling logic itself? The main problem
|
||||
// is that we don't have access to the device maps in those functions... could we set device names
|
||||
// as stand-ins and do a post-processing pass that *just* handles device linking and possibly mode
|
||||
// checking?
|
||||
//
|
||||
// In other words - can we unmarshal the config directly into our target structs and remove most of
|
||||
// this library?
|
||||
func makeMappingRuleButton(ruleConfig RuleConfigButton,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButton, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleButton(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleCombo(ruleConfig RuleConfigButtonCombo,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButtonCombo, error) {
|
||||
|
||||
inputs := make([]*mappingrules.RuleTargetButton, 0)
|
||||
for _, inputConfig := range ruleConfig.Inputs {
|
||||
input, err := makeRuleTargetButton(inputConfig, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputs = append(inputs, input)
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleButtonCombo(base, inputs, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleLatched(ruleConfig RuleConfigButtonLatched,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButtonLatched, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleButtonLatched(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxis(ruleConfig RuleConfigAxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxis, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetAxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxis(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisCombined(ruleConfig RuleConfigAxisCombined,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisCombined, error) {
|
||||
|
||||
inputLower, err := makeRuleTargetAxis(ruleConfig.InputLower, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputUpper, err := makeRuleTargetAxis(ruleConfig.InputUpper, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetAxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxisCombined(base, inputLower, inputUpper, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisToButton(ruleConfig RuleConfigAxisToButton,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisToButton, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxisToButton(base, input, output, ruleConfig.RepeatRateMin, ruleConfig.RepeatRateMax), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisToRelaxis(ruleConfig RuleConfigAxisToRelaxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisToRelaxis, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetRelaxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxisToRelaxis(base,
|
||||
input, output,
|
||||
ruleConfig.RepeatRateMin,
|
||||
ruleConfig.RepeatRateMax,
|
||||
ruleConfig.Increment), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleModeSelect(ruleConfig RuleConfigModeSelect,
|
||||
pDevs map[string]Device,
|
||||
modes []string,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleModeSelect, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetModeSelect(ruleConfig.Output, modes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleModeSelect(base, input, output), nil
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import "slices"
|
||||
|
||||
// validateModes checks the provided modes against a larger subset of modes (usually all defined ones)
|
||||
// and returns false if any of the modes are not defined.
|
||||
func validateModes(modes []string, allModes []string) bool {
|
||||
if len(modes) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
if !slices.Contains(allModes, mode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue