Implement basic config file parsing and create virtual devices from config file.
This commit is contained in:
parent
eeebb6d0b1
commit
faa51bdda2
6 changed files with 156 additions and 88 deletions
|
@ -2,6 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.annabunches.net/annabunches/joyful/internal/config"
|
"git.annabunches.net/annabunches/joyful/internal/config"
|
||||||
|
@ -10,18 +12,36 @@ import (
|
||||||
"github.com/holoplot/go-evdev"
|
"github.com/holoplot/go-evdev"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func readConfig() *config.ConfigParser {
|
||||||
// parse configs
|
|
||||||
parser := &config.ConfigParser{}
|
parser := &config.ConfigParser{}
|
||||||
parser.Parse("~/.config/joyful") // TODO: make ~ work here
|
homeDir, err := os.UserHomeDir()
|
||||||
|
logger.FatalIfError(err, "Can't get user home directory, so can't find configuration.")
|
||||||
|
err = parser.Parse(filepath.Join(homeDir, ".config/joyful"))
|
||||||
|
logger.FatalIfError(err, "")
|
||||||
|
return parser
|
||||||
|
}
|
||||||
|
|
||||||
vDevices := parser.CreateVirtualDevices()
|
func initVirtualDevices(config *config.ConfigParser) map[string]*virtualdevice.EventBuffer {
|
||||||
|
vDevices := config.CreateVirtualDevices()
|
||||||
vBuffers := make(map[string]*virtualdevice.EventBuffer)
|
vBuffers := make(map[string]*virtualdevice.EventBuffer)
|
||||||
for name, device := range vDevices {
|
for name, device := range vDevices {
|
||||||
vBuffers[name] = virtualdevice.NewEventBuffer(device)
|
vBuffers[name] = virtualdevice.NewEventBuffer(device)
|
||||||
}
|
}
|
||||||
|
return vBuffers
|
||||||
|
}
|
||||||
|
|
||||||
pDevice, err := evdev.Open("/dev/input/event12")
|
func main() {
|
||||||
|
// parse configs
|
||||||
|
config := readConfig()
|
||||||
|
|
||||||
|
// Initialize virtual devices and event buffers
|
||||||
|
vBuffers := initVirtualDevices(config)
|
||||||
|
|
||||||
|
// Initialize physical devices
|
||||||
|
// pDevices := config.ConnectPhysicalDevices()
|
||||||
|
|
||||||
|
// TEST CODE
|
||||||
|
pDevice, err := evdev.Open("/dev/input/event11")
|
||||||
logger.FatalIfError(err, "Couldn't open physical device")
|
logger.FatalIfError(err, "Couldn't open physical device")
|
||||||
|
|
||||||
name, err := pDevice.Name()
|
name, err := pDevice.Name()
|
||||||
|
@ -31,14 +51,13 @@ func main() {
|
||||||
fmt.Printf("Connected to physical device %s\n", name)
|
fmt.Printf("Connected to physical device %s\n", name)
|
||||||
|
|
||||||
var combo int32 = 0
|
var combo int32 = 0
|
||||||
buffer := vBuffers["test"]
|
buffer := vBuffers["main"]
|
||||||
for {
|
for {
|
||||||
last := combo
|
last := combo
|
||||||
|
|
||||||
event, err := pDevice.ReadOne()
|
event, err := pDevice.ReadOne()
|
||||||
logger.LogIfError(err, "Error while reading event")
|
logger.LogIfError(err, "Error while reading event")
|
||||||
|
|
||||||
// FIXME: test code
|
|
||||||
for event.Code != evdev.SYN_REPORT {
|
for event.Code != evdev.SYN_REPORT {
|
||||||
if event.Type == evdev.EV_KEY {
|
if event.Type == evdev.EV_KEY {
|
||||||
switch event.Code {
|
switch event.Code {
|
||||||
|
@ -95,8 +114,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.SendEvents()
|
buffer.SendEvents()
|
||||||
// FIXME: end test code
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
// END TEST CODE
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -13,18 +14,22 @@ import (
|
||||||
|
|
||||||
type ConfigParser struct {
|
type ConfigParser struct {
|
||||||
config Config
|
config Config
|
||||||
configFiles []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse all the config files and store the config data for further use
|
// Parse all the config files and store the config data for further use
|
||||||
func (parser *ConfigParser) Parse(directory string) {
|
func (parser *ConfigParser) Parse(directory string) error {
|
||||||
|
parser.config = Config{}
|
||||||
|
|
||||||
// Find the config files in the directory
|
// Find the config files in the directory
|
||||||
dirEntries, err := os.ReadDir(directory)
|
dirEntries, err := os.ReadDir(directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = os.Mkdir(directory, 0755)
|
err = os.Mkdir(directory, 0755)
|
||||||
logger.FatalIfError(err, "Failed to create config directory at "+directory)
|
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 {
|
for _, file := range dirEntries {
|
||||||
name := file.Name()
|
name := file.Name()
|
||||||
if file.IsDir() || !(strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml")) {
|
if file.IsDir() || !(strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml")) {
|
||||||
|
@ -33,42 +38,41 @@ func (parser *ConfigParser) Parse(directory string) {
|
||||||
|
|
||||||
filePath := filepath.Join(directory, name)
|
filePath := filepath.Join(directory, name)
|
||||||
if strings.HasSuffix(filePath, ".yaml") || strings.HasSuffix(filePath, ".yml") {
|
if strings.HasSuffix(filePath, ".yaml") || strings.HasSuffix(filePath, ".yml") {
|
||||||
parser.configFiles = append(parser.configFiles, filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rawData := parser.parseConfigFiles()
|
|
||||||
err = yaml.Unmarshal(rawData, &parser.config)
|
|
||||||
logger.FatalIfError(err, "Failed to parse config")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open each config file and concatenate their contents
|
|
||||||
func (parser *ConfigParser) parseConfigFiles() []byte {
|
|
||||||
var rawData []byte
|
|
||||||
|
|
||||||
for _, filePath := range parser.configFiles {
|
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIfError(err, fmt.Sprintf("Failed to read config file '%s'", filePath))
|
logger.LogError(err, "Error while opening config file")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
newConfig := Config{}
|
||||||
rawData = append(rawData, data...)
|
err = yaml.Unmarshal(data, &newConfig)
|
||||||
rawData = append(rawData, '\n')
|
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...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parser.config.Devices) == 0 {
|
||||||
|
return errors.New("Found no devices in configuration. Please add configuration at " + directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rawData) == 0 {
|
|
||||||
logger.Log("No config data found. Write .yml config files in ~/.config/joyful")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
return rawData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateVirtualDevices will register any configured devices with type = virtual
|
||||||
|
// using /dev/uinput, and return a map of those devices.
|
||||||
|
//
|
||||||
|
// This function assumes you have already called Parse() on the config directory.
|
||||||
|
//
|
||||||
|
// This function should only be called once, unless you want to create duplicate devices for some reason.
|
||||||
func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice {
|
func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice {
|
||||||
deviceMap := make(map[string]*evdev.InputDevice)
|
deviceMap := make(map[string]*evdev.InputDevice)
|
||||||
|
|
||||||
for _, deviceConfig := range parser.config.Devices.Virtual {
|
for _, deviceConfig := range parser.config.Devices {
|
||||||
|
if strings.ToLower(deviceConfig.Type) != DeviceTypeVirtual {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
vDevice, err := evdev.CreateDevice(
|
vDevice, err := evdev.CreateDevice(
|
||||||
fmt.Sprintf("joyful-%s", deviceConfig.Name),
|
fmt.Sprintf("joyful-%s", deviceConfig.Name),
|
||||||
// TODO: who knows what these should actually be
|
// TODO: who knows what these should actually be
|
||||||
|
@ -95,6 +99,36 @@ func (parser *ConfigParser) CreateVirtualDevices() map[string]*evdev.InputDevice
|
||||||
return deviceMap
|
return deviceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectPhysicalDevices will create InputDevices corresponding to any registered
|
||||||
|
// devices with type = physical. It will also attempt to acquire exclusive access
|
||||||
|
// to those devices, to prevent the same inputs from being read on multiple devices.
|
||||||
|
//
|
||||||
|
// This function assumes you have already called Parse() on the config directory.
|
||||||
|
//
|
||||||
|
// This function should only be called once.
|
||||||
|
//
|
||||||
|
// STUB: this function does not yet function.
|
||||||
|
func (parser *ConfigParser) ConnectPhysicalDevices() map[string]*evdev.InputDevice {
|
||||||
|
deviceMap := make(map[string]*evdev.InputDevice)
|
||||||
|
|
||||||
|
for _, deviceConfig := range parser.config.Devices {
|
||||||
|
if strings.ToLower(deviceConfig.Type) != DeviceTypePhysical {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vDevice, err := evdev.Open("/dev/input/foo")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIfError(err, "Failed to open physical device")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceMap[deviceConfig.Name] = vDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceMap
|
||||||
|
}
|
||||||
|
|
||||||
func makeButtons(numButtons int) []evdev.EvCode {
|
func makeButtons(numButtons int) []evdev.EvCode {
|
||||||
if numButtons > 56 {
|
if numButtons > 56 {
|
||||||
numButtons = 56
|
numButtons = 56
|
||||||
|
@ -110,8 +144,8 @@ func makeButtons(numButtons int) []evdev.EvCode {
|
||||||
|
|
||||||
if numButtons > 16 {
|
if numButtons > 16 {
|
||||||
startCode = 0x2c0
|
startCode = 0x2c0
|
||||||
for i := 0; i < numButtons - 16; i++ {
|
for i := 0; i < numButtons-16; i++ {
|
||||||
buttons[16+i] = evdev.EvCode(startCode+i)
|
buttons[16+i] = evdev.EvCode(startCode + i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
41
internal/config/schema.go
Normal file
41
internal/config/schema.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// These types comprise the YAML schema for configuring Joyful.
|
||||||
|
// The config files will be combined and then unmarshalled into this
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Devices []DeviceConfig `yaml:"devices"`
|
||||||
|
// TODO: add groups
|
||||||
|
// Groups []GroupConfig `yaml:"groups,omitempty"`
|
||||||
|
Rules []RuleConfig `yaml:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceConfig struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Uuid string `yaml:"uuid,omitempty"`
|
||||||
|
Buttons int `yaml:"buttons,omitempty"`
|
||||||
|
Axes int `yaml:"axes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleConfig struct {
|
||||||
|
Name string `yaml:"name,omitempty"`
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Input []RuleInputConfig `yaml:"input"`
|
||||||
|
Output RuleOutputConfig `yaml:"output"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleInputConfig struct {
|
||||||
|
Device string `yaml:"device"`
|
||||||
|
Button string `yaml:"button,omitempty"`
|
||||||
|
Buttons []string `yaml:"buttons,omitempty"`
|
||||||
|
Axis string `yaml:"axis,omitempty"`
|
||||||
|
Inverted bool `yaml:"inverted,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleOutputConfig struct {
|
||||||
|
Device string `yaml:"device,omitempty"`
|
||||||
|
Button string `yaml:"button,omitempty"`
|
||||||
|
Axis string `yaml:"axis,omitempty"`
|
||||||
|
Groups string `yaml:"groups,omitempty"`
|
||||||
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Devices DevicesConfig `yaml:"devices"`
|
|
||||||
Rules []RuleConfig `yaml:"rules"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DevicesConfig struct {
|
|
||||||
Physical []PhysicalDeviceConfig `yaml:"physical"`
|
|
||||||
Virtual []VirtualDeviceConfig `yaml:"virtual"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PhysicalDeviceConfig struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Uuid string `yaml:"uuid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VirtualDeviceConfig struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Buttons int `yaml:"buttons"`
|
|
||||||
Axes int `yaml:"axes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleConfig struct {
|
|
||||||
Type string `yaml:"type"`
|
|
||||||
Input RuleInputConfig `yaml:"input"`
|
|
||||||
Output RuleOutputConfig `yaml:"output"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleInputConfig struct {
|
|
||||||
Device string `yaml:"device"`
|
|
||||||
Button string `yaml:"button,omitempty"`
|
|
||||||
Buttons []string `yaml:"buttons,omitempty"`
|
|
||||||
Axis string `yaml:"axis,omitempty"`
|
|
||||||
Inverted bool `yaml:"inverted,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleOutputConfig struct {
|
|
||||||
Device string `yaml:"device"`
|
|
||||||
Button string `yaml:"button,omitempty"`
|
|
||||||
Axis string `yaml:"axis,omitempty"`
|
|
||||||
}
|
|
|
@ -60,3 +60,11 @@ var (
|
||||||
evdev.BTN_TRIGGER_HAPPY40,
|
evdev.BTN_TRIGGER_HAPPY40,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeviceTypePhysical = "physical"
|
||||||
|
DeviceTypeVirtual = "virtual"
|
||||||
|
|
||||||
|
RuleTypeSimple = "simple"
|
||||||
|
RuleTypeCombo = "combo"
|
||||||
|
)
|
|
@ -9,12 +9,20 @@ func Log(msg string) {
|
||||||
fmt.Println(msg)
|
fmt.Println(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LogError(err error, msg string) {
|
||||||
|
if msg == "" {
|
||||||
|
fmt.Printf("%s\n", err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s: %s\n", msg, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func LogIfError(err error, msg string) {
|
func LogIfError(err error, msg string) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s: %s\n", msg, err.Error())
|
LogError(err, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FatalIfError(err error, msg string) {
|
func FatalIfError(err error, msg string) {
|
||||||
|
@ -22,6 +30,6 @@ func FatalIfError(err error, msg string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
LogIfError(err, msg)
|
LogError(err, msg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue