(WIP) Move rule initialization into rule package.
This commit is contained in:
parent
727985f91c
commit
9e4062ba30
21 changed files with 366 additions and 489 deletions
|
@ -93,6 +93,11 @@ func initVirtualBuffers(config *configparser.Config) (map[string]*evdev.InputDev
|
|||
return vDevicesByName, vBuffersByName, vBuffersByDevice
|
||||
}
|
||||
|
||||
// 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[Device][]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[<struct of *inputdevice, type, and code>][]rule
|
||||
// For very large rule-bases this may be helpful for staying performant.
|
||||
func loadRules(
|
||||
config *configparser.Config,
|
||||
pDevices map[string]*evdev.InputDevice,
|
||||
|
@ -103,8 +108,21 @@ func loadRules(
|
|||
eventChannel := make(chan ChannelEvent, 1000)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Setup device mapping for the mappingrules package
|
||||
pDevs := mappingrules.ConvertDeviceMap(pDevices)
|
||||
vDevs := mappingrules.ConvertDeviceMap(vDevices)
|
||||
|
||||
// Initialize rules
|
||||
rules := configparser.InitRules(config.Rules, pDevices, vDevices, modes)
|
||||
rules := make([]mappingrules.MappingRule, 0)
|
||||
for _, ruleConfig := range config.Rules {
|
||||
newRule, err := mappingrules.NewRule(ruleConfig, pDevs, vDevs, modes)
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to create rule, skipping")
|
||||
continue
|
||||
}
|
||||
rules = append(rules, newRule)
|
||||
}
|
||||
|
||||
logger.Logf("Created %d mapping rules.", len(rules))
|
||||
|
||||
// start listening for events on devices and timers
|
||||
|
|
|
@ -1,15 +1,3 @@
|
|||
// The ConfigParser is the main structure you'll interact with when using this package.
|
||||
//
|
||||
// Example usage:
|
||||
// config := &config.ConfigParser{}
|
||||
// config.Parse(<some directory containing YAML files>)
|
||||
// virtualDevices := config.CreateVirtualDevices()
|
||||
// physicalDevices := config.ConnectVirtualDevices()
|
||||
// modes := config.GetModes()
|
||||
// rules := config.BuildRules(physicalDevices, virtualDevices, modes)
|
||||
//
|
||||
// nb: there are methods defined on ConfigParser in other files in this package!
|
||||
|
||||
package configparser
|
||||
|
||||
import (
|
||||
|
@ -22,60 +10,6 @@ import (
|
|||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
type ConfigParser struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// Parse all the config files and store the config data for further use
|
||||
func (parser *ConfigParser) Parse(directory string) error {
|
||||
parser.config = Config{}
|
||||
|
||||
// Find the config files in the directory
|
||||
dirEntries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
err = os.Mkdir(directory, 0755)
|
||||
if err != nil {
|
||||
return errors.New("Failed to create config directory at " + directory)
|
||||
}
|
||||
}
|
||||
|
||||
// Open each yaml file and add its contents to the global config
|
||||
for _, file := range dirEntries {
|
||||
name := file.Name()
|
||||
if file.IsDir() || !(strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml")) {
|
||||
continue
|
||||
}
|
||||
|
||||
filePath := filepath.Join(directory, name)
|
||||
if strings.HasSuffix(filePath, ".yaml") || strings.HasSuffix(filePath, ".yml") {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
logger.LogError(err, "Error while opening config file")
|
||||
continue
|
||||
}
|
||||
newConfig := Config{}
|
||||
err = yaml.Unmarshal(data, &newConfig)
|
||||
logger.LogIfError(err, "Error parsing YAML")
|
||||
parser.config.Rules = append(parser.config.Rules, newConfig.Rules...)
|
||||
parser.config.Devices = append(parser.config.Devices, newConfig.Devices...)
|
||||
parser.config.Modes = append(parser.config.Modes, newConfig.Modes...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parser.config.Devices) == 0 {
|
||||
return errors.New("Found no devices in configuration. Please add configuration at " + directory)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (parser *ConfigParser) GetModes() []string {
|
||||
if len(parser.config.Modes) == 0 {
|
||||
return []string{"*"}
|
||||
}
|
||||
return parser.config.Modes
|
||||
}
|
||||
|
||||
func ParseConfig(directory string) (*Config, error) {
|
||||
config := new(Config)
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
// InitPhysicalDevices will create InputDevices corresponding to any registered
|
||||
// devices with type = physical.
|
||||
//
|
||||
// This function assumes Parse() has been called.
|
||||
//
|
||||
// This function should only be called once.
|
||||
func (parser *ConfigParser) InitPhysicalDevices() map[string]*evdev.InputDevice {
|
||||
deviceMap := make(map[string]*evdev.InputDevice)
|
||||
|
||||
for _, deviceConfig := range parser.config.Devices {
|
||||
if strings.ToLower(deviceConfig.Type) != DeviceTypePhysical {
|
||||
continue
|
||||
}
|
||||
|
||||
deviceConfig := deviceConfig.Config.(DeviceConfigPhysical)
|
||||
|
||||
var infoName string
|
||||
var device *evdev.InputDevice
|
||||
var err error
|
||||
|
||||
if deviceConfig.DevicePath != "" {
|
||||
infoName = deviceConfig.DevicePath
|
||||
device, err = evdev.Open(deviceConfig.DevicePath)
|
||||
} else {
|
||||
infoName = deviceConfig.DeviceName
|
||||
device, err = evdev.OpenByName(deviceConfig.DeviceName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to open physical device, skipping. Confirm the device name or path with 'evinfo'")
|
||||
continue
|
||||
}
|
||||
|
||||
if deviceConfig.Lock {
|
||||
logger.LogDebugf("Locking device '%s'", infoName)
|
||||
err := device.Grab()
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to grab device for exclusive access")
|
||||
}
|
||||
}
|
||||
|
||||
logger.Log(fmt.Sprintf("Connected to '%s' as '%s'", infoName, deviceConfig.Name))
|
||||
deviceMap[deviceConfig.Name] = device
|
||||
}
|
||||
|
||||
return deviceMap
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
|
||||
type Device interface {
|
||||
AbsInfos() (map[evdev.EvCode]evdev.AbsInfo, error)
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||
"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[Device][]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[<struct of *inputdevice, type, and code>][]rule
|
||||
// For very large rule-bases this may be helpful for staying performant.
|
||||
func InitRules(config []RuleConfig, pInputDevs map[string]*evdev.InputDevice, vInputDevs map[string]*evdev.InputDevice, modes []string) []mappingrules.MappingRule {
|
||||
rules := make([]mappingrules.MappingRule, 0)
|
||||
|
||||
// Golang can't inspect the concrete map type to determine interface conformance,
|
||||
// so we handle that here.
|
||||
pDevs := make(map[string]Device)
|
||||
for name, dev := range pInputDevs {
|
||||
pDevs[name] = dev
|
||||
}
|
||||
vDevs := make(map[string]Device)
|
||||
for name, dev := range vInputDevs {
|
||||
vDevs[name] = dev
|
||||
}
|
||||
|
||||
for _, ruleConfig := range config {
|
||||
var newRule mappingrules.MappingRule
|
||||
var err error
|
||||
|
||||
if ok := validateModes(ruleConfig.Modes, modes); !ok {
|
||||
logger.Logf("Skipping rule '%s', mode list specifies undefined mode.", ruleConfig.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
base := mappingrules.NewMappingRuleBase(ruleConfig.Name, ruleConfig.Modes)
|
||||
|
||||
switch strings.ToLower(ruleConfig.Type) {
|
||||
case RuleTypeButton:
|
||||
newRule, err = makeMappingRuleButton(ruleConfig.Config.(RuleConfigButton), pDevs, vDevs, base)
|
||||
case RuleTypeButtonCombo:
|
||||
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.Config.(RuleConfigAxis), pDevs, vDevs, base)
|
||||
case RuleTypeAxisCombined:
|
||||
newRule, err = makeMappingRuleAxisCombined(ruleConfig.Config.(RuleConfigAxisCombined), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToButton:
|
||||
newRule, err = makeMappingRuleAxisToButton(ruleConfig.Config.(RuleConfigAxisToButton), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToRelaxis:
|
||||
newRule, err = makeMappingRuleAxisToRelaxis(ruleConfig.Config.(RuleConfigAxisToRelaxis), pDevs, vDevs, base)
|
||||
case RuleTypeModeSelect:
|
||||
newRule, err = makeMappingRuleModeSelect(ruleConfig.Config.(RuleConfigModeSelect), pDevs, modes, base)
|
||||
default:
|
||||
err = fmt.Errorf("bad rule type '%s' for rule '%s'", ruleConfig.Type, ruleConfig.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.LogErrorf(err, "Failed to build rule '%s'", ruleConfig.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, newRule)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleButton(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleCombo(ruleConfig RuleConfigButtonCombo,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButtonCombo, error) {
|
||||
|
||||
inputs := make([]*mappingrules.RuleTargetButton, 0)
|
||||
for _, inputConfig := range ruleConfig.Inputs {
|
||||
input, err := makeRuleTargetButton(inputConfig, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputs = append(inputs, input)
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleButtonCombo(base, inputs, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleLatched(ruleConfig RuleConfigButtonLatched,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleButtonLatched, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleButtonLatched(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxis(ruleConfig RuleConfigAxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxis, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetAxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxis(base, input, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisCombined(ruleConfig RuleConfigAxisCombined,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisCombined, error) {
|
||||
|
||||
inputLower, err := makeRuleTargetAxis(ruleConfig.InputLower, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputUpper, err := makeRuleTargetAxis(ruleConfig.InputUpper, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetAxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxisCombined(base, inputLower, inputUpper, output), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisToButton(ruleConfig RuleConfigAxisToButton,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisToButton, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxisToButton(base, input, output, ruleConfig.RepeatRateMin, ruleConfig.RepeatRateMax), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleAxisToRelaxis(ruleConfig RuleConfigAxisToRelaxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleAxisToRelaxis, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetRelaxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleAxisToRelaxis(base,
|
||||
input, output,
|
||||
ruleConfig.RepeatRateMin,
|
||||
ruleConfig.RepeatRateMax,
|
||||
ruleConfig.Increment), nil
|
||||
}
|
||||
|
||||
func makeMappingRuleModeSelect(ruleConfig RuleConfigModeSelect,
|
||||
pDevs map[string]Device,
|
||||
modes []string,
|
||||
base mappingrules.MappingRuleBase) (*mappingrules.MappingRuleModeSelect, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetModeSelect(ruleConfig.Output, modes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewMappingRuleModeSelect(base, input, output), nil
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package configparser
|
||||
|
||||
import "slices"
|
||||
|
||||
// validateModes checks the provided modes against a larger subset of modes (usually all defined ones)
|
||||
// and returns false if any of the modes are not defined.
|
||||
func validateModes(modes []string, allModes []string) bool {
|
||||
if len(modes) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
if !slices.Contains(allModes, mode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
package configparser
|
||||
package mappingrules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"git.annabunches.net/annabunches/joyful/internal/eventcodes"
|
||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
func makeRuleTargetButton(targetConfig RuleTargetConfigButton, devs map[string]Device) (*mappingrules.RuleTargetButton, error) {
|
||||
func makeRuleTargetButton(targetConfig configparser.RuleTargetConfigButton, devs map[string]Device) (*RuleTargetButton, error) {
|
||||
device, ok := devs[targetConfig.Device]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
||||
|
@ -20,7 +21,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfigButton, devs map[string]D
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetButton(
|
||||
return NewRuleTargetButton(
|
||||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
|
@ -28,7 +29,7 @@ func makeRuleTargetButton(targetConfig RuleTargetConfigButton, devs map[string]D
|
|||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Device) (*mappingrules.RuleTargetAxis, error) {
|
||||
func makeRuleTargetAxis(targetConfig configparser.RuleTargetConfigAxis, devs map[string]Device) (*RuleTargetAxis, error) {
|
||||
device, ok := devs[targetConfig.Device]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
||||
|
@ -48,7 +49,7 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Devic
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetAxis(
|
||||
return NewRuleTargetAxis(
|
||||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
|
@ -58,7 +59,7 @@ func makeRuleTargetAxis(targetConfig RuleTargetConfigAxis, devs map[string]Devic
|
|||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetRelaxis(targetConfig RuleTargetConfigRelaxis, devs map[string]Device) (*mappingrules.RuleTargetRelaxis, error) {
|
||||
func makeRuleTargetRelaxis(targetConfig configparser.RuleTargetConfigRelaxis, devs map[string]Device) (*RuleTargetRelaxis, error) {
|
||||
device, ok := devs[targetConfig.Device]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
||||
|
@ -69,25 +70,23 @@ func makeRuleTargetRelaxis(targetConfig RuleTargetConfigRelaxis, devs map[string
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetRelaxis(
|
||||
return NewRuleTargetRelaxis(
|
||||
targetConfig.Device,
|
||||
device,
|
||||
eventCode,
|
||||
)
|
||||
}
|
||||
|
||||
func makeRuleTargetModeSelect(targetConfig RuleTargetConfigModeSelect, allModes []string) (*mappingrules.RuleTargetModeSelect, error) {
|
||||
func makeRuleTargetModeSelect(targetConfig configparser.RuleTargetConfigModeSelect, allModes []string) (*RuleTargetModeSelect, error) {
|
||||
if ok := validateModes(targetConfig.Modes, allModes); !ok {
|
||||
return nil, errors.New("undefined mode in mode select list")
|
||||
}
|
||||
|
||||
return mappingrules.NewRuleTargetModeSelect(targetConfig.Modes)
|
||||
return NewRuleTargetModeSelect(targetConfig.Modes)
|
||||
}
|
||||
|
||||
// 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 RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) {
|
||||
func calculateDeadzones(targetConfig configparser.RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) {
|
||||
|
||||
var deadzoneStart, deadzoneEnd int32
|
||||
deadzoneStart = 0
|
||||
|
@ -101,8 +100,8 @@ func calculateDeadzones(targetConfig RuleTargetConfigAxis, device Device, axis e
|
|||
absInfoMap, err := device.AbsInfos()
|
||||
|
||||
if err != nil {
|
||||
min = mappingrules.AxisValueMin
|
||||
max = mappingrules.AxisValueMax
|
||||
min = AxisValueMin
|
||||
max = AxisValueMax
|
||||
} else {
|
||||
absInfo := absInfoMap[axis]
|
||||
min = absInfo.Minimum
|
||||
|
@ -139,3 +138,19 @@ func clampAndShift(start, end, min, max int32) (int32, int32) {
|
|||
|
||||
return start, end
|
||||
}
|
||||
|
||||
// validateModes checks the provided modes against a larger subset of modes (usually all defined ones)
|
||||
// and returns false if any of the modes are not defined.
|
||||
func validateModes(modes []string, allModes []string) bool {
|
||||
if len(modes) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
if !slices.Contains(allModes, mode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package configparser
|
||||
package mappingrules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
@ -48,7 +49,7 @@ func (t *MakeRuleTargetsTests) SetupSuite() {
|
|||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetButton() {
|
||||
config := RuleTargetConfigButton{Device: "test"}
|
||||
config := configparser.RuleTargetConfigButton{Device: "test"}
|
||||
|
||||
t.Run("Standard keycode", func() {
|
||||
config.Button = "BTN_TRIGGER"
|
||||
|
@ -103,7 +104,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
|
||||
for _, tc := range codeTestCases {
|
||||
t.Run(fmt.Sprintf("KeyCode %s", tc.input), func() {
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config := configparser.RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = tc.input
|
||||
rule, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.Nil(err)
|
||||
|
@ -113,14 +114,14 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
}
|
||||
|
||||
t.Run("Invalid code", func() {
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config := configparser.RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = "foo"
|
||||
_, err := makeRuleTargetAxis(config, t.devs)
|
||||
t.NotNil(err)
|
||||
})
|
||||
|
||||
t.Run("Invalid deadzone", func() {
|
||||
config := RuleTargetConfigAxis{Device: "test"}
|
||||
config := configparser.RuleTargetConfigAxis{Device: "test"}
|
||||
config.Axis = "x"
|
||||
config.DeadzoneEnd = 100
|
||||
config.DeadzoneStart = 1000
|
||||
|
@ -141,7 +142,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
|
||||
for _, tc := range relDeadzoneTestCases {
|
||||
t.Run(fmt.Sprintf("Relative Deadzone %d +- %d", tc.inCenter, tc.inSize), func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
config := configparser.RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: tc.inCenter,
|
||||
|
@ -156,7 +157,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
}
|
||||
|
||||
t.Run("Deadzone center/size invalid center", func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
config := configparser.RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: 20000,
|
||||
|
@ -179,7 +180,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
|
||||
for _, tc := range relDeadzonePercentTestCases {
|
||||
t.Run(fmt.Sprintf("Relative percent deadzone %d +- %d%%", tc.inCenter, tc.inSizePercent), func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
config := configparser.RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: tc.inCenter,
|
||||
|
@ -194,7 +195,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
}
|
||||
|
||||
t.Run("Deadzone center/percent invalid center", func() {
|
||||
config := RuleTargetConfigAxis{
|
||||
config := configparser.RuleTargetConfigAxis{
|
||||
Device: "test",
|
||||
Axis: "x",
|
||||
DeadzoneCenter: 20000,
|
||||
|
@ -206,7 +207,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
|||
}
|
||||
|
||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() {
|
||||
config := RuleTargetConfigRelaxis{Device: "test"}
|
||||
config := configparser.RuleTargetConfigRelaxis{Device: "test"}
|
||||
|
||||
t.Run("Standard keycode", func() {
|
||||
config.Axis = "REL_WHEEL"
|
62
internal/mappingrules/init_rules.go
Normal file
62
internal/mappingrules/init_rules.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package mappingrules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
func ConvertDeviceMap(inputDevs map[string]*evdev.InputDevice) map[string]Device {
|
||||
// Golang can't inspect the concrete map type to determine interface conformance,
|
||||
// so we handle that here.
|
||||
devices := make(map[string]Device)
|
||||
for name, dev := range inputDevs {
|
||||
devices[name] = dev
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
// NewRule parses a RuleConfig struct and creates and returns the appropriate rule type.
|
||||
// You can remap a map[string]*evdev.InputDevice to our interface type with ConvertDeviceMap
|
||||
func NewRule(config configparser.RuleConfig, pDevs map[string]Device, vDevs map[string]Device, modes []string) (MappingRule, error) {
|
||||
var newRule MappingRule
|
||||
var err error
|
||||
|
||||
if !validateModes(config.Modes, modes) {
|
||||
return nil, errors.New("mode list specifies undefined mode")
|
||||
}
|
||||
|
||||
base := NewMappingRuleBase(config.Name, config.Modes)
|
||||
|
||||
switch strings.ToLower(config.Type) {
|
||||
case RuleTypeButton:
|
||||
newRule, err = NewMappingRuleButton(config.Config.(configparser.RuleConfigButton), pDevs, vDevs, base)
|
||||
case RuleTypeButtonCombo:
|
||||
newRule, err = NewMappingRuleButtonCombo(config.Config.(configparser.RuleConfigButtonCombo), pDevs, vDevs, base)
|
||||
case RuleTypeButtonLatched:
|
||||
newRule, err = NewMappingRuleButtonLatched(config.Config.(configparser.RuleConfigButtonLatched), pDevs, vDevs, base)
|
||||
case RuleTypeAxis:
|
||||
newRule, err = NewMappingRuleAxis(config.Config.(configparser.RuleConfigAxis), pDevs, vDevs, base)
|
||||
case RuleTypeAxisCombined:
|
||||
newRule, err = NewMappingRuleAxisCombined(config.Config.(configparser.RuleConfigAxisCombined), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToButton:
|
||||
newRule, err = NewMappingRuleAxisToButton(config.Config.(configparser.RuleConfigAxisToButton), pDevs, vDevs, base)
|
||||
case RuleTypeAxisToRelaxis:
|
||||
newRule, err = NewMappingRuleAxisToRelaxis(config.Config.(configparser.RuleConfigAxisToRelaxis), pDevs, vDevs, base)
|
||||
case RuleTypeModeSelect:
|
||||
newRule, err = NewMappingRuleModeSelect(config.Config.(configparser.RuleConfigModeSelect), pDevs, modes, base)
|
||||
default:
|
||||
err = fmt.Errorf("bad rule type '%s' for rule '%s'", config.Type, config.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.LogErrorf(err, "Failed to build rule '%s'", config.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRule, nil
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
package mappingrules
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
import (
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
// A Simple Mapping Rule can map a button to a button or an axis to an axis.
|
||||
type MappingRuleAxis struct {
|
||||
|
@ -9,12 +12,26 @@ type MappingRuleAxis struct {
|
|||
Output *RuleTargetAxis
|
||||
}
|
||||
|
||||
func NewMappingRuleAxis(base MappingRuleBase, input *RuleTargetAxis, output *RuleTargetAxis) *MappingRuleAxis {
|
||||
func NewMappingRuleAxis(ruleConfig configparser.RuleConfigAxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleAxis, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetAxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleAxis{
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
Output: output,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleAxis) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package mappingrules
|
||||
|
||||
import (
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
@ -12,7 +13,26 @@ type MappingRuleAxisCombined struct {
|
|||
Output *RuleTargetAxis
|
||||
}
|
||||
|
||||
func NewMappingRuleAxisCombined(base MappingRuleBase, inputLower *RuleTargetAxis, inputUpper *RuleTargetAxis, output *RuleTargetAxis) *MappingRuleAxisCombined {
|
||||
func NewMappingRuleAxisCombined(ruleConfig configparser.RuleConfigAxisCombined,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleAxisCombined, error) {
|
||||
|
||||
inputLower, err := makeRuleTargetAxis(ruleConfig.InputLower, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputUpper, err := makeRuleTargetAxis(ruleConfig.InputUpper, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetAxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputLower.OutputMax = 0
|
||||
inputUpper.OutputMin = 0
|
||||
return &MappingRuleAxisCombined{
|
||||
|
@ -20,7 +40,7 @@ func NewMappingRuleAxisCombined(base MappingRuleBase, inputLower *RuleTargetAxis
|
|||
InputLower: inputLower,
|
||||
InputUpper: inputUpper,
|
||||
Output: output,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleAxisCombined) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||
|
|
|
@ -38,7 +38,9 @@ func (t *MappingRuleAxisCombinedTests) SetupTest() {
|
|||
}, nil)
|
||||
|
||||
t.inputTargetLower, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, true, 0, 0)
|
||||
t.inputTargetLower.OutputMax = 0
|
||||
t.inputTargetUpper, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_Y, false, 0, 0)
|
||||
t.inputTargetUpper.OutputMin = 0
|
||||
|
||||
t.outputDevice = &evdev.InputDevice{}
|
||||
t.outputTarget, _ = NewRuleTargetAxis("test-output", t.outputDevice, evdev.ABS_X, false, 0, 0)
|
||||
|
@ -63,13 +65,23 @@ func (t *MappingRuleAxisCombinedTests) TestNewMappingRuleAxisCombined() {
|
|||
evdev.ABS_Y: {Minimum: 0, Maximum: 10000},
|
||||
}, nil)
|
||||
|
||||
rule := NewMappingRuleAxisCombined(t.base, t.inputTargetLower, t.inputTargetUpper, t.outputTarget)
|
||||
rule := &MappingRuleAxisCombined{
|
||||
MappingRuleBase: t.base,
|
||||
InputLower: t.inputTargetLower,
|
||||
InputUpper: t.inputTargetUpper,
|
||||
Output: t.outputTarget,
|
||||
}
|
||||
t.EqualValues(0, rule.InputLower.OutputMax)
|
||||
t.EqualValues(0, rule.InputUpper.OutputMin)
|
||||
}
|
||||
|
||||
func (t *MappingRuleAxisCombinedTests) TestMatchEvent() {
|
||||
rule := NewMappingRuleAxisCombined(t.base, t.inputTargetLower, t.inputTargetUpper, t.outputTarget)
|
||||
rule := &MappingRuleAxisCombined{
|
||||
MappingRuleBase: t.base,
|
||||
InputLower: t.inputTargetLower,
|
||||
InputUpper: t.inputTargetUpper,
|
||||
Output: t.outputTarget,
|
||||
}
|
||||
|
||||
t.Run("Lower Input", func() {
|
||||
testCases := []struct{ in, out int32 }{
|
||||
|
|
|
@ -3,6 +3,7 @@ package mappingrules
|
|||
import (
|
||||
"time"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
"github.com/jonboulle/clockwork"
|
||||
)
|
||||
|
@ -23,20 +24,34 @@ type MappingRuleAxisToButton struct {
|
|||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
func NewMappingRuleAxisToButton(base MappingRuleBase, input *RuleTargetAxis, output *RuleTargetButton, repeatRateMin, repeatRateMax int) *MappingRuleAxisToButton {
|
||||
func NewMappingRuleAxisToButton(ruleConfig configparser.RuleConfigAxisToButton,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleAxisToButton, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleAxisToButton{
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
Output: output,
|
||||
RepeatRateMin: repeatRateMin,
|
||||
RepeatRateMax: repeatRateMax,
|
||||
RepeatRateMin: ruleConfig.RepeatRateMin,
|
||||
RepeatRateMax: ruleConfig.RepeatRateMax,
|
||||
lastEvent: time.Now(),
|
||||
nextEvent: NoNextEvent,
|
||||
repeat: repeatRateMin != 0 && repeatRateMax != 0,
|
||||
repeat: ruleConfig.RepeatRateMin != 0 && ruleConfig.RepeatRateMax != 0,
|
||||
pressed: false,
|
||||
active: false,
|
||||
clock: clockwork.NewRealClock(),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleAxisToButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||
|
|
|
@ -19,6 +19,44 @@ type MappingRuleAxisToButtonTests struct {
|
|||
base MappingRuleBase
|
||||
}
|
||||
|
||||
func TestRunnerMappingRuleAxisToButtonTests(t *testing.T) {
|
||||
suite.Run(t, new(MappingRuleAxisToButtonTests))
|
||||
}
|
||||
|
||||
// buildTimerRule creates a MappingRuleAxisToButton with a mocked clock
|
||||
func (t *MappingRuleAxisToButtonTests) buildTimerRule(
|
||||
repeatMin,
|
||||
repeatMax int,
|
||||
nextEvent time.Duration) (*MappingRuleAxisToButton, *clockwork.FakeClock) {
|
||||
|
||||
mockClock := clockwork.NewFakeClock()
|
||||
testRule := t.buildRule(repeatMin, repeatMax)
|
||||
testRule.clock = mockClock
|
||||
testRule.lastEvent = testRule.clock.Now()
|
||||
testRule.nextEvent = nextEvent
|
||||
if nextEvent != NoNextEvent {
|
||||
testRule.active = true
|
||||
}
|
||||
return testRule, mockClock
|
||||
}
|
||||
|
||||
// Todo: don't love this repeated logic...
|
||||
func (t *MappingRuleAxisToButtonTests) buildRule(repeatMin, repeatMax int) *MappingRuleAxisToButton {
|
||||
return &MappingRuleAxisToButton{
|
||||
MappingRuleBase: t.base,
|
||||
Input: t.inputRule,
|
||||
Output: t.outputRule,
|
||||
RepeatRateMin: repeatMin,
|
||||
RepeatRateMax: repeatMax,
|
||||
lastEvent: time.Now(),
|
||||
nextEvent: NoNextEvent,
|
||||
repeat: repeatMin != 0 && repeatMax != 0,
|
||||
pressed: false,
|
||||
active: false,
|
||||
clock: clockwork.NewRealClock(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MappingRuleAxisToButtonTests) SetupTest() {
|
||||
mode := "*"
|
||||
t.mode = &mode
|
||||
|
@ -40,7 +78,7 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() {
|
|||
|
||||
// A valid input should set a nextevent
|
||||
t.Run("No Repeat", func() {
|
||||
testRule := NewMappingRuleAxisToButton(t.base, t.inputRule, t.outputRule, 0, 0)
|
||||
testRule := t.buildRule(0, 0)
|
||||
|
||||
t.Run("Valid Input", func() {
|
||||
testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{
|
||||
|
@ -62,7 +100,7 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() {
|
|||
})
|
||||
|
||||
t.Run("Repeat", func() {
|
||||
testRule := NewMappingRuleAxisToButton(t.base, t.inputRule, t.outputRule, 750, 250)
|
||||
testRule := t.buildRule(750, 250)
|
||||
testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{
|
||||
Type: evdev.EV_ABS,
|
||||
Code: evdev.ABS_X,
|
||||
|
@ -90,7 +128,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
t.Run("No Repeat", func() {
|
||||
// Get event if called immediately
|
||||
t.Run("Event is available immediately", func() {
|
||||
testRule, _ := buildTimerRule(t, 0, 0, 0)
|
||||
testRule, _ := t.buildTimerRule(0, 0, 0)
|
||||
|
||||
event := testRule.TimerEvent()
|
||||
|
||||
|
@ -100,7 +138,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
|
||||
// Off event on second call
|
||||
t.Run("Event emits off on second call", func() {
|
||||
testRule, _ := buildTimerRule(t, 0, 0, 0)
|
||||
testRule, _ := t.buildTimerRule(0, 0, 0)
|
||||
|
||||
testRule.TimerEvent()
|
||||
event := testRule.TimerEvent()
|
||||
|
@ -111,7 +149,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
|
||||
// No further event, even if we wait a while
|
||||
t.Run("Additional events are not emitted while still active.", func() {
|
||||
testRule, mockClock := buildTimerRule(t, 0, 0, 0)
|
||||
testRule, mockClock := t.buildTimerRule(0, 0, 0)
|
||||
|
||||
testRule.TimerEvent()
|
||||
testRule.TimerEvent()
|
||||
|
@ -125,13 +163,13 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
|
||||
t.Run("Repeat", func() {
|
||||
t.Run("No event if called immediately", func() {
|
||||
testRule, _ := buildTimerRule(t, 100, 10, 50*time.Millisecond)
|
||||
testRule, _ := t.buildTimerRule(100, 10, 50*time.Millisecond)
|
||||
event := testRule.TimerEvent()
|
||||
t.Nil(event)
|
||||
})
|
||||
|
||||
t.Run("No event after 49ms", func() {
|
||||
testRule, mockClock := buildTimerRule(t, 100, 10, 50*time.Millisecond)
|
||||
testRule, mockClock := t.buildTimerRule(100, 10, 50*time.Millisecond)
|
||||
mockClock.Advance(49 * time.Millisecond)
|
||||
|
||||
event := testRule.TimerEvent()
|
||||
|
@ -140,7 +178,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
})
|
||||
|
||||
t.Run("Event after 50ms", func() {
|
||||
testRule, mockClock := buildTimerRule(t, 100, 10, 50*time.Millisecond)
|
||||
testRule, mockClock := t.buildTimerRule(100, 10, 50*time.Millisecond)
|
||||
mockClock.Advance(50 * time.Millisecond)
|
||||
|
||||
event := testRule.TimerEvent()
|
||||
|
@ -150,7 +188,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
})
|
||||
|
||||
t.Run("Additional event at 100ms", func() {
|
||||
testRule, mockClock := buildTimerRule(t, 100, 10, 50*time.Millisecond)
|
||||
testRule, mockClock := t.buildTimerRule(100, 10, 50*time.Millisecond)
|
||||
|
||||
mockClock.Advance(50 * time.Millisecond)
|
||||
testRule.TimerEvent()
|
||||
|
@ -163,24 +201,3 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunnerMappingRuleAxisToButtonTests(t *testing.T) {
|
||||
suite.Run(t, new(MappingRuleAxisToButtonTests))
|
||||
}
|
||||
|
||||
// buildTimerRule creates a MappingRuleAxisToButton with a mocked clock
|
||||
func buildTimerRule(t *MappingRuleAxisToButtonTests,
|
||||
repeatMin,
|
||||
repeatMax int,
|
||||
nextEvent time.Duration) (*MappingRuleAxisToButton, *clockwork.FakeClock) {
|
||||
|
||||
mockClock := clockwork.NewFakeClock()
|
||||
testRule := NewMappingRuleAxisToButton(t.base, t.inputRule, t.outputRule, repeatMin, repeatMax)
|
||||
testRule.clock = mockClock
|
||||
testRule.lastEvent = testRule.clock.Now()
|
||||
testRule.nextEvent = nextEvent
|
||||
if nextEvent != NoNextEvent {
|
||||
testRule.active = true
|
||||
}
|
||||
return testRule, mockClock
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package mappingrules
|
|||
import (
|
||||
"time"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
"github.com/jonboulle/clockwork"
|
||||
)
|
||||
|
@ -23,23 +24,32 @@ type MappingRuleAxisToRelaxis struct {
|
|||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
func NewMappingRuleAxisToRelaxis(
|
||||
base MappingRuleBase,
|
||||
input *RuleTargetAxis,
|
||||
output *RuleTargetRelaxis,
|
||||
repeatRateMin, repeatRateMax, increment int) *MappingRuleAxisToRelaxis {
|
||||
func NewMappingRuleAxisToRelaxis(ruleConfig configparser.RuleConfigAxisToRelaxis,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleAxisToRelaxis, error) {
|
||||
|
||||
input, err := makeRuleTargetAxis(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetRelaxis(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleAxisToRelaxis{
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
Output: output,
|
||||
RepeatRateMin: repeatRateMin,
|
||||
RepeatRateMax: repeatRateMax,
|
||||
Increment: int32(increment),
|
||||
RepeatRateMin: ruleConfig.RepeatRateMin,
|
||||
RepeatRateMax: ruleConfig.RepeatRateMax,
|
||||
Increment: int32(ruleConfig.Increment),
|
||||
lastEvent: time.Now(),
|
||||
nextEvent: NoNextEvent,
|
||||
clock: clockwork.NewRealClock(),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleAxisToRelaxis) MatchEvent(
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package mappingrules
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
import (
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"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 {
|
||||
|
@ -9,16 +12,26 @@ type MappingRuleButton struct {
|
|||
Output *RuleTargetButton
|
||||
}
|
||||
|
||||
func NewMappingRuleButton(
|
||||
base MappingRuleBase,
|
||||
input *RuleTargetButton,
|
||||
output *RuleTargetButton) *MappingRuleButton {
|
||||
func NewMappingRuleButton(ruleConfig configparser.RuleConfigButton,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleButton, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleButton{
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
Output: output,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package mappingrules
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
import (
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
// A Combo Mapping Rule can require multiple physical button presses for a single output button
|
||||
type MappingRuleButtonCombo struct {
|
||||
|
@ -10,17 +13,31 @@ type MappingRuleButtonCombo struct {
|
|||
State int
|
||||
}
|
||||
|
||||
func NewMappingRuleButtonCombo(
|
||||
base MappingRuleBase,
|
||||
inputs []*RuleTargetButton,
|
||||
output *RuleTargetButton) *MappingRuleButtonCombo {
|
||||
func NewMappingRuleButtonCombo(ruleConfig configparser.RuleConfigButtonCombo,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleButtonCombo, error) {
|
||||
|
||||
inputs := make([]*RuleTargetButton, 0)
|
||||
for _, inputConfig := range ruleConfig.Inputs {
|
||||
input, err := makeRuleTargetButton(inputConfig, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputs = append(inputs, input)
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleButtonCombo{
|
||||
MappingRuleBase: base,
|
||||
Inputs: inputs,
|
||||
Output: output,
|
||||
State: 0,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleButtonCombo) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package mappingrules
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
import (
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
type MappingRuleButtonLatched struct {
|
||||
MappingRuleBase
|
||||
|
@ -9,17 +12,27 @@ type MappingRuleButtonLatched struct {
|
|||
State bool
|
||||
}
|
||||
|
||||
func NewMappingRuleButtonLatched(
|
||||
base MappingRuleBase,
|
||||
input *RuleTargetButton,
|
||||
output *RuleTargetButton) *MappingRuleButtonLatched {
|
||||
func NewMappingRuleButtonLatched(ruleConfig configparser.RuleConfigButtonLatched,
|
||||
pDevs map[string]Device,
|
||||
vDevs map[string]Device,
|
||||
base MappingRuleBase) (*MappingRuleButtonLatched, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetButton(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleButtonLatched{
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
Output: output,
|
||||
State: false,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleButtonLatched) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||
|
|
|
@ -28,7 +28,11 @@ func (t *MappingRuleButtonTests) SetupTest() {
|
|||
func (t *MappingRuleButtonTests) TestMatchEvent() {
|
||||
inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, false)
|
||||
outputButton, _ := NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false)
|
||||
testRule := NewMappingRuleButton(t.base, inputButton, outputButton)
|
||||
testRule := &MappingRuleButton{
|
||||
MappingRuleBase: t.base,
|
||||
Input: inputButton,
|
||||
Output: outputButton,
|
||||
}
|
||||
|
||||
// A matching input event should produce an output event
|
||||
expected := &evdev.InputEvent{
|
||||
|
@ -58,7 +62,11 @@ func (t *MappingRuleButtonTests) TestMatchEvent() {
|
|||
func (t *MappingRuleButtonTests) TestMatchEventInverted() {
|
||||
inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, true)
|
||||
outputButton, _ := NewRuleTargetButton("", t.outputDevice, evdev.BTN_TRIGGER, false)
|
||||
testRule := NewMappingRuleButton(t.base, inputButton, outputButton)
|
||||
testRule := &MappingRuleButton{
|
||||
MappingRuleBase: t.base,
|
||||
Input: inputButton,
|
||||
Output: outputButton,
|
||||
}
|
||||
|
||||
// A matching input event should produce an output event
|
||||
expected := &evdev.InputEvent{
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package mappingrules
|
||||
|
||||
import "github.com/holoplot/go-evdev"
|
||||
import (
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
type MappingRuleModeSelect struct {
|
||||
MappingRuleBase
|
||||
|
@ -8,17 +11,26 @@ type MappingRuleModeSelect struct {
|
|||
Output *RuleTargetModeSelect
|
||||
}
|
||||
|
||||
func NewMappingRuleModeSelect(
|
||||
base MappingRuleBase,
|
||||
input *RuleTargetButton,
|
||||
output *RuleTargetModeSelect,
|
||||
) *MappingRuleModeSelect {
|
||||
func NewMappingRuleModeSelect(ruleConfig configparser.RuleConfigModeSelect,
|
||||
pDevs map[string]Device,
|
||||
modes []string,
|
||||
base MappingRuleBase) (*MappingRuleModeSelect, error) {
|
||||
|
||||
input, err := makeRuleTargetButton(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTargetModeSelect(ruleConfig.Output, modes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MappingRuleModeSelect{
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
Output: output,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rule *MappingRuleModeSelect) MatchEvent(
|
||||
|
|
12
internal/mappingrules/variables.go
Normal file
12
internal/mappingrules/variables.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package mappingrules
|
||||
|
||||
const (
|
||||
RuleTypeButton = "button"
|
||||
RuleTypeButtonCombo = "button-combo"
|
||||
RuleTypeButtonLatched = "button-latched"
|
||||
RuleTypeAxis = "axis"
|
||||
RuleTypeAxisCombined = "axis-combined"
|
||||
RuleTypeAxisToButton = "axis-to-button"
|
||||
RuleTypeAxisToRelaxis = "axis-to-relaxis"
|
||||
RuleTypeModeSelect = "mode-select"
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue