diff --git a/cmd/joyful-config/main.go b/cmd/joyful-config/main.go new file mode 100644 index 0000000..8ca321a --- /dev/null +++ b/cmd/joyful-config/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "git.annabunches.net/annabunches/joyful/internal/configparser" + "git.annabunches.net/annabunches/joyful/internal/logger" + flag "github.com/spf13/pflag" +) + +func getConfigDir(dir string) string { + configDir := strings.ReplaceAll(dir, "~", "${HOME}") + return os.ExpandEnv(configDir) +} + +func main() { + var configDir string + flag.StringVarP(&configDir, "config", "c", "~/.config/joyful", "Directory to read configuration from.") + flag.Parse() + configDir = getConfigDir(configDir) + + config, err := configparser.ParseConfig(configDir) + switch err.(type) { + case *configparser.EmptyConfigError: + config = &configparser.Config{} + default: + logger.FatalIfError(err, "Fatal error reading config") + } + + fmt.Printf("Config: %v\n", config) +} diff --git a/cmd/joyful/config.go b/cmd/joyful/config.go index 64d6b2d..2b43380 100644 --- a/cmd/joyful/config.go +++ b/cmd/joyful/config.go @@ -2,6 +2,7 @@ package main import ( "context" + "strings" "sync" "git.annabunches.net/annabunches/joyful/internal/configparser" @@ -15,7 +16,7 @@ func initPhysicalDevices(conf *configparser.Config) map[string]*evdev.InputDevic pDeviceMap := make(map[string]*evdev.InputDevice) for _, devConfig := range conf.Devices { - if devConfig.Type != configparser.DeviceTypePhysical { + if strings.ToLower(devConfig.Type) != configparser.DeviceTypePhysical { continue } @@ -70,7 +71,7 @@ func initVirtualBuffers(config *configparser.Config) (map[string]*evdev.InputDev vBuffersByDevice := make(map[*evdev.InputDevice]*virtualdevice.EventBuffer) for _, devConfig := range config.Devices { - if devConfig.Type != configparser.DeviceTypeVirtual { + if strings.ToLower(devConfig.Type) != configparser.DeviceTypeVirtual { continue } diff --git a/internal/configparser/configparser.go b/internal/configparser/configparser.go index 3daa217..deceb8a 100644 --- a/internal/configparser/configparser.go +++ b/internal/configparser/configparser.go @@ -50,7 +50,7 @@ func getConfigFilePaths(directory string) ([]string, error) { if err != nil { return nil, errors.New("failed to create config directory at " + directory) } else { - return nil, errors.New("no config files found at " + directory) + return nil, &EmptyConfigError{directory} } } @@ -63,5 +63,9 @@ func getConfigFilePaths(directory string) ([]string, error) { paths = append(paths, filepath.Join(directory, file.Name())) } + if len(paths) == 0 { + return nil, &EmptyConfigError{directory} + } + return paths, nil } diff --git a/internal/configparser/deviceconfig.go b/internal/configparser/deviceconfig.go deleted file mode 100644 index eafd8ca..0000000 --- a/internal/configparser/deviceconfig.go +++ /dev/null @@ -1,31 +0,0 @@ -package configparser - -// These top-level structs use custom unmarshaling to unpack each available sub-type -type DeviceConfig struct { - Type DeviceType - Config interface{} -} - -func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error { - metaConfig := &struct { - Type DeviceType - }{} - err := unmarshal(metaConfig) - if err != nil { - return err - } - dc.Type = metaConfig.Type - - err = nil - switch metaConfig.Type { - case DeviceTypePhysical: - config := DeviceConfigPhysical{} - err = unmarshal(&config) - dc.Config = config - case DeviceTypeVirtual: - config := DeviceConfigVirtual{} - err = unmarshal(&config) - dc.Config = config - } - return err -} diff --git a/internal/configparser/deviceconfigphysical.go b/internal/configparser/deviceconfigphysical.go deleted file mode 100644 index ecb5255..0000000 --- a/internal/configparser/deviceconfigphysical.go +++ /dev/null @@ -1,35 +0,0 @@ -package configparser - -type DeviceConfigPhysical struct { - Name string - DeviceName string `yaml:"device_name,omitempty"` - DevicePath string `yaml:"device_path,omitempty"` - Lock bool -} - -// TODO: custom yaml unmarshaling is obtuse; do we really need to do all of this work -// just to set a single default value? -func (dc *DeviceConfigPhysical) UnmarshalYAML(unmarshal func(data interface{}) error) error { - var raw struct { - Name string - DeviceName string `yaml:"device_name"` - DevicePath string `yaml:"device_path"` - Lock bool `yaml:"lock,omitempty"` - } - - // Set non-standard defaults - raw.Lock = true - - err := unmarshal(&raw) - if err != nil { - return err - } - - *dc = DeviceConfigPhysical{ - Name: raw.Name, - DeviceName: raw.DeviceName, - DevicePath: raw.DevicePath, - Lock: raw.Lock, - } - return nil -} diff --git a/internal/configparser/devicetype.go b/internal/configparser/devicetype.go deleted file mode 100644 index 7640304..0000000 --- a/internal/configparser/devicetype.go +++ /dev/null @@ -1,40 +0,0 @@ -package configparser - -import ( - "fmt" - "strings" -) - -type DeviceType string - -const ( - DeviceTypeNone DeviceType = "" - DeviceTypePhysical DeviceType = "physical" - DeviceTypeVirtual DeviceType = "virtual" -) - -var ( - deviceTypeMap = map[string]DeviceType{ - "physical": DeviceTypePhysical, - "virtual": DeviceTypeVirtual, - } -) - -func ParseDeviceType(in string) (DeviceType, error) { - deviceType, ok := deviceTypeMap[strings.ToLower(in)] - if !ok { - return DeviceTypeNone, fmt.Errorf("invalid rule type '%s'", in) - } - return deviceType, nil -} - -func (rt *DeviceType) UnmarshalYAML(unmarshal func(data interface{}) error) error { - var raw string - err := unmarshal(&raw) - if err != nil { - return err - } - - *rt, err = ParseDeviceType(raw) - return err -} diff --git a/internal/configparser/errors.go b/internal/configparser/errors.go new file mode 100644 index 0000000..4698152 --- /dev/null +++ b/internal/configparser/errors.go @@ -0,0 +1,11 @@ +package configparser + +import "fmt" + +type EmptyConfigError struct { + directory string +} + +func (e *EmptyConfigError) Error() string { + return fmt.Sprintf("no config files found at %s", e.directory) +} diff --git a/internal/configparser/ruleconfig.go b/internal/configparser/ruleconfig.go deleted file mode 100644 index b41e339..0000000 --- a/internal/configparser/ruleconfig.go +++ /dev/null @@ -1,60 +0,0 @@ -package configparser - -type RuleConfig struct { - Type RuleType - Name string - Modes []string - Config interface{} -} - -func (dc *RuleConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error { - metaConfig := &struct { - Type RuleType - Name string - Modes []string - }{} - err := unmarshal(metaConfig) - if err != nil { - return err - } - dc.Type = metaConfig.Type - dc.Name = metaConfig.Name - dc.Modes = metaConfig.Modes - - switch dc.Type { - case RuleTypeButton: - config := RuleConfigButton{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeButtonCombo: - config := RuleConfigButtonCombo{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeButtonLatched: - config := RuleConfigButtonLatched{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeAxis: - config := RuleConfigAxis{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeAxisCombined: - config := RuleConfigAxisCombined{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeAxisToButton: - config := RuleConfigAxisToButton{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeAxisToRelaxis: - config := RuleConfigAxisToRelaxis{} - err = unmarshal(&config) - dc.Config = config - case RuleTypeModeSelect: - config := RuleConfigModeSelect{} - err = unmarshal(&config) - dc.Config = config - } - - return err -} diff --git a/internal/configparser/ruletype.go b/internal/configparser/ruletype.go deleted file mode 100644 index 7f43001..0000000 --- a/internal/configparser/ruletype.go +++ /dev/null @@ -1,53 +0,0 @@ -package configparser - -import ( - "fmt" - "strings" -) - -// TODO: maybe these want to live somewhere other than configparser? -type RuleType string - -const ( - RuleTypeNone RuleType = "" - RuleTypeButton RuleType = "button" - RuleTypeButtonCombo RuleType = "button-combo" - RuleTypeButtonLatched RuleType = "button-latched" - RuleTypeAxis RuleType = "axis" - RuleTypeAxisCombined RuleType = "axis-combined" - RuleTypeAxisToButton RuleType = "axis-to-button" - RuleTypeAxisToRelaxis RuleType = "axis-to-relaxis" - RuleTypeModeSelect RuleType = "mode-select" -) - -var ( - ruleTypeMap = map[string]RuleType{ - "button": RuleTypeButton, - "button-combo": RuleTypeButtonCombo, - "button-latched": RuleTypeButtonLatched, - "axis": RuleTypeAxis, - "axis-combined": RuleTypeAxisCombined, - "axis-to-button": RuleTypeAxisToButton, - "axis-to-relaxis": RuleTypeAxisToRelaxis, - "mode-select": RuleTypeModeSelect, - } -) - -func ParseRuleType(in string) (RuleType, error) { - ruleType, ok := ruleTypeMap[strings.ToLower(in)] - if !ok { - return RuleTypeNone, fmt.Errorf("invalid rule type '%s'", in) - } - return ruleType, nil -} - -func (rt *RuleType) UnmarshalYAML(unmarshal func(data interface{}) error) error { - var raw string - err := unmarshal(&raw) - if err != nil { - return err - } - - *rt, err = ParseRuleType(raw) - return err -} diff --git a/internal/configparser/schema.go b/internal/configparser/schema.go index 942f873..8b70521 100644 --- a/internal/configparser/schema.go +++ b/internal/configparser/schema.go @@ -1,13 +1,38 @@ -// These types comprise the YAML schema that doesn't need custom unmarshalling. +// These types comprise the YAML schema for configuring Joyful. +// The config files will be combined and then unmarshalled into this package configparser +import ( + "fmt" +) + type Config struct { Devices []DeviceConfig Modes []string Rules []RuleConfig } +// These top-level structs use custom unmarshaling to unpack each available sub-type +type DeviceConfig struct { + Type string + Config interface{} +} + +type RuleConfig struct { + Type string + Name string + Modes []string + Config interface{} +} + +type DeviceConfigPhysical struct { + Name string + DeviceName string `yaml:"device_name,omitempty"` + DevicePath string `yaml:"device_path,omitempty"` + Lock bool +} + // TODO: configure custom unmarshaling so we can overload Buttons, Axes, and RelativeAxes... type DeviceConfigVirtual struct { Name string @@ -91,3 +116,110 @@ type RuleTargetConfigRelaxis struct { type RuleTargetConfigModeSelect struct { Modes []string } + +func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error { + metaConfig := &struct { + Type string + }{} + err := unmarshal(metaConfig) + if err != nil { + return err + } + dc.Type = metaConfig.Type + + err = nil + switch metaConfig.Type { + case DeviceTypePhysical: + config := DeviceConfigPhysical{} + err = unmarshal(&config) + dc.Config = config + case DeviceTypeVirtual: + config := DeviceConfigVirtual{} + err = unmarshal(&config) + dc.Config = config + default: + err = fmt.Errorf("invalid device type '%s'", dc.Type) + } + return err +} + +func (dc *RuleConfig) UnmarshalYAML(unmarshal func(data interface{}) error) error { + metaConfig := &struct { + Type string + Name string + Modes []string + }{} + err := unmarshal(metaConfig) + if err != nil { + return err + } + dc.Type = metaConfig.Type + dc.Name = metaConfig.Name + dc.Modes = metaConfig.Modes + + switch dc.Type { + case RuleTypeButton: + config := RuleConfigButton{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeButtonCombo: + config := RuleConfigButtonCombo{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeButtonLatched: + config := RuleConfigButtonLatched{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeAxis: + config := RuleConfigAxis{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeAxisCombined: + config := RuleConfigAxisCombined{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeAxisToButton: + config := RuleConfigAxisToButton{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeAxisToRelaxis: + config := RuleConfigAxisToRelaxis{} + err = unmarshal(&config) + dc.Config = config + case RuleTypeModeSelect: + config := RuleConfigModeSelect{} + err = unmarshal(&config) + dc.Config = config + default: + err = fmt.Errorf("invalid rule type '%s'", dc.Type) + } + + return err +} + +// TODO: custom yaml unmarshaling is obtuse; do we really need to do all of this work +// just to set a single default value? +func (dc *DeviceConfigPhysical) UnmarshalYAML(unmarshal func(data interface{}) error) error { + var raw struct { + Name string + DeviceName string `yaml:"device_name"` + DevicePath string `yaml:"device_path"` + Lock bool `yaml:"lock,omitempty"` + } + + // Set non-standard defaults + raw.Lock = true + + err := unmarshal(&raw) + if err != nil { + return err + } + + *dc = DeviceConfigPhysical{ + Name: raw.Name, + DeviceName: raw.DeviceName, + DevicePath: raw.DevicePath, + Lock: raw.Lock, + } + return nil +} diff --git a/internal/configparser/variables.go b/internal/configparser/variables.go new file mode 100644 index 0000000..77e2b9c --- /dev/null +++ b/internal/configparser/variables.go @@ -0,0 +1,15 @@ +package configparser + +const ( + DeviceTypePhysical = "physical" + DeviceTypeVirtual = "virtual" + + RuleTypeButton = "button" + RuleTypeButtonCombo = "button-combo" + RuleTypeButtonLatched = "button-latched" + RuleTypeAxis = "axis" + RuleTypeAxisCombined = "axis-combined" + RuleTypeAxisToButton = "axis-to-button" + RuleTypeAxisToRelaxis = "axis-to-relaxis" + RuleTypeModeSelect = "mode-select" +) diff --git a/internal/mappingrules/init_rules.go b/internal/mappingrules/init_rules.go index f621875..7ea0ea4 100644 --- a/internal/mappingrules/init_rules.go +++ b/internal/mappingrules/init_rules.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "slices" + "strings" "git.annabunches.net/annabunches/joyful/internal/configparser" "git.annabunches.net/annabunches/joyful/internal/logger" @@ -32,25 +33,24 @@ func NewRule(config configparser.RuleConfig, pDevs map[string]Device, vDevs map[ base := NewMappingRuleBase(config.Name, config.Modes) - switch config.Type { - case configparser.RuleTypeButton: + switch strings.ToLower(config.Type) { + case RuleTypeButton: newRule, err = NewMappingRuleButton(config.Config.(configparser.RuleConfigButton), pDevs, vDevs, base) - case configparser.RuleTypeButtonCombo: + case RuleTypeButtonCombo: newRule, err = NewMappingRuleButtonCombo(config.Config.(configparser.RuleConfigButtonCombo), pDevs, vDevs, base) - case configparser.RuleTypeButtonLatched: + case RuleTypeButtonLatched: newRule, err = NewMappingRuleButtonLatched(config.Config.(configparser.RuleConfigButtonLatched), pDevs, vDevs, base) - case configparser.RuleTypeAxis: + case RuleTypeAxis: newRule, err = NewMappingRuleAxis(config.Config.(configparser.RuleConfigAxis), pDevs, vDevs, base) - case configparser.RuleTypeAxisCombined: + case RuleTypeAxisCombined: newRule, err = NewMappingRuleAxisCombined(config.Config.(configparser.RuleConfigAxisCombined), pDevs, vDevs, base) - case configparser.RuleTypeAxisToButton: + case RuleTypeAxisToButton: newRule, err = NewMappingRuleAxisToButton(config.Config.(configparser.RuleConfigAxisToButton), pDevs, vDevs, base) - case configparser.RuleTypeAxisToRelaxis: + case RuleTypeAxisToRelaxis: newRule, err = NewMappingRuleAxisToRelaxis(config.Config.(configparser.RuleConfigAxisToRelaxis), pDevs, vDevs, base) - case configparser.RuleTypeModeSelect: + case RuleTypeModeSelect: newRule, err = NewMappingRuleModeSelect(config.Config.(configparser.RuleConfigModeSelect), pDevs, modes, base) default: - // Shouldn't actually be possible to get here... err = fmt.Errorf("bad rule type '%s' for rule '%s'", config.Type, config.Name) } diff --git a/internal/mappingrules/variables.go b/internal/mappingrules/variables.go new file mode 100644 index 0000000..d9a171b --- /dev/null +++ b/internal/mappingrules/variables.go @@ -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" +)