From 32fa7d27e07d89b10df77c688d84bb1f899db48b Mon Sep 17 00:00:00 2001 From: Anna Rose Wiggins Date: Fri, 1 Aug 2025 17:26:19 -0400 Subject: [PATCH] Allow button targets to handle keyboard events. --- internal/config/codes.go | 12 +- internal/config/codes_test.go | 192 +++++++++++++++------------ internal/config/make_rule_targets.go | 6 +- internal/config/variables.go | 1 + 4 files changed, 123 insertions(+), 88 deletions(-) diff --git a/internal/config/codes.go b/internal/config/codes.go index fc819cb..c879feb 100644 --- a/internal/config/codes.go +++ b/internal/config/codes.go @@ -8,13 +8,23 @@ import ( "github.com/holoplot/go-evdev" ) +func parseCodeButton(code string) (evdev.EvCode, error) { + prefix := CodePrefixButton + + if strings.HasPrefix(code, CodePrefixKey+"_") { + prefix = CodePrefixKey + } + + return parseCode(code, prefix) +} + func parseCode(code, prefix string) (evdev.EvCode, error) { code = strings.ToUpper(code) var codeLookup map[string]evdev.EvCode switch prefix { - case CodePrefixButton: + case CodePrefixButton, CodePrefixKey: codeLookup = evdev.KEYFromString case CodePrefixAxis: codeLookup = evdev.ABSFromString diff --git a/internal/config/codes_test.go b/internal/config/codes_test.go index cf1741c..6e80291 100644 --- a/internal/config/codes_test.go +++ b/internal/config/codes_test.go @@ -16,7 +16,7 @@ func TestRunnerEventCodeParserTests(t *testing.T) { suite.Run(t, new(EventCodeParserTests)) } -func parseCodeTestCase(t *EventCodeParserTests, in string, out int, prefix string) { +func parseCodeTestCase(t *EventCodeParserTests, in string, out evdev.EvCode, prefix string) { t.Run(fmt.Sprintf("%s: %s", prefix, in), func() { code, err := parseCode(in, prefix) t.Nil(err) @@ -24,95 +24,119 @@ func parseCodeTestCase(t *EventCodeParserTests, in string, out int, prefix strin }) } -func (t *EventCodeParserTests) TestParseCodeABS() { +func (t *EventCodeParserTests) TestParseCodeButton() { testCases := []struct { in string - out int + out evdev.EvCode }{ - {"ABS_X", evdev.ABS_X}, - {"ABS_Y", evdev.ABS_Y}, - {"ABS_Z", evdev.ABS_Z}, - {"ABS_RX", evdev.ABS_RX}, - {"ABS_RY", evdev.ABS_RY}, - {"ABS_RZ", evdev.ABS_RZ}, - {"ABS_THROTTLE", evdev.ABS_THROTTLE}, - {"ABS_RUDDER", evdev.ABS_RUDDER}, - {"x", evdev.ABS_X}, - {"y", evdev.ABS_Y}, - {"z", evdev.ABS_Z}, - {"throttle", evdev.ABS_THROTTLE}, - {"rudder", evdev.ABS_RUDDER}, - {"0x0", evdev.ABS_X}, - {"0x1", evdev.ABS_Y}, - {"0x2", evdev.ABS_Z}, + {"BTN_A", evdev.BTN_A}, + {"A", evdev.BTN_A}, + {"BTN_TRIGGER_HAPPY", evdev.BTN_TRIGGER_HAPPY}, + {"KEY_A", evdev.KEY_A}, + {"KEY_ESC", evdev.KEY_ESC}, } for _, testCase := range testCases { - parseCodeTestCase(t, testCase.in, testCase.out, "ABS") - } -} - -func (t *EventCodeParserTests) TestParseCodeREL() { - testCases := []struct { - in string - out int - }{ - {"REL_X", evdev.REL_X}, - {"REL_Y", evdev.REL_Y}, - {"REL_Z", evdev.REL_Z}, - {"REL_RX", evdev.REL_RX}, - {"REL_RY", evdev.REL_RY}, - {"REL_RZ", evdev.REL_RZ}, - {"REL_WHEEL", evdev.REL_WHEEL}, - {"REL_HWHEEL", evdev.REL_HWHEEL}, - {"REL_MISC", evdev.REL_MISC}, - {"x", evdev.REL_X}, - {"y", evdev.REL_Y}, - {"wheel", evdev.REL_WHEEL}, - {"0x0", evdev.REL_X}, - {"0x1", evdev.REL_Y}, - {"0x2", evdev.REL_Z}, - } - - for _, testCase := range testCases { - parseCodeTestCase(t, testCase.in, testCase.out, "REL") - } -} - -func (t *EventCodeParserTests) TestParseCodeBTN() { - testCases := []struct { - in string - out int - }{ - {"BTN_TRIGGER", evdev.BTN_TRIGGER}, - {"trigger", evdev.BTN_TRIGGER}, - {"0", evdev.BTN_TRIGGER}, - {"0x120", evdev.BTN_TRIGGER}, - } - - for _, testCase := range testCases { - parseCodeTestCase(t, testCase.in, testCase.out, "BTN") - } -} - -func (t *EventCodeParserTests) TestParseCodeInvalid() { - testCases := []struct { - in string - prefix string - }{ - {"badbutton", "BTN"}, - {"ABS_X", "BTN"}, - {"!@#$%^&*(){}-_", "BTN"}, - {"REL_X", "ABS"}, - {"ABS_W", "ABS"}, - {"0", "ABS"}, - {"0xg", "ABS"}, - } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("%s - '%s'", testCase.prefix, testCase.in), func() { - _, err := parseCode(testCase.in, testCase.prefix) - t.NotNil(err) + t.Run(testCase.in, func() { + code, err := parseCodeButton(testCase.in) + t.Nil(err) + t.EqualValues(code, testCase.out) }) } } + +func (t *EventCodeParserTests) TestParseCode() { + + t.Run("ABS", func() { + testCases := []struct { + in string + out evdev.EvCode + }{ + {"ABS_X", evdev.ABS_X}, + {"ABS_Y", evdev.ABS_Y}, + {"ABS_Z", evdev.ABS_Z}, + {"ABS_RX", evdev.ABS_RX}, + {"ABS_RY", evdev.ABS_RY}, + {"ABS_RZ", evdev.ABS_RZ}, + {"ABS_THROTTLE", evdev.ABS_THROTTLE}, + {"ABS_RUDDER", evdev.ABS_RUDDER}, + {"x", evdev.ABS_X}, + {"y", evdev.ABS_Y}, + {"z", evdev.ABS_Z}, + {"throttle", evdev.ABS_THROTTLE}, + {"rudder", evdev.ABS_RUDDER}, + {"0x0", evdev.ABS_X}, + {"0x1", evdev.ABS_Y}, + {"0x2", evdev.ABS_Z}, + } + + for _, testCase := range testCases { + parseCodeTestCase(t, testCase.in, testCase.out, "ABS") + } + }) + + t.Run("REL", func() { + testCases := []struct { + in string + out evdev.EvCode + }{ + {"REL_X", evdev.REL_X}, + {"REL_Y", evdev.REL_Y}, + {"REL_Z", evdev.REL_Z}, + {"REL_RX", evdev.REL_RX}, + {"REL_RY", evdev.REL_RY}, + {"REL_RZ", evdev.REL_RZ}, + {"REL_WHEEL", evdev.REL_WHEEL}, + {"REL_HWHEEL", evdev.REL_HWHEEL}, + {"REL_MISC", evdev.REL_MISC}, + {"x", evdev.REL_X}, + {"y", evdev.REL_Y}, + {"wheel", evdev.REL_WHEEL}, + {"0x0", evdev.REL_X}, + {"0x1", evdev.REL_Y}, + {"0x2", evdev.REL_Z}, + } + + for _, testCase := range testCases { + parseCodeTestCase(t, testCase.in, testCase.out, "REL") + } + }) + + t.Run("BTN", func() { + testCases := []struct { + in string + out evdev.EvCode + }{ + {"BTN_TRIGGER", evdev.BTN_TRIGGER}, + {"trigger", evdev.BTN_TRIGGER}, + {"0", evdev.BTN_TRIGGER}, + {"0x120", evdev.BTN_TRIGGER}, + } + + for _, testCase := range testCases { + parseCodeTestCase(t, testCase.in, testCase.out, "BTN") + } + }) + + t.Run("Invalid", func() { + testCases := []struct { + in string + prefix string + }{ + {"badbutton", "BTN"}, + {"ABS_X", "BTN"}, + {"!@#$%^&*(){}-_", "BTN"}, + {"REL_X", "ABS"}, + {"ABS_W", "ABS"}, + {"0", "ABS"}, + {"0xg", "ABS"}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%s - '%s'", testCase.prefix, testCase.in), func() { + _, err := parseCode(testCase.in, testCase.prefix) + t.NotNil(err) + }) + } + }) +} diff --git a/internal/config/make_rule_targets.go b/internal/config/make_rule_targets.go index 47d28e5..7e8c2eb 100644 --- a/internal/config/make_rule_targets.go +++ b/internal/config/make_rule_targets.go @@ -14,7 +14,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfig, devs map[string]Device) return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) } - eventCode, err := parseCode(targetConfig.Button, "BTN") + eventCode, err := parseCodeButton(targetConfig.Button) if err != nil { return nil, err } @@ -37,7 +37,7 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfig, devs map[string]Device) ( return nil, errors.New("deadzone_end must be greater than deadzone_start") } - eventCode, err := parseCode(targetConfig.Axis, "ABS") + eventCode, err := parseCode(targetConfig.Axis, CodePrefixAxis) if err != nil { return nil, err } @@ -63,7 +63,7 @@ func makeRuleTargetRelaxis(targetConfig RuleTargetConfig, devs map[string]Device return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device) } - eventCode, err := parseCode(targetConfig.Axis, "REL") + eventCode, err := parseCode(targetConfig.Axis, CodePrefixRelaxis) if err != nil { return nil, err } diff --git a/internal/config/variables.go b/internal/config/variables.go index 9c126bc..7ca4335 100644 --- a/internal/config/variables.go +++ b/internal/config/variables.go @@ -18,6 +18,7 @@ const ( RuleTypeAxisToRelaxis = "axis-to-relaxis" CodePrefixButton = "BTN" + CodePrefixKey = "KEY" CodePrefixAxis = "ABS" CodePrefixRelaxis = "REL"