diff --git a/cmd/joyful/config.go b/cmd/joyful/config.go index 997b0fc..2b43380 100644 --- a/cmd/joyful/config.go +++ b/cmd/joyful/config.go @@ -93,6 +93,11 @@ func initVirtualBuffers(config *configparser.Config) (map[string]*evdev.InputDev return vDevicesByName, vBuffersByName, vBuffersByDevice } +// 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[][]rule +// For very large rule-bases this may be helpful for staying performant. func loadRules( config *configparser.Config, pDevices map[string]*evdev.InputDevice, @@ -103,8 +108,21 @@ func loadRules( eventChannel := make(chan ChannelEvent, 1000) ctx, cancel := context.WithCancel(context.Background()) + // Setup device mapping for the mappingrules package + pDevs := mappingrules.ConvertDeviceMap(pDevices) + vDevs := mappingrules.ConvertDeviceMap(vDevices) + // Initialize rules - rules := configparser.InitRules(config.Rules, pDevices, vDevices, modes) + rules := make([]mappingrules.MappingRule, 0) + for _, ruleConfig := range config.Rules { + newRule, err := mappingrules.NewRule(ruleConfig, pDevs, vDevs, modes) + if err != nil { + logger.LogError(err, "Failed to create rule, skipping") + continue + } + rules = append(rules, newRule) + } + logger.Logf("Created %d mapping rules.", len(rules)) // start listening for events on devices and timers diff --git a/internal/configparser/configparser.go b/internal/configparser/configparser.go index 864551e..3daa217 100644 --- a/internal/configparser/configparser.go +++ b/internal/configparser/configparser.go @@ -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() -// 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) diff --git a/internal/configparser/devices.go b/internal/configparser/devices.go deleted file mode 100644 index 23029ff..0000000 --- a/internal/configparser/devices.go +++ /dev/null @@ -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 -} diff --git a/internal/configparser/interfaces.go b/internal/configparser/interfaces.go deleted file mode 100644 index 9ebe1b1..0000000 --- a/internal/configparser/interfaces.go +++ /dev/null @@ -1,7 +0,0 @@ -package configparser - -import "github.com/holoplot/go-evdev" - -type Device interface { - AbsInfos() (map[evdev.EvCode]evdev.AbsInfo, error) -} diff --git a/internal/configparser/make_rules.go b/internal/configparser/make_rules.go deleted file mode 100644 index bffd94b..0000000 --- a/internal/configparser/make_rules.go +++ /dev/null @@ -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[][]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 -} diff --git a/internal/configparser/modes.go b/internal/configparser/modes.go deleted file mode 100644 index 4888be0..0000000 --- a/internal/configparser/modes.go +++ /dev/null @@ -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 -} diff --git a/internal/configparser/make_rule_targets.go b/internal/mappingrules/init_rule_targets.go similarity index 69% rename from internal/configparser/make_rule_targets.go rename to internal/mappingrules/init_rule_targets.go index f86473c..2e26276 100644 --- a/internal/configparser/make_rule_targets.go +++ b/internal/mappingrules/init_rule_targets.go @@ -1,15 +1,16 @@ -package configparser +package mappingrules import ( "errors" "fmt" + "slices" + "git.annabunches.net/annabunches/joyful/internal/configparser" "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) { +func makeRuleTargetButton(targetConfig configparser.RuleTargetConfigButton, devs map[string]Device) (*RuleTargetButton, error) { device, ok := devs[targetConfig.Device] if !ok { return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) @@ -20,7 +21,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfigButton, devs map[string]D return nil, err } - return mappingrules.NewRuleTargetButton( + return NewRuleTargetButton( targetConfig.Device, device, eventCode, @@ -28,7 +29,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfigButton, devs map[string]D ) } -func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Device) (*mappingrules.RuleTargetAxis, error) { +func makeRuleTargetAxis(targetConfig configparser.RuleTargetConfigAxis, devs map[string]Device) (*RuleTargetAxis, error) { device, ok := devs[targetConfig.Device] if !ok { return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) @@ -48,7 +49,7 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Devic return nil, err } - return mappingrules.NewRuleTargetAxis( + return NewRuleTargetAxis( targetConfig.Device, device, eventCode, @@ -58,7 +59,7 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Devic ) } -func makeRuleTargetRelaxis(targetConfig RuleTargetConfigRelaxis, devs map[string]Device) (*mappingrules.RuleTargetRelaxis, error) { +func makeRuleTargetRelaxis(targetConfig configparser.RuleTargetConfigRelaxis, devs map[string]Device) (*RuleTargetRelaxis, error) { device, ok := devs[targetConfig.Device] if !ok { return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) @@ -69,25 +70,23 @@ func makeRuleTargetRelaxis(targetConfig RuleTargetConfigRelaxis, devs map[string return nil, err } - return mappingrules.NewRuleTargetRelaxis( + return NewRuleTargetRelaxis( targetConfig.Device, device, eventCode, ) } -func makeRuleTargetModeSelect(targetConfig RuleTargetConfigModeSelect, allModes []string) (*mappingrules.RuleTargetModeSelect, error) { +func makeRuleTargetModeSelect(targetConfig configparser.RuleTargetConfigModeSelect, allModes []string) (*RuleTargetModeSelect, error) { if ok := validateModes(targetConfig.Modes, allModes); !ok { return nil, errors.New("undefined mode in mode select list") } - return mappingrules.NewRuleTargetModeSelect(targetConfig.Modes) + return 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) { +func calculateDeadzones(targetConfig configparser.RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) { var deadzoneStart, deadzoneEnd int32 deadzoneStart = 0 @@ -101,8 +100,8 @@ func calculateDeadzones(targetConfig RuleTargetConfigAxis, device Device, axis e absInfoMap, err := device.AbsInfos() if err != nil { - min = mappingrules.AxisValueMin - max = mappingrules.AxisValueMax + min = AxisValueMin + max = AxisValueMax } else { absInfo := absInfoMap[axis] min = absInfo.Minimum @@ -139,3 +138,19 @@ func clampAndShift(start, end, min, max int32) (int32, int32) { return start, end } + +// 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 +} diff --git a/internal/configparser/make_rule_targets_test.go b/internal/mappingrules/init_rule_targets_test.go similarity index 89% rename from internal/configparser/make_rule_targets_test.go rename to internal/mappingrules/init_rule_targets_test.go index 30fa4ae..2aa85b4 100644 --- a/internal/configparser/make_rule_targets_test.go +++ b/internal/mappingrules/init_rule_targets_test.go @@ -1,9 +1,10 @@ -package configparser +package mappingrules import ( "fmt" "testing" + "git.annabunches.net/annabunches/joyful/internal/configparser" "github.com/holoplot/go-evdev" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -48,7 +49,7 @@ func (t *MakeRuleTargetsTests) SetupSuite() { } func (t *MakeRuleTargetsTests) TestMakeRuleTargetButton() { - config := RuleTargetConfigButton{Device: "test"} + config := configparser.RuleTargetConfigButton{Device: "test"} t.Run("Standard keycode", func() { config.Button = "BTN_TRIGGER" @@ -103,7 +104,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { for _, tc := range codeTestCases { t.Run(fmt.Sprintf("KeyCode %s", tc.input), func() { - config := RuleTargetConfigAxis{Device: "test"} + config := configparser.RuleTargetConfigAxis{Device: "test"} config.Axis = tc.input rule, err := makeRuleTargetAxis(config, t.devs) t.Nil(err) @@ -113,14 +114,14 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { } t.Run("Invalid code", func() { - config := RuleTargetConfigAxis{Device: "test"} + config := configparser.RuleTargetConfigAxis{Device: "test"} config.Axis = "foo" _, err := makeRuleTargetAxis(config, t.devs) t.NotNil(err) }) t.Run("Invalid deadzone", func() { - config := RuleTargetConfigAxis{Device: "test"} + config := configparser.RuleTargetConfigAxis{Device: "test"} config.Axis = "x" config.DeadzoneEnd = 100 config.DeadzoneStart = 1000 @@ -141,7 +142,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { for _, tc := range relDeadzoneTestCases { t.Run(fmt.Sprintf("Relative Deadzone %d +- %d", tc.inCenter, tc.inSize), func() { - config := RuleTargetConfigAxis{ + config := configparser.RuleTargetConfigAxis{ Device: "test", Axis: "x", DeadzoneCenter: tc.inCenter, @@ -156,7 +157,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { } t.Run("Deadzone center/size invalid center", func() { - config := RuleTargetConfigAxis{ + config := configparser.RuleTargetConfigAxis{ Device: "test", Axis: "x", DeadzoneCenter: 20000, @@ -179,7 +180,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { for _, tc := range relDeadzonePercentTestCases { t.Run(fmt.Sprintf("Relative percent deadzone %d +- %d%%", tc.inCenter, tc.inSizePercent), func() { - config := RuleTargetConfigAxis{ + config := configparser.RuleTargetConfigAxis{ Device: "test", Axis: "x", DeadzoneCenter: tc.inCenter, @@ -194,7 +195,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { } t.Run("Deadzone center/percent invalid center", func() { - config := RuleTargetConfigAxis{ + config := configparser.RuleTargetConfigAxis{ Device: "test", Axis: "x", DeadzoneCenter: 20000, @@ -206,7 +207,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() { } func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() { - config := RuleTargetConfigRelaxis{Device: "test"} + config := configparser.RuleTargetConfigRelaxis{Device: "test"} t.Run("Standard keycode", func() { config.Axis = "REL_WHEEL" diff --git a/internal/mappingrules/init_rules.go b/internal/mappingrules/init_rules.go new file mode 100644 index 0000000..189e335 --- /dev/null +++ b/internal/mappingrules/init_rules.go @@ -0,0 +1,62 @@ +package mappingrules + +import ( + "errors" + "fmt" + "strings" + + "git.annabunches.net/annabunches/joyful/internal/configparser" + "git.annabunches.net/annabunches/joyful/internal/logger" + "github.com/holoplot/go-evdev" +) + +func ConvertDeviceMap(inputDevs map[string]*evdev.InputDevice) map[string]Device { + // Golang can't inspect the concrete map type to determine interface conformance, + // so we handle that here. + devices := make(map[string]Device) + for name, dev := range inputDevs { + devices[name] = dev + } + return devices +} + +// NewRule parses a RuleConfig struct and creates and returns the appropriate rule type. +// You can remap a map[string]*evdev.InputDevice to our interface type with ConvertDeviceMap +func NewRule(config configparser.RuleConfig, pDevs map[string]Device, vDevs map[string]Device, modes []string) (MappingRule, error) { + var newRule MappingRule + var err error + + if !validateModes(config.Modes, modes) { + return nil, errors.New("mode list specifies undefined mode") + } + + base := NewMappingRuleBase(config.Name, config.Modes) + + switch strings.ToLower(config.Type) { + case RuleTypeButton: + newRule, err = NewMappingRuleButton(config.Config.(configparser.RuleConfigButton), pDevs, vDevs, base) + case RuleTypeButtonCombo: + newRule, err = NewMappingRuleButtonCombo(config.Config.(configparser.RuleConfigButtonCombo), pDevs, vDevs, base) + case RuleTypeButtonLatched: + newRule, err = NewMappingRuleButtonLatched(config.Config.(configparser.RuleConfigButtonLatched), pDevs, vDevs, base) + case RuleTypeAxis: + newRule, err = NewMappingRuleAxis(config.Config.(configparser.RuleConfigAxis), pDevs, vDevs, base) + case RuleTypeAxisCombined: + newRule, err = NewMappingRuleAxisCombined(config.Config.(configparser.RuleConfigAxisCombined), pDevs, vDevs, base) + case RuleTypeAxisToButton: + newRule, err = NewMappingRuleAxisToButton(config.Config.(configparser.RuleConfigAxisToButton), pDevs, vDevs, base) + case RuleTypeAxisToRelaxis: + newRule, err = NewMappingRuleAxisToRelaxis(config.Config.(configparser.RuleConfigAxisToRelaxis), pDevs, vDevs, base) + case RuleTypeModeSelect: + newRule, err = NewMappingRuleModeSelect(config.Config.(configparser.RuleConfigModeSelect), pDevs, modes, base) + default: + err = fmt.Errorf("bad rule type '%s' for rule '%s'", config.Type, config.Name) + } + + if err != nil { + logger.LogErrorf(err, "Failed to build rule '%s'", config.Name) + return nil, err + } + + return newRule, nil +} diff --git a/internal/mappingrules/mapping_rule_axis.go b/internal/mappingrules/mapping_rule_axis.go index a2ab41d..fb2d95c 100644 --- a/internal/mappingrules/mapping_rule_axis.go +++ b/internal/mappingrules/mapping_rule_axis.go @@ -1,6 +1,9 @@ package mappingrules -import "github.com/holoplot/go-evdev" +import ( + "git.annabunches.net/annabunches/joyful/internal/configparser" + "github.com/holoplot/go-evdev" +) // A Simple Mapping Rule can map a button to a button or an axis to an axis. type MappingRuleAxis struct { @@ -9,12 +12,26 @@ type MappingRuleAxis struct { Output *RuleTargetAxis } -func NewMappingRuleAxis(base MappingRuleBase, input *RuleTargetAxis, output *RuleTargetAxis) *MappingRuleAxis { +func NewMappingRuleAxis(ruleConfig configparser.RuleConfigAxis, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*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 &MappingRuleAxis{ MappingRuleBase: base, Input: input, Output: output, - } + }, nil } func (rule *MappingRuleAxis) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { diff --git a/internal/mappingrules/mapping_rule_axis_combined.go b/internal/mappingrules/mapping_rule_axis_combined.go index 36562b8..9ff0ccc 100644 --- a/internal/mappingrules/mapping_rule_axis_combined.go +++ b/internal/mappingrules/mapping_rule_axis_combined.go @@ -1,6 +1,7 @@ package mappingrules import ( + "git.annabunches.net/annabunches/joyful/internal/configparser" "git.annabunches.net/annabunches/joyful/internal/logger" "github.com/holoplot/go-evdev" ) @@ -12,7 +13,26 @@ type MappingRuleAxisCombined struct { Output *RuleTargetAxis } -func NewMappingRuleAxisCombined(base MappingRuleBase, inputLower *RuleTargetAxis, inputUpper *RuleTargetAxis, output *RuleTargetAxis) *MappingRuleAxisCombined { +func NewMappingRuleAxisCombined(ruleConfig configparser.RuleConfigAxisCombined, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*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 + } + inputLower.OutputMax = 0 inputUpper.OutputMin = 0 return &MappingRuleAxisCombined{ @@ -20,7 +40,7 @@ func NewMappingRuleAxisCombined(base MappingRuleBase, inputLower *RuleTargetAxis InputLower: inputLower, InputUpper: inputUpper, Output: output, - } + }, nil } func (rule *MappingRuleAxisCombined) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { diff --git a/internal/mappingrules/mapping_rule_axis_combined_test.go b/internal/mappingrules/mapping_rule_axis_combined_test.go index 631d7a0..7914a50 100644 --- a/internal/mappingrules/mapping_rule_axis_combined_test.go +++ b/internal/mappingrules/mapping_rule_axis_combined_test.go @@ -38,7 +38,9 @@ func (t *MappingRuleAxisCombinedTests) SetupTest() { }, nil) t.inputTargetLower, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, true, 0, 0) + t.inputTargetLower.OutputMax = 0 t.inputTargetUpper, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_Y, false, 0, 0) + t.inputTargetUpper.OutputMin = 0 t.outputDevice = &evdev.InputDevice{} t.outputTarget, _ = NewRuleTargetAxis("test-output", t.outputDevice, evdev.ABS_X, false, 0, 0) @@ -63,13 +65,23 @@ func (t *MappingRuleAxisCombinedTests) TestNewMappingRuleAxisCombined() { evdev.ABS_Y: {Minimum: 0, Maximum: 10000}, }, nil) - rule := NewMappingRuleAxisCombined(t.base, t.inputTargetLower, t.inputTargetUpper, t.outputTarget) + rule := &MappingRuleAxisCombined{ + MappingRuleBase: t.base, + InputLower: t.inputTargetLower, + InputUpper: t.inputTargetUpper, + Output: t.outputTarget, + } t.EqualValues(0, rule.InputLower.OutputMax) t.EqualValues(0, rule.InputUpper.OutputMin) } func (t *MappingRuleAxisCombinedTests) TestMatchEvent() { - rule := NewMappingRuleAxisCombined(t.base, t.inputTargetLower, t.inputTargetUpper, t.outputTarget) + rule := &MappingRuleAxisCombined{ + MappingRuleBase: t.base, + InputLower: t.inputTargetLower, + InputUpper: t.inputTargetUpper, + Output: t.outputTarget, + } t.Run("Lower Input", func() { testCases := []struct{ in, out int32 }{ diff --git a/internal/mappingrules/mapping_rule_axis_to_button.go b/internal/mappingrules/mapping_rule_axis_to_button.go index 3356dbe..72ab081 100644 --- a/internal/mappingrules/mapping_rule_axis_to_button.go +++ b/internal/mappingrules/mapping_rule_axis_to_button.go @@ -3,6 +3,7 @@ package mappingrules import ( "time" + "git.annabunches.net/annabunches/joyful/internal/configparser" "github.com/holoplot/go-evdev" "github.com/jonboulle/clockwork" ) @@ -23,20 +24,34 @@ type MappingRuleAxisToButton struct { clock clockwork.Clock } -func NewMappingRuleAxisToButton(base MappingRuleBase, input *RuleTargetAxis, output *RuleTargetButton, repeatRateMin, repeatRateMax int) *MappingRuleAxisToButton { +func NewMappingRuleAxisToButton(ruleConfig configparser.RuleConfigAxisToButton, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*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 &MappingRuleAxisToButton{ MappingRuleBase: base, Input: input, Output: output, - RepeatRateMin: repeatRateMin, - RepeatRateMax: repeatRateMax, + RepeatRateMin: ruleConfig.RepeatRateMin, + RepeatRateMax: ruleConfig.RepeatRateMax, lastEvent: time.Now(), nextEvent: NoNextEvent, - repeat: repeatRateMin != 0 && repeatRateMax != 0, + repeat: ruleConfig.RepeatRateMin != 0 && ruleConfig.RepeatRateMax != 0, pressed: false, active: false, clock: clockwork.NewRealClock(), - } + }, nil } func (rule *MappingRuleAxisToButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { diff --git a/internal/mappingrules/mapping_rule_axis_to_button_test.go b/internal/mappingrules/mapping_rule_axis_to_button_test.go index 976506c..0da086a 100644 --- a/internal/mappingrules/mapping_rule_axis_to_button_test.go +++ b/internal/mappingrules/mapping_rule_axis_to_button_test.go @@ -19,6 +19,44 @@ type MappingRuleAxisToButtonTests struct { base MappingRuleBase } +func TestRunnerMappingRuleAxisToButtonTests(t *testing.T) { + suite.Run(t, new(MappingRuleAxisToButtonTests)) +} + +// buildTimerRule creates a MappingRuleAxisToButton with a mocked clock +func (t *MappingRuleAxisToButtonTests) buildTimerRule( + repeatMin, + repeatMax int, + nextEvent time.Duration) (*MappingRuleAxisToButton, *clockwork.FakeClock) { + + mockClock := clockwork.NewFakeClock() + testRule := t.buildRule(repeatMin, repeatMax) + testRule.clock = mockClock + testRule.lastEvent = testRule.clock.Now() + testRule.nextEvent = nextEvent + if nextEvent != NoNextEvent { + testRule.active = true + } + return testRule, mockClock +} + +// Todo: don't love this repeated logic... +func (t *MappingRuleAxisToButtonTests) buildRule(repeatMin, repeatMax int) *MappingRuleAxisToButton { + return &MappingRuleAxisToButton{ + MappingRuleBase: t.base, + Input: t.inputRule, + Output: t.outputRule, + RepeatRateMin: repeatMin, + RepeatRateMax: repeatMax, + lastEvent: time.Now(), + nextEvent: NoNextEvent, + repeat: repeatMin != 0 && repeatMax != 0, + pressed: false, + active: false, + clock: clockwork.NewRealClock(), + } +} + func (t *MappingRuleAxisToButtonTests) SetupTest() { mode := "*" t.mode = &mode @@ -40,7 +78,7 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() { // A valid input should set a nextevent t.Run("No Repeat", func() { - testRule := NewMappingRuleAxisToButton(t.base, t.inputRule, t.outputRule, 0, 0) + testRule := t.buildRule(0, 0) t.Run("Valid Input", func() { testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{ @@ -62,7 +100,7 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() { }) t.Run("Repeat", func() { - testRule := NewMappingRuleAxisToButton(t.base, t.inputRule, t.outputRule, 750, 250) + testRule := t.buildRule(750, 250) testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{ Type: evdev.EV_ABS, Code: evdev.ABS_X, @@ -90,7 +128,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { t.Run("No Repeat", func() { // Get event if called immediately t.Run("Event is available immediately", func() { - testRule, _ := buildTimerRule(t, 0, 0, 0) + testRule, _ := t.buildTimerRule(0, 0, 0) event := testRule.TimerEvent() @@ -100,7 +138,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { // Off event on second call t.Run("Event emits off on second call", func() { - testRule, _ := buildTimerRule(t, 0, 0, 0) + testRule, _ := t.buildTimerRule(0, 0, 0) testRule.TimerEvent() event := testRule.TimerEvent() @@ -111,7 +149,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { // No further event, even if we wait a while t.Run("Additional events are not emitted while still active.", func() { - testRule, mockClock := buildTimerRule(t, 0, 0, 0) + testRule, mockClock := t.buildTimerRule(0, 0, 0) testRule.TimerEvent() testRule.TimerEvent() @@ -125,13 +163,13 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { t.Run("Repeat", func() { t.Run("No event if called immediately", func() { - testRule, _ := buildTimerRule(t, 100, 10, 50*time.Millisecond) + testRule, _ := t.buildTimerRule(100, 10, 50*time.Millisecond) event := testRule.TimerEvent() t.Nil(event) }) t.Run("No event after 49ms", func() { - testRule, mockClock := buildTimerRule(t, 100, 10, 50*time.Millisecond) + testRule, mockClock := t.buildTimerRule(100, 10, 50*time.Millisecond) mockClock.Advance(49 * time.Millisecond) event := testRule.TimerEvent() @@ -140,7 +178,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { }) t.Run("Event after 50ms", func() { - testRule, mockClock := buildTimerRule(t, 100, 10, 50*time.Millisecond) + testRule, mockClock := t.buildTimerRule(100, 10, 50*time.Millisecond) mockClock.Advance(50 * time.Millisecond) event := testRule.TimerEvent() @@ -150,7 +188,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { }) t.Run("Additional event at 100ms", func() { - testRule, mockClock := buildTimerRule(t, 100, 10, 50*time.Millisecond) + testRule, mockClock := t.buildTimerRule(100, 10, 50*time.Millisecond) mockClock.Advance(50 * time.Millisecond) testRule.TimerEvent() @@ -163,24 +201,3 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() { }) }) } - -func TestRunnerMappingRuleAxisToButtonTests(t *testing.T) { - suite.Run(t, new(MappingRuleAxisToButtonTests)) -} - -// buildTimerRule creates a MappingRuleAxisToButton with a mocked clock -func buildTimerRule(t *MappingRuleAxisToButtonTests, - repeatMin, - repeatMax int, - nextEvent time.Duration) (*MappingRuleAxisToButton, *clockwork.FakeClock) { - - mockClock := clockwork.NewFakeClock() - testRule := NewMappingRuleAxisToButton(t.base, t.inputRule, t.outputRule, repeatMin, repeatMax) - testRule.clock = mockClock - testRule.lastEvent = testRule.clock.Now() - testRule.nextEvent = nextEvent - if nextEvent != NoNextEvent { - testRule.active = true - } - return testRule, mockClock -} diff --git a/internal/mappingrules/mapping_rule_axis_to_relaxis.go b/internal/mappingrules/mapping_rule_axis_to_relaxis.go index 153b992..e07afd9 100644 --- a/internal/mappingrules/mapping_rule_axis_to_relaxis.go +++ b/internal/mappingrules/mapping_rule_axis_to_relaxis.go @@ -3,6 +3,7 @@ package mappingrules import ( "time" + "git.annabunches.net/annabunches/joyful/internal/configparser" "github.com/holoplot/go-evdev" "github.com/jonboulle/clockwork" ) @@ -23,23 +24,32 @@ type MappingRuleAxisToRelaxis struct { clock clockwork.Clock } -func NewMappingRuleAxisToRelaxis( - base MappingRuleBase, - input *RuleTargetAxis, - output *RuleTargetRelaxis, - repeatRateMin, repeatRateMax, increment int) *MappingRuleAxisToRelaxis { +func NewMappingRuleAxisToRelaxis(ruleConfig configparser.RuleConfigAxisToRelaxis, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*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 &MappingRuleAxisToRelaxis{ MappingRuleBase: base, Input: input, Output: output, - RepeatRateMin: repeatRateMin, - RepeatRateMax: repeatRateMax, - Increment: int32(increment), + RepeatRateMin: ruleConfig.RepeatRateMin, + RepeatRateMax: ruleConfig.RepeatRateMax, + Increment: int32(ruleConfig.Increment), lastEvent: time.Now(), nextEvent: NoNextEvent, clock: clockwork.NewRealClock(), - } + }, nil } func (rule *MappingRuleAxisToRelaxis) MatchEvent( diff --git a/internal/mappingrules/mapping_rule_button.go b/internal/mappingrules/mapping_rule_button.go index 69a7cfe..4781191 100644 --- a/internal/mappingrules/mapping_rule_button.go +++ b/internal/mappingrules/mapping_rule_button.go @@ -1,6 +1,9 @@ package mappingrules -import "github.com/holoplot/go-evdev" +import ( + "git.annabunches.net/annabunches/joyful/internal/configparser" + "github.com/holoplot/go-evdev" +) // A Simple Mapping Rule can map a button to a button or an axis to an axis. type MappingRuleButton struct { @@ -9,16 +12,26 @@ type MappingRuleButton struct { Output *RuleTargetButton } -func NewMappingRuleButton( - base MappingRuleBase, - input *RuleTargetButton, - output *RuleTargetButton) *MappingRuleButton { +func NewMappingRuleButton(ruleConfig configparser.RuleConfigButton, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*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 &MappingRuleButton{ MappingRuleBase: base, Input: input, Output: output, - } + }, nil } func (rule *MappingRuleButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { diff --git a/internal/mappingrules/mapping_rule_button_combo.go b/internal/mappingrules/mapping_rule_button_combo.go index a7b7c23..62c7fe4 100644 --- a/internal/mappingrules/mapping_rule_button_combo.go +++ b/internal/mappingrules/mapping_rule_button_combo.go @@ -1,6 +1,9 @@ package mappingrules -import "github.com/holoplot/go-evdev" +import ( + "git.annabunches.net/annabunches/joyful/internal/configparser" + "github.com/holoplot/go-evdev" +) // A Combo Mapping Rule can require multiple physical button presses for a single output button type MappingRuleButtonCombo struct { @@ -10,17 +13,31 @@ type MappingRuleButtonCombo struct { State int } -func NewMappingRuleButtonCombo( - base MappingRuleBase, - inputs []*RuleTargetButton, - output *RuleTargetButton) *MappingRuleButtonCombo { +func NewMappingRuleButtonCombo(ruleConfig configparser.RuleConfigButtonCombo, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*MappingRuleButtonCombo, error) { + + inputs := make([]*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 &MappingRuleButtonCombo{ MappingRuleBase: base, Inputs: inputs, Output: output, State: 0, - } + }, nil } func (rule *MappingRuleButtonCombo) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { diff --git a/internal/mappingrules/mapping_rule_button_latched.go b/internal/mappingrules/mapping_rule_button_latched.go index d8e5bec..aeabef4 100644 --- a/internal/mappingrules/mapping_rule_button_latched.go +++ b/internal/mappingrules/mapping_rule_button_latched.go @@ -1,6 +1,9 @@ package mappingrules -import "github.com/holoplot/go-evdev" +import ( + "git.annabunches.net/annabunches/joyful/internal/configparser" + "github.com/holoplot/go-evdev" +) type MappingRuleButtonLatched struct { MappingRuleBase @@ -9,17 +12,27 @@ type MappingRuleButtonLatched struct { State bool } -func NewMappingRuleButtonLatched( - base MappingRuleBase, - input *RuleTargetButton, - output *RuleTargetButton) *MappingRuleButtonLatched { +func NewMappingRuleButtonLatched(ruleConfig configparser.RuleConfigButtonLatched, + pDevs map[string]Device, + vDevs map[string]Device, + base MappingRuleBase) (*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 &MappingRuleButtonLatched{ MappingRuleBase: base, Input: input, Output: output, State: false, - } + }, nil } func (rule *MappingRuleButtonLatched) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { diff --git a/internal/mappingrules/mapping_rule_button_test.go b/internal/mappingrules/mapping_rule_button_test.go index 28fba1b..740c1ce 100644 --- a/internal/mappingrules/mapping_rule_button_test.go +++ b/internal/mappingrules/mapping_rule_button_test.go @@ -28,7 +28,11 @@ func (t *MappingRuleButtonTests) SetupTest() { func (t *MappingRuleButtonTests) TestMatchEvent() { inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, false) outputButton, _ := NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false) - testRule := NewMappingRuleButton(t.base, inputButton, outputButton) + testRule := &MappingRuleButton{ + MappingRuleBase: t.base, + Input: inputButton, + Output: outputButton, + } // A matching input event should produce an output event expected := &evdev.InputEvent{ @@ -58,7 +62,11 @@ func (t *MappingRuleButtonTests) TestMatchEvent() { func (t *MappingRuleButtonTests) TestMatchEventInverted() { inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, true) outputButton, _ := NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false) - testRule := NewMappingRuleButton(t.base, inputButton, outputButton) + testRule := &MappingRuleButton{ + MappingRuleBase: t.base, + Input: inputButton, + Output: outputButton, + } // A matching input event should produce an output event expected := &evdev.InputEvent{ diff --git a/internal/mappingrules/mapping_rule_mode_select.go b/internal/mappingrules/mapping_rule_mode_select.go index 69afd0b..2ed793f 100644 --- a/internal/mappingrules/mapping_rule_mode_select.go +++ b/internal/mappingrules/mapping_rule_mode_select.go @@ -1,6 +1,9 @@ package mappingrules -import "github.com/holoplot/go-evdev" +import ( + "git.annabunches.net/annabunches/joyful/internal/configparser" + "github.com/holoplot/go-evdev" +) type MappingRuleModeSelect struct { MappingRuleBase @@ -8,17 +11,26 @@ type MappingRuleModeSelect struct { Output *RuleTargetModeSelect } -func NewMappingRuleModeSelect( - base MappingRuleBase, - input *RuleTargetButton, - output *RuleTargetModeSelect, -) *MappingRuleModeSelect { +func NewMappingRuleModeSelect(ruleConfig configparser.RuleConfigModeSelect, + pDevs map[string]Device, + modes []string, + base MappingRuleBase) (*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 &MappingRuleModeSelect{ MappingRuleBase: base, Input: input, Output: output, - } + }, nil } func (rule *MappingRuleModeSelect) MatchEvent( diff --git a/internal/mappingrules/variables.go b/internal/mappingrules/variables.go new file mode 100644 index 0000000..d9a171b --- /dev/null +++ b/internal/mappingrules/variables.go @@ -0,0 +1,12 @@ +package mappingrules + +const ( + RuleTypeButton = "button" + RuleTypeButtonCombo = "button-combo" + RuleTypeButtonLatched = "button-latched" + RuleTypeAxis = "axis" + RuleTypeAxisCombined = "axis-combined" + RuleTypeAxisToButton = "axis-to-button" + RuleTypeAxisToRelaxis = "axis-to-relaxis" + RuleTypeModeSelect = "mode-select" +)