diff --git a/internal/config/configparser.go b/internal/config/configparser.go index ae464d8..2e5904a 100644 --- a/internal/config/configparser.go +++ b/internal/config/configparser.go @@ -1,15 +1,25 @@ +// The ConfigParser is the main structure you'll interact with when using this package. +// +// Example usage: +// config := &config.ConfigParser{} +// config.Parse() +// virtualDevices, err := config.CreateVirtualDevices() +// physicalDevices, err := config.ConnectVirtualDevices() +// modes, err := config.GetModes() +// rules, err := config.BuildRules(physicalDevices, virtualDevices, modes) +// +// nb: there are methods defined on ConfigParser in other files in this package! + package config import ( "errors" - "fmt" "os" "path/filepath" "strings" "git.annabunches.net/annabunches/joyful/internal/logger" "github.com/goccy/go-yaml" - "github.com/holoplot/go-evdev" ) type ConfigParser struct { @@ -58,112 +68,3 @@ func (parser *ConfigParser) Parse(directory string) error { return nil } - -// CreateVirtualDevices 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 should only be called once, unless you want to create duplicate devices for some reason. -func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice { - deviceMap := make(map[string]*evdev.InputDevice) - - for _, deviceConfig := range parser.config.Devices { - if strings.ToLower(deviceConfig.Type) != DeviceTypeVirtual { - continue - } - - name := fmt.Sprintf("joyful-%s", deviceConfig.Name) - device, err := evdev.CreateDevice( - name, - // TODO: who knows what these should actually be - evdev.InputID{ - BusType: 0x03, - Vendor: 0x4711, - Product: 0x0816, - Version: 1, - }, - map[evdev.EvType][]evdev.EvCode{ - evdev.EV_KEY: makeButtons(int(deviceConfig.Buttons)), - evdev.EV_ABS: makeAxes(int(deviceConfig.Axes)), - }, - ) - - if err != nil { - logger.LogIfError(err, "Failed to create virtual device") - continue - } - - deviceMap[deviceConfig.Name] = device - logger.Log(fmt.Sprintf("Created virtual device '%s'", name)) - } - - return deviceMap -} - -// ConnectPhysicalDevices will create InputDevices corresponding to any registered -// devices with type = physical. It will also attempt to acquire exclusive access -// to those devices, to prevent the same inputs from being read on multiple devices. -// -// This function assumes you have already called Parse() on the config directory. -// -// This function should only be called once. -func (parser *ConfigParser) ConnectPhysicalDevices() map[string]*evdev.InputDevice { - deviceMap := make(map[string]*evdev.InputDevice) - - for _, deviceConfig := range parser.config.Devices { - if strings.ToLower(deviceConfig.Type) != DeviceTypePhysical { - continue - } - - device, err := evdev.OpenByName(deviceConfig.DeviceName) - if err != nil { - logger.LogError(err, "Failed to open physical device, skipping. Confirm the device name with 'evlist'. Watch out for spaces.") - continue - } - - // TODO: grab exclusive access to device - - logger.Log(fmt.Sprintf("Connected to '%s' as '%s'", deviceConfig.DeviceName, deviceConfig.Name)) - deviceMap[deviceConfig.Name] = device - } - - return deviceMap -} - -func makeButtons(numButtons int) []evdev.EvCode { - if numButtons > 56 { - numButtons = 56 - logger.Log("Limiting virtual device buttons to 56") - } - - buttons := make([]evdev.EvCode, numButtons) - - startCode := 0x120 - for i := 0; i < numButtons && i < 16; i++ { - buttons[i] = evdev.EvCode(startCode + i) - } - - if numButtons > 16 { - startCode = 0x2c0 - for i := 0; i < numButtons-16; i++ { - buttons[16+i] = evdev.EvCode(startCode + i) - } - } - - return buttons -} - -func makeAxes(numAxes int) []evdev.EvCode { - if numAxes > 8 { - numAxes = 8 - logger.Log("Limiting virtual device axes to 8") - } - - axes := make([]evdev.EvCode, numAxes) - for i := 0; i < numAxes; i++ { - axes[i] = evdev.EvCode(i) - } - - return axes -} diff --git a/internal/config/devices.go b/internal/config/devices.go new file mode 100644 index 0000000..e4157e2 --- /dev/null +++ b/internal/config/devices.go @@ -0,0 +1,118 @@ +package config + +import ( + "fmt" + "strings" + + "git.annabunches.net/annabunches/joyful/internal/logger" + "github.com/holoplot/go-evdev" +) + +// CreateVirtualDevices 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 should only be called once, unless you want to create duplicate devices for some reason. +func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice { + deviceMap := make(map[string]*evdev.InputDevice) + + for _, deviceConfig := range parser.config.Devices { + if strings.ToLower(deviceConfig.Type) != DeviceTypeVirtual { + continue + } + + name := fmt.Sprintf("joyful-%s", deviceConfig.Name) + device, err := evdev.CreateDevice( + name, + // TODO: who knows what these should actually be + evdev.InputID{ + BusType: 0x03, + Vendor: 0x4711, + Product: 0x0816, + Version: 1, + }, + map[evdev.EvType][]evdev.EvCode{ + evdev.EV_KEY: makeButtons(int(deviceConfig.Buttons)), + evdev.EV_ABS: makeAxes(int(deviceConfig.Axes)), + }, + ) + + if err != nil { + logger.LogIfError(err, "Failed to create virtual device") + continue + } + + deviceMap[deviceConfig.Name] = device + logger.Log(fmt.Sprintf("Created virtual device '%s'", name)) + } + + return deviceMap +} + +// ConnectPhysicalDevices will create InputDevices corresponding to any registered +// devices with type = physical. It will also attempt to acquire exclusive access +// to those devices, to prevent the same inputs from being read on multiple devices. +// +// This function assumes you have already called Parse() on the config directory. +// +// This function should only be called once. +func (parser *ConfigParser) ConnectPhysicalDevices() map[string]*evdev.InputDevice { + deviceMap := make(map[string]*evdev.InputDevice) + + for _, deviceConfig := range parser.config.Devices { + if strings.ToLower(deviceConfig.Type) != DeviceTypePhysical { + continue + } + + device, err := evdev.OpenByName(deviceConfig.DeviceName) + if err != nil { + logger.LogError(err, "Failed to open physical device, skipping. Confirm the device name with 'evlist'. Watch out for spaces.") + continue + } + + // TODO: grab exclusive access to device (add config option) + + logger.Log(fmt.Sprintf("Connected to '%s' as '%s'", deviceConfig.DeviceName, deviceConfig.Name)) + deviceMap[deviceConfig.Name] = device + } + + return deviceMap +} + +func makeButtons(numButtons int) []evdev.EvCode { + if numButtons > 56 { + numButtons = 56 + logger.Log("Limiting virtual device buttons to 56") + } + + buttons := make([]evdev.EvCode, numButtons) + + startCode := 0x120 + for i := 0; i < numButtons && i < 16; i++ { + buttons[i] = evdev.EvCode(startCode + i) + } + + if numButtons > 16 { + startCode = 0x2c0 + for i := 0; i < numButtons-16; i++ { + buttons[16+i] = evdev.EvCode(startCode + i) + } + } + + return buttons +} + +func makeAxes(numAxes int) []evdev.EvCode { + if numAxes > 8 { + numAxes = 8 + logger.Log("Limiting virtual device axes to 8") + } + + axes := make([]evdev.EvCode, numAxes) + for i := 0; i < numAxes; i++ { + axes[i] = evdev.EvCode(i) + } + + return axes +} diff --git a/internal/config/rules.go b/internal/config/rules.go index 3d7c478..6a5fa4d 100644 --- a/internal/config/rules.go +++ b/internal/config/rules.go @@ -9,6 +9,11 @@ import ( "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[*evdev.InputDevice][]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[*evdev.InputDevice]map[evdev.InputType]map[evdev.InputCode][]mappingrule.MappingRule +// For very large rule-bases this may be helpful for staying performant. func (parser *ConfigParser) BuildRules(pDevs map[string]*evdev.InputDevice, vDevs map[string]*evdev.InputDevice) []mappingrules.MappingRule { rules := make([]mappingrules.MappingRule, 0) @@ -49,8 +54,8 @@ func makeSimpleRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, MappingRuleBase: mappingrules.MappingRuleBase{ Output: output, }, - Input: input, - Name: ruleConfig.Name, + Input: input, + Name: ruleConfig.Name, }, nil } @@ -74,7 +79,7 @@ func makeComboRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, v Output: output, }, Inputs: inputs, - State: 0, + State: 0, Name: ruleConfig.Name, }, nil } @@ -94,9 +99,9 @@ func makeLatchedRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, MappingRuleBase: mappingrules.MappingRuleBase{ Output: output, }, - Input: input, - Name: ruleConfig.Name, - State: false, + Input: input, + Name: ruleConfig.Name, + State: false, }, nil }