(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
|
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(
|
func loadRules(
|
||||||
config *configparser.Config,
|
config *configparser.Config,
|
||||||
pDevices map[string]*evdev.InputDevice,
|
pDevices map[string]*evdev.InputDevice,
|
||||||
|
@ -103,8 +108,21 @@ func loadRules(
|
||||||
eventChannel := make(chan ChannelEvent, 1000)
|
eventChannel := make(chan ChannelEvent, 1000)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Setup device mapping for the mappingrules package
|
||||||
|
pDevs := mappingrules.ConvertDeviceMap(pDevices)
|
||||||
|
vDevs := mappingrules.ConvertDeviceMap(vDevices)
|
||||||
|
|
||||||
// Initialize rules
|
// 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))
|
logger.Logf("Created %d mapping rules.", len(rules))
|
||||||
|
|
||||||
// start listening for events on devices and timers
|
// 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
|
package configparser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -22,60 +10,6 @@ import (
|
||||||
"github.com/goccy/go-yaml"
|
"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) {
|
func ParseConfig(directory string) (*Config, error) {
|
||||||
config := new(Config)
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
"git.annabunches.net/annabunches/joyful/internal/eventcodes"
|
"git.annabunches.net/annabunches/joyful/internal/eventcodes"
|
||||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
|
||||||
"github.com/holoplot/go-evdev"
|
"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]
|
device, ok := devs[targetConfig.Device]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappingrules.NewRuleTargetButton(
|
return NewRuleTargetButton(
|
||||||
targetConfig.Device,
|
targetConfig.Device,
|
||||||
device,
|
device,
|
||||||
eventCode,
|
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]
|
device, ok := devs[targetConfig.Device]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappingrules.NewRuleTargetAxis(
|
return NewRuleTargetAxis(
|
||||||
targetConfig.Device,
|
targetConfig.Device,
|
||||||
device,
|
device,
|
||||||
eventCode,
|
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]
|
device, ok := devs[targetConfig.Device]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("non-existent device '%s'", targetConfig.Device)
|
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappingrules.NewRuleTargetRelaxis(
|
return NewRuleTargetRelaxis(
|
||||||
targetConfig.Device,
|
targetConfig.Device,
|
||||||
device,
|
device,
|
||||||
eventCode,
|
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 {
|
if ok := validateModes(targetConfig.Modes, allModes); !ok {
|
||||||
return nil, errors.New("undefined mode in mode select list")
|
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
|
// 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,
|
func calculateDeadzones(targetConfig configparser.RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) {
|
||||||
// passing even more parameters to NewRuleTargetAxis feels terrible
|
|
||||||
func calculateDeadzones(targetConfig RuleTargetConfigAxis, device Device, axis evdev.EvCode) (int32, int32, error) {
|
|
||||||
|
|
||||||
var deadzoneStart, deadzoneEnd int32
|
var deadzoneStart, deadzoneEnd int32
|
||||||
deadzoneStart = 0
|
deadzoneStart = 0
|
||||||
|
@ -101,8 +100,8 @@ func calculateDeadzones(targetConfig RuleTargetConfigAxis, device Device, axis e
|
||||||
absInfoMap, err := device.AbsInfos()
|
absInfoMap, err := device.AbsInfos()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
min = mappingrules.AxisValueMin
|
min = AxisValueMin
|
||||||
max = mappingrules.AxisValueMax
|
max = AxisValueMax
|
||||||
} else {
|
} else {
|
||||||
absInfo := absInfoMap[axis]
|
absInfo := absInfoMap[axis]
|
||||||
min = absInfo.Minimum
|
min = absInfo.Minimum
|
||||||
|
@ -139,3 +138,19 @@ func clampAndShift(start, end, min, max int32) (int32, int32) {
|
||||||
|
|
||||||
return start, end
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
"github.com/holoplot/go-evdev"
|
"github.com/holoplot/go-evdev"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
@ -48,7 +49,7 @@ func (t *MakeRuleTargetsTests) SetupSuite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetButton() {
|
func (t *MakeRuleTargetsTests) TestMakeRuleTargetButton() {
|
||||||
config := RuleTargetConfigButton{Device: "test"}
|
config := configparser.RuleTargetConfigButton{Device: "test"}
|
||||||
|
|
||||||
t.Run("Standard keycode", func() {
|
t.Run("Standard keycode", func() {
|
||||||
config.Button = "BTN_TRIGGER"
|
config.Button = "BTN_TRIGGER"
|
||||||
|
@ -103,7 +104,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
|
|
||||||
for _, tc := range codeTestCases {
|
for _, tc := range codeTestCases {
|
||||||
t.Run(fmt.Sprintf("KeyCode %s", tc.input), func() {
|
t.Run(fmt.Sprintf("KeyCode %s", tc.input), func() {
|
||||||
config := RuleTargetConfigAxis{Device: "test"}
|
config := configparser.RuleTargetConfigAxis{Device: "test"}
|
||||||
config.Axis = tc.input
|
config.Axis = tc.input
|
||||||
rule, err := makeRuleTargetAxis(config, t.devs)
|
rule, err := makeRuleTargetAxis(config, t.devs)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
@ -113,14 +114,14 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Invalid code", func() {
|
t.Run("Invalid code", func() {
|
||||||
config := RuleTargetConfigAxis{Device: "test"}
|
config := configparser.RuleTargetConfigAxis{Device: "test"}
|
||||||
config.Axis = "foo"
|
config.Axis = "foo"
|
||||||
_, err := makeRuleTargetAxis(config, t.devs)
|
_, err := makeRuleTargetAxis(config, t.devs)
|
||||||
t.NotNil(err)
|
t.NotNil(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Invalid deadzone", func() {
|
t.Run("Invalid deadzone", func() {
|
||||||
config := RuleTargetConfigAxis{Device: "test"}
|
config := configparser.RuleTargetConfigAxis{Device: "test"}
|
||||||
config.Axis = "x"
|
config.Axis = "x"
|
||||||
config.DeadzoneEnd = 100
|
config.DeadzoneEnd = 100
|
||||||
config.DeadzoneStart = 1000
|
config.DeadzoneStart = 1000
|
||||||
|
@ -141,7 +142,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
|
|
||||||
for _, tc := range relDeadzoneTestCases {
|
for _, tc := range relDeadzoneTestCases {
|
||||||
t.Run(fmt.Sprintf("Relative Deadzone %d +- %d", tc.inCenter, tc.inSize), func() {
|
t.Run(fmt.Sprintf("Relative Deadzone %d +- %d", tc.inCenter, tc.inSize), func() {
|
||||||
config := RuleTargetConfigAxis{
|
config := configparser.RuleTargetConfigAxis{
|
||||||
Device: "test",
|
Device: "test",
|
||||||
Axis: "x",
|
Axis: "x",
|
||||||
DeadzoneCenter: tc.inCenter,
|
DeadzoneCenter: tc.inCenter,
|
||||||
|
@ -156,7 +157,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Deadzone center/size invalid center", func() {
|
t.Run("Deadzone center/size invalid center", func() {
|
||||||
config := RuleTargetConfigAxis{
|
config := configparser.RuleTargetConfigAxis{
|
||||||
Device: "test",
|
Device: "test",
|
||||||
Axis: "x",
|
Axis: "x",
|
||||||
DeadzoneCenter: 20000,
|
DeadzoneCenter: 20000,
|
||||||
|
@ -179,7 +180,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
|
|
||||||
for _, tc := range relDeadzonePercentTestCases {
|
for _, tc := range relDeadzonePercentTestCases {
|
||||||
t.Run(fmt.Sprintf("Relative percent deadzone %d +- %d%%", tc.inCenter, tc.inSizePercent), func() {
|
t.Run(fmt.Sprintf("Relative percent deadzone %d +- %d%%", tc.inCenter, tc.inSizePercent), func() {
|
||||||
config := RuleTargetConfigAxis{
|
config := configparser.RuleTargetConfigAxis{
|
||||||
Device: "test",
|
Device: "test",
|
||||||
Axis: "x",
|
Axis: "x",
|
||||||
DeadzoneCenter: tc.inCenter,
|
DeadzoneCenter: tc.inCenter,
|
||||||
|
@ -194,7 +195,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Deadzone center/percent invalid center", func() {
|
t.Run("Deadzone center/percent invalid center", func() {
|
||||||
config := RuleTargetConfigAxis{
|
config := configparser.RuleTargetConfigAxis{
|
||||||
Device: "test",
|
Device: "test",
|
||||||
Axis: "x",
|
Axis: "x",
|
||||||
DeadzoneCenter: 20000,
|
DeadzoneCenter: 20000,
|
||||||
|
@ -206,7 +207,7 @@ func (t *MakeRuleTargetsTests) TestMakeRuleTargetAxis() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() {
|
func (t *MakeRuleTargetsTests) TestMakeRuleTargetRelaxis() {
|
||||||
config := RuleTargetConfigRelaxis{Device: "test"}
|
config := configparser.RuleTargetConfigRelaxis{Device: "test"}
|
||||||
|
|
||||||
t.Run("Standard keycode", func() {
|
t.Run("Standard keycode", func() {
|
||||||
config.Axis = "REL_WHEEL"
|
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
|
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.
|
// A Simple Mapping Rule can map a button to a button or an axis to an axis.
|
||||||
type MappingRuleAxis struct {
|
type MappingRuleAxis struct {
|
||||||
|
@ -9,12 +12,26 @@ type MappingRuleAxis struct {
|
||||||
Output *RuleTargetAxis
|
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{
|
return &MappingRuleAxis{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Input: input,
|
Input: input,
|
||||||
Output: output,
|
Output: output,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleAxis) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
func (rule *MappingRuleAxis) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package mappingrules
|
package mappingrules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||||
"github.com/holoplot/go-evdev"
|
"github.com/holoplot/go-evdev"
|
||||||
)
|
)
|
||||||
|
@ -12,7 +13,26 @@ type MappingRuleAxisCombined struct {
|
||||||
Output *RuleTargetAxis
|
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
|
inputLower.OutputMax = 0
|
||||||
inputUpper.OutputMin = 0
|
inputUpper.OutputMin = 0
|
||||||
return &MappingRuleAxisCombined{
|
return &MappingRuleAxisCombined{
|
||||||
|
@ -20,7 +40,7 @@ func NewMappingRuleAxisCombined(base MappingRuleBase, inputLower *RuleTargetAxis
|
||||||
InputLower: inputLower,
|
InputLower: inputLower,
|
||||||
InputUpper: inputUpper,
|
InputUpper: inputUpper,
|
||||||
Output: output,
|
Output: output,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleAxisCombined) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
func (rule *MappingRuleAxisCombined) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||||
|
|
|
@ -38,7 +38,9 @@ func (t *MappingRuleAxisCombinedTests) SetupTest() {
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
t.inputTargetLower, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_X, true, 0, 0)
|
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, _ = NewRuleTargetAxis("test-input", t.inputDevice, evdev.ABS_Y, false, 0, 0)
|
||||||
|
t.inputTargetUpper.OutputMin = 0
|
||||||
|
|
||||||
t.outputDevice = &evdev.InputDevice{}
|
t.outputDevice = &evdev.InputDevice{}
|
||||||
t.outputTarget, _ = NewRuleTargetAxis("test-output", t.outputDevice, evdev.ABS_X, false, 0, 0)
|
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},
|
evdev.ABS_Y: {Minimum: 0, Maximum: 10000},
|
||||||
}, nil)
|
}, 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.InputLower.OutputMax)
|
||||||
t.EqualValues(0, rule.InputUpper.OutputMin)
|
t.EqualValues(0, rule.InputUpper.OutputMin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MappingRuleAxisCombinedTests) TestMatchEvent() {
|
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() {
|
t.Run("Lower Input", func() {
|
||||||
testCases := []struct{ in, out int32 }{
|
testCases := []struct{ in, out int32 }{
|
||||||
|
|
|
@ -3,6 +3,7 @@ package mappingrules
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
"github.com/holoplot/go-evdev"
|
"github.com/holoplot/go-evdev"
|
||||||
"github.com/jonboulle/clockwork"
|
"github.com/jonboulle/clockwork"
|
||||||
)
|
)
|
||||||
|
@ -23,20 +24,34 @@ type MappingRuleAxisToButton struct {
|
||||||
clock clockwork.Clock
|
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{
|
return &MappingRuleAxisToButton{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Input: input,
|
Input: input,
|
||||||
Output: output,
|
Output: output,
|
||||||
RepeatRateMin: repeatRateMin,
|
RepeatRateMin: ruleConfig.RepeatRateMin,
|
||||||
RepeatRateMax: repeatRateMax,
|
RepeatRateMax: ruleConfig.RepeatRateMax,
|
||||||
lastEvent: time.Now(),
|
lastEvent: time.Now(),
|
||||||
nextEvent: NoNextEvent,
|
nextEvent: NoNextEvent,
|
||||||
repeat: repeatRateMin != 0 && repeatRateMax != 0,
|
repeat: ruleConfig.RepeatRateMin != 0 && ruleConfig.RepeatRateMax != 0,
|
||||||
pressed: false,
|
pressed: false,
|
||||||
active: false,
|
active: false,
|
||||||
clock: clockwork.NewRealClock(),
|
clock: clockwork.NewRealClock(),
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleAxisToButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
func (rule *MappingRuleAxisToButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||||
|
|
|
@ -19,6 +19,44 @@ type MappingRuleAxisToButtonTests struct {
|
||||||
base MappingRuleBase
|
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() {
|
func (t *MappingRuleAxisToButtonTests) SetupTest() {
|
||||||
mode := "*"
|
mode := "*"
|
||||||
t.mode = &mode
|
t.mode = &mode
|
||||||
|
@ -40,7 +78,7 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() {
|
||||||
|
|
||||||
// A valid input should set a nextevent
|
// A valid input should set a nextevent
|
||||||
t.Run("No Repeat", func() {
|
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() {
|
t.Run("Valid Input", func() {
|
||||||
testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{
|
testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{
|
||||||
|
@ -62,7 +100,7 @@ func (t *MappingRuleAxisToButtonTests) TestMatchEvent() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Repeat", func() {
|
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{
|
testRule.MatchEvent(t.inputDevice, &evdev.InputEvent{
|
||||||
Type: evdev.EV_ABS,
|
Type: evdev.EV_ABS,
|
||||||
Code: evdev.ABS_X,
|
Code: evdev.ABS_X,
|
||||||
|
@ -90,7 +128,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
||||||
t.Run("No Repeat", func() {
|
t.Run("No Repeat", func() {
|
||||||
// Get event if called immediately
|
// Get event if called immediately
|
||||||
t.Run("Event is available immediately", func() {
|
t.Run("Event is available immediately", func() {
|
||||||
testRule, _ := buildTimerRule(t, 0, 0, 0)
|
testRule, _ := t.buildTimerRule(0, 0, 0)
|
||||||
|
|
||||||
event := testRule.TimerEvent()
|
event := testRule.TimerEvent()
|
||||||
|
|
||||||
|
@ -100,7 +138,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
||||||
|
|
||||||
// Off event on second call
|
// Off event on second call
|
||||||
t.Run("Event emits off on second call", func() {
|
t.Run("Event emits off on second call", func() {
|
||||||
testRule, _ := buildTimerRule(t, 0, 0, 0)
|
testRule, _ := t.buildTimerRule(0, 0, 0)
|
||||||
|
|
||||||
testRule.TimerEvent()
|
testRule.TimerEvent()
|
||||||
event := testRule.TimerEvent()
|
event := testRule.TimerEvent()
|
||||||
|
@ -111,7 +149,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
||||||
|
|
||||||
// No further event, even if we wait a while
|
// No further event, even if we wait a while
|
||||||
t.Run("Additional events are not emitted while still active.", func() {
|
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()
|
||||||
testRule.TimerEvent()
|
testRule.TimerEvent()
|
||||||
|
@ -125,13 +163,13 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
||||||
|
|
||||||
t.Run("Repeat", func() {
|
t.Run("Repeat", func() {
|
||||||
t.Run("No event if called immediately", 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()
|
event := testRule.TimerEvent()
|
||||||
t.Nil(event)
|
t.Nil(event)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("No event after 49ms", func() {
|
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)
|
mockClock.Advance(49 * time.Millisecond)
|
||||||
|
|
||||||
event := testRule.TimerEvent()
|
event := testRule.TimerEvent()
|
||||||
|
@ -140,7 +178,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Event after 50ms", func() {
|
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)
|
mockClock.Advance(50 * time.Millisecond)
|
||||||
|
|
||||||
event := testRule.TimerEvent()
|
event := testRule.TimerEvent()
|
||||||
|
@ -150,7 +188,7 @@ func (t *MappingRuleAxisToButtonTests) TestTimerEvent() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Additional event at 100ms", func() {
|
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)
|
mockClock.Advance(50 * time.Millisecond)
|
||||||
testRule.TimerEvent()
|
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 (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
"github.com/holoplot/go-evdev"
|
"github.com/holoplot/go-evdev"
|
||||||
"github.com/jonboulle/clockwork"
|
"github.com/jonboulle/clockwork"
|
||||||
)
|
)
|
||||||
|
@ -23,23 +24,32 @@ type MappingRuleAxisToRelaxis struct {
|
||||||
clock clockwork.Clock
|
clock clockwork.Clock
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMappingRuleAxisToRelaxis(
|
func NewMappingRuleAxisToRelaxis(ruleConfig configparser.RuleConfigAxisToRelaxis,
|
||||||
base MappingRuleBase,
|
pDevs map[string]Device,
|
||||||
input *RuleTargetAxis,
|
vDevs map[string]Device,
|
||||||
output *RuleTargetRelaxis,
|
base MappingRuleBase) (*MappingRuleAxisToRelaxis, error) {
|
||||||
repeatRateMin, repeatRateMax, increment int) *MappingRuleAxisToRelaxis {
|
|
||||||
|
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{
|
return &MappingRuleAxisToRelaxis{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Input: input,
|
Input: input,
|
||||||
Output: output,
|
Output: output,
|
||||||
RepeatRateMin: repeatRateMin,
|
RepeatRateMin: ruleConfig.RepeatRateMin,
|
||||||
RepeatRateMax: repeatRateMax,
|
RepeatRateMax: ruleConfig.RepeatRateMax,
|
||||||
Increment: int32(increment),
|
Increment: int32(ruleConfig.Increment),
|
||||||
lastEvent: time.Now(),
|
lastEvent: time.Now(),
|
||||||
nextEvent: NoNextEvent,
|
nextEvent: NoNextEvent,
|
||||||
clock: clockwork.NewRealClock(),
|
clock: clockwork.NewRealClock(),
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleAxisToRelaxis) MatchEvent(
|
func (rule *MappingRuleAxisToRelaxis) MatchEvent(
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package mappingrules
|
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.
|
// A Simple Mapping Rule can map a button to a button or an axis to an axis.
|
||||||
type MappingRuleButton struct {
|
type MappingRuleButton struct {
|
||||||
|
@ -9,16 +12,26 @@ type MappingRuleButton struct {
|
||||||
Output *RuleTargetButton
|
Output *RuleTargetButton
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMappingRuleButton(
|
func NewMappingRuleButton(ruleConfig configparser.RuleConfigButton,
|
||||||
base MappingRuleBase,
|
pDevs map[string]Device,
|
||||||
input *RuleTargetButton,
|
vDevs map[string]Device,
|
||||||
output *RuleTargetButton) *MappingRuleButton {
|
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{
|
return &MappingRuleButton{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Input: input,
|
Input: input,
|
||||||
Output: output,
|
Output: output,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
func (rule *MappingRuleButton) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package mappingrules
|
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
|
// A Combo Mapping Rule can require multiple physical button presses for a single output button
|
||||||
type MappingRuleButtonCombo struct {
|
type MappingRuleButtonCombo struct {
|
||||||
|
@ -10,17 +13,31 @@ type MappingRuleButtonCombo struct {
|
||||||
State int
|
State int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMappingRuleButtonCombo(
|
func NewMappingRuleButtonCombo(ruleConfig configparser.RuleConfigButtonCombo,
|
||||||
base MappingRuleBase,
|
pDevs map[string]Device,
|
||||||
inputs []*RuleTargetButton,
|
vDevs map[string]Device,
|
||||||
output *RuleTargetButton) *MappingRuleButtonCombo {
|
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{
|
return &MappingRuleButtonCombo{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
Output: output,
|
Output: output,
|
||||||
State: 0,
|
State: 0,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleButtonCombo) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
func (rule *MappingRuleButtonCombo) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package mappingrules
|
package mappingrules
|
||||||
|
|
||||||
import "github.com/holoplot/go-evdev"
|
import (
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
|
"github.com/holoplot/go-evdev"
|
||||||
|
)
|
||||||
|
|
||||||
type MappingRuleButtonLatched struct {
|
type MappingRuleButtonLatched struct {
|
||||||
MappingRuleBase
|
MappingRuleBase
|
||||||
|
@ -9,17 +12,27 @@ type MappingRuleButtonLatched struct {
|
||||||
State bool
|
State bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMappingRuleButtonLatched(
|
func NewMappingRuleButtonLatched(ruleConfig configparser.RuleConfigButtonLatched,
|
||||||
base MappingRuleBase,
|
pDevs map[string]Device,
|
||||||
input *RuleTargetButton,
|
vDevs map[string]Device,
|
||||||
output *RuleTargetButton) *MappingRuleButtonLatched {
|
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{
|
return &MappingRuleButtonLatched{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Input: input,
|
Input: input,
|
||||||
Output: output,
|
Output: output,
|
||||||
State: false,
|
State: false,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleButtonLatched) MatchEvent(device Device, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
|
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() {
|
func (t *MappingRuleButtonTests) TestMatchEvent() {
|
||||||
inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, false)
|
inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, false)
|
||||||
outputButton, _ := NewRuleTargetButton("", t.outputDevice, 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
|
// A matching input event should produce an output event
|
||||||
expected := &evdev.InputEvent{
|
expected := &evdev.InputEvent{
|
||||||
|
@ -58,7 +62,11 @@ func (t *MappingRuleButtonTests) TestMatchEvent() {
|
||||||
func (t *MappingRuleButtonTests) TestMatchEventInverted() {
|
func (t *MappingRuleButtonTests) TestMatchEventInverted() {
|
||||||
inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, true)
|
inputButton, _ := NewRuleTargetButton("", t.inputDevice, evdev.BTN_TRIGGER, true)
|
||||||
outputButton, _ := NewRuleTargetButton("", t.outputDevice, 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
|
// A matching input event should produce an output event
|
||||||
expected := &evdev.InputEvent{
|
expected := &evdev.InputEvent{
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package mappingrules
|
package mappingrules
|
||||||
|
|
||||||
import "github.com/holoplot/go-evdev"
|
import (
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||||
|
"github.com/holoplot/go-evdev"
|
||||||
|
)
|
||||||
|
|
||||||
type MappingRuleModeSelect struct {
|
type MappingRuleModeSelect struct {
|
||||||
MappingRuleBase
|
MappingRuleBase
|
||||||
|
@ -8,17 +11,26 @@ type MappingRuleModeSelect struct {
|
||||||
Output *RuleTargetModeSelect
|
Output *RuleTargetModeSelect
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMappingRuleModeSelect(
|
func NewMappingRuleModeSelect(ruleConfig configparser.RuleConfigModeSelect,
|
||||||
base MappingRuleBase,
|
pDevs map[string]Device,
|
||||||
input *RuleTargetButton,
|
modes []string,
|
||||||
output *RuleTargetModeSelect,
|
base MappingRuleBase) (*MappingRuleModeSelect, error) {
|
||||||
) *MappingRuleModeSelect {
|
|
||||||
|
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{
|
return &MappingRuleModeSelect{
|
||||||
MappingRuleBase: base,
|
MappingRuleBase: base,
|
||||||
Input: input,
|
Input: input,
|
||||||
Output: output,
|
Output: output,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *MappingRuleModeSelect) MatchEvent(
|
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