Allow button targets to handle keyboard events.

This commit is contained in:
Anna Rose Wiggins 2025-08-01 17:26:19 -04:00
parent 61fe5208e6
commit 32fa7d27e0
4 changed files with 123 additions and 88 deletions

View file

@ -8,13 +8,23 @@ import (
"github.com/holoplot/go-evdev" "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) { func parseCode(code, prefix string) (evdev.EvCode, error) {
code = strings.ToUpper(code) code = strings.ToUpper(code)
var codeLookup map[string]evdev.EvCode var codeLookup map[string]evdev.EvCode
switch prefix { switch prefix {
case CodePrefixButton: case CodePrefixButton, CodePrefixKey:
codeLookup = evdev.KEYFromString codeLookup = evdev.KEYFromString
case CodePrefixAxis: case CodePrefixAxis:
codeLookup = evdev.ABSFromString codeLookup = evdev.ABSFromString

View file

@ -16,7 +16,7 @@ func TestRunnerEventCodeParserTests(t *testing.T) {
suite.Run(t, new(EventCodeParserTests)) 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() { t.Run(fmt.Sprintf("%s: %s", prefix, in), func() {
code, err := parseCode(in, prefix) code, err := parseCode(in, prefix)
t.Nil(err) 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 { testCases := []struct {
in string in string
out int out evdev.EvCode
}{ }{
{"ABS_X", evdev.ABS_X}, {"BTN_A", evdev.BTN_A},
{"ABS_Y", evdev.ABS_Y}, {"A", evdev.BTN_A},
{"ABS_Z", evdev.ABS_Z}, {"BTN_TRIGGER_HAPPY", evdev.BTN_TRIGGER_HAPPY},
{"ABS_RX", evdev.ABS_RX}, {"KEY_A", evdev.KEY_A},
{"ABS_RY", evdev.ABS_RY}, {"KEY_ESC", evdev.KEY_ESC},
{"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 { for _, testCase := range testCases {
parseCodeTestCase(t, testCase.in, testCase.out, "ABS") t.Run(testCase.in, func() {
} code, err := parseCodeButton(testCase.in)
} t.Nil(err)
t.EqualValues(code, testCase.out)
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)
}) })
} }
} }
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)
})
}
})
}

View file

@ -14,7 +14,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfig, devs map[string]Device)
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.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 { if err != nil {
return nil, err 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") 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 { if err != nil {
return nil, err 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) 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -18,6 +18,7 @@ const (
RuleTypeAxisToRelaxis = "axis-to-relaxis" RuleTypeAxisToRelaxis = "axis-to-relaxis"
CodePrefixButton = "BTN" CodePrefixButton = "BTN"
CodePrefixKey = "KEY"
CodePrefixAxis = "ABS" CodePrefixAxis = "ABS"
CodePrefixRelaxis = "REL" CodePrefixRelaxis = "REL"