From a0949e719fc0417d690aa6fbb0529e3b2f7741da Mon Sep 17 00:00:00 2001 From: Anna Rose Wiggins Date: Sun, 6 Jul 2025 17:22:05 -0400 Subject: [PATCH] Start rulemapping refactor to be more explicit about typing intentions. --- cmd/joyful/main.go | 6 +- internal/config/make_rules.go | 81 +++++++++++++------ internal/mappingrules/interfaces.go | 7 +- internal/mappingrules/mapping_rule_base.go | 9 +-- internal/mappingrules/mapping_rule_button.go | 23 ++++++ ...le_test.go => mapping_rule_button_test.go} | 40 ++++----- internal/mappingrules/mapping_rule_combo.go | 15 ++-- internal/mappingrules/mapping_rule_latched.go | 17 ++-- .../mapping_rule_proportional_axis.go | 8 +- internal/mappingrules/mapping_rule_simple.go | 22 ----- 10 files changed, 128 insertions(+), 100 deletions(-) create mode 100644 internal/mappingrules/mapping_rule_button.go rename internal/mappingrules/{mapping_rule_simple_test.go => mapping_rule_button_test.go} (64%) delete mode 100644 internal/mappingrules/mapping_rule_simple.go diff --git a/cmd/joyful/main.go b/cmd/joyful/main.go index 42fd68c..5c6b373 100644 --- a/cmd/joyful/main.go +++ b/cmd/joyful/main.go @@ -103,11 +103,11 @@ func main() { case evdev.EV_KEY, evdev.EV_ABS: // We have a matchable event type. Check all the events for _, rule := range rules { - outputEvent := rule.MatchEvent(channelEvent.Device, channelEvent.Event, &mode) - if outputEvent == nil { + device, outputEvent := rule.MatchEvent(channelEvent.Device, channelEvent.Event, &mode) + if device == nil || outputEvent == nil { continue } - vBuffersByName[rule.OutputName()].AddEvent(outputEvent) + vBuffersByDevice[device].AddEvent(outputEvent) } } diff --git a/internal/config/make_rules.go b/internal/config/make_rules.go index cba1f99..2748bae 100644 --- a/internal/config/make_rules.go +++ b/internal/config/make_rules.go @@ -23,7 +23,7 @@ func (parser *ConfigParser) BuildRules(pDevs map[string]*evdev.InputDevice, vDev var newRule mappingrules.MappingRule var err error - baseParams, err := setBaseRuleParameters(ruleConfig, vDevs, modes) + baseParams, err := setBaseRuleParameters(ruleConfig, modes) if err != nil { logger.LogErrorf(err, "couldn't set output parameters, skipping rule '%s'", ruleConfig.Name) continue @@ -31,11 +31,11 @@ func (parser *ConfigParser) BuildRules(pDevs map[string]*evdev.InputDevice, vDev switch strings.ToLower(ruleConfig.Type) { case RuleTypeSimple: - newRule, err = makeSimpleRule(ruleConfig, pDevs, baseParams) + newRule, err = makeMappingRuleButton(ruleConfig, pDevs, vDevs, baseParams) case RuleTypeCombo: - newRule, err = makeComboRule(ruleConfig, pDevs, baseParams) + newRule, err = makeComboRule(ruleConfig, pDevs, vDevs, baseParams) case RuleTypeLatched: - newRule, err = makeLatchedRule(ruleConfig, pDevs, baseParams) + newRule, err = makeLatchedRule(ruleConfig, pDevs, vDevs, baseParams) } if err != nil { @@ -49,7 +49,7 @@ func (parser *ConfigParser) BuildRules(pDevs map[string]*evdev.InputDevice, vDev return rules } -func setBaseRuleParameters(ruleConfig RuleConfig, vDevs map[string]*evdev.InputDevice, modes []string) (mappingrules.MappingRuleBase, error) { +func setBaseRuleParameters(ruleConfig RuleConfig, modes []string) (mappingrules.MappingRuleBase, error) { // We perform this check here instead of in makeRuleTarget because only Output targets // can meaningfully have ModeSelect; this lets us avoid plumbing the modes in on every // makeRuleTarget call. @@ -60,12 +60,7 @@ func setBaseRuleParameters(ruleConfig RuleConfig, vDevs map[string]*evdev.InputD } } - output, err := makeRuleTarget(ruleConfig.Output, vDevs) - if err != nil { - return mappingrules.MappingRuleBase{}, err - } - - err = validateModes(ruleConfig.Modes, modes) + err := validateModes(ruleConfig.Modes, modes) if err != nil { return mappingrules.MappingRuleBase{}, err } @@ -73,28 +68,41 @@ func setBaseRuleParameters(ruleConfig RuleConfig, vDevs map[string]*evdev.InputD ruleModes := ensureModes(ruleConfig.Modes) return mappingrules.MappingRuleBase{ - Output: output, - Modes: ruleModes, - Name: ruleConfig.Name, + Modes: ruleModes, + Name: ruleConfig.Name, }, nil } -func makeSimpleRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleSimple, error) { +func makeMappingRuleButton(ruleConfig RuleConfig, + pDevs map[string]*evdev.InputDevice, + vDevs map[string]*evdev.InputDevice, + base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButton, error) { + input, err := makeRuleTarget(ruleConfig.Input, pDevs) if err != nil { return nil, err } - return &mappingrules.MappingRuleSimple{ + output, err := makeRuleTarget(ruleConfig.Output, vDevs) + if err != nil { + return nil, err + } + + return &mappingrules.MappingRuleButton{ MappingRuleBase: base, - Input: input, + Input: input.(*mappingrules.RuleTargetButton), + Output: output.(*mappingrules.RuleTargetButton), }, nil } -func makeComboRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleCombo, error) { - inputs := make([]mappingrules.RuleTarget, 0) +func makeComboRule(ruleConfig RuleConfig, + pDevs map[string]*evdev.InputDevice, + vDevs map[string]*evdev.InputDevice, + base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleCombo, error) { + + inputs := make([]*mappingrules.RuleTargetButton, 0) for _, inputConfig := range ruleConfig.Inputs { - input, err := makeRuleTarget(inputConfig, pDevs) + input, err := makeRuleTargetButton(inputConfig, pDevs) if err != nil { return nil, err } @@ -108,8 +116,12 @@ func makeComboRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, b }, nil } -func makeLatchedRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleLatched, error) { - input, err := makeRuleTarget(ruleConfig.Input, pDevs) +func makeLatchedRule(ruleConfig RuleConfig, + pDevs map[string]*evdev.InputDevice, + vDevs map[string]*evdev.InputDevice, + base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleLatched, error) { + + input, err := makeRuleTargetButton(ruleConfig.Input, pDevs) if err != nil { return nil, err } @@ -121,7 +133,30 @@ func makeLatchedRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, }, nil } -// makeInputRuleTarget takes an Input declaration from the YAML and returns a fully formed RuleTarget. +func makeRuleTargetButton(targetConfig RuleTargetConfig, devs map[string]*evdev.InputDevice) (*mappingrules.RuleTargetButton, error) { + device, ok := devs[targetConfig.Device] + if !ok { + return nil, fmt.Errorf("couldn't build rule due to non-existent device '%s'", targetConfig.Device) + } + + _, eventCode, err := decodeRuleTargetValues(targetConfig) + if err != nil { + return nil, err + } + + baseParams := mappingrules.RuleTargetBase{ + DeviceName: targetConfig.Device, + Device: device, + Inverted: targetConfig.Inverted, + Code: eventCode, + } + + return &mappingrules.RuleTargetButton{ + RuleTargetBase: baseParams, + }, nil +} + +// makeRuleTarget takes an Input declaration from the YAML and returns a fully formed RuleTarget. func makeRuleTarget(targetConfig RuleTargetConfig, devs map[string]*evdev.InputDevice) (mappingrules.RuleTarget, error) { if len(targetConfig.ModeSelect) > 0 { return &mappingrules.RuleTargetModeSelect{ diff --git a/internal/mappingrules/interfaces.go b/internal/mappingrules/interfaces.go index ef54fbf..802b484 100644 --- a/internal/mappingrules/interfaces.go +++ b/internal/mappingrules/interfaces.go @@ -3,8 +3,7 @@ package mappingrules import "github.com/holoplot/go-evdev" type MappingRule interface { - MatchEvent(*evdev.InputDevice, *evdev.InputEvent, *string) *evdev.InputEvent - OutputName() string + MatchEvent(*evdev.InputDevice, *evdev.InputEvent, *string) (*evdev.InputDevice, *evdev.InputEvent) } // RuleTargets represent either a device input to match on, or an output to produce. @@ -21,8 +20,4 @@ type RuleTarget interface { // // TODO: should we normalize inside this function to simplify the interface? CreateEvent(int32, *string) *evdev.InputEvent - - GetCode() evdev.EvCode - GetDeviceName() string - GetDevice() *evdev.InputDevice } diff --git a/internal/mappingrules/mapping_rule_base.go b/internal/mappingrules/mapping_rule_base.go index 72696b4..afdf4ee 100644 --- a/internal/mappingrules/mapping_rule_base.go +++ b/internal/mappingrules/mapping_rule_base.go @@ -3,13 +3,8 @@ package mappingrules import "slices" type MappingRuleBase struct { - Name string - Output RuleTarget - Modes []string -} - -func (rule *MappingRuleBase) OutputName() string { - return rule.Output.GetDeviceName() + Name string + Modes []string } func (rule *MappingRuleBase) modeCheck(mode *string) bool { diff --git a/internal/mappingrules/mapping_rule_button.go b/internal/mappingrules/mapping_rule_button.go new file mode 100644 index 0000000..57c123d --- /dev/null +++ b/internal/mappingrules/mapping_rule_button.go @@ -0,0 +1,23 @@ +package mappingrules + +import "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 { + MappingRuleBase + Input *RuleTargetButton + Output *RuleTargetButton +} + +func (rule *MappingRuleButton) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { + if !rule.MappingRuleBase.modeCheck(mode) { + return nil, nil + } + + if device != rule.Input.GetDevice() || + event.Code != rule.Input.GetCode() { + return nil, nil + } + + return rule.Output.Device, rule.Output.CreateEvent(rule.Input.NormalizeValue(event.Value), mode) +} diff --git a/internal/mappingrules/mapping_rule_simple_test.go b/internal/mappingrules/mapping_rule_button_test.go similarity index 64% rename from internal/mappingrules/mapping_rule_simple_test.go rename to internal/mappingrules/mapping_rule_button_test.go index b675a7b..df814ad 100644 --- a/internal/mappingrules/mapping_rule_simple_test.go +++ b/internal/mappingrules/mapping_rule_button_test.go @@ -7,17 +7,17 @@ import ( "github.com/stretchr/testify/suite" ) -type SimpleMappingRuleTests struct { +type MappingRuleButtonTests struct { suite.Suite inputDevice *evdev.InputDevice wrongInputDevice *evdev.InputDevice outputDevice *evdev.InputDevice mode *string - sampleRule *MappingRuleSimple - invertedRule *MappingRuleSimple + sampleRule *MappingRuleButton + invertedRule *MappingRuleButton } -func (t *SimpleMappingRuleTests) SetupTest() { +func (t *MappingRuleButtonTests) SetupTest() { t.inputDevice = &evdev.InputDevice{} t.wrongInputDevice = &evdev.InputDevice{} t.outputDevice = &evdev.InputDevice{} @@ -25,24 +25,24 @@ func (t *SimpleMappingRuleTests) SetupTest() { t.mode = &mode // TODO: implement a constructor function... - t.sampleRule = &MappingRuleSimple{ + t.sampleRule = &MappingRuleButton{ MappingRuleBase: MappingRuleBase{ - Output: NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false), - Modes: []string{"*"}, + Modes: []string{"*"}, }, - Input: NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, false), + Input: NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, false), + Output: NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false), } - t.invertedRule = &MappingRuleSimple{ + t.invertedRule = &MappingRuleButton{ MappingRuleBase: MappingRuleBase{ - Output: NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false), - Modes: []string{"*"}, + Modes: []string{"*"}, }, - Input: NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, true), + Output: NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false), + Input: NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, true), } } -func (t *SimpleMappingRuleTests) TestMatchEvent() { +func (t *MappingRuleButtonTests) TestMatchEvent() { // A matching input event should produce an output event correctOutput := &evdev.InputEvent{ Type: evdev.EV_KEY, @@ -50,25 +50,25 @@ func (t *SimpleMappingRuleTests) TestMatchEvent() { Value: 1, } - event := t.sampleRule.MatchEvent( + _, event := t.sampleRule.MatchEvent( t.inputDevice, &evdev.InputEvent{Code: evdev.BTN_TRIGGER, Value: 1}, t.mode) t.EqualValues(correctOutput, event) // An input event from the wrong device should produce a nil event - event = t.sampleRule.MatchEvent( + _, event = t.sampleRule.MatchEvent( t.wrongInputDevice, &evdev.InputEvent{Code: evdev.BTN_TRIGGER, Value: 1}, t.mode) t.Nil(event) // An input event from the wrong button should produce a nil event - event = t.sampleRule.MatchEvent( + _, event = t.sampleRule.MatchEvent( t.inputDevice, &evdev.InputEvent{Code: evdev.BTN_TOP, Value: 1}, t.mode) t.Nil(event) } -func (t *SimpleMappingRuleTests) TestMatchEventInverted() { +func (t *MappingRuleButtonTests) TestMatchEventInverted() { // A matching input event should produce an output event correctOutput := &evdev.InputEvent{ Type: evdev.EV_KEY, @@ -77,18 +77,18 @@ func (t *SimpleMappingRuleTests) TestMatchEventInverted() { // Should get the opposite value out that we send in correctOutput.Value = 0 - event := t.invertedRule.MatchEvent( + _, event := t.invertedRule.MatchEvent( t.inputDevice, &evdev.InputEvent{Code: evdev.BTN_TRIGGER, Value: 1}, t.mode) t.EqualValues(correctOutput, event) correctOutput.Value = 1 - event = t.invertedRule.MatchEvent( + _, event = t.invertedRule.MatchEvent( t.inputDevice, &evdev.InputEvent{Code: evdev.BTN_TRIGGER, Value: 0}, t.mode) t.EqualValues(correctOutput, event) } func TestRunnerMatching(t *testing.T) { - suite.Run(t, new(SimpleMappingRuleTests)) + suite.Run(t, new(MappingRuleButtonTests)) } diff --git a/internal/mappingrules/mapping_rule_combo.go b/internal/mappingrules/mapping_rule_combo.go index 017a2e7..d663fe5 100644 --- a/internal/mappingrules/mapping_rule_combo.go +++ b/internal/mappingrules/mapping_rule_combo.go @@ -5,13 +5,14 @@ import "github.com/holoplot/go-evdev" // A Combo Mapping Rule can require multiple physical button presses for a single output button type MappingRuleCombo struct { MappingRuleBase - Inputs []RuleTarget + Inputs []*RuleTargetButton + Output *RuleTargetButton State int } -func (rule *MappingRuleCombo) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent { +func (rule *MappingRuleCombo) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { if !rule.MappingRuleBase.modeCheck(mode) { - return nil + return nil, nil } // Check each of the inputs, and if we find a match, proceed @@ -24,7 +25,7 @@ func (rule *MappingRuleCombo) MatchEvent(device *evdev.InputDevice, event *evdev } if match == nil { - return nil + return nil, nil } // Get the value and add/subtract it from State @@ -39,10 +40,10 @@ func (rule *MappingRuleCombo) MatchEvent(device *evdev.InputDevice, event *evdev targetState := len(rule.Inputs) if oldState == targetState-1 && rule.State == targetState { - return rule.Output.CreateEvent(1, mode) + return rule.Output.GetDevice(), rule.Output.CreateEvent(1, mode) } if oldState == targetState && rule.State == targetState-1 { - return rule.Output.CreateEvent(0, mode) + return rule.Output.GetDevice(), rule.Output.CreateEvent(0, mode) } - return nil + return nil, nil } diff --git a/internal/mappingrules/mapping_rule_latched.go b/internal/mappingrules/mapping_rule_latched.go index 90b0394..f8c074a 100644 --- a/internal/mappingrules/mapping_rule_latched.go +++ b/internal/mappingrules/mapping_rule_latched.go @@ -4,19 +4,20 @@ import "github.com/holoplot/go-evdev" type MappingRuleLatched struct { MappingRuleBase - Input RuleTarget - State bool + Input *RuleTargetButton + Output *RuleTargetButton + State bool } -func (rule *MappingRuleLatched) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent { +func (rule *MappingRuleLatched) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { if !rule.MappingRuleBase.modeCheck(mode) { - return nil + return nil, nil } - if device != rule.Input.GetDevice() || - event.Code != rule.Input.GetCode() || + if device != rule.Input.Device || + event.Code != rule.Input.Code || rule.Input.NormalizeValue(event.Value) == 0 { - return nil + return nil, nil } // Input is pressed, so toggle state and emit event @@ -28,5 +29,5 @@ func (rule *MappingRuleLatched) MatchEvent(device *evdev.InputDevice, event *evd value = 0 } - return rule.Output.CreateEvent(value, mode) + return rule.Output.Device, rule.Output.CreateEvent(value, mode) } diff --git a/internal/mappingrules/mapping_rule_proportional_axis.go b/internal/mappingrules/mapping_rule_proportional_axis.go index 2f3b7c5..564c9ab 100644 --- a/internal/mappingrules/mapping_rule_proportional_axis.go +++ b/internal/mappingrules/mapping_rule_proportional_axis.go @@ -16,19 +16,19 @@ type MappingRuleProportionalAxis struct { LastEvent time.Time } -func (rule *MappingRuleProportionalAxis) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent { +func (rule *MappingRuleProportionalAxis) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) { if !rule.MappingRuleBase.modeCheck(mode) { - return nil + return nil, nil } if device != rule.Input.GetDevice() || event.Code != rule.Input.GetCode() { - return nil + return nil, nil } // set the last value to the normalized input value rule.LastValue = rule.Input.NormalizeValue(event.Value) - return nil + return nil, nil } // TimerEvent returns an event when enough time has passed (compared to the last recorded axis value) diff --git a/internal/mappingrules/mapping_rule_simple.go b/internal/mappingrules/mapping_rule_simple.go deleted file mode 100644 index b4539f8..0000000 --- a/internal/mappingrules/mapping_rule_simple.go +++ /dev/null @@ -1,22 +0,0 @@ -package mappingrules - -import "github.com/holoplot/go-evdev" - -// A Simple Mapping Rule can map a button to a button or an axis to an axis. -type MappingRuleSimple struct { - MappingRuleBase - Input RuleTarget -} - -func (rule *MappingRuleSimple) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent { - if !rule.MappingRuleBase.modeCheck(mode) { - return nil - } - - if device != rule.Input.GetDevice() || - event.Code != rule.Input.GetCode() { - return nil - } - - return rule.Output.CreateEvent(rule.Input.NormalizeValue(event.Value), mode) -}