Initial implementation of modes, though they're not quite working.
This commit is contained in:
parent
15b9fa6ac0
commit
cc37904fad
7 changed files with 116 additions and 67 deletions
|
@ -34,6 +34,7 @@ func initVirtualBuffers(config *config.ConfigParser) map[string]*virtualdevice.E
|
|||
return vBuffers
|
||||
}
|
||||
|
||||
// Extracts the evdev devices from a list of virtual buffers and returns them.
|
||||
func getVirtualDevices(buffers map[string]*virtualdevice.EventBuffer) map[string]*evdev.InputDevice {
|
||||
devices := make(map[string]*evdev.InputDevice)
|
||||
for name, buffer := range buffers {
|
||||
|
@ -79,6 +80,9 @@ func mapEvents(vBuffers map[string]*virtualdevice.EventBuffer, pDevices map[stri
|
|||
go eventWatcher(device, eventChannel)
|
||||
}
|
||||
|
||||
// initialize the mode variable
|
||||
mode := "main"
|
||||
|
||||
fmt.Println("Joyful Running! Press Ctrl+C to quit.")
|
||||
for {
|
||||
// Get an event (blocks if necessary)
|
||||
|
@ -95,7 +99,7 @@ func mapEvents(vBuffers map[string]*virtualdevice.EventBuffer, pDevices map[stri
|
|||
case evdev.EV_ABS:
|
||||
// We have a matchable event type. Check all the events
|
||||
for _, rule := range rules {
|
||||
outputEvent := rule.MatchEvent(wrapper.Device, wrapper.Event)
|
||||
outputEvent := rule.MatchEvent(wrapper.Device, wrapper.Event, &mode)
|
||||
if outputEvent == nil {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
// Example usage:
|
||||
// config := &config.ConfigParser{}
|
||||
// config.Parse(<some directory containing YAML files>)
|
||||
// virtualDevices, err := config.CreateVirtualDevices()
|
||||
// physicalDevices, err := config.ConnectVirtualDevices()
|
||||
// modes, err := config.GetModes()
|
||||
// rules, err := config.BuildRules(physicalDevices, virtualDevices, modes)
|
||||
// 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!
|
||||
|
||||
|
@ -58,7 +58,7 @@ func (parser *ConfigParser) Parse(directory string) error {
|
|||
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.Groups = append(parser.config.Groups, newConfig.Groups...)
|
||||
parser.config.Modes = append(parser.config.Modes, newConfig.Modes...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,3 +68,7 @@ func (parser *ConfigParser) Parse(directory string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (parser *ConfigParser) getModes() []string {
|
||||
return append([]string{"main"}, parser.config.Modes...)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
|
@ -12,54 +13,71 @@ import (
|
|||
// TODO: At some point it would *very likely* make sense to map each rule to all of the physical devices that can
|
||||
// trigger it, and return that instead. Something like a map[*evdev.InputDevice][]mappingrule.MappingRule.
|
||||
// This would speed up rule matching by only checking relevant rules for a given input event.
|
||||
// We could take this further and make it a map[*evdev.InputDevice]map[evdev.InputType]map[evdev.InputCode][]mappingrule.MappingRule
|
||||
// We could take this further and make it a map[<struct of *inputdevice, type, and code>][]rule
|
||||
// For very large rule-bases this may be helpful for staying performant.
|
||||
func (parser *ConfigParser) BuildRules(pDevs map[string]*evdev.InputDevice, vDevs map[string]*evdev.InputDevice) []mappingrules.MappingRule {
|
||||
rules := make([]mappingrules.MappingRule, 0)
|
||||
modes := parser.getModes()
|
||||
|
||||
for _, ruleConfig := range parser.config.Rules {
|
||||
var newRule mappingrules.MappingRule
|
||||
var err error
|
||||
|
||||
baseParams, err := setBaseRuleParameters(ruleConfig, vDevs, modes)
|
||||
if err != nil {
|
||||
logger.LogError(err, "couldn't set output parameters, skipping rule")
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Logf("DEBUG: Modes for rule '%s': %v", baseParams.Name, baseParams.Modes)
|
||||
|
||||
switch strings.ToLower(ruleConfig.Type) {
|
||||
case RuleTypeSimple:
|
||||
newRule, err = makeSimpleRule(ruleConfig, pDevs, vDevs)
|
||||
newRule, err = makeSimpleRule(ruleConfig, pDevs, baseParams)
|
||||
case RuleTypeCombo:
|
||||
newRule, err = makeComboRule(ruleConfig, pDevs, vDevs)
|
||||
newRule, err = makeComboRule(ruleConfig, pDevs, baseParams)
|
||||
case RuleTypeLatched:
|
||||
newRule, err = makeLatchedRule(ruleConfig, pDevs, vDevs)
|
||||
newRule, err = makeLatchedRule(ruleConfig, pDevs, baseParams)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.LogError(err, "")
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, newRule)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func makeSimpleRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, vDevs map[string]*evdev.InputDevice) (*mappingrules.SimpleMappingRule, error) {
|
||||
func setBaseRuleParameters(ruleConfig RuleConfig, vDevs map[string]*evdev.InputDevice, modes []string) (mappingrules.MappingRuleBase, error) {
|
||||
output, err := makeRuleTarget(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return mappingrules.MappingRuleBase{}, err
|
||||
}
|
||||
ruleModes := verifyModes(ruleConfig, modes)
|
||||
|
||||
return mappingrules.MappingRuleBase{
|
||||
Output: output,
|
||||
Modes: ruleModes,
|
||||
Name: ruleConfig.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeSimpleRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, base mappingrules.MappingRuleBase) (*mappingrules.SimpleMappingRule, error) {
|
||||
input, err := makeRuleTarget(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTarget(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mappingrules.SimpleMappingRule{
|
||||
MappingRuleBase: mappingrules.MappingRuleBase{
|
||||
Output: output,
|
||||
},
|
||||
Input: input,
|
||||
Name: ruleConfig.Name,
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeComboRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, vDevs map[string]*evdev.InputDevice) (*mappingrules.ComboMappingRule, error) {
|
||||
func makeComboRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, base mappingrules.MappingRuleBase) (*mappingrules.ComboMappingRule, error) {
|
||||
inputs := make([]mappingrules.RuleTarget, 0)
|
||||
for _, inputConfig := range ruleConfig.Inputs {
|
||||
input, err := makeRuleTarget(inputConfig, pDevs)
|
||||
|
@ -69,39 +87,23 @@ func makeComboRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, v
|
|||
inputs = append(inputs, input)
|
||||
}
|
||||
|
||||
output, err := makeRuleTarget(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mappingrules.ComboMappingRule{
|
||||
MappingRuleBase: mappingrules.MappingRuleBase{
|
||||
Output: output,
|
||||
},
|
||||
Inputs: inputs,
|
||||
State: 0,
|
||||
Name: ruleConfig.Name,
|
||||
MappingRuleBase: base,
|
||||
Inputs: inputs,
|
||||
State: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeLatchedRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, vDevs map[string]*evdev.InputDevice) (*mappingrules.LatchedMappingRule, error) {
|
||||
func makeLatchedRule(ruleConfig RuleConfig, pDevs map[string]*evdev.InputDevice, base mappingrules.MappingRuleBase) (*mappingrules.LatchedMappingRule, error) {
|
||||
input, err := makeRuleTarget(ruleConfig.Input, pDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := makeRuleTarget(ruleConfig.Output, vDevs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mappingrules.LatchedMappingRule{
|
||||
MappingRuleBase: mappingrules.MappingRuleBase{
|
||||
Output: output,
|
||||
},
|
||||
Input: input,
|
||||
Name: ruleConfig.Name,
|
||||
State: false,
|
||||
MappingRuleBase: base,
|
||||
Input: input,
|
||||
State: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -152,3 +154,21 @@ func decodeRuleTargetValues(target RuleTargetConfig) (evdev.EvType, evdev.EvCode
|
|||
|
||||
return eventType, eventCode, nil
|
||||
}
|
||||
|
||||
func verifyModes(ruleConfig RuleConfig, modes []string) []string {
|
||||
verifiedModes := make([]string, 0)
|
||||
|
||||
for _, configMode := range ruleConfig.Modes {
|
||||
if !slices.Contains(modes, configMode) {
|
||||
logger.Logf("rule '%s' specifies undefined mode '%s', skipping", ruleConfig.Name, configMode)
|
||||
continue
|
||||
}
|
||||
|
||||
verifiedModes = append(verifiedModes, configMode)
|
||||
}
|
||||
if len(verifiedModes) == 0 {
|
||||
verifiedModes = []string{"main"}
|
||||
}
|
||||
|
||||
return verifiedModes
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ package config
|
|||
|
||||
type Config struct {
|
||||
Devices []DeviceConfig `yaml:"devices"`
|
||||
// TODO: add groups
|
||||
// Groups []GroupConfig `yaml:"groups,omitempty"`
|
||||
Rules []RuleConfig `yaml:"rules"`
|
||||
Modes []string `yaml:"modes,omitempty"`
|
||||
Rules []RuleConfig `yaml:"rules"`
|
||||
}
|
||||
|
||||
type DeviceConfig struct {
|
||||
|
@ -25,12 +24,12 @@ type RuleConfig struct {
|
|||
Input RuleTargetConfig `yaml:"input,omitempty"`
|
||||
Inputs []RuleTargetConfig `yaml:"inputs,omitempty"`
|
||||
Output RuleTargetConfig `yaml:"output"`
|
||||
Modes []string `yaml:"modes,omitempty"`
|
||||
}
|
||||
|
||||
type RuleTargetConfig struct {
|
||||
Device string `yaml:"device"`
|
||||
Button string `yaml:"button,omitempty"`
|
||||
Axis string `yaml:"axis,omitempty"`
|
||||
Inverted bool `yaml:"inverted,omitempty"`
|
||||
Groups []string `yaml:"groups,omitempty"`
|
||||
Device string `yaml:"device"`
|
||||
Button string `yaml:"button,omitempty"`
|
||||
Axis string `yaml:"axis,omitempty"`
|
||||
Inverted bool `yaml:"inverted,omitempty"`
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package mappingrules
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
@ -9,6 +11,10 @@ func (rule *MappingRuleBase) OutputName() string {
|
|||
return rule.Output.DeviceName
|
||||
}
|
||||
|
||||
func (rule *MappingRuleBase) modeCheck(mode *string) bool {
|
||||
return slices.Contains(rule.Modes, *mode)
|
||||
}
|
||||
|
||||
// eventFromTarget creates an outputtable event from a RuleTarget
|
||||
func eventFromTarget(output RuleTarget, value int32) *evdev.InputEvent {
|
||||
return &evdev.InputEvent{
|
||||
|
@ -40,7 +46,11 @@ func valueFromTarget(rule RuleTarget, event *evdev.InputEvent) int32 {
|
|||
return value
|
||||
}
|
||||
|
||||
func (rule *SimpleMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent) *evdev.InputEvent {
|
||||
func (rule *SimpleMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent {
|
||||
if !rule.MappingRuleBase.modeCheck(mode) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if device != rule.Input.Device ||
|
||||
event.Code != rule.Input.Code {
|
||||
return nil
|
||||
|
@ -49,7 +59,11 @@ func (rule *SimpleMappingRule) MatchEvent(device *evdev.InputDevice, event *evde
|
|||
return eventFromTarget(rule.Output, valueFromTarget(rule.Input, event))
|
||||
}
|
||||
|
||||
func (rule *ComboMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent) *evdev.InputEvent {
|
||||
func (rule *ComboMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent {
|
||||
if !rule.MappingRuleBase.modeCheck(mode) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check each of the inputs, and if we find a match, proceed
|
||||
var match *RuleTarget
|
||||
for _, input := range rule.Inputs {
|
||||
|
@ -83,7 +97,11 @@ func (rule *ComboMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev
|
|||
return nil
|
||||
}
|
||||
|
||||
func (rule *LatchedMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent) *evdev.InputEvent {
|
||||
func (rule *LatchedMappingRule) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) *evdev.InputEvent {
|
||||
if !rule.MappingRuleBase.modeCheck(mode) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if device != rule.Input.Device ||
|
||||
event.Code != rule.Input.Code ||
|
||||
valueFromTarget(rule.Input, event) == 0 {
|
||||
|
|
|
@ -3,34 +3,33 @@ package mappingrules
|
|||
import "github.com/holoplot/go-evdev"
|
||||
|
||||
type MappingRule interface {
|
||||
MatchEvent(*evdev.InputDevice, *evdev.InputEvent) *evdev.InputEvent
|
||||
MatchEvent(*evdev.InputDevice, *evdev.InputEvent, *string) *evdev.InputEvent
|
||||
OutputName() string
|
||||
}
|
||||
|
||||
type MappingRuleBase struct {
|
||||
Name string
|
||||
Output RuleTarget
|
||||
Modes []string
|
||||
}
|
||||
|
||||
// A Simple Mapping Rule can map a button to a button or an axis to an axis.
|
||||
type SimpleMappingRule struct {
|
||||
MappingRuleBase
|
||||
Input RuleTarget
|
||||
Name string
|
||||
Input RuleTarget
|
||||
}
|
||||
|
||||
// A Combo Mapping Rule can require multiple physical button presses for a single output button
|
||||
type ComboMappingRule struct {
|
||||
MappingRuleBase
|
||||
Inputs []RuleTarget
|
||||
Name string
|
||||
State int
|
||||
}
|
||||
|
||||
type LatchedMappingRule struct {
|
||||
MappingRuleBase
|
||||
Input RuleTarget
|
||||
Name string
|
||||
State bool
|
||||
Input RuleTarget
|
||||
State bool
|
||||
}
|
||||
|
||||
type RuleTarget struct {
|
||||
|
|
|
@ -25,6 +25,7 @@ Joyful might be the tool for you.
|
|||
|
||||
* Multiple modes with per-mode behavior.
|
||||
* Partial axis mapping: map sections of an axis to different outputs.
|
||||
* Highly configurable deadzones
|
||||
* Macros - have a single input produce a sequence of button presses with configurable pauses.
|
||||
* Sequence combos - Button1, Button2, Button3 -> VirtualButtonA
|
||||
|
||||
|
@ -62,6 +63,10 @@ All `rules` must have a `type` field. Valid values for this field are:
|
|||
|
||||
Configuration options for each type vary. See <examples/ruletypes.yml> for an example of each type with all options specified.
|
||||
|
||||
### Modes
|
||||
|
||||
All rules can have a `modes` field that is a list of strings.
|
||||
|
||||
|
||||
## Technical details
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue