1
0
Fork 0
mirror of https://github.com/Luzifer/streamdeck.git synced 2025-01-04 17:06:09 +00:00
streamdeck/cmd/streamdeck/main.go
Knut Ahlers 41a34dd76c
Add support for SDXL and multiple StreamDecks
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2020-06-06 15:13:06 +02:00

244 lines
5.6 KiB
Go

package main
import (
"fmt"
"os"
"os/signal"
"path"
"syscall"
"time"
"github.com/Luzifer/rconfig/v2"
"github.com/Luzifer/streamdeck"
"github.com/fsnotify/fsnotify"
"github.com/pkg/errors"
"github.com/sashko/go-uinput"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
var (
cfg = struct {
Config string `flag:"config,c" vardefault:"config" description:"Configuration with page / key definitions"`
List bool `flag:"list,l" default:"false" description:"List all available StreamDecks"`
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
ProductID string `flag:"product-id,p" default:"" description:"Specify StreamDeck to use (use list to find ID), default first found"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
}{}
currentBrightness int
userConfig config
activePage page
activePageName string
activeLoops []refreshingDisplayElement
sd *streamdeck.Client
kbd uinput.Keyboard
version = "dev"
)
func init() {
cfgDir, _ := os.UserConfigDir()
rconfig.SetVariableDefaults(map[string]string{
"config": path.Join(cfgDir, "streamdeck.yaml"),
})
if err := rconfig.ParseAndValidate(&cfg); err != nil {
log.Fatalf("Unable to parse commandline options: %s", err)
}
if cfg.VersionAndExit {
fmt.Printf("streamdeck %s\n", version)
os.Exit(0)
}
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
log.WithError(err).Fatal("Unable to parse log level")
} else {
log.SetLevel(l)
}
}
func loadConfig() error {
userConfFile, err := os.Open(cfg.Config)
if err != nil {
return errors.Wrap(err, "Unable to open config")
}
defer userConfFile.Close()
var tempConf config
if err = yaml.NewDecoder(userConfFile).Decode(&tempConf); err != nil {
return errors.Wrap(err, "Unable to parse config")
}
applySystemPages(&tempConf)
userConfig = tempConf
return nil
}
func main() {
if cfg.List {
listAndQuit()
}
deck, err := selectDeckToUse()
if err != nil {
log.WithError(err).Fatal("Unable to select StreamDeck to use")
}
// Initalize control devices
kbd, err = uinput.CreateKeyboard()
if err != nil {
log.WithError(err).Fatal("Unable to create uinput keyboard")
}
defer kbd.Close()
// Initialize device
sd, err = streamdeck.New(deck)
if err != nil {
log.WithError(err).Fatal("Unable to open StreamDeck connection")
}
defer sd.Close()
serial, err := sd.Serial()
if err != nil {
log.WithError(err).Fatal("Unable to read serial")
}
firmware, err := sd.GetFimwareVersion()
if err != nil {
log.WithError(err).Fatal("Unable to read firmware")
}
log.WithFields(log.Fields{
"firmware": firmware,
"serial": serial,
}).Info("Found StreamDeck")
// Load config
if err = loadConfig(); err != nil {
log.WithError(err).Fatal("Unable to load config")
}
// Initial setup
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
defer sd.ResetToLogo()
if err = sd.SetBrightness(userConfig.DefaultBrightness); err != nil {
log.WithError(err).Fatal("Unable to set brightness")
}
currentBrightness = userConfig.DefaultBrightness
if err = togglePage(userConfig.DefaultPage); err != nil {
log.WithError(err).Error("Unable to load default page")
}
var offTimer *time.Timer = &time.Timer{}
if userConfig.DisplayOffTime > 0 {
offTimer = time.NewTimer(userConfig.DisplayOffTime)
}
fswatch, err := fsnotify.NewWatcher()
if err != nil {
log.WithError(err).Fatal("Unable to create file watcher")
}
if userConfig.AutoReload {
if err = fswatch.Add(cfg.Config); err != nil {
log.WithError(err).Error("Unable to watch config, auto-reload will not work")
}
}
for {
select {
case evt := <-sd.Subscribe():
if userConfig.DisplayOffTime > 0 {
offTimer.Reset(userConfig.DisplayOffTime)
}
if evt.Key >= len(activePage.Keys) {
continue
}
kd := activePage.Keys[evt.Key]
if kd.On == "down" && evt.Type == streamdeck.EventTypeDown || (kd.On == "up" || kd.On == "") && evt.Type == streamdeck.EventTypeUp || kd.On == "both" {
if err := triggerAction(kd); err != nil {
log.WithError(err).Error("Unable to execute action")
}
}
case <-offTimer.C:
if err := togglePage("@@blank"); err != nil {
log.WithError(err).Error("Unable to toggle to blank page")
}
case evt := <-fswatch.Events:
if evt.Op&fsnotify.Write == fsnotify.Write {
log.Info("Detected change of config, reloading")
if err := loadConfig(); err != nil {
log.WithError(err).Error("Unable to reload config")
continue
}
var nextPage = userConfig.DefaultPage
if _, ok := userConfig.Pages[activePageName]; ok {
nextPage = activePageName
}
if err := togglePage(nextPage); err != nil {
log.WithError(err).Error("Unable to reload page")
continue
}
}
case <-sigs:
return
}
}
}
func togglePage(page string) error {
// Reset potentially running looped elements
for _, l := range activeLoops {
if err := l.StopLoopDisplay(); err != nil {
return errors.Wrap(err, "Unable to stop element refresh")
}
}
activeLoops = nil
activePage = userConfig.Pages[page]
activePageName = page
sd.ClearAllKeys()
for idx, kd := range activePage.Keys {
if kd.Display.Type != "" {
if err := callDisplayElement(idx, kd); err != nil {
return errors.Wrapf(err, "Unable to execute display element on key %d", idx)
}
}
}
return nil
}
func triggerAction(kd keyDefinition) error {
for _, a := range kd.Actions {
if a.Type != "" {
if err := callAction(a); err != nil {
return err
}
}
}
return nil
}