Improve config yaml schema #16
8 changed files with 364 additions and 238 deletions
|
@ -28,8 +28,11 @@ func readConfig(configDir string) *config.ConfigParser {
|
|||
return parser
|
||||
}
|
||||
|
||||
func initVirtualBuffers(config *config.ConfigParser) (map[string]*virtualdevice.EventBuffer, map[*evdev.InputDevice]*virtualdevice.EventBuffer) {
|
||||
vDevices := config.CreateVirtualDevices()
|
||||
func initVirtualBuffers(config *config.ConfigParser) (map[string]*evdev.InputDevice,
|
||||
map[string]*virtualdevice.EventBuffer,
|
||||
map[*evdev.InputDevice]*virtualdevice.EventBuffer) {
|
||||
|
||||
vDevices := config.InitVirtualDevices()
|
||||
if len(vDevices) == 0 {
|
||||
logger.Log("Warning: no virtual devices found in configuration. No rules will work.")
|
||||
}
|
||||
|
@ -40,20 +43,11 @@ func initVirtualBuffers(config *config.ConfigParser) (map[string]*virtualdevice.
|
|||
vBuffersByName[name] = virtualdevice.NewEventBuffer(device)
|
||||
vBuffersByDevice[device] = vBuffersByName[name]
|
||||
}
|
||||
return vBuffersByName, vBuffersByDevice
|
||||
}
|
||||
|
||||
// Extracts the evdev devices from a list of virtual buffers and returns them.
|
||||
func getVirtualDevices(buffers map[string]*virtualdevice.EventBuffer) map[string]*evdev.InputDevice {
|
||||
devices := make(map[string]*evdev.InputDevice)
|
||||
for name, buffer := range buffers {
|
||||
devices[name] = buffer.Device.(*evdev.InputDevice)
|
||||
}
|
||||
return devices
|
||||
return vDevices, vBuffersByName, vBuffersByDevice
|
||||
}
|
||||
|
||||
func initPhysicalDevices(config *config.ConfigParser) map[string]*evdev.InputDevice {
|
||||
pDeviceMap := config.ConnectPhysicalDevices()
|
||||
pDeviceMap := config.InitPhysicalDevices()
|
||||
if len(pDeviceMap) == 0 {
|
||||
logger.Log("Warning: no physical devices found in configuration. No rules will work.")
|
||||
}
|
||||
|
@ -77,13 +71,13 @@ func main() {
|
|||
logger.LogIfError(err, "Failed to initialize TTS")
|
||||
|
||||
// Initialize virtual devices with event buffers
|
||||
vBuffersByName, vBuffersByDevice := initVirtualBuffers(config)
|
||||
vDevicesByName, vBuffersByName, vBuffersByDevice := initVirtualBuffers(config)
|
||||
|
||||
// Initialize physical devices
|
||||
pDevices := initPhysicalDevices(config)
|
||||
|
||||
// Load the rules
|
||||
rules, eventChannel, cancel, wg := loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
|
||||
rules, eventChannel, cancel, wg := loadRules(config, pDevices, vDevicesByName)
|
||||
|
||||
// initialize the mode variable
|
||||
mode := config.GetModes()[0]
|
||||
|
@ -139,7 +133,7 @@ func main() {
|
|||
wg.Wait()
|
||||
fmt.Println("Listeners exited. Parsing config.")
|
||||
config := readConfig(configDir) // reload the config
|
||||
rules, eventChannel, cancel, wg = loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
|
||||
rules, eventChannel, cancel, wg = loadRules(config, pDevices, vDevicesByName)
|
||||
fmt.Println("Config re-loaded. Only rule changes applied. Device and Mode changes require restart.")
|
||||
}
|
||||
|
||||
|
@ -159,7 +153,7 @@ func loadRules(
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Initialize rules
|
||||
rules := config.BuildRules(pDevices, vDevices)
|
||||
rules := config.InitRules(pDevices, vDevices)
|
||||
logger.Logf("Created %d mapping rules.", len(rules))
|
||||
|
||||
// start listening for events on devices and timers
|
||||
|
|
|
@ -8,13 +8,13 @@ import (
|
|||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
// CreateVirtualDevices will register any configured devices with type = virtual
|
||||
// InitVirtualDevices will register any configured devices with type = virtual
|
||||
// using /dev/uinput, and return a map of those devices.
|
||||
//
|
||||
// This function assumes you have already called Parse() on the config directory.
|
||||
// This function assumes Parse() has been called.
|
||||
//
|
||||
// This function should only be called once, unless you want to create duplicate devices for some reason.
|
||||
func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice {
|
||||
// This function should only be called once, unless we want to create duplicate devices for some reason.
|
||||
func (parser *ConfigParser) InitVirtualDevices() map[string]*evdev.InputDevice {
|
||||
deviceMap := make(map[string]*evdev.InputDevice)
|
||||
|
||||
for _, deviceConfig := range parser.config.Devices {
|
||||
|
@ -22,6 +22,8 @@ func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice
|
|||
continue
|
||||
}
|
||||
|
||||
deviceConfig := deviceConfig.Config.(DeviceConfigVirtual)
|
||||
|
||||
name := fmt.Sprintf("joyful-%s", deviceConfig.Name)
|
||||
|
||||
var capabilities map[evdev.EvType][]evdev.EvCode
|
||||
|
@ -74,13 +76,13 @@ func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice
|
|||
return deviceMap
|
||||
}
|
||||
|
||||
// ConnectPhysicalDevices will create InputDevices corresponding to any registered
|
||||
// InitPhysicalDevices will create InputDevices corresponding to any registered
|
||||
// devices with type = physical.
|
||||
//
|
||||
// This function assumes you have already called Parse() on the config directory.
|
||||
// This function assumes Parse() has been called.
|
||||
//
|
||||
// This function should only be called once.
|
||||
func (parser *ConfigParser) ConnectPhysicalDevices() map[string]*evdev.InputDevice {
|
||||
func (parser *ConfigParser) InitPhysicalDevices() map[string]*evdev.InputDevice {
|
||||
deviceMap := make(map[string]*evdev.InputDevice)
|
||||
|
||||
for _, deviceConfig := range parser.config.Devices {
|
||||
|
@ -88,6 +90,8 @@ func (parser *ConfigParser) ConnectPhysicalDevices() map[string]*evdev.InputDevi
|
|||
continue
|
||||
}
|
||||
|
||||
deviceConfig := deviceConfig.Config.(DeviceConfigPhysical)
|
||||
|
||||
var infoName string
|
||||
var device *evdev.InputDevice
|
||||
var err error
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
func makeRuleTargetButton(targetConfig RuleTargetConfig, devs map[string]Device) (*mappingrules.RuleTargetButton, error) {
|
||||
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)
|
||||
|
@ -27,7 +27,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfig, devs map[string]Device)
|
|||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetAxis(targetConfig RuleTargetConfig, devs map[string]Device) (*mappingrules.RuleTargetAxis, error) {
|
||||
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)
|
||||
|
@ -57,7 +57,7 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfig, devs map[string]Device) (
|
|||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetRelaxis(targetConfig RuleTargetConfig, devs map[string]Device) (*mappingrules.RuleTargetRelaxis, error) {
|
||||
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)
|
||||
|
@ -72,11 +72,10 @@ func makeRuleTargetRelaxis(targetConfig RuleTargetConfig, devs map[string]Device
|
|||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
targetConfig.Inverted,
|
||||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetModeSelect(targetConfig RuleTargetConfig, allModes []string) (*mappingrules.RuleTargetModeSelect, error) {
|
||||
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")
|
||||
}
|
||||
|
@ -92,7 +91,7 @@ func hasError(_ any, err error) bool {
|
|||
// 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 RuleTargetConfig, device Device, axis evdev.EvCode) (int32, int32, error) {
|
||||
func calculateDeadzones(targetConfig RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) {
|
||||
|
||||
var deadzoneStart, deadzoneEnd int32
|
||||
deadzoneStart = 0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/holoplot/go-evdev"
|
||||
|
@ -12,7 +13,6 @@ type MakeRuleTargetsTests struct {
|
|||
suite.Suite
|
||||
devs map[string]Device
|
||||
deviceMock *DeviceMock
|
||||
config RuleTargetConfig
|
||||
}
|
||||
|
||||
type DeviceMock struct {
|
||||
|
@ -47,198 +47,197 @@ func (t *MakeRuleTargetsTests) SetupSuite() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) SetupSubTest() {
|
||||
t.config = RuleTargetConfig{
|
||||
Device: "test",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetButton() {
|
||||
config := RuleTargetConfigButton{Device: "test"}
|
||||
|
||||
t.Run("Standard keycode", func() {
|
||||
t.config.Button = "BTN_TRIGGER"
|
||||
rule, err := makeRuleTargetButton(t.config, t.devs)
|
||||
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() {
|
||||
t.config.Button = "0x2fd"
|
||||
rule, err := makeRuleTargetButton(t.config, t.devs)
|
||||
config.Button = "0x2fd"
|
||||
rule, err := makeRuleTargetButton(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.EvCode(0x2fd), rule.Button)
|
||||
})
|
||||
|
||||
t.Run("Index", func() {
|
||||
t.config.Button = "3"
|
||||
rule, err := makeRuleTargetButton(t.config, t.devs)
|
||||
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() {
|
||||
t.config.Button = "74"
|
||||
_, err := makeRuleTargetButton(t.config, t.devs)
|
||||
config.Button = "74"
|
||||
_, err := makeRuleTargetButton(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Un-prefixed keycode", func() {
|
||||
t.config.Button = "pinkie"
|
||||
rule, err := makeRuleTargetButton(t.config, t.devs)
|
||||
config.Button = "pinkie"
|
||||
rule, err := makeRuleTargetButton(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.BTN_PINKIE, rule.Button)
|
||||
})
|
||||
|
||||
t.Run("Invalid keycode", func() {
|
||||
t.config.Button = "foo"
|
||||
_, err := makeRuleTargetButton(t.config, t.devs)
|
||||
config.Button = "foo"
|
||||
_, err := makeRuleTargetButton(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||
t.Run("Standard code", func() {
|
||||
t.config.Axis = "ABS_X"
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.ABS_X, rule.Axis)
|
||||
})
|
||||
codeTestCases := []struct {
|
||||
input string
|
||||
output evdev.EvCode
|
||||
}{
|
||||
{"ABS_X", evdev.ABS_X},
|
||||
{"0x01", evdev.ABS_Y},
|
||||
{"x", evdev.ABS_X},
|
||||
}
|
||||
|
||||
t.Run("Hex code", func() {
|
||||
t.config.Axis = "0x01"
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
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(evdev.ABS_Y, rule.Axis)
|
||||
})
|
||||
t.EqualValues(tc.output, rule.Axis)
|
||||
|
||||
t.Run("Un-prefixed code", func() {
|
||||
t.config.Axis = "x"
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.ABS_X, rule.Axis)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Invalid code", func() {
|
||||
t.config.Axis = "foo"
|
||||
_, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = "foo"
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Invalid deadzone", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneEnd = 100
|
||||
t.config.DeadzoneStart = 1000
|
||||
_, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = "x"
|
||||
config.DeadzoneEnd = 100
|
||||
config.DeadzoneStart = 1000
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Deadzone center/size", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 5000
|
||||
t.config.DeadzoneSize = 1000
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(4500, rule.DeadzoneStart)
|
||||
t.EqualValues(5500, rule.DeadzoneEnd)
|
||||
})
|
||||
relDeadzoneTestCases := []struct {
|
||||
inCenter int32
|
||||
inSize int32
|
||||
outStart int32
|
||||
outEnd int32
|
||||
}{
|
||||
{5000, 1000, 4500, 5500},
|
||||
{0, 500, 0, 500},
|
||||
{10000, 500, 9500, 10000},
|
||||
}
|
||||
|
||||
t.Run("Deadzone center/size lower boundary", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 0
|
||||
t.config.DeadzoneSize = 500
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(0, rule.DeadzoneStart)
|
||||
t.EqualValues(500, rule.DeadzoneEnd)
|
||||
})
|
||||
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.Run("Deadzone center/size upper boundary", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 10000
|
||||
t.config.DeadzoneSize = 500
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(9500, rule.DeadzoneStart)
|
||||
t.EqualValues(10000, rule.DeadzoneEnd)
|
||||
t.Equal(tc.outStart, rule.DeadzoneStart)
|
||||
t.Equal(tc.outEnd, rule.DeadzoneEnd)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Deadzone center/size invalid center", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 20000
|
||||
t.config.DeadzoneSize = 500
|
||||
_, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
config := RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: 20000,
|
||||
DeadzoneSize: 500,
|
||||
}
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Deadzone center/percent", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 5000
|
||||
t.config.DeadzoneSizePercent = 10
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(4500, rule.DeadzoneStart)
|
||||
t.EqualValues(5500, rule.DeadzoneEnd)
|
||||
})
|
||||
relDeadzonePercentTestCases := []struct {
|
||||
inCenter int32
|
||||
inSizePercent int32
|
||||
outStart int32
|
||||
outEnd int32
|
||||
}{
|
||||
{5000, 10, 4500, 5500},
|
||||
{0, 10, 0, 1000},
|
||||
{10000, 10, 9000, 10000},
|
||||
}
|
||||
|
||||
t.Run("Deadzone center/percent lower boundary", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 0
|
||||
t.config.DeadzoneSizePercent = 10
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(0, rule.DeadzoneStart)
|
||||
t.EqualValues(1000, rule.DeadzoneEnd)
|
||||
})
|
||||
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.Run("Deadzone center/percent upper boundary", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 10000
|
||||
t.config.DeadzoneSizePercent = 10
|
||||
rule, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(9000, rule.DeadzoneStart)
|
||||
t.EqualValues(10000, rule.DeadzoneEnd)
|
||||
t.Equal(tc.outStart, rule.DeadzoneStart)
|
||||
t.Equal(tc.outEnd, rule.DeadzoneEnd)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Deadzone center/percent invalid center", func() {
|
||||
t.config.Axis = "x"
|
||||
t.config.DeadzoneCenter = 20000
|
||||
t.config.DeadzoneSizePercent = 10
|
||||
_, err := makeRuleTargetAxis(t.config, t.devs)
|
||||
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() {
|
||||
t.config.Axis = "REL_WHEEL"
|
||||
rule, err := makeRuleTargetRelaxis(t.config, t.devs)
|
||||
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() {
|
||||
t.config.Axis = "0x00"
|
||||
rule, err := makeRuleTargetRelaxis(t.config, t.devs)
|
||||
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() {
|
||||
t.config.Axis = "wheel"
|
||||
rule, err := makeRuleTargetRelaxis(t.config, t.devs)
|
||||
config.Axis = "wheel"
|
||||
rule, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.Nil(err)
|
||||
t.EqualValues(evdev.REL_WHEEL, rule.Axis)
|
||||
})
|
||||
|
||||
t.Run("Invalid keycode", func() {
|
||||
t.config.Axis = "foo"
|
||||
_, err := makeRuleTargetRelaxis(t.config, t.devs)
|
||||
config.Axis = "foo"
|
||||
_, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Incorrect axis type", func() {
|
||||
t.config.Axis = "ABS_X"
|
||||
_, err := makeRuleTargetRelaxis(t.config, t.devs)
|
||||
config.Axis = "ABS_X"
|
||||
_, err := makeRuleTargetRelaxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
// 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 (parser *ConfigParser) BuildRules(pInputDevs map[string]*evdev.InputDevice, vInputDevs map[string]*evdev.InputDevice) []mappingrules.MappingRule {
|
||||
func (parser *ConfigParser) InitRules(pInputDevs map[string]*evdev.InputDevice, vInputDevs map[string]*evdev.InputDevice) []mappingrules.MappingRule {
|
||||
rules := make([]mappingrules.MappingRule, 0)
|
||||
modes := parser.GetModes()
|
||||
|
||||
|
@ -42,21 +42,21 @@ func (parser *ConfigParser) BuildRules(pInputDevs map[string]*evdev.InputDevice,
|
|||
|
||||
switch strings.ToLower(ruleConfig.Type) {
|
||||
case RuleTypeButton:
|
||||
newRule, err = makeMappingRuleButton(ruleConfig, pDevs, vDevs, base)
|
||||
newRule, err = makeMappingRuleButton(ruleConfig.Config.(RuleConfigButton), pDevs, vDevs, base)
|
||||
case RuleTypeButtonCombo:
|
||||
newRule, err = makeMappingRuleCombo(ruleConfig, pDevs, vDevs, base)
|
||||
case RuleTypeLatched:
|
||||
newRule, err = makeMappingRuleLatched(ruleConfig, pDevs, vDevs, base)
|
||||
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, pDevs, vDevs, base)
|
||||
newRule, err = makeMappingRuleAxis(ruleConfig.Config.(RuleConfigAxis), pDevs, vDevs, base)
|
||||
case RuleTypeAxisCombined:
|
||||
newRule, err = makeMappingRuleAxisCombined(ruleConfig, pDevs, vDevs, base)
|
||||
newRule, err = makeMappingRuleAxisCombined(ruleConfig.Config.(RuleConfigAxisCombined), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToButton:
|
||||
newRule, err = makeMappingRuleAxisToButton(ruleConfig, pDevs, vDevs, base)
|
||||
newRule, err = makeMappingRuleAxisToButton(ruleConfig.Config.(RuleConfigAxisToButton), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToRelaxis:
|
||||
newRule, err = makeMappingRuleAxisToRelaxis(ruleConfig, pDevs, vDevs, base)
|
||||
newRule, err = makeMappingRuleAxisToRelaxis(ruleConfig.Config.(RuleConfigAxisToRelaxis), pDevs, vDevs, base)
|
||||
case RuleTypeModeSelect:
|
||||
newRule, err = makeMappingRuleModeSelect(ruleConfig, pDevs, modes, base)
|
||||
newRule, err = makeMappingRuleModeSelect(ruleConfig.Config.(RuleConfigModeSelect), pDevs, modes, base)
|
||||
default:
|
||||
err = fmt.Errorf("bad rule type '%s' for rule '%s'", ruleConfig.Type, ruleConfig.Name)
|
||||
}
|
||||
|
@ -72,7 +72,14 @@ func (parser *ConfigParser) BuildRules(pInputDevs map[string]*evdev.InputDevice,
|
|||
return rules
|
||||
}
|
||||
|
||||
func makeMappingRuleButton(ruleConfig RuleConfig,
|
||||
// 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) {
|
||||
|
@ -90,7 +97,7 @@ func makeMappingRuleButton(ruleConfig RuleConfig,
|
|||
return mappingrules.NewMappingRuleButton(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleCombo(ruleConfig RuleConfig,
|
||||
func makeMappingRuleCombo(ruleConfig RuleConfigButtonCombo,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButtonCombo, error) {
|
||||
|
@ -112,7 +119,7 @@ func makeMappingRuleCombo(ruleConfig RuleConfig,
|
|||
return mappingrules.NewMappingRuleButtonCombo(base, inputs, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleLatched(ruleConfig RuleConfig,
|
||||
func makeMappingRuleLatched(ruleConfig RuleConfigButtonLatched,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButtonLatched, error) {
|
||||
|
@ -130,7 +137,7 @@ func makeMappingRuleLatched(ruleConfig RuleConfig,
|
|||
return mappingrules.NewMappingRuleButtonLatched(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxis(ruleConfig RuleConfig,
|
||||
func makeMappingRuleAxis(ruleConfig RuleConfigAxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxis, error) {
|
||||
|
@ -148,7 +155,7 @@ func makeMappingRuleAxis(ruleConfig RuleConfig,
|
|||
return mappingrules.NewMappingRuleAxis(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisCombined(ruleConfig RuleConfig,
|
||||
func makeMappingRuleAxisCombined(ruleConfig RuleConfigAxisCombined,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisCombined, error) {
|
||||
|
@ -171,7 +178,7 @@ func makeMappingRuleAxisCombined(ruleConfig RuleConfig,
|
|||
return mappingrules.NewMappingRuleAxisCombined(base, inputLower, inputUpper, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisToButton(ruleConfig RuleConfig,
|
||||
func makeMappingRuleAxisToButton(ruleConfig RuleConfigAxisToButton,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisToButton, error) {
|
||||
|
@ -189,7 +196,7 @@ func makeMappingRuleAxisToButton(ruleConfig RuleConfig,
|
|||
return mappingrules.NewMappingRuleAxisToButton(base, input, output, ruleConfig.RepeatRateMin, ruleConfig.RepeatRateMax), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisToRelaxis(ruleConfig RuleConfig,
|
||||
func makeMappingRuleAxisToRelaxis(ruleConfig RuleConfigAxisToRelaxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisToRelaxis, error) {
|
||||
|
@ -211,7 +218,7 @@ func makeMappingRuleAxisToRelaxis(ruleConfig RuleConfig,
|
|||
ruleConfig.Increment), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleModeSelect(ruleConfig RuleConfig,
|
||||
func makeMappingRuleModeSelect(ruleConfig RuleConfigModeSelect,
|
||||
pDevs map[string]Device,
|
||||
modes []string,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleModeSelect, error) {
|
||||
|
|
|
@ -1,79 +1,213 @@
|
|||
// These types comprise the YAML schema for configuring Joyful.
|
||||
// The config files will be combined and then unmarshalled into this
|
||||
//
|
||||
// TODO: currently the types in here aren't especially strong; each one is
|
||||
// decomposed into a different object based on the Type fields. We should implement
|
||||
// some sort of delayed unmarshalling technique, for example see ideas at
|
||||
// https://stackoverflow.com/questions/70635636/unmarshaling-yaml-into-different-struct-based-off-yaml-field
|
||||
// Then we can be more explicit about the interface here.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Devices []DeviceConfig `yaml:"devices"`
|
||||
Modes []string `yaml:"modes,omitempty"`
|
||||
Rules []RuleConfig `yaml:"rules"`
|
||||
Devices []DeviceConfig
|
||||
Modes []string
|
||||
Rules []RuleConfig
|
||||
}
|
||||
|
||||
// These top-level structs use custom unmarshaling to unpack each available sub-type
|
||||
type DeviceConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
DeviceName string `yaml:"device_name,omitempty"`
|
||||
DevicePath string `yaml:"device_path,omitempty"`
|
||||
Preset string `yaml:"preset,omitempty"`
|
||||
NumButtons int `yaml:"num_buttons,omitempty"`
|
||||
NumAxes int `yaml:"num_axes,omitempty"`
|
||||
NumRelativeAxes int `yaml:"num_rel_axes"`
|
||||
Buttons []string `yaml:"buttons,omitempty"`
|
||||
Axes []string `yaml:"axes,omitempty"`
|
||||
RelativeAxes []string `yaml:"rel_axes,omitempty"`
|
||||
Lock bool `yaml:"lock,omitempty"`
|
||||
Type string
|
||||
Config interface{}
|
||||
}
|
||||
|
||||
type RuleConfig struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Input RuleTargetConfig `yaml:"input,omitempty"`
|
||||
InputLower RuleTargetConfig `yaml:"input_lower,omitempty"`
|
||||
InputUpper RuleTargetConfig `yaml:"input_upper,omitempty"`
|
||||
Inputs []RuleTargetConfig `yaml:"inputs,omitempty"`
|
||||
Output RuleTargetConfig `yaml:"output"`
|
||||
Modes []string `yaml:"modes,omitempty"`
|
||||
RepeatRateMin int `yaml:"repeat_rate_min,omitempty"`
|
||||
RepeatRateMax int `yaml:"repeat_rate_max,omitempty"`
|
||||
Increment int `yaml:"increment,omitempty"`
|
||||
Type string
|
||||
Name string
|
||||
Modes []string
|
||||
Config interface{}
|
||||
}
|
||||
|
||||
type RuleTargetConfig struct {
|
||||
Device string `yaml:"device,omitempty"`
|
||||
Button string `yaml:"button,omitempty"`
|
||||
Axis string `yaml:"axis,omitempty"`
|
||||
type DeviceConfigPhysical struct {
|
||||
Name string
|
||||
DeviceName string `yaml:"device_name,omitempty"`
|
||||
DevicePath string `yaml:"device_path,omitempty"`
|
||||
Lock bool
|
||||
}
|
||||
|
||||
// TODO: configure custom unmarshaling so we can overload Buttons, Axes, and RelativeAxes...
|
||||
type DeviceConfigVirtual struct {
|
||||
Name string
|
||||
Preset string
|
||||
NumButtons int `yaml:"num_buttons,omitempty"`
|
||||
NumAxes int `yaml:"num_axes,omitempty"`
|
||||
NumRelativeAxes int `yaml:"num_rel_axes"`
|
||||
Buttons []string
|
||||
Axes []string
|
||||
RelativeAxes []string `yaml:"rel_axes,omitempty"`
|
||||
}
|
||||
|
||||
type RuleConfigButton struct {
|
||||
Input RuleTargetConfigButton
|
||||
Output RuleTargetConfigButton
|
||||
}
|
||||
|
||||
type RuleConfigButtonCombo struct {
|
||||
Inputs []RuleTargetConfigButton
|
||||
Output RuleTargetConfigButton
|
||||
}
|
||||
|
||||
type RuleConfigButtonLatched struct {
|
||||
Input RuleTargetConfigButton
|
||||
Output RuleTargetConfigButton
|
||||
}
|
||||
|
||||
type RuleConfigAxis struct {
|
||||
Input RuleTargetConfigAxis
|
||||
Output RuleTargetConfigAxis
|
||||
}
|
||||
|
||||
type RuleConfigAxisCombined struct {
|
||||
InputLower RuleTargetConfigAxis `yaml:"input_lower,omitempty"`
|
||||
InputUpper RuleTargetConfigAxis `yaml:"input_upper,omitempty"`
|
||||
Output RuleTargetConfigAxis
|
||||
}
|
||||
|
||||
type RuleConfigAxisToButton struct {
|
||||
RepeatRateMin int `yaml:"repeat_rate_min,omitempty"`
|
||||
RepeatRateMax int `yaml:"repeat_rate_max,omitempty"`
|
||||
Input RuleTargetConfigAxis
|
||||
Output RuleTargetConfigButton
|
||||
}
|
||||
|
||||
type RuleConfigAxisToRelaxis struct {
|
||||
RepeatRateMin int `yaml:"repeat_rate_min"`
|
||||
RepeatRateMax int `yaml:"repeat_rate_max"`
|
||||
Increment int
|
||||
Input RuleTargetConfigAxis
|
||||
Output RuleTargetConfigRelaxis
|
||||
}
|
||||
|
||||
type RuleConfigModeSelect struct {
|
||||
Input RuleTargetConfigButton
|
||||
Output RuleTargetConfigModeSelect
|
||||
}
|
||||
|
||||
type RuleTargetConfigButton struct {
|
||||
Device string
|
||||
Button string
|
||||
Inverted bool
|
||||
}
|
||||
|
||||
type RuleTargetConfigAxis struct {
|
||||
Device string
|
||||
Axis string
|
||||
DeadzoneCenter int32 `yaml:"deadzone_center,omitempty"`
|
||||
DeadzoneSize int32 `yaml:"deadzone_size,omitempty"`
|
||||
DeadzoneSizePercent int32 `yaml:"deadzone_size_percent,omitempty"`
|
||||
DeadzoneStart int32 `yaml:"deadzone_start,omitempty"`
|
||||
DeadzoneEnd int32 `yaml:"deadzone_end,omitempty"`
|
||||
Inverted bool `yaml:"inverted,omitempty"`
|
||||
Modes []string `yaml:"modes,omitempty"`
|
||||
Inverted bool
|
||||
}
|
||||
|
||||
type RuleTargetConfigRelaxis struct {
|
||||
Device string
|
||||
Axis string
|
||||
}
|
||||
|
||||
type RuleTargetConfigModeSelect struct {
|
||||
Modes []string
|
||||
}
|
||||
|
||||
func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error {
|
||||
metaConfig := &struct {
|
||||
Type string
|
||||
}{}
|
||||
err := unmarshal(metaConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dc.Type = metaConfig.Type
|
||||
|
||||
err = nil
|
||||
switch metaConfig.Type {
|
||||
case DeviceTypePhysical:
|
||||
config := DeviceConfigPhysical{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case DeviceTypeVirtual:
|
||||
config := DeviceConfigVirtual{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
default:
|
||||
err = fmt.Errorf("invalid device type '%s'", dc.Type)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (dc *RuleConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error {
|
||||
metaConfig := &struct {
|
||||
Type string
|
||||
Name string
|
||||
Modes []string
|
||||
}{}
|
||||
err := unmarshal(metaConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dc.Type = metaConfig.Type
|
||||
dc.Name = metaConfig.Name
|
||||
dc.Modes = metaConfig.Modes
|
||||
|
||||
switch dc.Type {
|
||||
case RuleTypeButton:
|
||||
config := RuleConfigButton{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeButtonCombo:
|
||||
config := RuleConfigButtonCombo{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeButtonLatched:
|
||||
config := RuleConfigButtonLatched{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeAxis:
|
||||
config := RuleConfigAxis{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeAxisCombined:
|
||||
config := RuleConfigAxisCombined{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeAxisToButton:
|
||||
config := RuleConfigAxisToButton{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeAxisToRelaxis:
|
||||
config := RuleConfigAxisToRelaxis{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
case RuleTypeModeSelect:
|
||||
config := RuleConfigModeSelect{}
|
||||
err = unmarshal(&config)
|
||||
dc.Config = config
|
||||
default:
|
||||
err = fmt.Errorf("invalid rule type '%s'", dc.Type)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: custom yaml unmarshaling is obtuse; do we really need to do all of this work
|
||||
// just to set a single default value?
|
||||
func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error {
|
||||
func (dc *DeviceConfigPhysical) UnmarshalYAML(unmarshal func(data interface{}) error) error {
|
||||
var raw struct {
|
||||
Name string
|
||||
Type string
|
||||
DeviceName string `yaml:"device_name"`
|
||||
DevicePath string `yaml:"device_path"`
|
||||
Preset string
|
||||
NumButtons int `yaml:"num_buttons"`
|
||||
NumAxes int `yaml:"num_axes"`
|
||||
NumRelativeAxes int `yaml:"num_rel_axes"`
|
||||
Buttons []string
|
||||
Axes []string
|
||||
RelativeAxes []string `yaml:"relative_axes"`
|
||||
Lock bool `yaml:"lock,omitempty"`
|
||||
}
|
||||
|
||||
// Set non-standard defaults
|
||||
raw.Lock = true
|
||||
|
||||
err := unmarshal(&raw)
|
||||
|
@ -81,18 +215,10 @@ func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) er
|
|||
return err
|
||||
}
|
||||
|
||||
*dc = DeviceConfig{
|
||||
*dc = DeviceConfigPhysical{
|
||||
Name: raw.Name,
|
||||
Type: raw.Type,
|
||||
DeviceName: raw.DeviceName,
|
||||
DevicePath: raw.DevicePath,
|
||||
Preset: raw.Preset,
|
||||
NumButtons: raw.NumButtons,
|
||||
NumAxes: raw.NumAxes,
|
||||
NumRelativeAxes: raw.NumRelativeAxes,
|
||||
Buttons: raw.Buttons,
|
||||
Axes: raw.Axes,
|
||||
RelativeAxes: raw.RelativeAxes,
|
||||
Lock: raw.Lock,
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -15,12 +15,12 @@ const (
|
|||
|
||||
RuleTypeButton = "button"
|
||||
RuleTypeButtonCombo = "button-combo"
|
||||
RuleTypeLatched = "button-latched"
|
||||
RuleTypeButtonLatched = "button-latched"
|
||||
RuleTypeAxis = "axis"
|
||||
RuleTypeAxisCombined = "axis-combined"
|
||||
RuleTypeModeSelect = "mode-select"
|
||||
RuleTypeAxisToButton = "axis-to-button"
|
||||
RuleTypeAxisToRelaxis = "axis-to-relaxis"
|
||||
RuleTypeModeSelect = "mode-select"
|
||||
|
||||
CodePrefixButton = "BTN"
|
||||
CodePrefixKey = "KEY"
|
||||
|
|
|
@ -8,19 +8,16 @@ type RuleTargetRelaxis struct {
|
|||
DeviceName string
|
||||
Device Device
|
||||
Axis evdev.EvCode
|
||||
Inverted bool
|
||||
}
|
||||
|
||||
func NewRuleTargetRelaxis(device_name string,
|
||||
device Device,
|
||||
axis evdev.EvCode,
|
||||
inverted bool) (*RuleTargetRelaxis, error) {
|
||||
axis evdev.EvCode) (*RuleTargetRelaxis, error) {
|
||||
|
||||
return &RuleTargetRelaxis{
|
||||
DeviceName: device_name,
|
||||
Device: device,
|
||||
Axis: axis,
|
||||
Inverted: inverted,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue