Support live re-loading of rules.

This commit is contained in:
Anna Rose Wiggins 2025-07-15 19:23:42 -04:00
parent 4ebcbb4dc9
commit 9c42e90750
4 changed files with 102 additions and 25 deletions

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"git.annabunches.net/annabunches/joyful/internal/config" "git.annabunches.net/annabunches/joyful/internal/config"
"git.annabunches.net/annabunches/joyful/internal/logger" "git.annabunches.net/annabunches/joyful/internal/logger"
@ -63,24 +64,7 @@ func main() {
// Initialize physical devices // Initialize physical devices
pDevices := initPhysicalDevices(config) pDevices := initPhysicalDevices(config)
// Initialize rules rules, eventChannel, doneChannel, wg := loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
rules := config.BuildRules(pDevices, getVirtualDevices(vBuffersByName))
logger.Logf("Created %d mapping rules.", len(rules))
// start listening for events on devices and timers
eventChannel := make(chan ChannelEvent, 1000)
for _, device := range pDevices {
go eventWatcher(device, eventChannel)
}
timerCount := 0
for _, rule := range rules {
if timedRule, ok := rule.(mappingrules.TimedEventEmitter); ok {
go timerWatcher(timedRule, eventChannel)
timerCount++
}
}
logger.Logf("registered %d timers", timerCount)
// initialize the mode variable // initialize the mode variable
mode := config.GetModes()[0] mode := config.GetModes()[0]
@ -117,6 +101,50 @@ func main() {
vBuffersByDevice[channelEvent.Device].AddEvent(channelEvent.Event) vBuffersByDevice[channelEvent.Device].AddEvent(channelEvent.Event)
// If we get a timer event, flush the output device buffer immediately // If we get a timer event, flush the output device buffer immediately
vBuffersByDevice[channelEvent.Device].SendEvents() vBuffersByDevice[channelEvent.Device].SendEvents()
case ChannelEventReload:
// stop existing channels
fmt.Println("Reloading rules.")
doneChannel <- true
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() // reload the config
rules, eventChannel, doneChannel, wg = loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
} }
} }
} }
func loadRules(
config *config.ConfigParser,
pDevices map[string]*evdev.InputDevice,
vDevices map[string]*evdev.InputDevice) ([]mappingrules.MappingRule, <-chan ChannelEvent, chan bool, *sync.WaitGroup) {
var wg sync.WaitGroup
eventChannel := make(chan ChannelEvent, 1000)
doneChannel := make(chan bool)
// Initialize rules
rules := config.BuildRules(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, doneChannel, &wg)
}
timerCount := 0
for _, rule := range rules {
if timedRule, ok := rule.(mappingrules.TimedEventEmitter); ok {
wg.Add(1)
go timerWatcher(timedRule, eventChannel, doneChannel, &wg)
timerCount++
}
}
logger.Logf("registered %d timers", timerCount)
go consoleWatcher(eventChannel, &wg)
return rules, eventChannel, doneChannel, &wg
}

View file

@ -1,6 +1,9 @@
package main package main
import ( import (
"bufio"
"os"
"sync"
"time" "time"
"git.annabunches.net/annabunches/joyful/internal/logger" "git.annabunches.net/annabunches/joyful/internal/logger"
@ -13,8 +16,24 @@ const (
DeviceCheckIntervalMs = 1 DeviceCheckIntervalMs = 1
) )
func eventWatcher(device *evdev.InputDevice, channel chan<- ChannelEvent) { func eventWatcher(
device *evdev.InputDevice,
channel chan<- ChannelEvent,
done chan bool,
wg *sync.WaitGroup) {
defer wg.Done()
for { for {
select {
case cancel := <-done:
if cancel {
done <- true
return
}
default:
}
event, err := device.ReadOne() event, err := device.ReadOne()
if err != nil { if err != nil {
logger.LogError(err, "Error while reading event. Disconnecting device.") logger.LogError(err, "Error while reading event. Disconnecting device.")
@ -28,8 +47,24 @@ func eventWatcher(device *evdev.InputDevice, channel chan<- ChannelEvent) {
} }
} }
func timerWatcher(rule mappingrules.TimedEventEmitter, channel chan<- ChannelEvent) { func timerWatcher(
rule mappingrules.TimedEventEmitter,
channel chan<- ChannelEvent,
done chan bool,
wg *sync.WaitGroup) {
defer wg.Done()
for { for {
select {
case cancel := <-done:
if cancel {
done <- true
return
}
default:
}
event := rule.TimerEvent() event := rule.TimerEvent()
if event != nil { if event != nil {
channel <- ChannelEvent{ channel <- ChannelEvent{
@ -41,3 +76,21 @@ func timerWatcher(rule mappingrules.TimedEventEmitter, channel chan<- ChannelEve
time.Sleep(TimerCheckIntervalMs * time.Millisecond) time.Sleep(TimerCheckIntervalMs * time.Millisecond)
} }
} }
// consoleWatcher reads input from stdin, and on receiving anything
func consoleWatcher(channel chan<- ChannelEvent, wg *sync.WaitGroup) {
defer wg.Done()
stdin := bufio.NewReader(os.Stdin)
for {
_, err := stdin.ReadString('\n')
if err != nil {
logger.LogErrorf(err, "Error in console input thread")
continue
}
channel <- ChannelEvent{
Type: ChannelEventReload,
}
return
}
}

View file

@ -7,6 +7,7 @@ type ChannelEventType int
const ( const (
ChannelEventInput ChannelEventType = iota ChannelEventInput ChannelEventType = iota
ChannelEventTimer ChannelEventTimer
ChannelEventReload
) )
type ChannelEvent struct { type ChannelEvent struct {

View file

@ -3,7 +3,6 @@ package mappingrules
import ( import (
"time" "time"
"git.annabunches.net/annabunches/joyful/internal/logger"
"github.com/holoplot/go-evdev" "github.com/holoplot/go-evdev"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
) )
@ -53,10 +52,6 @@ func (rule *MappingRuleAxisToRelaxis) MatchEvent(
return nil, nil return nil, nil
} }
defer func() {
logger.Logf("DEBUG: Rule '%s' nextEvent == '%v' with device value '%d'", rule.Name, rule.nextEvent, event.Value)
}()
// If we're inside the deadzone, unset the next event // If we're inside the deadzone, unset the next event
if rule.Input.InDeadZone(event.Value) { if rule.Input.InDeadZone(event.Value) {
rule.nextEvent = NoNextEvent rule.nextEvent = NoNextEvent