Move initialization code closer to the appropriate structs. (#17)
Reviewed-on: #17 Co-authored-by: Anna Rose Wiggins <annabunches@gmail.com> Co-committed-by: Anna Rose Wiggins <annabunches@gmail.com>
This commit is contained in:
parent
d9babf5dc0
commit
8d2b15a7c8
40 changed files with 1087 additions and 1109 deletions
|
@ -5,7 +5,8 @@ import (
|
|||
"slices"
|
||||
|
||||
// TODO: using config here feels like bad coupling... ButtonFromIndex might need a refactor / move
|
||||
"git.annabunches.net/annabunches/joyful/internal/config"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/eventcodes"
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"github.com/holoplot/go-evdev"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
@ -20,7 +21,7 @@ func isJoystickLike(device *evdev.InputDevice) bool {
|
|||
if slices.Contains(types, evdev.EV_KEY) {
|
||||
buttons := device.CapableEvents(evdev.EV_KEY)
|
||||
|
||||
for _, code := range config.ButtonFromIndex {
|
||||
for _, code := range eventcodes.ButtonFromIndex {
|
||||
if slices.Contains(buttons, code) {
|
||||
return true
|
||||
}
|
||||
|
|
147
cmd/joyful/config.go
Normal file
147
cmd/joyful/config.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||
"git.annabunches.net/annabunches/joyful/internal/virtualdevice"
|
||||
"github.com/holoplot/go-evdev"
|
||||
)
|
||||
|
||||
func initPhysicalDevices(conf *configparser.Config) map[string]*evdev.InputDevice {
|
||||
pDeviceMap := make(map[string]*evdev.InputDevice)
|
||||
|
||||
for _, devConfig := range conf.Devices {
|
||||
if strings.ToLower(devConfig.Type) != configparser.DeviceTypePhysical {
|
||||
continue
|
||||
}
|
||||
|
||||
innerConfig := devConfig.Config.(configparser.DeviceConfigPhysical)
|
||||
name, device, err := initPhysicalDevice(innerConfig)
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to initialize physical device")
|
||||
continue
|
||||
}
|
||||
|
||||
pDeviceMap[name] = device
|
||||
|
||||
displayName := innerConfig.DeviceName
|
||||
if innerConfig.DevicePath != "" {
|
||||
displayName = innerConfig.DevicePath
|
||||
}
|
||||
logger.Logf("Connected to '%s' as '%s'", displayName, name)
|
||||
}
|
||||
|
||||
if len(pDeviceMap) == 0 {
|
||||
logger.Log("Warning: no physical devices found in configuration. No rules will work.")
|
||||
}
|
||||
return pDeviceMap
|
||||
}
|
||||
|
||||
func initPhysicalDevice(config configparser.DeviceConfigPhysical) (string, *evdev.InputDevice, error) {
|
||||
name := config.Name
|
||||
var device *evdev.InputDevice
|
||||
var err error
|
||||
|
||||
if config.DevicePath != "" {
|
||||
device, err = evdev.Open(config.DevicePath)
|
||||
} else {
|
||||
device, err = evdev.OpenByName(config.DeviceName)
|
||||
}
|
||||
|
||||
if config.Lock && err == nil {
|
||||
grabErr := device.Grab()
|
||||
logger.LogIfError(grabErr, "Failed to lock device for exclusive access")
|
||||
}
|
||||
|
||||
return name, device, err
|
||||
}
|
||||
|
||||
// TODO: juggling all these maps is a pain. Is there a better solution here?
|
||||
func initVirtualBuffers(config *configparser.Config) (map[string]*evdev.InputDevice,
|
||||
map[string]*virtualdevice.EventBuffer,
|
||||
map[*evdev.InputDevice]*virtualdevice.EventBuffer) {
|
||||
|
||||
vDevicesByName := make(map[string]*evdev.InputDevice)
|
||||
vBuffersByName := make(map[string]*virtualdevice.EventBuffer)
|
||||
vBuffersByDevice := make(map[*evdev.InputDevice]*virtualdevice.EventBuffer)
|
||||
|
||||
for _, devConfig := range config.Devices {
|
||||
if strings.ToLower(devConfig.Type) != configparser.DeviceTypeVirtual {
|
||||
continue
|
||||
}
|
||||
|
||||
vConfig := devConfig.Config.(configparser.DeviceConfigVirtual)
|
||||
buffer, err := virtualdevice.NewEventBuffer(vConfig)
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to create virtual device, skipping")
|
||||
continue
|
||||
}
|
||||
vDevicesByName[buffer.Name] = buffer.Device.(*evdev.InputDevice)
|
||||
vBuffersByName[buffer.Name] = buffer
|
||||
vBuffersByDevice[buffer.Device.(*evdev.InputDevice)] = buffer
|
||||
}
|
||||
|
||||
if len(vDevicesByName) == 0 {
|
||||
logger.Log("Warning: no virtual devices found in configuration. No rules will work.")
|
||||
}
|
||||
|
||||
return vDevicesByName, vBuffersByName, vBuffersByDevice
|
||||
}
|
||||
|
||||
// TODO: At some point it would *very likely* make sense to map each rule to all of the physical devices that can
|
||||
// trigger it, and return that instead. Something like a map[Device][]mappingrule.MappingRule.
|
||||
// This would speed up rule matching by only checking relevant rules for a given input event.
|
||||
// We could take this further and make it a map[<struct of *inputdevice, type, and code>][]rule
|
||||
// For very large rule-bases this may be helpful for staying performant.
|
||||
func loadRules(
|
||||
config *configparser.Config,
|
||||
pDevices map[string]*evdev.InputDevice,
|
||||
vDevices map[string]*evdev.InputDevice,
|
||||
modes []string) ([]mappingrules.MappingRule, <-chan ChannelEvent, func(), *sync.WaitGroup) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
eventChannel := make(chan ChannelEvent, 1000)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Setup device mapping for the mappingrules package
|
||||
pDevs := mappingrules.ConvertDeviceMap(pDevices)
|
||||
vDevs := mappingrules.ConvertDeviceMap(vDevices)
|
||||
|
||||
// Initialize rules
|
||||
rules := make([]mappingrules.MappingRule, 0)
|
||||
for _, ruleConfig := range config.Rules {
|
||||
newRule, err := mappingrules.NewRule(ruleConfig, pDevs, vDevs, modes)
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to create rule, skipping")
|
||||
continue
|
||||
}
|
||||
rules = append(rules, newRule)
|
||||
}
|
||||
|
||||
logger.Logf("Created %d mapping rules.", len(rules))
|
||||
|
||||
// start listening for events on devices and timers
|
||||
for _, device := range pDevices {
|
||||
wg.Add(1)
|
||||
go eventWatcher(device, eventChannel, ctx, &wg)
|
||||
}
|
||||
|
||||
timerCount := 0
|
||||
for _, rule := range rules {
|
||||
if timedRule, ok := rule.(mappingrules.TimedEventEmitter); ok {
|
||||
wg.Add(1)
|
||||
go timerWatcher(timedRule, eventChannel, ctx, &wg)
|
||||
timerCount++
|
||||
}
|
||||
}
|
||||
logger.Logf("Registered %d timers.", timerCount)
|
||||
|
||||
go consoleWatcher(eventChannel)
|
||||
|
||||
return rules, eventChannel, cancel, &wg
|
||||
}
|
|
@ -1,19 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/holoplot/go-evdev"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"git.annabunches.net/annabunches/joyful/internal/config"
|
||||
"git.annabunches.net/annabunches/joyful/internal/configparser"
|
||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||
"git.annabunches.net/annabunches/joyful/internal/virtualdevice"
|
||||
)
|
||||
|
||||
func getConfigDir(dir string) string {
|
||||
|
@ -21,39 +17,6 @@ func getConfigDir(dir string) string {
|
|||
return os.ExpandEnv(configDir)
|
||||
}
|
||||
|
||||
func readConfig(configDir string) *config.ConfigParser {
|
||||
parser := &config.ConfigParser{}
|
||||
err := parser.Parse(configDir)
|
||||
logger.FatalIfError(err, "Failed to parse config")
|
||||
return parser
|
||||
}
|
||||
|
||||
func initVirtualBuffers(config *config.ConfigParser) (map[string]*evdev.InputDevice,
|
||||
map[string]*virtualdevice.EventBuffer,
|
||||
map[*evdev.InputDevice]*virtualdevice.EventBuffer) {
|
||||
|
||||
vDevices := config.InitVirtualDevices()
|
||||
if len(vDevices) == 0 {
|
||||
logger.Log("Warning: no virtual devices found in configuration. No rules will work.")
|
||||
}
|
||||
|
||||
vBuffersByName := make(map[string]*virtualdevice.EventBuffer)
|
||||
vBuffersByDevice := make(map[*evdev.InputDevice]*virtualdevice.EventBuffer)
|
||||
for name, device := range vDevices {
|
||||
vBuffersByName[name] = virtualdevice.NewEventBuffer(device)
|
||||
vBuffersByDevice[device] = vBuffersByName[name]
|
||||
}
|
||||
return vDevices, vBuffersByName, vBuffersByDevice
|
||||
}
|
||||
|
||||
func initPhysicalDevices(config *config.ConfigParser) map[string]*evdev.InputDevice {
|
||||
pDeviceMap := config.InitPhysicalDevices()
|
||||
if len(pDeviceMap) == 0 {
|
||||
logger.Log("Warning: no physical devices found in configuration. No rules will work.")
|
||||
}
|
||||
return pDeviceMap
|
||||
}
|
||||
|
||||
func main() {
|
||||
// parse command-line
|
||||
var configFlag string
|
||||
|
@ -64,7 +27,8 @@ func main() {
|
|||
|
||||
// parse configs
|
||||
configDir := getConfigDir(configFlag)
|
||||
config := readConfig(configDir)
|
||||
config, err := configparser.ParseConfig(configDir)
|
||||
logger.FatalIfError(err, "Failed to parse configuration")
|
||||
|
||||
// initialize TTS
|
||||
tts, err := newTTS(ttsOps)
|
||||
|
@ -76,20 +40,26 @@ func main() {
|
|||
// Initialize physical devices
|
||||
pDevices := initPhysicalDevices(config)
|
||||
|
||||
// Load the rules
|
||||
rules, eventChannel, cancel, wg := loadRules(config, pDevices, vDevicesByName)
|
||||
// initialize the mode variables
|
||||
var mode string
|
||||
modes := config.Modes
|
||||
if len(modes) == 0 {
|
||||
mode = "*"
|
||||
} else {
|
||||
mode = config.Modes[0]
|
||||
}
|
||||
|
||||
// initialize the mode variable
|
||||
mode := config.GetModes()[0]
|
||||
// Load the rules
|
||||
rules, eventChannel, cancel, wg := loadRules(config, pDevices, vDevicesByName, modes)
|
||||
|
||||
// initialize TTS phrases for modes
|
||||
for _, m := range config.GetModes() {
|
||||
for _, m := range modes {
|
||||
tts.AddMessage(m)
|
||||
logger.LogDebugf("Added TTS message '%s'", m)
|
||||
}
|
||||
|
||||
fmt.Println("Joyful Running! Press Ctrl+C to quit. Press Enter to reload rules.")
|
||||
if len(config.GetModes()) > 1 {
|
||||
if len(modes) > 0 {
|
||||
logger.Logf("Initial mode set to '%s'", mode)
|
||||
}
|
||||
|
||||
|
@ -127,13 +97,18 @@ func main() {
|
|||
|
||||
case ChannelEventReload:
|
||||
// stop existing channels
|
||||
config, err := configparser.ParseConfig(configDir) // reload the config
|
||||
if err != nil {
|
||||
logger.LogError(err, "Failed to parse config, no changes made")
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("Reloading rules.")
|
||||
cancel()
|
||||
fmt.Println("Waiting for existing listeners to exit. Provide input from each of your devices.")
|
||||
wg.Wait()
|
||||
fmt.Println("Listeners exited. Parsing config.")
|
||||
config := readConfig(configDir) // reload the config
|
||||
rules, eventChannel, cancel, wg = loadRules(config, pDevices, vDevicesByName)
|
||||
fmt.Println("Listeners exited. Loading new rules.")
|
||||
rules, eventChannel, cancel, wg = loadRules(config, pDevices, vDevicesByName, modes)
|
||||
fmt.Println("Config re-loaded. Only rule changes applied. Device and Mode changes require restart.")
|
||||
}
|
||||
|
||||
|
@ -142,37 +117,3 @@ func main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadRules(
|
||||
config *config.ConfigParser,
|
||||
pDevices map[string]*evdev.InputDevice,
|
||||
vDevices map[string]*evdev.InputDevice) ([]mappingrules.MappingRule, <-chan ChannelEvent, func(), *sync.WaitGroup) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
eventChannel := make(chan ChannelEvent, 1000)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Initialize rules
|
||||
rules := config.InitRules(pDevices, vDevices)
|
||||
logger.Logf("Created %d mapping rules.", len(rules))
|
||||
|
||||
// start listening for events on devices and timers
|
||||
for _, device := range pDevices {
|
||||
wg.Add(1)
|
||||
go eventWatcher(device, eventChannel, ctx, &wg)
|
||||
}
|
||||
|
||||
timerCount := 0
|
||||
for _, rule := range rules {
|
||||
if timedRule, ok := rule.(mappingrules.TimedEventEmitter); ok {
|
||||
wg.Add(1)
|
||||
go timerWatcher(timedRule, eventChannel, ctx, &wg)
|
||||
timerCount++
|
||||
}
|
||||
}
|
||||
logger.Logf("Registered %d timers.", timerCount)
|
||||
|
||||
go consoleWatcher(eventChannel)
|
||||
|
||||
return rules, eventChannel, cancel, &wg
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue