diff --git a/docs/custom-modules.md b/docs/custom-modules.md index c4b4415..387b265 100644 --- a/docs/custom-modules.md +++ b/docs/custom-modules.md @@ -35,10 +35,10 @@ import "github.com/unix-streamdeck/api/v2" func GetModule() api.Module { return api.Module{ Name: "MyModule", - NewIcon: func() api.IconHandler { return &MyIconHandler{} }, - NewKey: func() api.KeyHandler { return &MyKeyHandler{} }, - IconFields: []api.Field{ /* ... */ }, - KeyFields: []api.Field{ /* ... */ }, + NewForeground: func() api.ForegroundHandler { return &MyForegroundHandler{} }, + NewInput: func() api.InputHandler { return &MyInputHandler{} }, + ForegroundFields: []api.Field{ /* ... */ }, + InputFields: []api.Field{ /* ... */ }, } } ``` diff --git a/go.mod b/go.mod index db1834e..49cf735 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/linuxdeepin/go-x11-client v0.0.0-20240415051504-c8e43d028ff9 github.com/shirou/gopsutil/v3 v3.24.5 github.com/the-jonsey/pulseaudio v0.0.2-0.20260222211608-58a869b098fe - github.com/unix-streamdeck/api/v2 v2.0.15 + github.com/unix-streamdeck/api/v2 v2.0.16 github.com/unix-streamdeck/driver v0.0.0-20260313153150-8a1327d02063 golang.org/x/sync v0.20.0 golang.org/x/sys v0.42.0 diff --git a/go.sum b/go.sum index 3b4d23a..3bf982d 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/unix-streamdeck/api/v2 v2.0.15 h1:YugCSVZC0d+e/7jvCBamZEubgmw3jOiGm717jl242gg= -github.com/unix-streamdeck/api/v2 v2.0.15/go.mod h1:trzV1CEDHjSi9DAIkXOFhJyPETq7xCjg2sWjY6VtKYk= +github.com/unix-streamdeck/api/v2 v2.0.16 h1:d6CP2VJ9lKlIIyKD5ZRHsuPKC1yiyI7827IBLx6aI8c= +github.com/unix-streamdeck/api/v2 v2.0.16/go.mod h1:trzV1CEDHjSi9DAIkXOFhJyPETq7xCjg2sWjY6VtKYk= github.com/unix-streamdeck/driver v0.0.0-20260313153150-8a1327d02063 h1:lpAT1zkHZHlpF/QC4y1jId4iw9y4uBSvP15JWDMRLlg= github.com/unix-streamdeck/driver v0.0.0-20260313153150-8a1327d02063/go.mod h1:RiXT8GuYv4NQ0vZJ6eP2ksCD60vSYMD4wQFabALEbis= github.com/unix-streamdeck/gg v0.0.0-20260313120600-9d60d38ce9f9 h1:sVzoJvwd1QtjQPsfsRYM+R7Ff0TxeWI6GunRqNH+w3U= diff --git a/main.go b/main.go index 945fec2..8bcdfb4 100644 --- a/main.go +++ b/main.go @@ -21,18 +21,18 @@ var isRunning = true func main() { log.Default().SetFlags(log.Lshortfile | log.Ltime) + log.Default().SetPrefix("(global) ") checkDuplicateStreamdeckdInstance() configPtr := flag.String("config", "", "Path to config file") flag.Parse() streamdeckd.SetConfigPath(*configPtr) cleanupHook() - defer HandlePanic() go streamdeckd.InitDBUS() go streamdeckd.UpdateApplication() go streamdeckd.EnableVirtualKeyboard() examples.RegisterBaseModules() streamdeckd.LoadConfig() - streamdeckd.Devs = make(map[string]*streamdeckd.VirtualDev) + streamdeckd.Devs = make(map[string]streamdeckd.IVirtualDev) screensaverDbus, err := streamdeckd.ConnectScreensaver() if err != nil { log.Println(err) @@ -62,13 +62,6 @@ func attemptConnection() { } } -func HandlePanic() { - if err := recover(); err != nil { - log.Println("panic occurred:", err) - shutdown() - } -} - func cleanupHook() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGSTOP, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGINT) @@ -81,9 +74,9 @@ func cleanupHook() { func shutdown() { log.Println("Cleaning up") isRunning = false - go streamdeckd.UnmountHandlers() + streamdeckd.UnmountHandlers() for s := range streamdeckd.Devs { - streamdeckd.Devs[s].Stop() + streamdeckd.Devs[s].Close() } os.Exit(0) } diff --git a/streamdeckd/application_manager.go b/streamdeckd/application_manager.go new file mode 100644 index 0000000..6f8b81a --- /dev/null +++ b/streamdeckd/application_manager.go @@ -0,0 +1,33 @@ +package streamdeckd + +import "log" + +type IApplicationManager interface { + SetApplication(application string) + AttachListener(listener func(application string)) + GetApplication() string +} + +type ApplicationManager struct { + listeners []func(application string) + activeApplication string +} + +func (am *ApplicationManager) SetApplication(application string) { + + if am.activeApplication != application { + log.Println("Application updated to: " + application) + am.activeApplication = application + for _, listener := range am.listeners { + go listener(application) + } + } +} + +func (am *ApplicationManager) AttachListener(listener func(application string)) { + am.listeners = append(am.listeners, listener) +} + +func (am *ApplicationManager) GetApplication() string { + return am.activeApplication +} diff --git a/streamdeckd/backgrounder.go b/streamdeckd/backgrounder.go new file mode 100644 index 0000000..52df3e8 --- /dev/null +++ b/streamdeckd/backgrounder.go @@ -0,0 +1,230 @@ +package streamdeckd + +import ( + "image" + "log" + + "github.com/unix-streamdeck/api/v2" +) + +type IBackgrounder interface { + SetLcdBackground(backgrounder api.LcdBackgrounder) + SetKeyBackground(backgrounder api.KeyGridBackgrounder) + SetIndividualLcdBackground(backgrounder api.LcdSegmentBackgrounder, index int) + SetIndividualKeyBackground(backgrounder api.KeyBackgrounder, index int) + AttachPageChangeListener() +} + +type Backgrounder struct { + vdev IVirtualDev +} + +func (bg *Backgrounder) SetLcdBackground(backgrounder api.LcdBackgrounder) { + if backgrounder.GetTouchPanelBackground() == "" { + return + } + + if backgrounder.GetTouchPanelBackgroundHandler() == nil { + var handler api.BackgroundHandler + + for _, module := range modules { + if module.Name == backgrounder.GetTouchPanelBackground() { + handler = module.NewBackground() + } + } + + backgrounder.SetTouchPanelBackgroundHandler(handler) + } + + if backgrounder.GetTouchPanelBackgroundHandlerFields() != nil { + go backgrounder.GetTouchPanelBackgroundHandler().StartGrid(backgrounder.GetTouchPanelBackgroundHandlerFields(), api.LCD, *bg.vdev.SdInfo(), func(imgs []image.Image) { + if len(imgs) == bg.vdev.SdInfo().KnobCols { + backgrounder.SetTouchPanelBackgroundBuff(imgs) + + for u := range imgs { + bg.vdev.SetPanelBackground(u, bg.vdev.PageManager().GetPage()) + } + } + }) + return + } + + if backgrounder.GetTouchPanelBackgroundBuff() != nil { + return + } + + img, err := LoadImage(backgrounder.GetTouchPanelBackground()) + if err != nil { + bg.vdev.Logger().Println(err) + return + } + + img = api.ResizeImageWH(img, bg.vdev.SdInfo().LcdBackgroundWidth, bg.vdev.SdInfo().LcdBackgroundHeight) + + imgs := bg.vdev.SdInfo().SplitBackgroundImage(img, api.LCD) + + backgrounder.SetTouchPanelBackgroundBuff(imgs) + + for index, _ := range imgs { + bg.vdev.SetPanelBackground(index, bg.vdev.PageManager().GetPage()) + } +} + +func (bg *Backgrounder) SetKeyBackground(backgrounder api.KeyGridBackgrounder) { + if backgrounder.GetKeyGridBackground() == "" { + return + } + + if backgrounder.GetKeyGridBackgroundHandler() == nil { + var handler api.BackgroundHandler + + for _, module := range modules { + if module.Name == backgrounder.GetKeyGridBackground() { + handler = module.NewBackground() + } + } + + backgrounder.SetKeyGridBackgroundHandler(handler) + } + + if backgrounder.GetKeyGridBackgroundHandler() != nil { + go backgrounder.GetKeyGridBackgroundHandler().StartGrid(backgrounder.GetKeyGridBackgroundHandlerFields(), api.KEY, *bg.vdev.SdInfo(), func(imgs []image.Image) { + if len(imgs) == bg.vdev.SdInfo().Cols*bg.vdev.SdInfo().Rows { + backgrounder.SetKeyGridBackgroundBuff(imgs) + + for u := range imgs { + bg.vdev.SetKeyBackground(u, bg.vdev.PageManager().GetPage()) + } + } + }) + return + } + + if backgrounder.GetKeyGridBackgroundBuff() != nil { + return + } + + img, err := LoadImage(backgrounder.GetKeyGridBackground()) + if err != nil { + bg.vdev.Logger().Println(err) + return + } + + img = api.ResizeImageWH(img, bg.vdev.SdInfo().KeyGridBackgroundWidth, bg.vdev.SdInfo().KeyGridBackgroundHeight) + + imgs := bg.vdev.SdInfo().SplitBackgroundImage(img, api.KEY) + + backgrounder.SetKeyGridBackgroundBuff(imgs) + + for index, _ := range imgs { + bg.vdev.SetKeyBackground(index, bg.vdev.PageManager().GetPage()) + } +} + +func (bg *Backgrounder) SetIndividualLcdBackground(backgrounder api.LcdSegmentBackgrounder, index int) { + if backgrounder.GetTouchPanelBackground() == "" { + return + } + + if backgrounder.GetTouchPanelBackgroundHandler() == nil { + var handler api.BackgroundHandler + + for _, module := range modules { + if module.Name == backgrounder.GetTouchPanelBackground() { + handler = module.NewBackground() + } + } + + backgrounder.SetTouchPanelBackgroundHandler(handler) + } + + if backgrounder.GetTouchPanelBackgroundHandlerFields() != nil { + go backgrounder.GetTouchPanelBackgroundHandler().Start(backgrounder.GetTouchPanelBackgroundHandlerFields(), api.LCD, *bg.vdev.SdInfo(), func(img image.Image) { + backgrounder.SetTouchPanelBackgroundBuff(img) + + bg.vdev.SetPanelBackground(index, bg.vdev.PageManager().GetPage()) + }) + return + } + + if backgrounder.GetTouchPanelBackgroundBuff() != nil { + return + } + + img, err := LoadImage(backgrounder.GetTouchPanelBackground()) + if err != nil { + bg.vdev.Logger().Println(err) + return + } + + img = api.ResizeImageWH(img, bg.vdev.SdInfo().LcdWidth, bg.vdev.SdInfo().LcdHeight) + + backgrounder.SetTouchPanelBackgroundBuff(img) + + bg.vdev.SetPanelBackground(index, bg.vdev.PageManager().GetPage()) +} + +func (bg *Backgrounder) SetIndividualKeyBackground(backgrounder api.KeyBackgrounder, index int) { + if backgrounder.GetKeyBackground() == "" { + return + } + + if backgrounder.GetKeyBackgroundHandler() == nil { + var handler api.BackgroundHandler + + for _, module := range modules { + if module.Name == backgrounder.GetKeyBackground() { + handler = module.NewBackground() + } + } + + backgrounder.SetKeyBackgroundHandler(handler) + } + + if backgrounder.GetKeyBackgroundHandler() != nil { + go backgrounder.GetKeyBackgroundHandler().Start(backgrounder.GetKeyBackgroundHandlerFields(), api.KEY, *bg.vdev.SdInfo(), func(img image.Image) { + backgrounder.SetKeyBackgroundBuff(img) + + bg.vdev.SetKeyBackground(index, bg.vdev.PageManager().GetPage()) + }) + return + } + + if backgrounder.GetKeyBackgroundBuff() != nil { + return + } + + img, err := LoadImage(backgrounder.GetKeyBackground()) + if err != nil { + log.Println(err) + return + } + + img = api.ResizeImage(img, bg.vdev.SdInfo().IconSize) + + bg.vdev.SetKeyBackground(index, bg.vdev.PageManager().GetPage()) +} + +func (bg *Backgrounder) AttachPageChangeListener() { + + bg.vdev.PageManager().AttachListener(func(newPage, _ int) { + + currentPage := bg.vdev.Config().Pages[newPage] + + go bg.SetKeyBackground(¤tPage) + + go bg.SetLcdBackground(¤tPage) + + for i := range currentPage.Keys { + key := ¤tPage.Keys[i] + + go bg.SetIndividualKeyBackground(key, i) + } + + for i := range currentPage.Knobs { + knob := ¤tPage.Knobs[i] + + go bg.SetIndividualLcdBackground(knob, i) + } + }) +} diff --git a/streamdeckd/config.go b/streamdeckd/config.go index 41886e2..1ef2c9e 100644 --- a/streamdeckd/config.go +++ b/streamdeckd/config.go @@ -7,6 +7,7 @@ import ( "os" "github.com/unix-streamdeck/api/v2" + streamdeck "github.com/unix-streamdeck/driver" ) var configPath string @@ -17,7 +18,6 @@ var basicConfig = api.ConfigV3{ }, } var config *api.ConfigV3 -var migrateConfigFromV1 = false func LoadConfig() { var err error @@ -72,11 +72,11 @@ func SetConfig(configString string) error { for s := range Devs { dev := Devs[s] for i := range config.Decks { - if dev.Deck.Serial == config.Decks[i].Serial { - dev.Config = config.Decks[i] + if dev.Serial() == config.Decks[i].Serial { + dev.SetConfig(config.Decks[i]) } } - dev.SetPage(Devs[s].Page) + dev.PageManager().SetPage(Devs[s].PageManager().GetPage()) } return nil } @@ -87,11 +87,11 @@ func ReloadConfig() error { for s := range Devs { dev := Devs[s] for i := range config.Decks { - if dev.Deck.Serial == config.Decks[i].Serial { - dev.Config = config.Decks[i] + if dev.Serial() == config.Decks[i].Serial { + dev.SetConfig(config.Decks[i]) } } - dev.SetPage(Devs[s].Page) + dev.PageManager().SetPage(Devs[s].PageManager().GetPage()) } return nil } @@ -133,3 +133,30 @@ func SetConfigPath(path string) { configPath = basePath + string(os.PathSeparator) + ".streamdeck-config.json" } } + +func findConfig(device *streamdeck.Device) api.DeckV3 { + for _, deck := range config.Decks { + if deck.Serial == device.Serial { + return deck + } + } + + return makeEmptyDeckConfig(device) +} + +func makeEmptyDeckConfig(device *streamdeck.Device) api.DeckV3 { + var pages []api.PageV3 + page := api.PageV3{} + for i := 0; i < int(device.Rows)*int(device.Columns); i++ { + applications := make(map[string]*api.KeyConfigV3) + applications[""] = &api.KeyConfigV3{} + page.Keys = append(page.Keys, api.KeyV3{ + Application: applications, + }) + } + pages = append(pages, page) + devConf := api.DeckV3{Serial: device.Serial, Pages: pages} + config.Decks = append(config.Decks, devConf) + _ = SaveConfig() + return devConf +} diff --git a/streamdeckd/dbus.go b/streamdeckd/dbus.go index 99563b2..84ebe46 100644 --- a/streamdeckd/dbus.go +++ b/streamdeckd/dbus.go @@ -12,11 +12,25 @@ import ( "github.com/godbus/dbus/v5" "github.com/unix-streamdeck/api/v2" + streamdeck "github.com/unix-streamdeck/driver" ) var conn *dbus.Conn -var sDbus *StreamDeckDBus +var sDbus IStreamDeckDBus + +type IStreamDeckDBus interface { + GetDeckInfo() (string, *dbus.Error) + GetConfig() (string, *dbus.Error) + ReloadConfig() *dbus.Error + SetPage(serial string, page int) *dbus.Error + SetConfig(configString string) *dbus.Error + CommitConfig() *dbus.Error + GetModules() (string, *dbus.Error) + PressButton(serial string, keyIndex int) *dbus.Error + GetHandlerExample(serial string, keyString string) (string, *dbus.Error) + GetKnobHandlerExample(serial string, keyString string) (string, *dbus.Error) +} type StreamDeckDBus struct { } @@ -24,7 +38,7 @@ type StreamDeckDBus struct { func (s StreamDeckDBus) GetDeckInfo() (string, *dbus.Error) { var decks []api.StreamDeckInfoV1 for _, dev := range Devs { - decks = append(decks, dev.sdInfo) + decks = append(decks, *dev.SdInfo()) } infoString, err := json.Marshal(decks) if err != nil { @@ -51,9 +65,9 @@ func (StreamDeckDBus) ReloadConfig() *dbus.Error { func (StreamDeckDBus) SetPage(serial string, page int) *dbus.Error { for s := range Devs { - if Devs[s].Deck.Serial == serial { + if Devs[s].Serial() == serial { dev := Devs[s] - dev.SetPage(page) + dev.PageManager().SetPage(page) return nil } } @@ -86,10 +100,12 @@ func (StreamDeckDBus) GetModules() (string, *dbus.Error) { func (StreamDeckDBus) PressButton(serial string, keyIndex int) *dbus.Error { dev, ok := Devs[serial] - if !ok || !dev.IsOpen { + if !ok || !dev.IsOpen() { return dbus.MakeFailedError(errors.New("Can't find connected device: " + serial)) } - HandleKeyInput(dev, &dev.Config.Pages[dev.Page].Keys[keyIndex], true) + dev.InputManager().HandleKeyInput(&dev.Config().Pages[dev.PageManager().GetPage()].Keys[keyIndex], streamdeck.InputEvent{ + EventType: streamdeck.KEY_PRESS, + }) return nil } @@ -99,30 +115,29 @@ func (StreamDeckDBus) GetHandlerExample(serial string, keyString string) (string if err != nil { return "", dbus.MakeFailedError(err) } - key.SharedState = make(map[string]interface{}) if key.IconHandler == "" || key.IconHandler == "Default" { return "", dbus.MakeFailedError(errors.New("Invalid icon handler")) } - var handler api.IconHandler + var handler api.ForegroundHandler modules := AvailableModules() for _, module := range modules { if module.Name == key.IconHandler { - handler = module.NewIcon() + handler = module.NewForeground() break } } if handler == nil { return "", dbus.MakeFailedError(errors.New("Invalid icon handler")) } - var dev api.StreamDeckInfoV1 + var dev *api.StreamDeckInfoV1 sd, ok := Devs[serial] if !ok { return "", dbus.MakeFailedError(errors.New("could not find device")) } - dev = sd.sdInfo + dev = sd.SdInfo() var img image.Image log.Println("Created and running " + key.IconHandler + " for dbus") - handler.Start(*key, dev, func(image image.Image) { + handler.Start(key.IconHandlerFields, api.LCD, *dev, func(image image.Image) { if image.Bounds().Max.X != dev.IconSize || image.Bounds().Max.Y != dev.IconSize { image = api.ResizeImage(image, dev.IconSize) } @@ -153,30 +168,29 @@ func (StreamDeckDBus) GetKnobHandlerExample(serial string, keyString string) (st if err != nil { return "", dbus.MakeFailedError(err) } - key.SharedState = make(map[string]interface{}) if key.LcdHandler == "" || key.LcdHandler == "Default" { return "", dbus.MakeFailedError(errors.New("Invalid icon handler")) } - var handler api.LcdHandler + var handler api.ForegroundHandler modules := AvailableModules() for _, module := range modules { if module.Name == key.LcdHandler { - handler = module.NewLcd() + handler = module.NewForeground() break } } if handler == nil { return "", dbus.MakeFailedError(errors.New("Invalid icon handler")) } - var dev api.StreamDeckInfoV1 + var dev *api.StreamDeckInfoV1 sd, ok := Devs[serial] if !ok { return "", dbus.MakeFailedError(errors.New("could not find device")) } - dev = sd.sdInfo + dev = sd.SdInfo() var img image.Image log.Println("Created and running " + key.LcdHandler + " for dbus") - go handler.Start(*key, dev, func(image image.Image) { + go handler.Start(key.LcdHandlerFields, api.LCD, *dev, func(image image.Image) { if image.Bounds().Max.X != dev.LcdWidth || image.Bounds().Max.Y != dev.LcdHeight { image = api.ResizeImageWH(image, dev.LcdWidth, dev.LcdHeight) } @@ -202,9 +216,9 @@ func (StreamDeckDBus) GetKnobHandlerExample(serial string, keyString string) (st } } -func EmitPage(dev *VirtualDev, page int) { +func EmitPage(dev IVirtualDev, page int) { if conn != nil { - conn.Emit("/com/unixstreamdeck/streamdeckd", "com.unixstreamdeck.streamdeckd.Page", dev.Deck.Serial, page) + conn.Emit("/com/unixstreamdeck/streamdeckd", "com.unixstreamdeck.streamdeckd.Page", dev.Serial(), page) } } diff --git a/streamdeckd/examples/counter.go b/streamdeckd/examples/counter.go index 0cfcd15..e83473b 100644 --- a/streamdeckd/examples/counter.go +++ b/streamdeckd/examples/counter.go @@ -8,33 +8,30 @@ import ( "github.com/unix-streamdeck/api/v2" ) -type CounterIconHandler struct { +type CounterHandler struct { Count int Running bool Callback func(image image.Image) Update chan int } -func (c *CounterIconHandler) Start(k api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (c *CounterHandler) Start(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { if c.Callback == nil { c.Callback = callback } if c.Update == nil { log.Println("Test") c.Update = make(chan int) - k.SharedState["channel"] = c.Update } if c.Running { for { select { - case <-c.Update: - c.Count = c.Count + 1 - img := image.NewRGBA(image.Rect(0, 0, info.IconSize, info.IconSize)) + case delta := <-c.Update: + c.Count = c.Count + delta + width, height := info.GetDimensions(handlerType) + img := image.NewRGBA(image.Rect(0, 0, width, height)) Count := strconv.Itoa(c.Count) - imgParsed, err := api.DrawText(img, Count, api.DrawTextOptions{ - FontSize: int64(k.TextSize), - VerticalAlignment: api.VerticalAlignment(k.TextAlignment), - }) + imgParsed, err := api.DrawText(img, Count, api.DrawTextOptions{}) if err != nil { log.Println(err) } else { @@ -48,32 +45,43 @@ func (c *CounterIconHandler) Start(k api.KeyConfigV3, info api.StreamDeckInfoV1, } } -func (c *CounterIconHandler) IsRunning() bool { +func (c *CounterHandler) IsRunning() bool { return c.Running } -func (c *CounterIconHandler) SetRunning(running bool) { +func (c *CounterHandler) SetRunning(running bool) { c.Running = running } -func (c CounterIconHandler) Stop() { +func (c *CounterHandler) Stop() { c.Running = false } -type CounterKeyHandler struct{} +func (c *CounterHandler) Input(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, event api.InputEvent) { + var delta int + if event.EventType == api.KEY_PRESS || event.EventType == api.KNOB_PRESS || event.EventType == api.SCREEN_SHORT_TAP { + delta = 1 + } + + if event.EventType == api.KNOB_CW { + delta = int(event.RotateNotches) + } + + if event.EventType == api.KNOB_CCW { + delta = int(event.RotateNotches) * -1 + } -func (CounterKeyHandler) Key(key api.KeyConfigV3, info api.StreamDeckInfoV1) { - channel, ok := key.SharedState["channel"] - if !ok { - return + if c.Update != nil { + c.Update <- delta } - channel.(chan int) <- 1 } func RegisterCounter() api.Module { - return api.Module{NewIcon: func() api.IconHandler { - return &CounterIconHandler{Running: true, Count: 0} - }, NewKey: func() api.KeyHandler { - return &CounterKeyHandler{} - }, Name: "Counter"} + return api.Module{ + NewForeground: func() api.ForegroundHandler { + return &CounterHandler{Running: true, Count: 0} + }, + NewInput: func() api.InputHandler { + return &CounterHandler{Running: true, Count: 0} + }, Name: "Counter"} } diff --git a/streamdeckd/examples/gif.go b/streamdeckd/examples/gif.go index 4fe14be..3258cb7 100644 --- a/streamdeckd/examples/gif.go +++ b/streamdeckd/examples/gif.go @@ -1,41 +1,26 @@ package examples import ( + "context" + "image" + "image/draw" + "image/gif" + "log" + "os" + "strconv" + "time" + "github.com/unix-streamdeck/api/v2" "golang.org/x/sync/semaphore" - - "github.com/unix-streamdeck/streamdeckd/streamdeckd/examples/gif" ) func RegisterGif() api.Module { return api.Module{ Name: "Gif", - NewIcon: func() api.IconHandler { - return &gif.GifIconHandler{Running: true, Lock: semaphore.NewWeighted(1)} - }, - IconFields: []api.Field{ - {Title: "Icon", Name: "icon", Type: api.File, FileTypes: []string{".gif"}}, - {Title: "Text", Name: "text", Type: api.Text}, - {Title: "Text Size", Name: "text_size", Type: api.Number}, - {Title: "Text Alignment", Name: "text_alignment", Type: api.TextAlignment}, - {Title: "Font Face", Name: "font_face", Type: api.FontFace}, - {Title: "Text Colour", Name: "text_colour", Type: api.Colour}, - }, - NewLcd: func() api.LcdHandler { - return &gif.GifLcdHandler{Running: true, Lock: semaphore.NewWeighted(1)} - }, - LcdFields: []api.Field{ - {Title: "Icon", Name: "icon", Type: api.File, FileTypes: []string{".gif"}}, - {Title: "Text", Name: "text", Type: api.Text}, - {Title: "Text Size", Name: "text_size", Type: api.Number}, - {Title: "Text Alignment", Name: "text_alignment", Type: api.TextAlignment}, - {Title: "Font Face", Name: "font_face", Type: api.FontFace}, - {Title: "Text Colour", Name: "text_colour", Type: api.Colour}, + NewForeground: func() api.ForegroundHandler { + return &GifHandler{Running: true, Lock: semaphore.NewWeighted(1)} }, - NewKeyGridBackground: func() api.KeyGridBackgroundHandler { - return &gif.GifKeyGridBackgroundHandler{Running: true, Lock: semaphore.NewWeighted(1)} - }, - KeyGridBackgroundFields: []api.Field{ + ForegroundFields: []api.Field{ {Title: "Icon", Name: "icon", Type: api.File, FileTypes: []string{".gif"}}, {Title: "Text", Name: "text", Type: api.Text}, {Title: "Text Size", Name: "text_size", Type: api.Number}, @@ -43,10 +28,10 @@ func RegisterGif() api.Module { {Title: "Font Face", Name: "font_face", Type: api.FontFace}, {Title: "Text Colour", Name: "text_colour", Type: api.Colour}, }, - NewTouchPanelBackgroundHandler: func() api.TouchPanelBackgroundHandler { - return &gif.GifTouchPanelBackgroundHandler{Running: true, Lock: semaphore.NewWeighted(1)} + NewBackground: func() api.BackgroundHandler { + return &GifHandler{Running: true, Lock: semaphore.NewWeighted(1)} }, - TouchPanelBackgroundFields: []api.Field{ + BackgroundFields: []api.Field{ {Title: "Icon", Name: "icon", Type: api.File, FileTypes: []string{".gif"}}, {Title: "Text", Name: "text", Type: api.Text}, {Title: "Text Size", Name: "text_size", Type: api.Number}, @@ -56,3 +41,171 @@ func RegisterGif() api.Module { }, } } + +type GifHandler struct { + Running bool + Lock *semaphore.Weighted + Quit chan bool + Gifs []*image.Paletted +} + +func (s *GifHandler) Start(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { + if s.Quit == nil { + s.Quit = make(chan bool) + } + if s.Lock == nil { + s.Lock = semaphore.NewWeighted(1) + } + s.Running = true + icon, ok := fields["icon"] + if !ok { + return + } + f, err := os.Open(icon.(string)) + if err != nil { + log.Println(err) + return + } + gifs, err := gif.DecodeAll(f) + s.Gifs = gifs.Image + if err != nil { + log.Println(err) + return + } + timeDelay := gifs.Delay[0] + if timeDelay < 1 { + timeDelay = 8 + } + width, height := info.GetDimensions(handlerType) + + frames := getFrames(fields, gifs, width, height, err) + go loop(s, frames, timeDelay, callback) +} + +func (s *GifHandler) StartGrid(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image []image.Image)) { + if s.Quit == nil { + s.Quit = make(chan bool) + } + if s.Lock == nil { + s.Lock = semaphore.NewWeighted(1) + } + s.Running = true + icon, ok := fields["icon"] + if !ok { + return + } + f, err := os.Open(icon.(string)) + if err != nil { + log.Println(err) + return + } + gifs, err := gif.DecodeAll(f) + s.Gifs = gifs.Image + if err != nil { + log.Println(err) + return + } + timeDelay := gifs.Delay[0] + if timeDelay < 1 { + timeDelay = 8 + } + width, height := info.GetGridDimensions(handlerType) + + frames := getFrames(fields, gifs, width, height, err) + + var gridFrames [][]image.Image + + for _, frame := range frames { + + gridFrame := info.SplitBackgroundImage(frame, handlerType) + + gridFrames = append(gridFrames, gridFrame) + } + + go loop(s, gridFrames, timeDelay, callback) +} + +func getFrames(fields map[string]any, gifs *gif.GIF, width int, height int, err error) []image.Image { + frames := make([]image.Image, len(gifs.Image)) + + overPaintImage := image.NewRGBA(image.Rect(0, 0, width, height)) + draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(gifs.Image[0], width, height), image.ZP, draw.Src) + + for i, frame := range gifs.Image { + draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(frame, width, height), image.ZP, draw.Over) + frame := image.NewRGBA(image.Rect(0, 0, width, height)) + draw.Draw(frame, frame.Bounds(), overPaintImage, image.ZP, draw.Over) + img := frame.SubImage(frame.Rect) + text, ok := fields["text"] + if ok { + text_size, ok := fields["text_size"] + var size int64 + if ok { + size, _ = strconv.ParseInt(text_size.(string), 10, 0) + } else { + size = 0 + } + alignment, ok := fields["text_alignment"] + if !ok { + alignment = "" + } + fontFace, ok := fields["font_face"] + if !ok { + fontFace = "" + } + textColour, ok := fields["text_colour"] + if !ok { + textColour = "" + } + img, err = api.DrawText(img, text.(string), api.DrawTextOptions{ + FontSize: size, + VerticalAlignment: api.VerticalAlignment(alignment.(string)), + FontFace: fontFace.(string), + Colour: textColour.(string), + }) + if err != nil { + log.Println(err) + } + } + frames[i] = img + } + return frames +} + +func (s *GifHandler) IsRunning() bool { + return s.Running +} + +func (s *GifHandler) SetRunning(running bool) { + s.Running = running +} + +func (s *GifHandler) Stop() { + s.Running = false + s.Quit <- true +} + +func loop[T any](s *GifHandler, frames []T, timeDelay int, callback func(image T)) { + + ctx := context.Background() + err := s.Lock.Acquire(ctx, 1) + if err != nil { + return + } + defer s.Lock.Release(1) + gifIndex := 0 + for { + select { + case <-s.Quit: + return + default: + img := frames[gifIndex] + callback(img) + gifIndex++ + if gifIndex >= len(frames) { + gifIndex = 0 + } + time.Sleep(time.Duration(timeDelay*10) * time.Millisecond) + } + } +} diff --git a/streamdeckd/examples/gif/key.go b/streamdeckd/examples/gif/key.go deleted file mode 100644 index 0440cb2..0000000 --- a/streamdeckd/examples/gif/key.go +++ /dev/null @@ -1,135 +0,0 @@ -package gif - -import ( - "context" - "image" - "image/draw" - "image/gif" - "log" - "os" - "strconv" - "time" - - "github.com/unix-streamdeck/api/v2" - "golang.org/x/sync/semaphore" -) - -type GifIconHandler struct { - Running bool - Lock *semaphore.Weighted - Quit chan bool - Gifs []*image.Paletted -} - -func (s *GifIconHandler) Start(key api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { - if s.Quit == nil { - s.Quit = make(chan bool) - } - if s.Lock == nil { - s.Lock = semaphore.NewWeighted(1) - } - s.Running = true - icon, ok := key.IconHandlerFields["icon"] - if !ok { - return - } - f, err := os.Open(icon.(string)) - if err != nil { - log.Println(err) - return - } - gifs, err := gif.DecodeAll(f) - s.Gifs = gifs.Image - if err != nil { - log.Println(err) - return - } - timeDelay := gifs.Delay[0] - if timeDelay < 1 { - timeDelay = 8 - } - frames := make([]image.Image, len(gifs.Image)) - - iconSize := info.IconSize - - overPaintImage := image.NewRGBA(image.Rect(0, 0, iconSize, iconSize)) - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImage(gifs.Image[0], iconSize), image.ZP, draw.Src) - - for i, frame := range gifs.Image { - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImage(frame, iconSize), image.ZP, draw.Over) - frame := image.NewRGBA(image.Rect(0, 0, iconSize, iconSize)) - draw.Draw(frame, frame.Bounds(), overPaintImage, image.ZP, draw.Over) - img := frame.SubImage(frame.Rect) - text, ok := key.IconHandlerFields["text"] - if ok { - text_size, ok := key.IconHandlerFields["text_size"] - var size int64 - if ok { - size, _ = strconv.ParseInt(text_size.(string), 10, 0) - } else { - size = 0 - } - alignment, ok := key.IconHandlerFields["text_alignment"] - if !ok { - alignment = "" - } - fontFace, ok := key.IconHandlerFields["font_face"] - if !ok { - fontFace = "" - } - textColour, ok := key.IconHandlerFields["text_colour"] - if !ok { - textColour = "" - } - img, err = api.DrawText(img, text.(string), api.DrawTextOptions{ - FontSize: size, - VerticalAlignment: api.VerticalAlignment(alignment.(string)), - FontFace: fontFace.(string), - Colour: textColour.(string), - }) - if err != nil { - log.Println(err) - } - } - frames[i] = img - } - go s.loop(frames, timeDelay, callback) -} - -func (s *GifIconHandler) IsRunning() bool { - return s.Running -} - -func (s *GifIconHandler) SetRunning(running bool) { - s.Running = running -} - -func (s *GifIconHandler) Stop() { - s.Running = false - s.Quit <- true -} - -func (s *GifIconHandler) loop(frames []image.Image, timeDelay int, callback func(image image.Image)) { - - ctx := context.Background() - err := s.Lock.Acquire(ctx, 1) - if err != nil { - return - } - defer s.Lock.Release(1) - gifIndex := 0 - for { - select { - case <-s.Quit: - return - default: - img := frames[gifIndex] - callback(img) - gifIndex++ - if gifIndex >= len(frames) { - gifIndex = 0 - } - time.Sleep(time.Duration(timeDelay*10) * time.Millisecond) - } - } -} diff --git a/streamdeckd/examples/gif/key_grid_background.go b/streamdeckd/examples/gif/key_grid_background.go deleted file mode 100644 index 76164cb..0000000 --- a/streamdeckd/examples/gif/key_grid_background.go +++ /dev/null @@ -1,152 +0,0 @@ -package gif - -import ( - "context" - "image" - "image/draw" - "image/gif" - "log" - "math" - "os" - "strconv" - "time" - - "github.com/unix-streamdeck/api/v2" - "golang.org/x/sync/semaphore" -) - -type GifKeyGridBackgroundHandler struct { - Running bool - Lock *semaphore.Weighted - Quit chan bool - Gifs []*image.Paletted -} - -func (s *GifKeyGridBackgroundHandler) StartIndividual(fields map[string]any, info api.StreamDeckInfoV1, callback func(img image.Image)) { - log.Println("Not Implemented") - return -} - -func (s *GifKeyGridBackgroundHandler) Start(fields map[string]any, info api.StreamDeckInfoV1, callback func(imgs []image.Image)) { - if s.Quit == nil { - s.Quit = make(chan bool) - } - if s.Lock == nil { - s.Lock = semaphore.NewWeighted(1) - } - s.Running = true - icon, ok := fields["icon"] - if !ok { - return - } - f, err := os.Open(icon.(string)) - if err != nil { - log.Println(err) - return - } - gifs, err := gif.DecodeAll(f) - s.Gifs = gifs.Image - if err != nil { - log.Println(err) - return - } - timeDelay := gifs.Delay[0] - if timeDelay < 1 { - timeDelay = 8 - } - - w := (info.IconSize * info.Cols) + (info.PaddingX * (info.Cols - 1)) - h := (info.IconSize * info.Rows) + (info.PaddingX * (info.Rows - 1)) - - frames := make([][]image.Image, len(gifs.Image)) - - overPaintImage := image.NewRGBA(image.Rect(0, 0, w, h)) - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(gifs.Image[0], w, h), image.ZP, draw.Src) - - for i, frame := range gifs.Image { - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(frame, w, h), image.ZP, draw.Over) - frame := image.NewRGBA(image.Rect(0, 0, w, h)) - draw.Draw(frame, frame.Bounds(), overPaintImage, image.ZP, draw.Over) - img := frame.SubImage(frame.Rect) - text, ok := fields["text"] - if ok { - text_size, ok := fields["text_size"] - var size int64 - if ok { - size, _ = strconv.ParseInt(text_size.(string), 10, 0) - } else { - size = 0 - } - alignment, ok := fields["text_alignment"] - if !ok { - alignment = "" - } - fontFace, ok := fields["font_face"] - if !ok { - fontFace = "" - } - textColour, ok := fields["text_colour"] - if !ok { - textColour = "" - } - img, err = api.DrawText(img, text.(string), api.DrawTextOptions{ - FontSize: size, - VerticalAlignment: api.VerticalAlignment(alignment.(string)), - FontFace: fontFace.(string), - Colour: textColour.(string), - }) - if err != nil { - log.Println(err) - } - } - frameArr := make([]image.Image, info.LcdCols) - for keyIndex := range info.Cols * info.Rows { - keyX := keyIndex % info.Cols - keyY := int(math.Floor(float64(keyIndex) / float64(info.Cols))) - - x0, y0 := keyX*(info.IconSize+info.PaddingX), keyY*(info.IconSize+info.PaddingY) - x1, y1 := keyX*(info.IconSize+info.PaddingX)+info.IconSize, keyY*(info.IconSize+info.PaddingY)+info.IconSize - - frameArr = append(frameArr, api.SubImage(img, x0, y0, x1, y1)) - } - frames[i] = frameArr - } - go s.loop(frames, timeDelay, callback) -} - -func (s *GifKeyGridBackgroundHandler) IsRunning() bool { - return s.Running -} - -func (s *GifKeyGridBackgroundHandler) SetRunning(running bool) { - s.Running = running -} - -func (s *GifKeyGridBackgroundHandler) Stop() { - s.Running = false - s.Quit <- true -} - -func (s *GifKeyGridBackgroundHandler) loop(frames [][]image.Image, timeDelay int, callback func(image []image.Image)) { - ctx := context.Background() - err := s.Lock.Acquire(ctx, 1) - if err != nil { - return - } - defer s.Lock.Release(1) - gifIndex := 0 - for { - select { - case <-s.Quit: - return - default: - img := frames[gifIndex] - callback(img) - gifIndex++ - if gifIndex >= len(frames) { - gifIndex = 0 - } - time.Sleep(time.Duration(timeDelay*10) * time.Millisecond) - } - } -} diff --git a/streamdeckd/examples/gif/lcd.go b/streamdeckd/examples/gif/lcd.go deleted file mode 100644 index ed6e932..0000000 --- a/streamdeckd/examples/gif/lcd.go +++ /dev/null @@ -1,132 +0,0 @@ -package gif - -import ( - "context" - "image" - "image/draw" - "image/gif" - "log" - "os" - "strconv" - "time" - - "github.com/unix-streamdeck/api/v2" - "golang.org/x/sync/semaphore" -) - -type GifLcdHandler struct { - Running bool - Lock *semaphore.Weighted - Quit chan bool - Gifs []*image.Paletted -} - -func (s *GifLcdHandler) Start(key api.KnobConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { - if s.Quit == nil { - s.Quit = make(chan bool) - } - if s.Lock == nil { - s.Lock = semaphore.NewWeighted(1) - } - s.Running = true - icon, ok := key.LcdHandlerFields["icon"] - if !ok { - return - } - f, err := os.Open(icon.(string)) - if err != nil { - log.Println(err) - return - } - gifs, err := gif.DecodeAll(f) - s.Gifs = gifs.Image - if err != nil { - log.Println(err) - return - } - timeDelay := gifs.Delay[0] - if timeDelay < 1 { - timeDelay = 8 - } - frames := make([]image.Image, len(gifs.Image)) - - overPaintImage := image.NewRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(gifs.Image[0], info.LcdWidth, info.LcdHeight), image.ZP, draw.Src) - - for i, frame := range gifs.Image { - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(frame, info.LcdWidth, info.LcdHeight), image.ZP, draw.Over) - frame := image.NewRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) - draw.Draw(frame, frame.Bounds(), overPaintImage, image.ZP, draw.Over) - img := frame.SubImage(frame.Rect) - text, ok := key.LcdHandlerFields["text"] - if ok { - text_size, ok := key.LcdHandlerFields["text_size"] - var size int64 - if ok { - size, _ = strconv.ParseInt(text_size.(string), 10, 0) - } else { - size = 0 - } - alignment, ok := key.LcdHandlerFields["text_alignment"] - if !ok { - alignment = "" - } - fontFace, ok := key.LcdHandlerFields["font_face"] - if !ok { - fontFace = "" - } - textColour, ok := key.LcdHandlerFields["text_colour"] - if !ok { - textColour = "" - } - img, err = api.DrawText(img, text.(string), api.DrawTextOptions{ - FontSize: size, - VerticalAlignment: api.VerticalAlignment(alignment.(string)), - FontFace: fontFace.(string), - Colour: textColour.(string), - }) - if err != nil { - log.Println(err) - } - } - frames[i] = img - } - go s.loop(frames, timeDelay, callback) -} - -func (s *GifLcdHandler) IsRunning() bool { - return s.Running -} - -func (s *GifLcdHandler) SetRunning(running bool) { - s.Running = running -} - -func (s *GifLcdHandler) Stop() { - s.Running = false - s.Quit <- true -} - -func (s *GifLcdHandler) loop(frames []image.Image, timeDelay int, callback func(image image.Image)) { - ctx := context.Background() - err := s.Lock.Acquire(ctx, 1) - if err != nil { - return - } - defer s.Lock.Release(1) - gifIndex := 0 - for { - select { - case <-s.Quit: - return - default: - img := frames[gifIndex] - callback(img) - gifIndex++ - if gifIndex >= len(frames) { - gifIndex = 0 - } - time.Sleep(time.Duration(timeDelay*10) * time.Millisecond) - } - } -} \ No newline at end of file diff --git a/streamdeckd/examples/gif/lcd_background.go b/streamdeckd/examples/gif/lcd_background.go deleted file mode 100644 index ff302fd..0000000 --- a/streamdeckd/examples/gif/lcd_background.go +++ /dev/null @@ -1,146 +0,0 @@ -package gif - -import ( - "context" - "image" - "image/draw" - "image/gif" - "log" - "os" - "strconv" - "time" - - "github.com/unix-streamdeck/api/v2" - "golang.org/x/sync/semaphore" -) - -type GifTouchPanelBackgroundHandler struct { - Running bool - Lock *semaphore.Weighted - Quit chan bool - Gifs []*image.Paletted -} - -func (s *GifTouchPanelBackgroundHandler) StartIndividual(fields map[string]any, info api.StreamDeckInfoV1, callback func(img image.Image)) { - log.Println("Not Implemented") - return -} - -func (s *GifTouchPanelBackgroundHandler) Start(fields map[string]any, info api.StreamDeckInfoV1, callback func(imgs []image.Image)) { - if s.Quit == nil { - s.Quit = make(chan bool) - } - if s.Lock == nil { - s.Lock = semaphore.NewWeighted(1) - } - s.Running = true - icon, ok := fields["icon"] - if !ok { - return - } - f, err := os.Open(icon.(string)) - if err != nil { - log.Println(err) - return - } - gifs, err := gif.DecodeAll(f) - s.Gifs = gifs.Image - if err != nil { - log.Println(err) - return - } - timeDelay := gifs.Delay[0] - if timeDelay < 1 { - timeDelay = 8 - } - frames := make([][]image.Image, len(gifs.Image)) - - overPaintImage := image.NewRGBA(image.Rect(0, 0, info.LcdWidth*info.LcdCols, info.LcdHeight)) - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(gifs.Image[0], info.LcdWidth*info.LcdCols, info.LcdHeight), image.ZP, draw.Src) - - for i, frame := range gifs.Image { - draw.Draw(overPaintImage, overPaintImage.Bounds(), api.ResizeImageWH(frame, info.LcdWidth*info.LcdCols, info.LcdHeight), image.ZP, draw.Over) - frame := image.NewRGBA(image.Rect(0, 0, info.LcdWidth*info.LcdCols, info.LcdHeight)) - draw.Draw(frame, frame.Bounds(), overPaintImage, image.ZP, draw.Over) - img := frame.SubImage(frame.Rect) - text, ok := fields["text"] - if ok { - text_size, ok := fields["text_size"] - var size int64 - if ok { - size, _ = strconv.ParseInt(text_size.(string), 10, 0) - } else { - size = 0 - } - alignment, ok := fields["text_alignment"] - if !ok { - alignment = "" - } - fontFace, ok := fields["font_face"] - if !ok { - fontFace = "" - } - textColour, ok := fields["text_colour"] - if !ok { - textColour = "" - } - img, err = api.DrawText(img, text.(string), api.DrawTextOptions{ - FontSize: size, - VerticalAlignment: api.VerticalAlignment(alignment.(string)), - FontFace: fontFace.(string), - Colour: textColour.(string), - }) - if err != nil { - log.Println(err) - } - } - var frameArr []image.Image - for lcdIndex := range info.LcdCols { - x0, y0 := info.LcdWidth*lcdIndex, 0 - x1, y1 := info.LcdWidth*(lcdIndex+1), info.LcdHeight - - subImage := api.SubImage(img, x0, y0, x1, y1) - - frameArr = append(frameArr, subImage) - } - frames[i] = frameArr - } - go s.loop(frames, timeDelay, callback) -} - -func (s *GifTouchPanelBackgroundHandler) IsRunning() bool { - return s.Running -} - -func (s *GifTouchPanelBackgroundHandler) SetRunning(running bool) { - s.Running = running -} - -func (s *GifTouchPanelBackgroundHandler) Stop() { - s.Running = false - s.Quit <- true -} - -func (s *GifTouchPanelBackgroundHandler) loop(frames [][]image.Image, timeDelay int, callback func(image []image.Image)) { - ctx := context.Background() - err := s.Lock.Acquire(ctx, 1) - if err != nil { - return - } - defer s.Lock.Release(1) - gifIndex := 0 - for { - select { - case <-s.Quit: - return - default: - img := frames[gifIndex] - callback(img) - gifIndex++ - if gifIndex >= len(frames) { - gifIndex = 0 - } - time.Sleep(time.Duration(timeDelay*10) * time.Millisecond) - } - } -} diff --git a/streamdeckd/examples/init.go b/streamdeckd/examples/init.go index f1b4843..76a1ab4 100644 --- a/streamdeckd/examples/init.go +++ b/streamdeckd/examples/init.go @@ -8,7 +8,6 @@ func RegisterBaseModules() { streamdeckd.RegisterModule(RegisterGif()) streamdeckd.RegisterModule(RegisterTime()) streamdeckd.RegisterModule(RegisterCounter()) - streamdeckd.RegisterModule(RegisterSpotify()) streamdeckd.RegisterModule(RegisterToggle()) streamdeckd.RegisterModule(RegisterPlayerCtl()) streamdeckd.RegisterModule(RegisterVolume()) diff --git a/streamdeckd/examples/playerctl.go b/streamdeckd/examples/playerctl.go index 8a383fc..8e43a7f 100644 --- a/streamdeckd/examples/playerctl.go +++ b/streamdeckd/examples/playerctl.go @@ -47,14 +47,14 @@ var operationsMap = map[string]KeypressOperation{ "LoopStatus": LoopStatus, } -type LcdKnobHandlerType string +type PlayerctlHandlerType string const ( - Playback LcdKnobHandlerType = "Playback" - Volume LcdKnobHandlerType = "Volume" + Playback PlayerctlHandlerType = "Playback" + Volume PlayerctlHandlerType = "Volume" ) -var playerFilters = map[LcdKnobHandlerType]func(player *mpris.Player) bool{ +var playerFilters = map[PlayerctlHandlerType]func(player *mpris.Player) bool{ Playback: func(player *mpris.Player) bool { return true }, @@ -64,7 +64,7 @@ var playerFilters = map[LcdKnobHandlerType]func(player *mpris.Player) bool{ }, } -var calculateTextAndPercentage = map[LcdKnobHandlerType]func(player *mpris.Player) (string, float64){ +var calculateTextAndPercentage = map[PlayerctlHandlerType]func(player *mpris.Player) (string, float64){ Playback: func(player *mpris.Player) (string, float64) { position, err := player.GetPosition() if err != nil { @@ -93,7 +93,7 @@ var calculateTextAndPercentage = map[LcdKnobHandlerType]func(player *mpris.Playe }, } -type PlayerCtlLcdHandler struct { +type PlayerCtlHandler struct { Running bool Quit chan bool Lock *semaphore.Weighted @@ -107,11 +107,11 @@ type PlayerCtlLcdHandler struct { Text string FinalImage image.Image PreviousPlayer string - Type LcdKnobHandlerType + Type PlayerctlHandlerType ActivePlayer *mpris.Player } -func (v *PlayerCtlLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (v *PlayerCtlHandler) Start(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { if v.Quit == nil { v.Quit = make(chan bool) } @@ -119,43 +119,44 @@ func (v *PlayerCtlLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckIn v.Lock = semaphore.NewWeighted(1) } if v.CurrentPlayerImage == nil { - v.CurrentPlayerImage = v.GetImage("icon", knob, info) + v.CurrentPlayerImage = v.GetImage("icon", fields, info, handlerType) if v.CurrentPlayerImage != nil { v.StaticImage = true } } - accentColour, ok := knob.LcdHandlerFields["colour"] + accentColour, ok := fields["colour"] if ok { v.AccentColour = accentColour.(string) } - handlerType, ok := knob.LcdHandlerFields["type"] + playerctlHandlerType, ok := fields["type"] if !ok { log.Println("Type not specified") return } - v.Type = LcdKnobHandlerType(handlerType.(string)) + v.Type = PlayerctlHandlerType(playerctlHandlerType.(string)) - playerName, ok := knob.LcdHandlerFields["player_name"] + playerName, ok := fields["player_name"] if ok { v.PlayerName = playerName.(string) } v.Running = true - v.Run(info, callback) + v.Run(info, handlerType, callback) } -func (v *PlayerCtlLcdHandler) IsRunning() bool { + +func (v *PlayerCtlHandler) IsRunning() bool { return v.Running } -func (v *PlayerCtlLcdHandler) SetRunning(running bool) { +func (v *PlayerCtlHandler) SetRunning(running bool) { v.Running = running } -func (v *PlayerCtlLcdHandler) Stop() { +func (v *PlayerCtlHandler) Stop() { v.Running = false v.Quit <- true v.CurrentPlayerImage = nil @@ -169,8 +170,8 @@ func (v *PlayerCtlLcdHandler) Stop() { v.ActivePlayer = nil } -func (v *PlayerCtlLcdHandler) GetImage(index string, knob api.KnobConfigV3, info api.StreamDeckInfoV1) image.Image { - path, ok := knob.LcdHandlerFields[index] +func (v *PlayerCtlHandler) GetImage(index string, fields map[string]any, info api.StreamDeckInfoV1, handlerType api.HandlerType) image.Image { + path, ok := fields[index] if !ok { return nil } @@ -185,10 +186,10 @@ func (v *PlayerCtlLcdHandler) GetImage(index string, knob api.KnobConfigV3, info log.Println(err) return nil } - return resizeThumbnail(img, info) + return resizeThumbnail(img, info, handlerType) } -func (v *PlayerCtlLcdHandler) Run(info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (v *PlayerCtlHandler) Run(info api.StreamDeckInfoV1, handlerType api.HandlerType, callback func(image image.Image)) { ctx := context.Background() err := v.Lock.Acquire(ctx, 1) defer v.Lock.Release(1) @@ -203,7 +204,6 @@ func (v *PlayerCtlLcdHandler) Run(info api.StreamDeckInfoV1, callback func(image if playerNeedsRefreshing(v.ActivePlayer) { v.ActivePlayer = choosePlayer(v.Client, v.PlayerName, v.PreviousPlayer, playerFilters[v.Type]) if v.ActivePlayer == nil { - //log.Println("No player found") break } v.PreviousPlayer = v.ActivePlayer.GetShortName() @@ -212,13 +212,14 @@ func (v *PlayerCtlLcdHandler) Run(info api.StreamDeckInfoV1, callback func(image img = v.CurrentPlayerImage previousImage := v.CurrentPlayerImage if !v.StaticImage { - img, err = v.FindImage(v.ActivePlayer, info) + img, err = v.FindImage(v.ActivePlayer, info, handlerType) if img == nil { if v.CurrentPlayerImageSource == v.PreviousPlayer { img = v.CurrentPlayerImage } else { - img = image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) - img = resizeThumbnail(img, info) + w, h := info.GetDimensions(handlerType) + img = image.NewNRGBA(image.Rect(0, 0, w, h)) + img = resizeThumbnail(img, info, handlerType) img, err = api.DrawText(img, v.PreviousPlayer, api.DrawTextOptions{ VerticalAlignment: api.Center, }) @@ -230,7 +231,7 @@ func (v *PlayerCtlLcdHandler) Run(info api.StreamDeckInfoV1, callback func(image imgNeedsRefreshing := previousImage != img || v.FinalImage == nil finalImage := v.FinalImage if imgNeedsRefreshing { - finalImage = overlayImage(img, info) + finalImage = overlayImage(img, info, handlerType) v.FinalImage = finalImage } text, percentage := calculateTextAndPercentage[v.Type](v.ActivePlayer) @@ -252,7 +253,8 @@ func (v *PlayerCtlLcdHandler) Run(info api.StreamDeckInfoV1, callback func(image if v.AccentColour == "" { v.AccentColour = getAverageColour(img) } - imgParsed, err := api.DrawProgressBarWithAccent(finalImage, text, 5, float64(info.LcdHeight-25), 20, float64(info.LcdWidth-10), percentage, v.AccentColour) + w, h := info.GetDimensions(handlerType) + imgParsed, err := api.DrawProgressBarWithAccent(finalImage, text, 5, float64(h-25), 20, float64(w-10), percentage, v.AccentColour) if err != nil { log.Println(err) } else { @@ -263,7 +265,7 @@ func (v *PlayerCtlLcdHandler) Run(info api.StreamDeckInfoV1, callback func(image } } -func (v *PlayerCtlLcdHandler) FindImage(player *mpris.Player, info api.StreamDeckInfoV1) (image.Image, error) { +func (v *PlayerCtlHandler) FindImage(player *mpris.Player, info api.StreamDeckInfoV1, handlerType api.HandlerType) (image.Image, error) { metadata, err := player.GetMetadata() if err == nil && metadata != nil { artUrl, err := metadata.ArtURL() @@ -278,7 +280,7 @@ func (v *PlayerCtlLcdHandler) FindImage(player *mpris.Player, info api.StreamDec err = nil } if img != nil { - img = resizeThumbnail(img, info) + img = resizeThumbnail(img, info, handlerType) v.CurrentPlayerImage = img v.CurrentPlayerImageSource = artUrl return img, nil @@ -352,34 +354,33 @@ func loadImage(path string) (image.Image, error) { return img, nil } -type PlayerCtlKnobOrTouchHandler struct { - Client *dbus.Conn - PreviousPlayer string -} - -func (v *PlayerCtlKnobOrTouchHandler) Input(knob api.KnobConfigV3, info api.StreamDeckInfoV1, event api.InputEvent) { +func (v *PlayerCtlHandler) Input(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, event api.InputEvent) { - handlerTypeString, ok := knob.KnobOrTouchHandlerFields["type"] + handlerTypeString, ok := fields["type"] - if !ok { + if !ok && handlerType == api.LCD { log.Println("Type not specified") return } - handlerType := LcdKnobHandlerType(handlerTypeString.(string)) + if handlerType == api.KEY { + handlerTypeString = "Playback" + } + + playerctlHandlerType := PlayerctlHandlerType(handlerTypeString.(string)) - playerName, ok := knob.KnobOrTouchHandlerFields["player_name"] + playerName, ok := fields["player_name"] var playerNameString string if ok { playerNameString = playerName.(string) } - player := choosePlayer(v.Client, playerNameString, v.PreviousPlayer, playerFilters[handlerType]) + player := choosePlayer(v.Client, playerNameString, v.PreviousPlayer, playerFilters[playerctlHandlerType]) if player == nil { return } v.PreviousPlayer = player.GetName() - if handlerType == Volume { + if playerctlHandlerType == Volume { volume, err := player.GetVolume() if err != nil { log.Println(err) @@ -388,9 +389,9 @@ func (v *PlayerCtlKnobOrTouchHandler) Input(knob api.KnobConfigV3, info api.Stre volume = math.Round(volume * 100.0) if event.EventType == api.KNOB_CCW { - volume -= 1.0 + volume -= 1.0 * float64(event.RotateNotches) } else if event.EventType == api.KNOB_CW { - volume += 1.0 + volume += 1.0 * float64(event.RotateNotches) } volume /= 100.0 err = player.SetVolume(volume) @@ -429,69 +430,54 @@ func (v *PlayerCtlKnobOrTouchHandler) Input(knob api.KnobConfigV3, info api.Stre return } err = player.SetShuffle(!shuffle) + } else if event.EventType == api.KEY_PRESS { + operation, ok := fields["operation"] + if !ok { + log.Println("No MPRIS player operation specified") + return + } + op, ok := operationsMap[operation.(string)] + if !ok { + log.Println("Invalid MPRIS player operation specified") + return + } + var err error + switch op { + case PlayPause: + err = player.PlayPause() + case Play: + err = player.Play() + case Pause: + err = player.Pause() + case Previous: + err = player.Previous() + case Next: + err = player.Next() + case Shuffle: + shuffle, err := player.GetShuffle() + if err != nil { + log.Println(err) + return + } + err = player.SetShuffle(!shuffle) + break + case LoopStatus: + status, err := player.GetLoopStatus() + if err != nil { + log.Println(err) + return + } + err = player.SetLoopStatus(getNextLoopStatus(status)) + break + } + if err != nil { + log.Println(err) + return + } } } } -type PlayerCtlKeyHandler struct { - Client *dbus.Conn - PreviousPlayer string -} - -func (v *PlayerCtlKeyHandler) Key(key api.KeyConfigV3, info api.StreamDeckInfoV1) { - operation, ok := key.KeyHandlerFields["operation"] - if !ok { - log.Println("No MPRIS player operation specified") - } - op, ok := operationsMap[operation.(string)] - if !ok { - log.Println("Invalid MPRIS player operation specified") - } - playerName, ok := key.KeyHandlerFields["player_name"] - var playerNameString string - if ok { - playerNameString = playerName.(string) - } - player := choosePlayer(v.Client, playerNameString, v.PreviousPlayer, playerFilters[Playback]) - if player == nil { - return - } - v.PreviousPlayer = player.GetName() - var err error - switch op { - case PlayPause: - err = player.PlayPause() - case Play: - err = player.Play() - case Pause: - err = player.Pause() - case Previous: - err = player.Previous() - case Next: - err = player.Next() - case Shuffle: - shuffle, err := player.GetShuffle() - if err != nil { - log.Println(err) - return - } - err = player.SetShuffle(!shuffle) - break - case LoopStatus: - status, err := player.GetLoopStatus() - if err != nil { - log.Println(err) - return - } - err = player.SetLoopStatus(getNextLoopStatus(status)) - break - } - if err != nil { - log.Println(err) - return - } -} - func getNextLoopStatus(status mpris.LoopStatus) mpris.LoopStatus { switch status { case "None": @@ -582,8 +568,9 @@ func pad(timeSegment string) string { return timeSegment } -func resizeThumbnail(img image.Image, info api.StreamDeckInfoV1) image.Image { - newSize := float64(info.LcdHeight - 30) +func resizeThumbnail(img image.Image, info api.StreamDeckInfoV1, handlerType api.HandlerType) image.Image { + _, height := info.GetDimensions(handlerType) + newSize := float64(height - 30) scalingFactor := newSize / float64(img.Bounds().Max.Y) x := float64(img.Bounds().Max.X) * scalingFactor y := float64(img.Bounds().Max.Y) * scalingFactor @@ -591,11 +578,12 @@ func resizeThumbnail(img image.Image, info api.StreamDeckInfoV1) image.Image { return img } -func overlayImage(img image.Image, info api.StreamDeckInfoV1) image.Image { +func overlayImage(img image.Image, info api.StreamDeckInfoV1, handlerType api.HandlerType) image.Image { + width, height := info.GetDimensions(handlerType) mprisImg := img - img = image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) + img = image.NewNRGBA(image.Rect(0, 0, width, height)) ggImg := gg.NewContextForImage(img) - ggImg.DrawImageAnchored(mprisImg, info.LcdWidth/2, 35, 0.5, 0.5) + ggImg.DrawImageAnchored(mprisImg, width/2, 35, 0.5, 0.5) return ggImg.Image() } @@ -667,33 +655,26 @@ func RegisterPlayerCtl() api.Module { return api.Module{ Name: "Playerctl", - NewKey: func() api.KeyHandler { - client, err := dbus.SessionBus() - if err != nil { - panic(err) - } - return &PlayerCtlKeyHandler{Client: client} - }, - KeyFields: []api.Field{ - {Title: "Operation", Name: "operation", Type: api.Select, ListItems: slices.Collect(maps.Keys(operationsMap))}, - }, - NewLcd: func() api.LcdHandler { + NewForeground: func() api.ForegroundHandler { client, err := dbus.SessionBus() if err != nil { panic(err) } - return &PlayerCtlLcdHandler{Running: true, Lock: semaphore.NewWeighted(1), Client: client} + return &PlayerCtlHandler{Client: client} }, - LcdFields: []api.Field{ + ForegroundFields: []api.Field{ {Title: "Icon", Name: "icon", Type: api.File, FileTypes: []string{".png", ".jpg", ".jpeg"}}, {Title: "Accent Colour", Name: "colour", Type: api.Colour}, }, - NewKnobOrTouch: func() api.KnobOrTouchHandler { + NewInput: func() api.InputHandler { client, err := dbus.SessionBus() if err != nil { panic(err) } - return &PlayerCtlKnobOrTouchHandler{Client: client} + return &PlayerCtlHandler{Running: true, Lock: semaphore.NewWeighted(1), Client: client} + }, + InputFields: []api.Field{ + {Title: "Operation", Name: "operation", Type: api.Select, ListItems: slices.Collect(maps.Keys(operationsMap))}, }, LinkedFields: []api.Field{ {Title: "Player Name", Name: "player_name", Type: api.Text}, diff --git a/streamdeckd/examples/spotify.go b/streamdeckd/examples/spotify.go deleted file mode 100644 index 81f9758..0000000 --- a/streamdeckd/examples/spotify.go +++ /dev/null @@ -1,146 +0,0 @@ -package examples - -import ( - "errors" - - "image" - "log" - "net/http" - "strings" - "time" - - "github.com/godbus/dbus/v5" - "github.com/unix-streamdeck/api/v2" -) - -type SpotifyIconHandler struct { - Running bool - oldUrl string - Quit chan bool -} - -func (s *SpotifyIconHandler) Start(key api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { - s.Running = true - if s.Quit == nil { - s.Quit = make(chan bool) - } - c, err := Connect() - if err != nil { - if err.Error() != "The name org.mpris.MediaPlayer2.spotify was not provided by any .service files" { - log.Println(err) - } - return - } - go s.run(c, callback) -} - -func (s *SpotifyIconHandler) IsRunning() bool { - return s.Running -} - -func (s *SpotifyIconHandler) SetRunning(running bool) { - s.Running = running -} - -func (s *SpotifyIconHandler) Stop() { - s.Running = false - s.Quit <- true - s.oldUrl = "" -} - -func (s *SpotifyIconHandler) run(c *Connection, callback func(image image.Image)) { - defer c.Close() - for { - select { - case <-s.Quit: - return - default: - url, err := c.GetAlbumArtUrl() - if err != nil { - if err.Error() != "The name org.mpris.MediaPlayer2.spotify was not provided by any .service files" { - log.Println(err) - } - time.Sleep(time.Second) - continue - } - if url == s.oldUrl { - time.Sleep(time.Second) - continue - } - img, err := getImage(url) - if err != nil { - log.Println(err) - time.Sleep(time.Second) - continue - } - callback(img) - s.oldUrl = url - time.Sleep(time.Second) - } - } -} - -func RegisterSpotify() api.Module { - return api.Module{NewIcon: func() api.IconHandler { - return &SpotifyIconHandler{Running: true} - }, Name: "Spotify"} -} - -// region DBus -func getImage(url string) (image.Image, error) { - response, err := http.Get(url) - if err != nil { - return nil, err - } - if response.StatusCode != 200 { - return nil, errors.New("Couldn't get Image from URL") - } - defer response.Body.Close() - img, _, err := image.Decode(response.Body) - if err != nil { - return nil, err - } - return img, nil -} - -type Connection struct { - busobj dbus.BusObject - conn *dbus.Conn -} - -func Connect() (*Connection, error) { - conn, err := dbus.ConnectSessionBus() - if err != nil { - return nil, err - } - return &Connection{ - conn: conn, - busobj: conn.Object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2"), - }, nil -} - -func (c *Connection) GetAlbumArtUrl() (string, error) { - variant, err := c.busobj.GetProperty("org.mpris.MediaPlayer2.Player.Metadata") - if err != nil { - return "", err - } - metadataMap := variant.Value().(map[string]dbus.Variant) - var url string - for key, val := range metadataMap { - if key == "mpris:artUrl" { - url = val.String() - } - } - if url == "" { - return "", errors.New("Couldn't get URL from DBus") - } - url = strings.ReplaceAll(url, "\"", "") - url = strings.ReplaceAll(url, "https://open.spotify.com/image/", "https://i.scdn.co/image/") - return url, nil -} - -func (c *Connection) Close() { - c.conn.Close() -} - -// endregion diff --git a/streamdeckd/examples/time.go b/streamdeckd/examples/time.go index f00aa66..0146662 100644 --- a/streamdeckd/examples/time.go +++ b/streamdeckd/examples/time.go @@ -8,45 +8,43 @@ import ( "github.com/unix-streamdeck/api/v2" ) -type TimeIconHandler struct { +type TimeHandler struct { Running bool Quit chan bool } -func (t *TimeIconHandler) Start(k api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (t *TimeHandler) Start(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { t.Running = true if t.Quit == nil { t.Quit = make(chan bool) } - go t.timeLoop(k, info, callback) + go t.timeLoop(fields, handlerType, info, callback) } -func (t *TimeIconHandler) IsRunning() bool { +func (t *TimeHandler) IsRunning() bool { return t.Running } -func (t *TimeIconHandler) SetRunning(running bool) { +func (t *TimeHandler) SetRunning(running bool) { t.Running = running } -func (t *TimeIconHandler) Stop() { +func (t *TimeHandler) Stop() { t.Running = false t.Quit <- true } -func (t *TimeIconHandler) timeLoop(k api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (t *TimeHandler) timeLoop(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { for { select { case <-t.Quit: return default: - img := image.NewRGBA(image.Rect(0, 0, info.IconSize, info.IconSize)) + w, h := info.GetDimensions(handlerType) + img := image.NewRGBA(image.Rect(0, 0, w, h)) t := time.Now() tString := t.Format("15:04:05") - imgParsed, err := api.DrawText(img, tString, api.DrawTextOptions{ - FontSize: int64(k.TextSize), - VerticalAlignment: api.VerticalAlignment(k.TextAlignment), - }) + imgParsed, err := api.DrawText(img, tString, api.DrawTextOptions{}) if err != nil { log.Println(err) } else { @@ -58,7 +56,7 @@ func (t *TimeIconHandler) timeLoop(k api.KeyConfigV3, info api.StreamDeckInfoV1, } func RegisterTime() api.Module { - return api.Module{NewIcon: func() api.IconHandler { - return &TimeIconHandler{Running: true} + return api.Module{NewForeground: func() api.ForegroundHandler { + return &TimeHandler{Running: true} }, Name: "Time"} } diff --git a/streamdeckd/examples/toggle.go b/streamdeckd/examples/toggle.go index 2f6f4b4..ae6a109 100644 --- a/streamdeckd/examples/toggle.go +++ b/streamdeckd/examples/toggle.go @@ -12,7 +12,7 @@ import ( "golang.org/x/sync/semaphore" ) -type ToggleIconHandler struct { +type ToggleHandler struct { Status bool Running bool Lock *semaphore.Weighted @@ -23,7 +23,7 @@ type ToggleIconHandler struct { FirstLoop bool } -func (c *ToggleIconHandler) Start(k api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (c *ToggleHandler) Start(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { if c.Lock == nil { c.Lock = semaphore.NewWeighted(1) } @@ -31,36 +31,37 @@ func (c *ToggleIconHandler) Start(k api.KeyConfigV3, info api.StreamDeckInfoV1, c.Quit = make(chan bool) } if c.UpIconBuff == nil { - c.UpIconBuff = c.GetImage("up_icon", k, info) + c.UpIconBuff = c.GetImage("up_icon", fields, handlerType, info) } if c.DownIconBuff == nil { - c.DownIconBuff = c.GetImage("down_icon", k, info) + c.DownIconBuff = c.GetImage("down_icon", fields, handlerType, info) } c.FirstLoop = true - go c.loop(k, callback) + go c.loop(fields, callback) } -func (c *ToggleIconHandler) GetImage(index string, k api.KeyConfigV3, info api.StreamDeckInfoV1) image.Image { - path, ok := k.IconHandlerFields[index] +func (c *ToggleHandler) GetImage(index string, fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1) image.Image { + path, ok := fields[index] + w, h := info.GetDimensions(handlerType) if !ok { log.Println("image missing: " + index) - return image.NewNRGBA(image.Rect(0, 0, info.IconSize, info.IconSize)) + return image.NewNRGBA(image.Rect(0, 0, w, h)) } f, err := os.Open(path.(string)) defer f.Close() if err != nil { log.Println(err) - return image.NewNRGBA(image.Rect(0, 0, info.IconSize, info.IconSize)) + return image.NewNRGBA(image.Rect(0, 0, w, h)) } img, _, err := image.Decode(f) if err != nil { log.Println(err) - return image.NewNRGBA(image.Rect(0, 0, info.IconSize, info.IconSize)) + return image.NewNRGBA(image.Rect(0, 0, w, h)) } - return api.ResizeImage(img, info.IconSize) + return api.ResizeImageWH(img, w, h) } -func (c *ToggleIconHandler) loop(k api.KeyConfigV3, callback func(image image.Image)) { +func (c *ToggleHandler) loop(fields map[string]any, callback func(image image.Image)) { ctx := context.Background() err := c.Lock.Acquire(ctx, 1) if err != nil { @@ -72,7 +73,7 @@ func (c *ToggleIconHandler) loop(k api.KeyConfigV3, callback func(image image.Im case <-c.Quit: return default: - command, ok := k.IconHandlerFields["check_command"] + command, ok := fields["check_command"] if !ok { break @@ -86,7 +87,7 @@ func (c *ToggleIconHandler) loop(k api.KeyConfigV3, callback func(image image.Im if err != nil { status = false } - sharedStatus, ok := k.SharedState["status"].(bool) + sharedStatus, ok := fields["status"].(bool) if !ok { sharedStatus = false } @@ -95,7 +96,7 @@ func (c *ToggleIconHandler) loop(k api.KeyConfigV3, callback func(image image.Im continue } sharedStatus = status - k.SharedState["status"] = sharedStatus + fields["status"] = sharedStatus c.FirstLoop = false img := c.UpIconBuff if sharedStatus == false { @@ -107,28 +108,26 @@ func (c *ToggleIconHandler) loop(k api.KeyConfigV3, callback func(image image.Im } } -func (c *ToggleIconHandler) IsRunning() bool { +func (c *ToggleHandler) IsRunning() bool { return c.Running } -func (c *ToggleIconHandler) SetRunning(running bool) { +func (c *ToggleHandler) SetRunning(running bool) { c.Running = running } -func (c *ToggleIconHandler) Stop() { +func (c *ToggleHandler) Stop() { c.Running = false c.Quit <- true } -type ToggleKeyHandler struct{} - -func (ToggleKeyHandler) Key(key api.KeyConfigV3, info api.StreamDeckInfoV1) { - sharedStatus := key.SharedState["status"].(bool) +func (t *ToggleHandler) Input(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, event api.InputEvent) { + sharedStatus := fields["status"].(bool) index := "down_command" if !sharedStatus { index = "up_command" } - command, ok := key.KeyHandlerFields[index] + command, ok := fields[index] commandString := command.(string) if !ok { return @@ -153,16 +152,16 @@ func RegisterToggle() api.Module { return api.Module{ Name: "Toggle", - NewIcon: func() api.IconHandler { - return &ToggleIconHandler{Running: true, Lock: semaphore.NewWeighted(1), FirstLoop: true} + NewForeground: func() api.ForegroundHandler { + return &ToggleHandler{Running: true, Lock: semaphore.NewWeighted(1), FirstLoop: true} }, - NewKey: func() api.KeyHandler { return &ToggleKeyHandler{} }, - IconFields: []api.Field{ + NewInput: func() api.InputHandler { return &ToggleHandler{} }, + ForegroundFields: []api.Field{ {Title: "Up Icon", Name: "up_icon", Type: api.File, FileTypes: []string{".png", ".jpg", ".jpeg"}}, {Title: "Down Icon", Name: "down_icon", Type: api.File, FileTypes: []string{".png", ".jpg", ".jpeg"}}, {Title: "Check Command", Name: "check_command", Type: api.Text}, }, - KeyFields: []api.Field{ + InputFields: []api.Field{ {Title: "Up Command", Name: "up_command", Type: api.Text}, {Title: "Down Command", Name: "down_command", Type: api.Text}, }, diff --git a/streamdeckd/examples/volume.go b/streamdeckd/examples/volume.go index 1a72598..d059ccd 100644 --- a/streamdeckd/examples/volume.go +++ b/streamdeckd/examples/volume.go @@ -15,7 +15,7 @@ import ( "golang.org/x/sync/semaphore" ) -type VolumeLcdHandler struct { +type VolumeHandler struct { Running bool Quit chan bool Lock *semaphore.Weighted @@ -30,7 +30,7 @@ type VolumeLcdHandler struct { client *pulseaudio.Client } -func (v *VolumeLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (v *VolumeHandler) Start(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { if v.Quit == nil { v.Quit = make(chan bool) @@ -39,14 +39,14 @@ func (v *VolumeLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckInfoV v.Lock = semaphore.NewWeighted(1) } if v.MuteBuff == nil { - v.MuteBuff = v.GetImage("mute_icon", knob, info) + v.MuteBuff = v.GetImage("mute_icon", fields, handlerType, info) } if v.UnmuteBuff == nil { - v.UnmuteBuff = v.GetImage("unmute_icon", knob, info) + v.UnmuteBuff = v.GetImage("unmute_icon", fields, handlerType, info) } - devType, ok := knob.LcdHandlerFields["device_type"] + devType, ok := fields["device_type"] if !ok { - devType, ok = knob.SharedHandlerFields["device_type"] + devType, ok = fields["device_type"] if !ok { log.Println("Device type missing") return @@ -55,9 +55,9 @@ func (v *VolumeLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckInfoV if devType == "sink_input" || devType == "source_output" { var inputName string props := make(map[string]string) - inputName, ok := knob.LcdHandlerFields["input_name"].(string) + inputName, ok := fields["input_name"].(string) if !ok { - propss, ok := knob.LcdHandlerFields["props"] + propss, ok := fields["props"] if !ok { log.Println("No Input Name or Props") return @@ -74,17 +74,17 @@ func (v *VolumeLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckInfoV } v.DevType = devType.(string) v.Running = true - v.Run(knob, info, callback) + v.Run(handlerType, info, callback) } -func (v *VolumeLcdHandler) IsRunning() bool { +func (v *VolumeHandler) IsRunning() bool { return v.Running } -func (v *VolumeLcdHandler) SetRunning(running bool) { +func (v *VolumeHandler) SetRunning(running bool) { v.Running = running } -func (v *VolumeLcdHandler) Stop() { +func (v *VolumeHandler) Stop() { v.Running = false v.Quit <- true v.FirstLoop = true @@ -92,27 +92,28 @@ func (v *VolumeLcdHandler) Stop() { v.Volume = 0 } -func (v *VolumeLcdHandler) GetImage(index string, knob api.KnobConfigV3, info api.StreamDeckInfoV1) image.Image { - path, ok := knob.LcdHandlerFields[index] +func (v *VolumeHandler) GetImage(index string, fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1) image.Image { + path, ok := fields[index] + w, h := info.GetDimensions(handlerType) if !ok { log.Println("image missing: " + index) - return image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) + return image.NewNRGBA(image.Rect(0, 0, w, h)) } f, err := os.Open(path.(string)) defer f.Close() if err != nil { log.Println(err) - return image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) + return image.NewNRGBA(image.Rect(0, 0, w, h)) } img, _, err := image.Decode(f) if err != nil { log.Println(err) - return image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) + return image.NewNRGBA(image.Rect(0, 0, w, h)) } - return api.ResizeImageWH(img, info.LcdWidth, info.LcdHeight) + return api.ResizeImageWH(img, w, h) } -func (v *VolumeLcdHandler) Run(knob api.KnobConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { +func (v *VolumeHandler) Run(handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) { ctx := context.Background() err := v.Lock.Acquire(ctx, 1) if err != nil { @@ -133,7 +134,7 @@ func (v *VolumeLcdHandler) Run(knob api.KnobConfigV3, info api.StreamDeckInfoV1, if err != nil { return } - err = update(v, info, callback) + err = update(v, handlerType, info, callback) if err != nil { log.Println(err) } @@ -143,7 +144,7 @@ func (v *VolumeLcdHandler) Run(knob api.KnobConfigV3, info api.StreamDeckInfoV1, log.Println("Volume Quit") return case <-v.client.Events: - err := update(v, info, callback) + err := update(v, handlerType, info, callback) if err != nil { log.Println(err) } @@ -151,9 +152,10 @@ func (v *VolumeLcdHandler) Run(knob api.KnobConfigV3, info api.StreamDeckInfoV1, } } -func update(v *VolumeLcdHandler, info api.StreamDeckInfoV1, callback func(image image.Image)) error { +func update(v *VolumeHandler, handlerType api.HandlerType, info api.StreamDeckInfoV1, callback func(image image.Image)) error { var device pulseaudio.Device var err error + w, h := info.GetDimensions(handlerType) if v.DevType == "sink" { device, err = v.client.GetDefaultSink() } else if v.DevType == "source" { @@ -184,7 +186,7 @@ func update(v *VolumeLcdHandler, info api.StreamDeckInfoV1, callback func(image } } if err != nil { - img := image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) + img := image.NewNRGBA(image.Rect(0, 0, w, h)) var text string if v.DevType == "sink" || v.DevType == "source" { text = "Could not find default " + v.DevType @@ -228,10 +230,10 @@ func update(v *VolumeLcdHandler, info api.StreamDeckInfoV1, callback func(image } if img == nil { - image.NewNRGBA(image.Rect(0, 0, info.LcdWidth, info.LcdHeight)) + image.NewNRGBA(image.Rect(0, 0, w, h)) } - imgParsed, err := api.DrawProgressBarWithAccent(img, text, 5, float64(info.LcdHeight-25), 20, float64(info.LcdWidth-10), float64(vol), "#cc3333") + imgParsed, err := api.DrawProgressBarWithAccent(img, text, 5, float64(h-25), 20, float64(w-10), float64(vol), "#cc3333") if err != nil { log.Println(err) @@ -242,14 +244,10 @@ func update(v *VolumeLcdHandler, info api.StreamDeckInfoV1, callback func(image } } -type VolumeKnobOrTouchHandler struct { - client *pulseaudio.Client -} - -func (v *VolumeKnobOrTouchHandler) Input(knob api.KnobConfigV3, info api.StreamDeckInfoV1, event api.InputEvent) { - dev, ok := knob.KnobOrTouchHandlerFields["device_type"] +func (v *VolumeHandler) Input(fields map[string]any, handlerType api.HandlerType, info api.StreamDeckInfoV1, event api.InputEvent) { + dev, ok := fields["device_type"] if !ok { - dev, ok = knob.SharedHandlerFields["device_type"] + dev, ok = fields["device_type"] if !ok { log.Println("Device type missing") return @@ -259,9 +257,9 @@ func (v *VolumeKnobOrTouchHandler) Input(knob api.KnobConfigV3, info api.StreamD if dev == "sink_input" || dev == "source_output" { var inputName string props := make(map[string]string) - inputName, ok := knob.KnobOrTouchHandlerFields["input_name"].(string) + inputName, ok := fields["input_name"].(string) if !ok { - propss, ok := knob.KnobOrTouchHandlerFields["props"] + propss, ok := fields["props"] if !ok { log.Println("No Input Name or Props") return @@ -336,129 +334,39 @@ func updateDevice(device pulseaudio.Device, event api.InputEvent) { if !muted && device.GetVolume() < 1 { device.SetVolume(device.GetVolume() + (float32(event.RotateNotches) * 0.01)) } - } else if event.EventType == api.KNOB_PRESS || event.EventType == api.SCREEN_SHORT_TAP { + } else if event.EventType == api.KNOB_PRESS || event.EventType == api.SCREEN_SHORT_TAP || event.EventType == api.KEY_PRESS { device.ToggleMute() } } -type VolumeKeyHandler struct { - client *pulseaudio.Client -} - -func (v *VolumeKeyHandler) Key(key api.KeyConfigV3, info api.StreamDeckInfoV1) { - dev, ok := key.KeyHandlerFields["device_type"] - if !ok { - log.Println("Device type missing") - return - } - var err error - if dev == "sink_input" || dev == "source_output" { - var inputName string - props := make(map[string]string) - inputName, ok := key.KeyHandlerFields["input_name"].(string) - if !ok { - propss, ok := key.KeyHandlerFields["props"] - if !ok { - log.Println("No Input Name or Props") - return - } - for key, value := range propss.(map[string]interface{}) { - if value == nil { - continue - } - props[key] = value.(string) - } - } - if dev == "sink_input" { - var devices []pulseaudio.SinkInput - if inputName == "" { - devices, err = v.client.GetSinkInputsByProps(props) - } else { - devices, err = v.client.GetSinkInputsByName(inputName) - } - if err != nil { - log.Println(err) - return - } - for _, device := range devices { - updateDevice(device, api.InputEvent{EventType: api.KNOB_PRESS}) - } - } else { - var devices []pulseaudio.SourceOutput - if inputName == "" { - devices, err = v.client.GetSourceOutputsByProps(props) - } else { - devices, err = v.client.GetSourceOutputsByName(inputName) - } - if err != nil { - log.Println(err) - return - } - for _, device := range devices { - updateDevice(device, api.InputEvent{EventType: api.KNOB_PRESS}) - } - } - - } else { - var device pulseaudio.Device - if dev == "sink" { - device, err = v.client.GetDefaultSink() - } else if dev == "source" { - device, err = v.client.GetDefaultSource() - } - if device == nil { - err = errors.New("No device found") - } - if err != nil { - log.Println(err) - return - } - updateDevice(device, api.InputEvent{EventType: api.KNOB_PRESS}) - } - -} - func RegisterVolume() api.Module { return api.Module{ - NewLcd: func() api.LcdHandler { + NewForeground: func() api.ForegroundHandler { client, err := pulseaudio.NewClient() if err != nil { panic(err) } - return &VolumeLcdHandler{Running: true, Lock: semaphore.NewWeighted(1), FirstLoop: true, client: client} + return &VolumeHandler{Running: true, Lock: semaphore.NewWeighted(1), FirstLoop: true, client: client} }, - LcdFields: []api.Field{ + ForegroundFields: []api.Field{ {Title: "Unmuted Icon", Name: "unmute_icon", Type: api.File, FileTypes: []string{".png", ".jpg", ".jpeg"}}, {Title: "Muted Icon", Name: "mute_icon", Type: api.File}, {Title: "Device Type", Name: "device_type", Type: api.Text}, {Title: "Input Name", Name: "input_name", Type: api.Text}, {Title: "Props", Name: "props", Type: api.Text}, }, - NewKnobOrTouch: func() api.KnobOrTouchHandler { + NewInput: func() api.InputHandler { client, err := pulseaudio.NewClient() if err != nil { panic(err) } - return &VolumeKnobOrTouchHandler{client: client} + return &VolumeHandler{client: client} }, - KnobOrTouchFields: []api.Field{ + InputFields: []api.Field{ {Title: "Device Type", Name: "device_type", Type: api.Text}, {Title: "Input Name", Name: "input_name", Type: api.Text}, {Title: "Props", Name: "props", Type: api.Text}, }, - NewKey: func() api.KeyHandler { - client, err := pulseaudio.NewClient() - if err != nil { - panic(err) - } - return &VolumeKeyHandler{client: client} - }, - KeyFields: []api.Field{ - {Title: "Device Type", Name: "device_type", Type: api.Text}, - {Title: "Input Name", Name: "input_name", Type: api.Text}, - {Title: "Props", Name: "props", Type: api.Text}, - }, - Name: "Volume", } } diff --git a/streamdeckd/extcall_linux.go b/streamdeckd/extcall_linux.go index d23d1ed..b869f8f 100644 --- a/streamdeckd/extcall_linux.go +++ b/streamdeckd/extcall_linux.go @@ -46,13 +46,7 @@ func UpdateApplication() { func setApplication(activePs string) { activePs = strings.Trim(activePs, "\n") - if currentApplication != activePs { - currentApplication = activePs - log.Println("Application updated to: " + currentApplication) - for _, dev := range Devs { - dev.ApplicationUpdated() - } - } + applicationManager.SetApplication(activePs) } type HyprlandActiveWindow struct { @@ -206,7 +200,7 @@ func (c *ScreensaverConnection) RegisterScreensaverActiveListener() { if locked != v.Body[0].(bool) { locked = v.Body[0].(bool) for _, deck := range Devs { - if deck.IsOpen { + if deck.IsOpen() { deck.HandleScreenLockChange(locked) } } diff --git a/streamdeckd/foregrounder.go b/streamdeckd/foregrounder.go new file mode 100644 index 0000000..7adb382 --- /dev/null +++ b/streamdeckd/foregrounder.go @@ -0,0 +1,225 @@ +package streamdeckd + +import ( + "fmt" + "image" + + "github.com/unix-streamdeck/api/v2" +) + +type IForegrounder interface { + SetKnob(currentKnobConfig *api.KnobConfigV3, knobIndex int, page int, activeApp string) + SetKey(currentKeyConfig *api.KeyConfigV3, keyIndex int, page int, activeApp string) + AttachPageChangeListener() + AttachAppChangeListener() +} + +type Foregrounder struct { + vdev IVirtualDev +} + +func (f *Foregrounder) SetKnob(currentKnobConfig *api.KnobConfigV3, knobIndex int, page int, activeApp string) { + defer HandlePanic(func() { + f.vdev.Logger().Println("Restarting SetKnob") + go f.SetKnob(currentKnobConfig, knobIndex, page, activeApp) + }) + if currentKnobConfig.LcdHandler != "" { + f.setKnobHandler(currentKnobConfig, knobIndex, page, activeApp) + } + if currentKnobConfig.LcdHandlerStruct == nil { + img := f.loadStaticImage(currentKnobConfig, f.vdev.SdInfo().LcdWidth, f.vdev.SdInfo().LcdHeight) + if img != nil { + f.vdev.SetKeyForeground(img, knobIndex, page) + } + } +} + +func (f *Foregrounder) setKnobHandler(currentKnobConfig *api.KnobConfigV3, knobIndex int, page int, activeApp string) { + if currentKnobConfig.LcdHandlerStruct == nil { + var handler api.ForegroundHandler + modules := AvailableModules() + for _, module := range modules { + if module.Name == currentKnobConfig.LcdHandler { + handler = module.NewForeground() + } + } + if handler == nil { + f.vdev.Logger().Println("Could not find handler:", currentKnobConfig.LcdHandler) + return + } + f.vdev.Logger().Printf("Created %s\n", currentKnobConfig.LcdHandler) + currentKnobConfig.LcdHandlerStruct = handler + } + f.vdev.Logger().Printf("Started %s on knob %d with app profile %s\n", currentKnobConfig.LcdHandler, knobIndex, activeApp) + trimmedKnobConfig := api.KnobConfigV3{LcdHandlerFields: currentKnobConfig.LcdHandlerFields} + + if currentKnobConfig.LcdHandler == currentKnobConfig.KnobOrTouchHandler { + trimmedKnobConfig.LcdHandlerFields = mergeSharedConfig(currentKnobConfig.SharedHandlerFields, currentKnobConfig.LcdHandlerFields) + } + + go currentKnobConfig.LcdHandlerStruct.Start(trimmedKnobConfig.LcdHandlerFields, + api.LCD, *f.vdev.SdInfo(), func(image image.Image) { + if image.Bounds().Max.X != f.vdev.SdInfo().LcdWidth || image.Bounds().Max.Y != f.vdev.SdInfo().LcdHeight { + image = api.ResizeImageWH(image, f.vdev.SdInfo().LcdWidth, f.vdev.SdInfo().LcdHeight) + } + f.vdev.SetPanelForeground(image, knobIndex, page) + }) +} + +func (f *Foregrounder) SetKey(currentKeyConfig *api.KeyConfigV3, keyIndex int, page int, activeApp string) { + defer HandlePanic(func() { + f.vdev.Logger().Println("Restarting SetKey") + go f.SetKey(currentKeyConfig, keyIndex, page, activeApp) + }) + if currentKeyConfig.IconHandler != "" { + f.setKeyHandler(currentKeyConfig, keyIndex, page, activeApp) + } + if currentKeyConfig.IconHandlerStruct == nil { + img := f.loadStaticImage(currentKeyConfig, f.vdev.SdInfo().IconSize, f.vdev.SdInfo().IconSize) + if img != nil { + f.vdev.SetKeyForeground(img, keyIndex, page) + } + } +} + +func (f *Foregrounder) setKeyHandler(currentKeyConfig *api.KeyConfigV3, keyIndex int, page int, activeApp string) { + if currentKeyConfig.IconHandlerStruct == nil { + var handler api.ForegroundHandler + modules := AvailableModules() + for _, module := range modules { + if module.Name == currentKeyConfig.IconHandler { + handler = module.NewForeground() + } + } + if handler == nil { + f.vdev.Logger().Println("Could not find handler:", currentKeyConfig.IconHandler) + return + } + f.vdev.Logger().Printf("Created %s\n", currentKeyConfig.IconHandler) + currentKeyConfig.IconHandlerStruct = handler + } + f.vdev.Logger().Printf("Started %s on key %d with app profile `%s`\n", currentKeyConfig.IconHandler, keyIndex, activeApp) + trimmedKeyConfig := api.KeyConfigV3{IconHandlerFields: currentKeyConfig.IconHandlerFields} + + if currentKeyConfig.IconHandler == currentKeyConfig.KeyHandler { + trimmedKeyConfig.IconHandlerFields = mergeSharedConfig(currentKeyConfig.SharedHandlerFields, currentKeyConfig.IconHandlerFields) + } + + currentKeyConfig.IconHandlerStruct.Start(trimmedKeyConfig.IconHandlerFields, + api.KEY, *f.vdev.SdInfo(), func(image image.Image) { + if image.Bounds().Max.X != f.vdev.SdInfo().IconSize || image.Bounds().Max.Y != f.vdev.SdInfo().IconSize { + image = api.ResizeImage(image, f.vdev.SdInfo().IconSize) + } + f.vdev.SetKeyForeground(image, keyIndex, page) + }) +} + +func (f *Foregrounder) loadStaticImage(fa api.ForegroundActions, w, h int) image.Image { + var img image.Image + if fa.GetIcon() == "" { + img = image.NewRGBA(image.Rect(0, 0, w, h)) + } else { + var err error + img, err = LoadImage(fa.GetIcon()) + if err != nil { + f.vdev.Logger().Println(err) + return nil + } + } + if fa.GetText() != "" { + var err error + img, err = api.DrawText(img, fa.GetText(), api.DrawTextOptions{ + FontSize: int64(fa.GetTextSize()), + VerticalAlignment: fa.GetTextAlignment(), + FontFace: fa.GetFontFace(), + Colour: fa.GetTextColour(), + }) + if err != nil { + f.vdev.Logger().Println(err) + return nil + } + } + return img +} + +func (f *Foregrounder) AttachPageChangeListener() { + f.vdev.PageManager().AttachListener(func(newPage, _ int) { + currentPage := f.vdev.Config().Pages[newPage] + + for i, _ := range currentPage.Keys { + key := ¤tPage.Keys[i] + + if key.Application == nil { + key.Application = map[string]*api.KeyConfigV3{} + key.Application[""] = &api.KeyConfigV3{} + currentPage.Keys[i] = *key + f.vdev.Logger().Println(fmt.Sprintf("Setting empty application on key: %d on page: %d", i, newPage)) + SaveConfig() + } + _, keyHasApp := key.Application[applicationManager.GetApplication()] + if key.ActiveApplication != "" && !keyHasApp { + key.ActiveApplication = "" + } + if keyHasApp { + key.ActiveApplication = applicationManager.GetApplication() + } + go f.SetKey(key.Application[key.ActiveApplication], i, newPage, key.ActiveApplication) + } + for i, _ := range currentPage.Knobs { + knob := ¤tPage.Knobs[i] + + if knob.Application == nil { + knob.Application = map[string]*api.KnobConfigV3{} + knob.Application[""] = &api.KnobConfigV3{} + currentPage.Knobs[i] = *knob + f.vdev.Logger().Println(fmt.Sprintf("Setting empty application on knob: %d on page: %d", i, newPage)) + SaveConfig() + } + _, knobHasApp := knob.Application[applicationManager.GetApplication()] + if knob.ActiveApplication != "" && !knobHasApp { + knob.ActiveApplication = "" + } + if knobHasApp { + knob.ActiveApplication = applicationManager.GetApplication() + } + go f.SetKnob(knob.Application[knob.ActiveApplication], i, newPage, knob.ActiveApplication) + } + }) +} + +func (f *Foregrounder) AttachAppChangeListener() { + applicationManager.AttachListener(func(application string) { + page := f.vdev.Config().Pages[f.vdev.PageManager().GetPage()] + for i := range page.Keys { + key := &page.Keys[i] + _, keyHasApp := key.Application[application] + activeApp := key.ActiveApplication + if key.Application[key.ActiveApplication].KeyHold != 0 && (keyHasApp || key.ActiveApplication != "") { + kb.KeyUp(key.Application[key.ActiveApplication].KeyHold) + } + if key.ActiveApplication != "" && !keyHasApp { + key.ActiveApplication = "" + } + if keyHasApp { + key.ActiveApplication = application + } + if key.ActiveApplication != activeApp { + go f.SetKey(key.Application[key.ActiveApplication], i, f.vdev.PageManager().GetPage(), key.ActiveApplication) + } + } + for i := range page.Knobs { + knob := &page.Knobs[i] + _, keyHasApp := knob.Application[application] + activeApp := knob.ActiveApplication + if knob.ActiveApplication != "" && !keyHasApp { + knob.ActiveApplication = "" + } + if keyHasApp { + knob.ActiveApplication = application + } + if knob.ActiveApplication != activeApp { + go f.SetKnob(knob.Application[knob.ActiveApplication], i, f.vdev.PageManager().GetPage(), knob.ActiveApplication) + } + } + }) +} diff --git a/streamdeckd/handler_pruner.go b/streamdeckd/handler_pruner.go new file mode 100644 index 0000000..942a112 --- /dev/null +++ b/streamdeckd/handler_pruner.go @@ -0,0 +1,144 @@ +package streamdeckd + +import ( + "fmt" + + "github.com/unix-streamdeck/api/v2" +) + +type IHandlerPruner interface { + OnPageChange() + OnAppSwitch() + StopAllHandlers() +} + +type HandlerPruner struct { + vdev IVirtualDev +} + +func (hp *HandlerPruner) OnPageChange() { + hp.vdev.PageManager().AttachListener(func(_, previousPage int) { + hp.stopPageHandlers(previousPage) + }) +} + +func (hp *HandlerPruner) stopPageHandlers(pageNo int) { + page := hp.vdev.Config().Pages[pageNo] + + if page.TouchPanelBackgroundHandler != nil { + go hp.stopHandler(page.TouchPanelBackgroundHandler, page.TouchPanelBackground, fmt.Sprintf("page %d background", pageNo)) + } + + if page.KeyGridBackgroundHandler != nil { + go hp.stopHandler(page.KeyGridBackgroundHandler, page.KeyGridBackground, fmt.Sprintf("page %d background", pageNo)) + } + + for i, key := range page.Keys { + + if key.KeyBackgroundHandler != nil { + go hp.stopHandler(key.KeyBackgroundHandler, key.KeyBackground, fmt.Sprintf("page %d, key %d background", pageNo, i)) + } + + for _, keyConfig := range key.Application { + if keyConfig.KeyBackgroundHandler != nil { + go hp.stopHandler(keyConfig.KeyBackgroundHandler, keyConfig.KeyBackground, fmt.Sprintf("page %d, key %d background", pageNo, i)) + } + + if keyConfig.IconHandlerStruct != nil { + go hp.stopHandler(keyConfig.IconHandlerStruct, keyConfig.IconHandler, fmt.Sprintf("page %d, key %d", pageNo, i)) + } + } + } + + for i, knob := range page.Knobs { + + if knob.TouchPanelBackgroundHandler != nil { + go hp.stopHandler(knob.TouchPanelBackgroundHandler, knob.TouchPanelBackground, fmt.Sprintf("page %d, knob %d background", pageNo, i)) + } + + for _, knobConfig := range knob.Application { + + if knobConfig.TouchPanelBackgroundHandler != nil { + go hp.stopHandler(knobConfig.TouchPanelBackgroundHandler, knobConfig.TouchPanelBackground, fmt.Sprintf("page %d, knob %d background", pageNo, i)) + } + + if knobConfig.LcdHandlerStruct != nil { + go hp.stopHandler(knobConfig.LcdHandlerStruct, knobConfig.LcdHandler, fmt.Sprintf("page %d, knob %d", pageNo, i)) + } + } + } +} + +func (hp *HandlerPruner) OnAppSwitch() { + applicationManager.AttachListener(func(activeApp string) { + page := hp.vdev.Config().Pages[hp.vdev.PageManager().GetPage()] + + for i, key := range page.Keys { + _, hasNewActiveApp := key.Application[activeApp] + + var newActiveApp string + + if hasNewActiveApp { + newActiveApp = activeApp + } else { + newActiveApp = "" + } + + for appName, keyConfig := range key.Application { + if appName == newActiveApp { + continue + } + + if keyConfig.KeyBackgroundHandler != nil { + go hp.stopHandler(keyConfig.KeyBackgroundHandler, keyConfig.KeyBackground, fmt.Sprintf("key %d app `%s`", i, appName)) + } + + if keyConfig.IconHandlerStruct != nil { + go hp.stopHandler(keyConfig.IconHandlerStruct, keyConfig.IconHandler, fmt.Sprintf("key %d app `%s`", i, appName)) + } + } + } + + for i, knob := range page.Knobs { + + _, hasNewActiveApp := knob.Application[activeApp] + + var newActiveApp string + + if hasNewActiveApp { + newActiveApp = activeApp + } else { + newActiveApp = "" + } + + for appName, knobConfig := range knob.Application { + if appName == newActiveApp { + continue + } + + if knobConfig.TouchPanelBackgroundHandler != nil { + go hp.stopHandler(knobConfig.TouchPanelBackgroundHandler, knobConfig.TouchPanelBackground, fmt.Sprintf("knob %d app `%s`", i, appName)) + } + + if knobConfig.LcdHandlerStruct != nil { + go hp.stopHandler(knobConfig.LcdHandlerStruct, knobConfig.LcdHandler, fmt.Sprintf("knob %d app `%s`", i, appName)) + } + } + } + }) +} + +func (hp *HandlerPruner) StopAllHandlers() { + for page := range hp.vdev.Config().Pages { + hp.stopPageHandlers(page) + } +} + +func (hp *HandlerPruner) stopHandler(handler api.VisualHandler, name, context string) { + if !handler.IsRunning() { + return + } + hp.vdev.Logger().Printf("Stopping handler: %s on %s\n", name, context) + handler.Stop() + hp.vdev.Logger().Printf("Stopped handler: %s on %s\n", name, context) +} diff --git a/streamdeckd/handlers.go b/streamdeckd/handlers.go index 7a146ab..81babd3 100644 --- a/streamdeckd/handlers.go +++ b/streamdeckd/handlers.go @@ -21,10 +21,9 @@ func RegisterModule(m api.Module) { } } log.Println("Loaded module " + m.Name) - m.IsKey = m.NewKey != nil - m.IsIcon = m.NewIcon != nil - m.IsKnob = m.NewKnobOrTouch != nil - m.IsLcd = m.NewLcd != nil + m.IsForeground = m.NewForeground != nil + m.IsInput = m.NewInput != nil + m.IsBackground = m.NewBackground != nil m.IsLinkedHandlers = m.LinkedFields != nil && len(m.LinkedFields) != 0 modules = append(modules, m) } @@ -52,15 +51,6 @@ func LoadModule(path string) { func UnmountHandlers() { for s := range Devs { dev := Devs[s] - dev.UnmountHandlers() + dev.HandlerPruner().StopAllHandlers() } } - -func UnmountKeyHandler(keyConfig *api.KeyConfigV3) { - keyConfig.IconHandlerStruct.Stop() - log.Printf("Stopped %s\n", keyConfig.IconHandler) -} -func UnmountKnobHandler(keyConfig *api.KnobConfigV3) { - keyConfig.LcdHandlerStruct.Stop() - log.Printf("Stopped %s\n", keyConfig.LcdHandler) -} diff --git a/streamdeckd/input_manager.go b/streamdeckd/input_manager.go new file mode 100644 index 0000000..9d56d38 --- /dev/null +++ b/streamdeckd/input_manager.go @@ -0,0 +1,147 @@ +package streamdeckd + +import ( + "github.com/unix-streamdeck/api/v2" + streamdeck "github.com/unix-streamdeck/driver" +) + +type IInputManager interface { + HandleKeyInput(key *api.KeyV3, event streamdeck.InputEvent) + HandleKnobInput(knob *api.KnobV3, event streamdeck.InputEvent) +} + +type InputManager struct { + vdev IVirtualDev +} + +func (im *InputManager) HandleKeyInput(key *api.KeyV3, event streamdeck.InputEvent) { + keyConfig, ok := key.Application[key.ActiveApplication] + if !ok { + im.vdev.Logger().Println("Err getting correct config for key") + return + } + if event.EventType == streamdeck.KEY_PRESS { + im.handleStandardActions(keyConfig) + + if keyConfig.KeyHandler != "" { + var deckInfo api.StreamDeckInfoV1 + deckInfo = *im.vdev.SdInfo() + if keyConfig.KeyHandlerStruct == nil { + var handler api.InputHandler + modules := AvailableModules() + for _, module := range modules { + if module.Name == keyConfig.KeyHandler { + handler = module.NewInput() + } + } + if handler == nil { + im.vdev.Logger().Println("Could not find handler:", keyConfig.KeyHandler) + return + } + keyConfig.KeyHandlerStruct = handler + } + trimmedKeyConfig := api.KeyConfigV3{KeyHandlerFields: keyConfig.KeyHandlerFields} + if keyConfig.IconHandler == keyConfig.KeyHandler { + trimmedKeyConfig.KeyHandlerFields = mergeSharedConfig(keyConfig.SharedHandlerFields, keyConfig.KeyHandlerFields) + iconHandler := keyConfig.IconHandlerStruct + comboHandler, ok := iconHandler.(api.CombinedHandler) + if ok { + keyConfig.KeyHandlerStruct = comboHandler + } + } + keyConfig.KeyHandlerStruct.Input(trimmedKeyConfig.KeyHandlerFields, api.KEY, deckInfo, api.InputEvent{ + EventType: api.InputEventType(event.EventType), + RotateNotches: event.RotateNotches, + }) + } + } + if keyConfig.KeyHold != 0 { + if event.EventType == streamdeck.KEY_PRESS { + err := kb.KeyDown(keyConfig.KeyHold) + if err != nil { + im.vdev.Logger().Println(err) + } + } else { + err := kb.KeyUp(keyConfig.KeyHold) + if err != nil { + im.vdev.Logger().Println(err) + } + } + } +} + +func (im *InputManager) HandleKnobInput(knob *api.KnobV3, event streamdeck.InputEvent) { + knobConfig, ok := knob.Application[knob.ActiveApplication] + if !ok { + im.vdev.Logger().Println("Err getting correct config for knob") + return + } + if knobConfig.KnobOrTouchHandler != "" { + var deckInfo api.StreamDeckInfoV1 + deckInfo = *im.vdev.SdInfo() + if knobConfig.KnobOrTouchHandlerStruct == nil { + var handler api.InputHandler + modules := AvailableModules() + for _, module := range modules { + if module.Name == knobConfig.KnobOrTouchHandler { + handler = module.NewInput() + } + } + if handler == nil { + im.vdev.Logger().Println("Could not find handler:", knobConfig.KnobOrTouchHandler) + return + } + knobConfig.KnobOrTouchHandlerStruct = handler + } + trimmedKnobConfig := api.KnobConfigV3{KnobOrTouchHandlerFields: knobConfig.KnobOrTouchHandlerFields} + if knobConfig.LcdHandler == knobConfig.KnobOrTouchHandler { + trimmedKnobConfig.KnobOrTouchHandlerFields = mergeSharedConfig(knobConfig.SharedHandlerFields, knobConfig.KnobOrTouchHandlerFields) + iconHandler := knobConfig.LcdHandlerStruct + comboHandler, ok := iconHandler.(api.CombinedHandler) + if ok { + knobConfig.KnobOrTouchHandlerStruct = comboHandler + } + } + knobConfig.KnobOrTouchHandlerStruct.Input(trimmedKnobConfig.KnobOrTouchHandlerFields, api.LCD, deckInfo, api.InputEvent{ + EventType: api.InputEventType(event.EventType), + RotateNotches: event.RotateNotches, + }) + } + var actions api.KnobActionV3 + if event.EventType == streamdeck.KNOB_PRESS { + actions = knobConfig.KnobPressAction + } else if event.EventType == streamdeck.KNOB_CCW { + actions = knobConfig.KnobTurnDownAction + } else if event.EventType == streamdeck.KNOB_CW { + actions = knobConfig.KnobTurnUpAction + } + im.handleStandardActions(&actions) +} + +func (im *InputManager) handleStandardActions(ia api.InputActions) { + if ia.GetCommand() != "" { + RunCommand(ia.GetCommand()) + } + if ia.GetKeyBind() != "" { + err := ExecuteKeybind(ia.GetKeyBind()) + if err != nil { + im.vdev.Logger().Println("[ERROR] Failed to execute keybind:", err) + } + } + if ia.GetSwitchPage() != 0 { + page := ia.GetSwitchPage() - 1 + im.vdev.PageManager().SetPage(page) + } + if ia.GetBrightness() != 0 { + err := im.vdev.SetBrightness(uint8(ia.GetBrightness())) + if err != nil { + im.vdev.Logger().Println(err) + } + } + if ia.GetUrl() != "" { + RunCommand("xdg-open " + ia.GetUrl()) + } + if ia.GetObsCommand() != "" { + runObsCommand(ia.GetObsCommand(), ia.GetObsCommandParams()) + } +} diff --git a/streamdeckd/interface.go b/streamdeckd/interface.go index 46c273e..acf38cb 100644 --- a/streamdeckd/interface.go +++ b/streamdeckd/interface.go @@ -9,10 +9,10 @@ import ( "os/exec" "github.com/unix-streamdeck/api/v2" - streamdeck "github.com/unix-streamdeck/driver" ) -var currentApplication = "" +var applicationManager IApplicationManager = &ApplicationManager{} + var locked = false func LoadImage(path string) (image.Image, error) { @@ -30,162 +30,6 @@ func LoadImage(path string) (image.Image, error) { return img, nil } -func SetKey(dev *VirtualDev, currentKeyConfig *api.KeyConfigV3, keyIndex int, page int, activeApp string) { - defer HandlePanic(func() { - log.Println("Restarting SetKey") - go SetKey(dev, currentKeyConfig, keyIndex, page, activeApp) - }) - - go dev.setIndividualKeyBackground(currentKeyConfig, keyIndex, dev.sdInfo.IconSize, dev.sdInfo.IconSize) - - dev.CompositeKeyImage(keyIndex, page) - if currentKeyConfig.IconHandler != "" { - SetKeyImageHandler(dev, currentKeyConfig, keyIndex, page, activeApp) - } - if currentKeyConfig.IconHandlerStruct == nil { - SetKeyImageHandlerless(dev, currentKeyConfig, keyIndex, page) - } -} -func SetKeyImageHandler(dev *VirtualDev, currentKeyConfig *api.KeyConfigV3, keyIndex int, page int, activeApp string) { - if currentKeyConfig.IconHandlerStruct == nil { - var handler api.IconHandler - modules := AvailableModules() - for _, module := range modules { - if module.Name == currentKeyConfig.IconHandler { - handler = module.NewIcon() - } - } - if handler == nil { - return - } - log.Printf("Created %s\n", currentKeyConfig.IconHandler) - currentKeyConfig.IconHandlerStruct = handler - } - log.Printf("Started %s on key %d with app profile %s\n", currentKeyConfig.IconHandler, keyIndex, activeApp) - trimmedKeyConfig := api.KeyConfigV3{IconHandlerFields: currentKeyConfig.IconHandlerFields} - if currentKeyConfig.IconHandler == currentKeyConfig.KeyHandler { - if currentKeyConfig.SharedState == nil { - currentKeyConfig.SharedState = make(map[string]any) - } - trimmedKeyConfig.SharedState = currentKeyConfig.SharedState - trimmedKeyConfig.IconHandlerFields = mergeSharedConfig(currentKeyConfig.SharedHandlerFields, currentKeyConfig.IconHandlerFields) - } else { - trimmedKeyConfig.SharedState = make(map[string]any) - } - currentKeyConfig.IconHandlerStruct.Start(trimmedKeyConfig, dev.sdInfo, func(image image.Image) { - if image.Bounds().Max.X != int(dev.Deck.Pixels) || image.Bounds().Max.Y != int(dev.Deck.Pixels) { - image = api.ResizeImage(image, int(dev.Deck.Pixels)) - } - dev.SetKeyForeground(image, keyIndex, page) - }) -} - -func SetKeyImageHandlerless(dev *VirtualDev, currentKeyConfig *api.KeyConfigV3, keyIndex int, page int) { - var img image.Image - if currentKeyConfig.Icon == "" { - img = image.NewRGBA(image.Rect(0, 0, int(dev.Deck.Pixels), int(dev.Deck.Pixels))) - } else { - var err error - img, err = LoadImage(currentKeyConfig.Icon) - if err != nil { - log.Println(err) - return - } - } - if currentKeyConfig.Text != "" { - var err error - img, err = api.DrawText(img, currentKeyConfig.Text, api.DrawTextOptions{ - FontSize: int64(currentKeyConfig.TextSize), - VerticalAlignment: api.VerticalAlignment(currentKeyConfig.TextAlignment), - FontFace: currentKeyConfig.FontFace, - Colour: currentKeyConfig.TextColour, - }) - if err != nil { - log.Println(err) - } - } - dev.SetKeyForeground(img, keyIndex, page) -} - -func SetKnob(dev *VirtualDev, currentKnobConfig *api.KnobConfigV3, knobIndex int, page int, activeApp string) { - defer HandlePanic(func() { - log.Println("Restarting SetKnob") - go SetKnob(dev, currentKnobConfig, knobIndex, page, activeApp) - }) - - go dev.setIndividualLcdBackground(currentKnobConfig, knobIndex, dev.sdInfo.LcdWidth, dev.sdInfo.LcdHeight) - - dev.CompositePanelImage(knobIndex, page) - if currentKnobConfig.LcdHandler != "" { - SetKnobHandler(dev, currentKnobConfig, knobIndex, page, activeApp) - } - if currentKnobConfig.LcdHandlerStruct == nil { - SetKnobHandlerless(dev, currentKnobConfig, knobIndex, page) - } -} - -func SetKnobHandlerless(dev *VirtualDev, currentKnobConfig *api.KnobConfigV3, knobIndex int, page int) { - var img image.Image - if currentKnobConfig.Icon == "" { - img = image.NewRGBA(image.Rect(0, 0, 200, 100)) - } else { - var err error - img, err = LoadImage(currentKnobConfig.Icon) - if err != nil { - log.Println(err) - return - } - } - if currentKnobConfig.Text != "" { - var err error - img, err = api.DrawText(img, currentKnobConfig.Text, api.DrawTextOptions{ - FontSize: int64(currentKnobConfig.TextSize), - VerticalAlignment: api.VerticalAlignment(currentKnobConfig.TextAlignment), - FontFace: currentKnobConfig.FontFace, - Colour: currentKnobConfig.TextColour, - }) - if err != nil { - log.Println(err) - } - } - dev.SetPanelForeground(img, knobIndex, page) -} - -func SetKnobHandler(dev *VirtualDev, currentKnobConfig *api.KnobConfigV3, knobIndex int, page int, activeApp string) { - if currentKnobConfig.LcdHandlerStruct == nil { - var handler api.LcdHandler - modules := AvailableModules() - for _, module := range modules { - if module.Name == currentKnobConfig.LcdHandler { - handler = module.NewLcd() - } - } - if handler == nil { - return - } - log.Printf("Created %s\n", currentKnobConfig.LcdHandler) - currentKnobConfig.LcdHandlerStruct = handler - } - log.Printf("Started %s on knob %d with app profile %s\n", currentKnobConfig.LcdHandler, knobIndex, activeApp) - trimmedKnobConfig := api.KnobConfigV3{LcdHandlerFields: currentKnobConfig.LcdHandlerFields} - if currentKnobConfig.LcdHandler == currentKnobConfig.KnobOrTouchHandler { - if currentKnobConfig.SharedState == nil { - currentKnobConfig.SharedState = make(map[string]any) - } - trimmedKnobConfig.SharedState = currentKnobConfig.SharedState - trimmedKnobConfig.LcdHandlerFields = mergeSharedConfig(currentKnobConfig.SharedHandlerFields, currentKnobConfig.LcdHandlerFields) - } else { - trimmedKnobConfig.SharedState = make(map[string]any) - } - - go currentKnobConfig.LcdHandlerStruct.Start(trimmedKnobConfig, dev.sdInfo, func(image image.Image) { - if image.Bounds().Max.X != int(dev.Deck.LcdWidth) || image.Bounds().Max.Y != int(dev.Deck.LcdHeight) { - image = api.ResizeImageWH(image, int(dev.Deck.LcdWidth), int(dev.Deck.LcdHeight)) - } - dev.SetPanelForeground(image, knobIndex, page) - }) -} - func RunCommand(command string) { go func() { cmd := exec.Command("/bin/sh", "-c", command) @@ -203,148 +47,6 @@ func RunCommand(command string) { }() } -func HandleKeyInput(dev *VirtualDev, key *api.KeyV3, keyDown bool) { - keyConfig, ok := key.Application[key.ActiveApplication] - if !ok { - log.Println("Err getting correct config for key") - return - } - if keyDown { - if keyConfig.Command != "" { - RunCommand(keyConfig.Command) - } - if keyConfig.Keybind != "" { - err := ExecuteKeybind(keyConfig.Keybind) - if err != nil { - log.Println("[ERROR] Failed to execute keybind:", err) - } - } - if keyConfig.SwitchPage != 0 { - page := keyConfig.SwitchPage - 1 - dev.SetPage(page) - } - if keyConfig.Brightness != 0 { - err := dev.SetBrightness(uint8(keyConfig.Brightness)) - if err != nil { - log.Println(err) - } - } - if keyConfig.Url != "" { - RunCommand("xdg-open " + keyConfig.Url) - } - if keyConfig.ObsCommand != "" { - runObsCommand(keyConfig.ObsCommand, keyConfig.ObsCommandParams) - } - if keyConfig.KeyHandler != "" { - var deckInfo api.StreamDeckInfoV1 - deckInfo = dev.sdInfo - if keyConfig.KeyHandlerStruct == nil { - var handler api.KeyHandler - modules := AvailableModules() - for _, module := range modules { - if module.Name == keyConfig.KeyHandler { - handler = module.NewKey() - } - } - if handler == nil { - return - } - keyConfig.KeyHandlerStruct = handler - } - trimmedKeyConfig := api.KeyConfigV3{KeyHandlerFields: keyConfig.KeyHandlerFields} - if keyConfig.IconHandler == keyConfig.KeyHandler { - trimmedKeyConfig.SharedState = keyConfig.SharedState - trimmedKeyConfig.KeyHandlerFields = mergeSharedConfig(keyConfig.SharedHandlerFields, keyConfig.KeyHandlerFields) - } else { - trimmedKeyConfig.SharedState = make(map[string]any) - } - keyConfig.KeyHandlerStruct.Key(trimmedKeyConfig, deckInfo) - } - } - if keyConfig.KeyHold != 0 { - if keyDown { - err := kb.KeyDown(keyConfig.KeyHold) - if err != nil { - log.Println(err) - } - } else { - err := kb.KeyUp(keyConfig.KeyHold) - if err != nil { - log.Println(err) - } - } - } -} - -func HandleKnobInput(dev *VirtualDev, knob *api.KnobV3, event streamdeck.InputEvent) { - knobConfig, ok := knob.Application[knob.ActiveApplication] - if !ok { - log.Println("Err getting correct config for knob") - return - } - if knobConfig.KnobOrTouchHandler != "" { - var deckInfo api.StreamDeckInfoV1 - deckInfo = dev.sdInfo - if knobConfig.KnobOrTouchHandlerStruct == nil { - var handler api.KnobOrTouchHandler - modules := AvailableModules() - for _, module := range modules { - if module.Name == knobConfig.KnobOrTouchHandler { - handler = module.NewKnobOrTouch() - } - } - if handler == nil { - return - } - knobConfig.KnobOrTouchHandlerStruct = handler - } - trimmedKnobConfig := api.KnobConfigV3{KnobOrTouchHandlerFields: knobConfig.KnobOrTouchHandlerFields} - if knobConfig.LcdHandler == knobConfig.KnobOrTouchHandler { - trimmedKnobConfig.SharedState = knobConfig.SharedState - trimmedKnobConfig.KnobOrTouchHandlerFields = mergeSharedConfig(knobConfig.SharedHandlerFields, knobConfig.KnobOrTouchHandlerFields) - } else { - trimmedKnobConfig.SharedState = make(map[string]any) - } - knobConfig.KnobOrTouchHandlerStruct.Input(trimmedKnobConfig, deckInfo, api.InputEvent{ - EventType: api.InputEventType(event.EventType), - RotateNotches: event.RotateNotches, - }) - } - var actions api.KnobActionV3 - if event.EventType == streamdeck.KNOB_PRESS { - actions = knobConfig.KnobPressAction - } else if event.EventType == streamdeck.KNOB_CCW { - actions = knobConfig.KnobTurnDownAction - } else if event.EventType == streamdeck.KNOB_CW { - actions = knobConfig.KnobTurnUpAction - } - if actions.Command != "" { - RunCommand(actions.Command) - } - if actions.Keybind != "" { - err := ExecuteKeybind(actions.Keybind) - if err != nil { - log.Println("[ERROR] Failed to execute keybind:", err) - } - } - if actions.SwitchPage != 0 { - page := actions.SwitchPage - 1 - dev.SetPage(page) - } - if actions.Brightness != 0 { - err := dev.SetBrightness(uint8(actions.Brightness)) - if err != nil { - log.Println(err) - } - } - if actions.Url != "" { - RunCommand("xdg-open " + actions.Url) - } - if actions.ObsCommand != "" { - runObsCommand(actions.ObsCommand, actions.ObsCommandParams) - } -} - func ExecuteKeybind(keybind string) error { keys, err := api.ParseXDoToolKeybindString(keybind) if err != nil { diff --git a/streamdeckd/obs.go b/streamdeckd/obs.go index 8e27ffb..def790c 100644 --- a/streamdeckd/obs.go +++ b/streamdeckd/obs.go @@ -2,10 +2,11 @@ package streamdeckd import ( "errors" - obsws "github.com/christopher-dG/go-obs-websocket" - "github.com/unix-streamdeck/api/v2" "log" "strconv" + + obsws "github.com/christopher-dG/go-obs-websocket" + "github.com/unix-streamdeck/api/v2" ) var paramlessObsCommands = map[string]func() obsws.Request{ @@ -153,9 +154,9 @@ func getObsHandlerFields() ([]api.Module, error) { var modules []api.Module for key := range paramlessObsCommands { modules = append(modules, api.Module{ - Name: key, - IsIcon: false, - IsKey: true, + Name: key, + IsForeground: false, + IsInput: true, }) } @@ -185,30 +186,30 @@ func getObsHandlerFields() ([]api.Module, error) { } modules = append(modules, api.Module{ - Name: "SetVolume", - IsIcon: false, - IsKey: true, - KeyFields: []api.Field{ + Name: "SetVolume", + IsForeground: false, + IsInput: true, + InputFields: []api.Field{ {Title: "Volume", Name: "volume", Type: "Number"}, {Title: "Audio Source", Name: "source", Type: "List", ListItems: sources}, }, }) modules = append(modules, api.Module{ - Name: "SetMute", - IsIcon: false, - IsKey: true, - KeyFields: []api.Field{ + Name: "SetMute", + IsForeground: false, + IsInput: true, + InputFields: []api.Field{ {Title: "Mute", Name: "mute", Type: "Checkbox"}, {Title: "Audio Source", Name: "source", Type: "List", ListItems: sources}, }, }) modules = append(modules, api.Module{ - Name: "ToggleMute", - IsIcon: false, - IsKey: true, - KeyFields: []api.Field{ + Name: "ToggleMute", + IsForeground: false, + IsInput: true, + InputFields: []api.Field{ {Title: "Mute", Name: "mute", Type: "Checkbox"}, {Title: "Audio Source", Name: "source", Type: "List", ListItems: sources}, }, diff --git a/streamdeckd/page_manager.go b/streamdeckd/page_manager.go new file mode 100644 index 0000000..b3371db --- /dev/null +++ b/streamdeckd/page_manager.go @@ -0,0 +1,38 @@ +package streamdeckd + +type IPageManager interface { + SetPage(page int) + AttachListener(channel func(newPage, previousPage int)) + GetPage() int +} + +type PageManager struct { + vdev IVirtualDev + page int + listeners []func(newPage, previousPage int) +} + +func (pm *PageManager) SetPage(page int) { + + if page != pm.page || page == 0 { + + oldPage := pm.page + + pm.page = page + + pm.vdev.SdInfo().Page = page + EmitPage(pm.vdev, page) + + for _, listener := range pm.listeners { + go listener(pm.page, oldPage) + } + } +} + +func (pm *PageManager) AttachListener(channel func(newPage, previousPage int)) { + pm.listeners = append(pm.listeners, channel) +} + +func (pm *PageManager) GetPage() int { + return pm.page +} diff --git a/streamdeckd/virtualdev.go b/streamdeckd/virtualdev.go index ed33755..a057bcd 100644 --- a/streamdeckd/virtualdev.go +++ b/streamdeckd/virtualdev.go @@ -5,7 +5,7 @@ import ( "fmt" "image" "log" - "math" + "os" "regexp" "sync" "time" @@ -16,7 +16,30 @@ import ( var disconnectSem sync.Mutex var connectSem sync.Mutex -var Devs map[string]*VirtualDev +var Devs map[string]IVirtualDev + +type IVirtualDev interface { + IsOpen() bool + Config() api.DeckV3 + SetConfig(v3 api.DeckV3) + SdInfo() *api.StreamDeckInfoV1 + Serial() string + Foregrounder() IForegrounder + Backgrounder() IBackgrounder + PageManager() IPageManager + HandlerPruner() IHandlerPruner + InputManager() IInputManager + Logger() *log.Logger + + Open(rawDev *streamdeck.Device) error + SetKeyBackground(keyIndex int, page int) + SetKeyForeground(img image.Image, keyIndex int, page int) + SetPanelBackground(knobIndex int, page int) + SetPanelForeground(img image.Image, knobIndex int, page int) + SetBrightness(brightness uint8) error + HandleScreenLockChange(locked bool) + Close() +} func OpenDevice() error { connectSem.Lock() @@ -33,179 +56,170 @@ func OpenDevice() error { continue } dev, ok := Devs[rawDev.Serial] - if ok && dev.IsOpen { + if ok && dev.IsOpen() { continue } - err = rawDev.Open() - if err != nil { - log.Println(err) - continue - } - if !ok { - // initial connect - config := findConfig(rawDev) - dev = &VirtualDev{ - Deck: rawDev, - Page: 0, - IsOpen: true, - Config: config, - keyUpdateChan: make(chan int), - knobUpdateChan: make(chan int), - KeyBGBuffs: make([]image.Image, rawDev.Keys), - KeyFGBuffs: make([]image.Image, rawDev.Keys), - PanelBGBuffs: make([]image.Image, rawDev.LcdColumns), - PanelFGBuffs: make([]image.Image, rawDev.LcdColumns), - } - dev.SetSdInfo() - Devs[rawDev.Serial] = dev - } else { - //reconnect - dev.IsOpen = true - dev.Deck = rawDev - dev.sdInfo.LastConnected = time.Now() - dev.sdInfo.Connected = true - } - - w := (dev.Deck.Pixels * uint(dev.Deck.Columns)) + (dev.Deck.PaddingX * uint(dev.Deck.Columns-1)) - h := (dev.Deck.Pixels * uint(dev.Deck.Rows)) + (dev.Deck.PaddingX * uint(dev.Deck.Rows-1)) - go dev.setKeyBackground(&dev.Config, int(w), int(h)) - go dev.setLcdBackground(&dev.Config, dev.sdInfo.LcdWidth*dev.sdInfo.LcdCols, dev.sdInfo.LcdHeight) - - go dev.HandleInput() - dev.Render() - dev.SetPage(dev.Page) - log.Println(fmt.Sprintf("Device (%s) connected", rawDev.Serial)) - } - return nil -} - -func findConfig(device *streamdeck.Device) api.DeckV3 { - if migrateConfigFromV1 { - config.Decks[0].Serial = device.Serial - _ = SaveConfig() - migrateConfigFromV1 = false - return config.Decks[0] - } - for _, deck := range config.Decks { - if deck.Serial == device.Serial { - return deck + if dev == nil { + dev = &VirtualDev{} } - } - - return makeEmptyDeckConfig(device) -} -func makeEmptyDeckConfig(device *streamdeck.Device) api.DeckV3 { - var pages []api.PageV3 - page := api.PageV3{} - for i := 0; i < int(device.Rows)*int(device.Columns); i++ { - applications := make(map[string]*api.KeyConfigV3) - applications[""] = &api.KeyConfigV3{} - page.Keys = append(page.Keys, api.KeyV3{ - Application: applications, - }) + err := dev.Open(rawDev) + if err == nil { + log.Println(fmt.Sprintf("Device (%s) connected", rawDev.Serial)) + } } - pages = append(pages, page) - devConf := api.DeckV3{Serial: device.Serial, Pages: pages} - config.Decks = append(config.Decks, devConf) - _ = SaveConfig() - return devConf + return nil } type VirtualDev struct { - Deck *streamdeck.Device - Page int - IsOpen bool - Config api.DeckV3 + + //Internal Properties mu sync.Mutex shuttingDown bool - sdInfo api.StreamDeckInfoV1 keyUpdateChan chan int knobUpdateChan chan int - KeyFGBuffs []image.Image - KeyBGBuffs []image.Image - PanelFGBuffs []image.Image - PanelBGBuffs []image.Image + keyFGBuffs []image.Image + keyBGBuffs []image.Image + panelFGBuffs []image.Image + panelBGBuffs []image.Image + + //External Properties + isOpen bool + config api.DeckV3 + sdInfo *api.StreamDeckInfoV1 + deck *streamdeck.Device + foregrounder IForegrounder + backgrounder IBackgrounder + pageManager IPageManager + handlerPruner IHandlerPruner + inputManager IInputManager + logger *log.Logger } -func (dev *VirtualDev) SetPage(page int) { - if locked { - return - } - if page != dev.Page { - dev.unmountPageHandlersOnPageSwitch(dev.Config.Pages[dev.Page]) - } - dev.Page = page - currentPage := dev.Config.Pages[page] - - w := (dev.Deck.Pixels * uint(dev.Deck.Columns)) + (dev.Deck.PaddingX * uint(dev.Deck.Columns-1)) - h := (dev.Deck.Pixels * uint(dev.Deck.Rows)) + (dev.Deck.PaddingX * uint(dev.Deck.Rows-1)) - go dev.setKeyBackground(&dev.Config.Pages[page], int(w), int(h)) - - go dev.setLcdBackground(&dev.Config.Pages[page], dev.sdInfo.LcdWidth*dev.sdInfo.LcdCols, dev.sdInfo.LcdHeight) +func (dev *VirtualDev) Open(rawDev *streamdeck.Device) error { - if dev.Config.Pages[page].GetKeyGridBackgroundHandler() != nil { - go dev.Config.GetKeyGridBackgroundHandler().Stop() - } else { - go dev.setKeyBackground(&dev.Config, int(w), int(h)) + err := rawDev.Open() + if err != nil { + log.Println(err) + return err } - if dev.Config.Pages[page].GetTouchPanelBackgroundHandler() != nil { - go dev.Config.GetTouchPanelBackgroundHandler().Stop() - } else { - go dev.setLcdBackground(&dev.Config, dev.sdInfo.LcdWidth*dev.sdInfo.LcdCols, dev.sdInfo.LcdHeight) - } + if dev.deck == nil { + // initial connect + config := findConfig(rawDev) + dev = &VirtualDev{ + deck: rawDev, + isOpen: true, + config: config, + keyUpdateChan: make(chan int), + knobUpdateChan: make(chan int), + keyBGBuffs: make([]image.Image, rawDev.Keys), + keyFGBuffs: make([]image.Image, rawDev.Keys), + panelBGBuffs: make([]image.Image, rawDev.LcdColumns), + panelFGBuffs: make([]image.Image, rawDev.LcdColumns), + } + dev.setSdInfo() - for i, _ := range currentPage.Keys { - key := ¤tPage.Keys[i] + dev.backgrounder = &Backgrounder{ + vdev: dev, + } - go dev.setIndividualKeyBackground(key, i, dev.sdInfo.IconSize, dev.sdInfo.IconSize) + dev.pageManager = &PageManager{ + vdev: dev, + page: 0, + } - if key.Application == nil { - key.Application = map[string]*api.KeyConfigV3{} - key.Application[""] = &api.KeyConfigV3{} - currentPage.Keys[i] = *key - log.Println(fmt.Sprintf("Setting empty application on key: %d on page: %d", i, page)) - SaveConfig() + dev.handlerPruner = &HandlerPruner{ + vdev: dev, } - _, keyHasApp := key.Application[currentApplication] - if key.ActiveApplication != "" && !keyHasApp { - key.ActiveApplication = "" + + dev.inputManager = &InputManager{ + vdev: dev, } - if keyHasApp { - key.ActiveApplication = currentApplication + + dev.foregrounder = &Foregrounder{ + vdev: dev, } - go SetKey(dev, key.Application[key.ActiveApplication], i, page, key.ActiveApplication) - } - for i, _ := range currentPage.Knobs { - knob := ¤tPage.Knobs[i] - go dev.setIndividualLcdBackground(knob, i, dev.sdInfo.LcdWidth, dev.sdInfo.LcdHeight) + dev.backgrounder.AttachPageChangeListener() - if knob.Application == nil { - knob.Application = map[string]*api.KnobConfigV3{} - knob.Application[""] = &api.KnobConfigV3{} - currentPage.Knobs[i] = *knob - log.Println(fmt.Sprintf("Setting empty application on knob: %d on page: %d", i, page)) - SaveConfig() - } - _, knobHasApp := knob.Application[currentApplication] - if knob.ActiveApplication != "" && !knobHasApp { - knob.ActiveApplication = "" - } - if knobHasApp { - knob.ActiveApplication = currentApplication - } - go SetKnob(dev, knob.Application[knob.ActiveApplication], i, page, knob.ActiveApplication) + dev.foregrounder.AttachPageChangeListener() + dev.foregrounder.AttachAppChangeListener() + + dev.handlerPruner.OnPageChange() + dev.handlerPruner.OnAppSwitch() + + dev.logger = log.New(os.Stdout, fmt.Sprintf("(%s) ", dev.sdInfo.Serial), log.Lshortfile|log.Ltime) + + Devs[rawDev.Serial] = dev + } else { + //reconnect + dev.isOpen = true + dev.deck = rawDev + dev.sdInfo.LastConnected = time.Now() + dev.sdInfo.Connected = true } - dev.sdInfo.Page = page - EmitPage(dev, page) + + dev.pageManager.SetPage(dev.pageManager.GetPage()) + + go dev.backgrounder.SetKeyBackground(&dev.config) + + go dev.backgrounder.SetLcdBackground(&dev.config) + + go dev.handleInput() + dev.render() + + return nil +} + +func (dev *VirtualDev) IsOpen() bool { + return dev.isOpen +} + +func (dev *VirtualDev) Config() api.DeckV3 { + return dev.config +} + +func (dev *VirtualDev) SetConfig(config api.DeckV3) { + dev.config = config +} + +func (dev *VirtualDev) SdInfo() *api.StreamDeckInfoV1 { + return dev.sdInfo +} + +func (dev *VirtualDev) Serial() string { + return dev.deck.Serial +} + +func (dev *VirtualDev) Foregrounder() IForegrounder { + return dev.foregrounder +} + +func (dev *VirtualDev) Backgrounder() IBackgrounder { + return dev.backgrounder +} + +func (dev *VirtualDev) PageManager() IPageManager { + return dev.pageManager +} + +func (dev *VirtualDev) HandlerPruner() IHandlerPruner { + return dev.handlerPruner +} + +func (dev *VirtualDev) InputManager() IInputManager { + return dev.inputManager } -func (dev *VirtualDev) CompositeKeyImage(keyIndex int, page int) { +func (dev *VirtualDev) Logger() *log.Logger { + return dev.logger +} + +func (dev *VirtualDev) SetKeyBackground(keyIndex int, page int) { var background image.Image - keyV3 := dev.Config.Pages[page].Keys[keyIndex] + keyV3 := dev.config.Pages[page].Keys[keyIndex] keyConfigV3 := keyV3.Application[keyV3.ActiveApplication] kcbg := keyConfigV3.GetKeyBackgroundBuff() @@ -222,7 +236,7 @@ func (dev *VirtualDev) CompositeKeyImage(keyIndex int, page int) { } if background == nil { - pbg := dev.Config.Pages[page].GetTouchPanelBackgroundBuff() + pbg := dev.config.Pages[page].GetTouchPanelBackgroundBuff() if pbg != nil { background = pbg[keyIndex] @@ -230,21 +244,21 @@ func (dev *VirtualDev) CompositeKeyImage(keyIndex int, page int) { } if background == nil { - cbg := dev.Config.GetKeyGridBackgroundBuff() + cbg := dev.config.GetKeyGridBackgroundBuff() if cbg != nil { background = cbg[keyIndex] } } - if dev.KeyBGBuffs[keyIndex] != background { - dev.KeyBGBuffs[keyIndex] = background + if dev.keyBGBuffs[keyIndex] != background { + dev.keyBGBuffs[keyIndex] = background dev.keyUpdateChan <- keyIndex } } func (dev *VirtualDev) SetKeyForeground(img image.Image, keyIndex int, page int) { - if dev.Page != page { + if dev.pageManager.GetPage() != page { return } @@ -253,15 +267,15 @@ func (dev *VirtualDev) SetKeyForeground(img image.Image, keyIndex int, page int) img = api.ResizeImage(img, dev.sdInfo.IconSize) } - if dev.KeyFGBuffs[keyIndex] != img { - dev.KeyFGBuffs[keyIndex] = img + if dev.keyFGBuffs[keyIndex] != img { + dev.keyFGBuffs[keyIndex] = img dev.keyUpdateChan <- keyIndex } } -func (dev *VirtualDev) CompositePanelImage(knobIndex int, page int) { +func (dev *VirtualDev) SetPanelBackground(knobIndex int, page int) { var background image.Image - knobV3 := dev.Config.Pages[page].Knobs[knobIndex] + knobV3 := dev.config.Pages[page].Knobs[knobIndex] knobConfigV3 := knobV3.Application[knobV3.ActiveApplication] kcbg := knobConfigV3.GetTouchPanelBackgroundBuff() @@ -278,27 +292,27 @@ func (dev *VirtualDev) CompositePanelImage(knobIndex int, page int) { } if background == nil { - pbg := dev.Config.Pages[page].GetTouchPanelBackgroundBuff() + pbg := dev.config.Pages[page].GetTouchPanelBackgroundBuff() if pbg != nil { background = pbg[knobIndex] } } if background == nil { - cbg := dev.Config.GetTouchPanelBackgroundBuff() + cbg := dev.config.GetTouchPanelBackgroundBuff() if cbg != nil { background = cbg[knobIndex] } } - if dev.PanelBGBuffs[knobIndex] != background { - dev.PanelBGBuffs[knobIndex] = background + if dev.panelBGBuffs[knobIndex] != background { + dev.panelBGBuffs[knobIndex] = background dev.knobUpdateChan <- knobIndex } } func (dev *VirtualDev) SetPanelForeground(img image.Image, knobIndex int, page int) { - if dev.Page != page { + if dev.pageManager.GetPage() != page { return } @@ -307,122 +321,82 @@ func (dev *VirtualDev) SetPanelForeground(img image.Image, knobIndex int, page i img = api.ResizeImageWH(img, dev.sdInfo.LcdWidth, dev.sdInfo.LcdHeight) } - if dev.PanelFGBuffs[knobIndex] != img { - dev.PanelFGBuffs[knobIndex] = img + if dev.panelFGBuffs[knobIndex] != img { + dev.panelFGBuffs[knobIndex] = img dev.knobUpdateChan <- knobIndex } } -func (dev *VirtualDev) UnmountHandlers() { - for i := range dev.Config.Pages { - dev.unmountPageHandlersOnPageSwitch(dev.Config.Pages[i]) - } -} - func (dev *VirtualDev) SetBrightness(brightness uint8) error { - return dev.Deck.SetBrightness(brightness) + return dev.deck.SetBrightness(brightness) } -func (dev *VirtualDev) SetSdInfo() { +func (dev *VirtualDev) setSdInfo() { - manufacturer, err := dev.Deck.Device.GetManufacturer() + manufacturer, err := dev.deck.Device.GetManufacturer() if err != nil { - log.Println(err) + dev.logger.Println(err) } - product, err := dev.Deck.Device.GetProduct() + product, err := dev.deck.Device.GetProduct() if err != nil { - log.Println(err) - } - info := api.StreamDeckInfoV1{ - Cols: int(dev.Deck.Columns), - Rows: int(dev.Deck.Rows), - IconSize: int(dev.Deck.Pixels), - Page: 0, - Serial: dev.Deck.Serial, - Name: manufacturer + " " + product, - Connected: true, - LastConnected: time.Now(), - LcdWidth: int(dev.Deck.LcdWidth), - LcdHeight: int(dev.Deck.LcdHeight), - LcdCols: int(dev.Deck.LcdColumns), - KnobCols: int(dev.Deck.Knobs), - PaddingX: int(dev.Deck.PaddingX), - PaddingY: int(dev.Deck.PaddingY), + dev.logger.Println(err) } - dev.sdInfo = info -} - -func (dev *VirtualDev) ApplicationUpdated() { - if locked { - return - } - page := dev.Config.Pages[dev.Page] - for i := range page.Keys { - key := &page.Keys[i] - _, keyHasApp := key.Application[currentApplication] - activeApp := key.ActiveApplication - if key.Application[key.ActiveApplication].KeyHold != 0 && (keyHasApp || key.ActiveApplication != "") { - kb.KeyUp(key.Application[key.ActiveApplication].KeyHold) - } - if key.ActiveApplication != "" && !keyHasApp { - key.ActiveApplication = "" - } - if keyHasApp { - key.ActiveApplication = currentApplication - } - if key.ActiveApplication != activeApp { - go SetKey(dev, key.Application[key.ActiveApplication], i, dev.Page, key.ActiveApplication) - } - } - for i := range page.Knobs { - knob := &page.Knobs[i] - _, keyHasApp := knob.Application[currentApplication] - activeApp := knob.ActiveApplication - if knob.ActiveApplication != "" && !keyHasApp { - knob.ActiveApplication = "" - } - if keyHasApp { - knob.ActiveApplication = currentApplication - } - if knob.ActiveApplication != activeApp { - go SetKnob(dev, knob.Application[knob.ActiveApplication], i, dev.Page, knob.ActiveApplication) - } - } - dev.unmountPageHandlersOnAppSwitch(page) + info := api.StreamDeckInfoV1{ + Cols: int(dev.deck.Columns), + Rows: int(dev.deck.Rows), + IconSize: int(dev.deck.Pixels), + Page: 0, + Serial: dev.deck.Serial, + Name: manufacturer + " " + product, + Connected: true, + LastConnected: time.Now(), + LcdWidth: int(dev.deck.LcdWidth), + LcdHeight: int(dev.deck.LcdHeight), + LcdCols: int(dev.deck.LcdColumns), + KnobCols: int(dev.deck.Knobs), + PaddingX: int(dev.deck.PaddingX), + PaddingY: int(dev.deck.PaddingY), + KeyGridBackgroundWidth: int((dev.deck.Pixels * uint(dev.deck.Columns)) + (dev.deck.PaddingX * uint(dev.deck.Columns-1))), + KeyGridBackgroundHeight: int((dev.deck.Pixels * uint(dev.deck.Rows)) + (dev.deck.PaddingX * uint(dev.deck.Rows-1))), + LcdBackgroundWidth: int(dev.deck.LcdWidth * uint(dev.deck.LcdColumns)), + LcdBackgroundHeight: int(dev.deck.LcdHeight), + } + + dev.sdInfo = &info } func (dev *VirtualDev) HandleScreenLockChange(locked bool) { if locked { - dev.UnmountHandlers() - dev.Deck.Reset() + dev.handlerPruner.StopAllHandlers() + dev.deck.Reset() } else { - dev.SetPage(dev.Page) + dev.pageManager.SetPage(dev.pageManager.GetPage()) } } -func (dev *VirtualDev) Render() { - err := dev.Deck.SetImage(0, image.NewRGBA(image.Rect(0, 0, int(dev.Deck.Pixels), int(dev.Deck.Pixels)))) +func (dev *VirtualDev) render() { + err := dev.deck.SetImage(0, image.NewRGBA(image.Rect(0, 0, dev.sdInfo.IconSize, dev.sdInfo.IconSize))) if err != nil { - log.Println(err) + dev.logger.Println(err) return } - go dev.RenderKey() + go dev.renderKey() - go dev.RenderKnob() + go dev.renderKnob() } -func (dev *VirtualDev) RenderKey() { - for dev.IsOpen && !dev.shuttingDown { +func (dev *VirtualDev) renderKey() { + for dev.isOpen && !dev.shuttingDown { keyIndex := <-dev.keyUpdateChan - mergedImage, err := api.LayerImages(int(dev.Deck.Pixels), int(dev.Deck.Pixels), dev.KeyBGBuffs[keyIndex], dev.KeyFGBuffs[keyIndex]) + mergedImage, err := api.LayerImages(dev.sdInfo.IconSize, dev.sdInfo.IconSize, dev.keyBGBuffs[keyIndex], dev.keyFGBuffs[keyIndex]) if err != nil { dev.keyUpdateChan <- keyIndex - log.Println(err) + dev.logger.Println(err) continue } @@ -430,7 +404,7 @@ func (dev *VirtualDev) RenderKey() { dev.mu.Lock() - err = dev.Deck.SetImage(uint8(keyIndex), mergedImage) + err = dev.deck.SetImage(uint8(keyIndex), mergedImage) dev.mu.Unlock() @@ -438,30 +412,30 @@ func (dev *VirtualDev) RenderKey() { dev.keyUpdateChan <- keyIndex match, _ := regexp.MatchString(`.*hidapi.*`, err.Error()) if match { - dev.Disconnect() + dev.disconnect() return } match, _ = regexp.MatchString(`.*dimensions.*`, err.Error()) if match { - log.Println(fmt.Sprintf("%s provided: %d x %d", err.Error(), bounds.X, bounds.Y)) + dev.logger.Println(fmt.Sprintf("%s provided: %d x %d", err.Error(), bounds.X, bounds.Y)) return } - log.Println(err) + dev.logger.Println(err) } } } -func (dev *VirtualDev) RenderKnob() { - for dev.IsOpen && !dev.shuttingDown { +func (dev *VirtualDev) renderKnob() { + for dev.isOpen && !dev.shuttingDown { knobIndex := <-dev.knobUpdateChan - mergedImage, err := api.LayerImages(int(dev.Deck.LcdWidth), int(dev.Deck.LcdHeight), dev.PanelBGBuffs[knobIndex], dev.PanelFGBuffs[knobIndex]) + mergedImage, err := api.LayerImages(dev.sdInfo.LcdWidth, dev.sdInfo.LcdHeight, dev.panelBGBuffs[knobIndex], dev.panelFGBuffs[knobIndex]) if err != nil { dev.knobUpdateChan <- knobIndex - log.Println(err) + dev.logger.Println(err) continue } @@ -469,7 +443,7 @@ func (dev *VirtualDev) RenderKnob() { dev.mu.Lock() - err = dev.Deck.SetLcdImage(knobIndex, mergedImage) + err = dev.deck.SetLcdImage(knobIndex, mergedImage) dev.mu.Unlock() @@ -477,385 +451,77 @@ func (dev *VirtualDev) RenderKnob() { dev.knobUpdateChan <- knobIndex match, _ := regexp.MatchString(`.*hidapi.*`, err.Error()) if match { - dev.Disconnect() + dev.disconnect() return } match, _ = regexp.MatchString(`.*dimensions.*`, err.Error()) if match { - log.Println(fmt.Sprintf("%s provided: %d x %d", err.Error(), bounds.X, bounds.Y)) + dev.logger.Println(fmt.Sprintf("%s provided: %d x %d", err.Error(), bounds.X, bounds.Y)) return } - log.Println(err) + dev.logger.Println(err) } } } -func (dev *VirtualDev) HandleInput() { +func (dev *VirtualDev) handleInput() { defer func() { if err := recover(); err != nil { - dev.Disconnect() + dev.disconnect() } }() - dev.Deck.HandleInput(func(event streamdeck.InputEvent) { + dev.deck.HandleInput(func(event streamdeck.InputEvent) { if !locked { if event.EventType == streamdeck.KEY_PRESS || event.EventType == streamdeck.KEY_RELEASE { - page := dev.Config.Pages[dev.Page] + page := dev.config.Pages[dev.pageManager.GetPage()] if uint8(len(page.Keys)) > event.Index { - HandleKeyInput(dev, &page.Keys[event.Index], event.EventType == streamdeck.KEY_PRESS) + dev.inputManager.HandleKeyInput(&page.Keys[event.Index], event) } } else if event.EventType == streamdeck.SCREEN_SWIPE { if event.ScreenEndX < event.ScreenX { - if dev.Page < len(dev.Config.Pages)-1 { - dev.SetPage(dev.Page + 1) + if dev.pageManager.GetPage() < len(dev.config.Pages)-1 { + dev.pageManager.SetPage(dev.pageManager.GetPage() + 1) } } else { - if dev.Page > 0 { - dev.SetPage(dev.Page - 1) + if dev.pageManager.GetPage() > 0 { + dev.pageManager.SetPage(dev.pageManager.GetPage() - 1) } } - } else if dev.Deck.HasLCD && dev.Deck.HasKnobs { - page := dev.Config.Pages[dev.Page] + } else if dev.deck.HasLCD && dev.deck.HasKnobs { + page := dev.config.Pages[dev.pageManager.GetPage()] if uint8(len(page.Knobs)) > event.Index { - HandleKnobInput(dev, &page.Knobs[event.Index], event) + dev.inputManager.HandleKnobInput(&page.Knobs[event.Index], event) } } } }) } -func (dev *VirtualDev) Disconnect() { +func (dev *VirtualDev) disconnect() { disconnectSem.Lock() defer disconnectSem.Unlock() - if !dev.IsOpen { + if !dev.isOpen { return } - log.Println("Device (" + dev.Deck.Serial + ") disconnected") - err := dev.Deck.Close() + dev.logger.Println("Device (" + dev.deck.Serial + ") disconnected") + err := dev.deck.Close() if err != nil { - log.Println(err) + dev.logger.Println(err) } - dev.IsOpen = false + dev.isOpen = false dev.sdInfo.Connected = false dev.sdInfo.LastDisconnected = time.Now() - dev.UnmountHandlers() -} - -func (dev *VirtualDev) unmountPageHandlersOnPageSwitch(page api.PageV3) { - - if page.KeyGridBackgroundHandler != nil { - page.KeyGridBackgroundHandler.Stop() - } - - if page.TouchPanelBackgroundHandler != nil { - page.TouchPanelBackgroundHandler.Stop() - } - - for i2 := 0; i2 < len(page.Keys); i2++ { - key := &page.Keys[i2] - - if key.KeyBackgroundHandler != nil { - key.KeyBackgroundHandler.Stop() - } - - for _, keyConfig := range key.Application { - - if keyConfig.KeyBackgroundHandler != nil { - keyConfig.KeyBackgroundHandler.Stop() - } - - if keyConfig.IconHandlerStruct != nil { - log.Printf("Stopping %s\n", keyConfig.IconHandler) - if keyConfig.IconHandlerStruct.IsRunning() { - go UnmountKeyHandler(keyConfig) - } - } - } - - } - for i2 := 0; i2 < len(page.Knobs); i2++ { - knob := &page.Knobs[i2] - - if knob.TouchPanelBackgroundHandler != nil { - knob.TouchPanelBackgroundHandler.Stop() - } - - for _, knobConfig := range knob.Application { - - if knobConfig.TouchPanelBackgroundHandler != nil { - knobConfig.TouchPanelBackgroundHandler.Stop() - } - - if knobConfig.LcdHandlerStruct != nil { - log.Printf("Stopping %s\n", knobConfig.LcdHandler) - if knobConfig.LcdHandlerStruct.IsRunning() { - go UnmountKnobHandler(knobConfig) - } - } - } - } -} - -func (dev *VirtualDev) unmountPageHandlersOnAppSwitch(page api.PageV3) { - - for i2 := 0; i2 < len(page.Keys); i2++ { - key := &page.Keys[i2] - - _, keyHasApp := key.Application[currentApplication] - for app := range key.Application { - keyConfig := key.Application[app] - - if keyConfig.KeyBackgroundHandler != nil { - keyConfig.KeyBackgroundHandler.Stop() - } - - if (keyHasApp && app == currentApplication) || (!keyHasApp && app == "") { - continue - } - if keyConfig.IconHandlerStruct != nil && keyConfig.IconHandlerStruct.IsRunning() { - log.Printf("Stopping %s\n", keyConfig.IconHandler) - if keyConfig.IconHandlerStruct.IsRunning() { - go UnmountKeyHandler(keyConfig) - } - } - } - - } - for i2 := 0; i2 < len(page.Knobs); i2++ { - knob := &page.Knobs[i2] - - _, keyHasApp := knob.Application[currentApplication] - for app := range knob.Application { - knobConfig := knob.Application[app] - - if knobConfig.TouchPanelBackgroundHandler != nil { - go knobConfig.TouchPanelBackgroundHandler.Stop() - } - - if (keyHasApp && app == currentApplication) || (!keyHasApp && app == "") { - continue - } - if knobConfig.LcdHandlerStruct != nil && knobConfig.LcdHandlerStruct.IsRunning() { - log.Printf("Stopping %s\n", knobConfig.LcdHandler) - if knobConfig.LcdHandlerStruct.IsRunning() { - go UnmountKnobHandler(knobConfig) - } - } - } - } -} - -func (dev *VirtualDev) setLcdBackground(backgrounder api.LcdBackgrounder, w, h int) { - if backgrounder.GetTouchPanelBackground() == "" { - return - } - - if backgrounder.GetTouchPanelBackgroundHandler() == nil { - var handler api.TouchPanelBackgroundHandler - - for _, module := range modules { - if module.Name == backgrounder.GetTouchPanelBackground() { - handler = module.NewTouchPanelBackgroundHandler() - } - } - - backgrounder.SetTouchPanelBackgroundHandler(handler) - } - - if backgrounder.GetTouchPanelBackgroundHandlerFields() != nil { - go backgrounder.GetTouchPanelBackgroundHandler().Start(backgrounder.GetTouchPanelBackgroundHandlerFields(), dev.sdInfo, func(imgs []image.Image) { - if len(imgs) == int(dev.Deck.Knobs) { - backgrounder.SetTouchPanelBackgroundBuff(imgs) - - for u := range imgs { - dev.CompositePanelImage(u, dev.Page) - } - } - }) - return - } - - if backgrounder.GetTouchPanelBackgroundBuff() != nil { - return - } - - img, err := LoadImage(backgrounder.GetTouchPanelBackground()) - if err != nil { - log.Println(err) - return - } - - img = api.ResizeImageWH(img, w, h) - - var imgs []image.Image - - for lcdIndex := range int(dev.Deck.LcdColumns) { - x0, y0 := dev.sdInfo.LcdWidth*lcdIndex, 0 - x1, y1 := dev.sdInfo.LcdWidth*(lcdIndex+1), dev.sdInfo.LcdHeight - - imgs = append(imgs, api.SubImage(img, x0, y0, x1, y1)) - } - - backgrounder.SetTouchPanelBackgroundBuff(imgs) - - for index, _ := range imgs { - dev.CompositePanelImage(index, dev.Page) - } -} - -func (dev *VirtualDev) setKeyBackground(backgrounder api.KeyGridBackgrounder, w, h int) { - if backgrounder.GetKeyGridBackground() == "" { - return - } - - if backgrounder.GetKeyGridBackgroundHandler() == nil { - var handler api.KeyGridBackgroundHandler - - for _, module := range modules { - if module.Name == backgrounder.GetKeyGridBackground() { - handler = module.NewKeyGridBackground() - } - } - - backgrounder.SetKeyGridBackgroundHandler(handler) - } - - if backgrounder.GetKeyGridBackgroundHandler() != nil { - go backgrounder.GetKeyGridBackgroundHandler().Start(backgrounder.GetKeyGridBackgroundHandlerFields(), dev.sdInfo, func(imgs []image.Image) { - if len(imgs) == int(dev.Deck.Keys) { - backgrounder.SetKeyGridBackgroundBuff(imgs) - - for u := range imgs { - dev.CompositeKeyImage(u, dev.Page) - } - } - }) - return - } - - if backgrounder.GetKeyGridBackgroundBuff() != nil { - return - } - - img, err := LoadImage(backgrounder.GetKeyGridBackground()) - if err != nil { - log.Println(err) - return - } - - img = api.ResizeImageWH(img, w, h) - - var imgs []image.Image - for keyIndex := range int(dev.Deck.Keys) { - keyX := keyIndex % int(dev.Deck.Columns) - keyY := int(math.Floor(float64(keyIndex) / float64(dev.Deck.Columns))) - - x0, y0 := keyX*int(dev.Deck.Pixels+dev.Deck.PaddingX), keyY*int(dev.Deck.Pixels+dev.Deck.PaddingY) - x1, y1 := keyX*int(dev.Deck.Pixels+dev.Deck.PaddingX)+int(dev.Deck.Pixels), keyY*int(dev.Deck.Pixels+dev.Deck.PaddingY)+int(dev.Deck.Pixels) - - imgs = append(imgs, api.SubImage(img, x0, y0, x1, y1)) - } - backgrounder.SetKeyGridBackgroundBuff(imgs) - - for index, _ := range imgs { - dev.CompositeKeyImage(index, dev.Page) - } + dev.handlerPruner.StopAllHandlers() } -func (dev *VirtualDev) setIndividualLcdBackground(backgrounder api.LcdSegmentBackgrounder, index, w, h int) { - if backgrounder.GetTouchPanelBackground() == "" { - return - } - - if backgrounder.GetTouchPanelBackgroundHandler() == nil { - var handler api.TouchPanelBackgroundHandler - - for _, module := range modules { - if module.Name == backgrounder.GetTouchPanelBackground() { - handler = module.NewTouchPanelBackgroundHandler() - } - } - - backgrounder.SetTouchPanelBackgroundHandler(handler) - } - - if backgrounder.GetTouchPanelBackgroundHandlerFields() != nil { - go backgrounder.GetTouchPanelBackgroundHandler().StartIndividual(backgrounder.GetTouchPanelBackgroundHandlerFields(), dev.sdInfo, func(img image.Image) { - backgrounder.SetTouchPanelBackgroundBuff(img) - - dev.CompositePanelImage(index, dev.Page) - }) - return - } - - if backgrounder.GetTouchPanelBackgroundBuff() != nil { - return - } - - img, err := LoadImage(backgrounder.GetTouchPanelBackground()) - if err != nil { - log.Println(err) - return - } - - img = api.ResizeImageWH(img, w, h) - - backgrounder.SetTouchPanelBackgroundBuff(img) - - dev.CompositePanelImage(index, dev.Page) -} - -func (dev *VirtualDev) setIndividualKeyBackground(backgrounder api.KeyBackgrounder, index, w, h int) { - if backgrounder.GetKeyBackground() == "" { - return - } - - if backgrounder.GetKeyBackgroundHandler() == nil { - var handler api.KeyGridBackgroundHandler - - for _, module := range modules { - if module.Name == backgrounder.GetKeyBackground() { - handler = module.NewKeyGridBackground() - } - } - - backgrounder.SetKeyBackgroundHandler(handler) - } - - if backgrounder.GetKeyBackgroundHandler() != nil { - go backgrounder.GetKeyBackgroundHandler().StartIndividual(backgrounder.GetKeyBackgroundHandlerFields(), dev.sdInfo, func(img image.Image) { - backgrounder.SetKeyBackgroundBuff(img) - - dev.CompositeKeyImage(index, dev.Page) - }) - return - } - - if backgrounder.GetKeyBackgroundBuff() != nil { - return - } - - img, err := LoadImage(backgrounder.GetKeyBackground()) - if err != nil { - log.Println(err) - return - } - - img = api.ResizeImageWH(img, w, h) - - dev.CompositeKeyImage(index, dev.Page) -} - -func (dev *VirtualDev) Stop() { +func (dev *VirtualDev) Close() { dev.shuttingDown = true - if dev.IsOpen { - err := dev.Deck.Reset() - if err != nil { - log.Println(err) - } - err = dev.Deck.Close() + if dev.isOpen { + err := dev.deck.Reset() if err != nil { - log.Println(err) + dev.logger.Println(err) } + dev.disconnect() } } diff --git a/vendor/github.com/unix-streamdeck/api/v2/Module.go b/vendor/github.com/unix-streamdeck/api/v2/Module.go index 3dde72d..ad0673f 100644 --- a/vendor/github.com/unix-streamdeck/api/v2/Module.go +++ b/vendor/github.com/unix-streamdeck/api/v2/Module.go @@ -3,29 +3,20 @@ package api type Module struct { Name string `json:"name,omitempty"` - NewIcon func() IconHandler `json:"-"` - NewKey func() KeyHandler `json:"-"` - NewLcd func() LcdHandler `json:"-"` - NewKnobOrTouch func() KnobOrTouchHandler `json:"-"` - NewKeyGridBackground func() KeyGridBackgroundHandler `json:"-"` - NewTouchPanelBackgroundHandler func() TouchPanelBackgroundHandler `json:"-"` + NewForeground func() ForegroundHandler `json:"-"` + NewInput func() InputHandler `json:"-"` + NewBackground func() BackgroundHandler `json:"-"` - IconFields []Field `json:"icon_fields,omitempty"` - KeyFields []Field `json:"key_fields,omitempty"` - LcdFields []Field `json:"lcd_fields,omitempty"` - KnobOrTouchFields []Field `json:"knob_or_touch_fields,omitempty"` - LinkedFields []Field `json:"linked_fields,omitempty"` - KeyGridBackgroundFields []Field `json:"key_grid_background_fields"` - TouchPanelBackgroundFields []Field `json:"touch_panel_background_fields"` + ForegroundFields []Field `json:"icon_fields,omitempty"` + InputFields []Field `json:"key_fields,omitempty"` + BackgroundFields []Field `json:"lcd_fields,omitempty"` + LinkedFields []Field `json:"linked_fields,omitempty"` - IsIcon bool `json:"is_icon,omitempty"` - IsKey bool `json:"is_key,omitempty"` - IsLcd bool `json:"is_lcd,omitempty"` - IsKnob bool `json:"is_knob,omitempty"` - IsLinkedHandlers bool `json:"is_linked_handlers,omitempty"` - IsKeyGridBackground bool `json:"is_key_grid_background"` - IsTouchPanelBackground bool `json:"is_touch_panel_background"` - Linked bool `json:"linked,omitempty"` + IsForeground bool `json:"is_foreground,omitempty"` + IsInput bool `json:"is_input,omitempty"` + IsBackground bool `json:"is_background,omitempty"` + IsLinkedHandlers bool `json:"is_linked_handlers,omitempty"` + Linked bool `json:"linked,omitempty"` } type FieldType string diff --git a/vendor/github.com/unix-streamdeck/api/v2/README.md b/vendor/github.com/unix-streamdeck/api/v2/README.md index 4d3c747..59ed7b6 100644 --- a/vendor/github.com/unix-streamdeck/api/v2/README.md +++ b/vendor/github.com/unix-streamdeck/api/v2/README.md @@ -66,72 +66,40 @@ imgWithText, _ := api.DrawText(resizedImg, "Hello", 0, "CENTER") ### Implementing handlers ```go -// Implement a key handler -type MyKeyHandler struct{} - -func (h *MyKeyHandler) Key(key api.KeyConfigV3, info api.StreamDeckInfoV1) { - // Handle key press +// Implement a handler +type MyHandler struct{ + running bool } -// Implement an icon handler -type MyIconHandler struct { - running bool +func (h *MyHandler) Input(fields map[string]any, handlerType HandlerType, info StreamDeckInfoV1, event InputEvent) { + switch event.EventType { + case api.KNOB_CW: + // Handle clockwise rotation + case api.KNOB_CCW: + // Handle counter-clockwise rotation + case api.KNOB_PRESS: + // Handle knob press + case api.KEY_PRESS: + // Handle key press + } } -func (h *MyIconHandler) Start(key api.KeyConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { - h.running = true - // Generate and update icon - // Call callback with new images when needed +func (h *MyHandler) Start(fields map[string]any, handlerType HandlerType, info StreamDeckInfoV1, callback func(image image.Image)) { + // Generate image and send it back via callback } -func (h *MyIconHandler) IsRunning() bool { +func (h *MyHandler) IsRunning() bool { return h.running } -func (h *MyIconHandler) SetRunning(running bool) { +func (h *MyHandler) SetRunning(running bool) { h.running = running } -func (h *MyIconHandler) Stop() { +func (h *MyHandler) Stop() { h.running = false // Clean up resources and stop calling callback } - -// Implement an LCD handler (for Stream Deck Plus) -type MyLcdHandler struct { - running bool -} - -func (h *MyLcdHandler) Start(knob api.KnobConfigV3, info api.StreamDeckInfoV1, callback func(image image.Image)) { - h.running = true - // Generate and update LCD image -} - -func (h *MyLcdHandler) IsRunning() bool { - return h.running -} - -func (h *MyLcdHandler) SetRunning(running bool) { - h.running = running -} - -func (h *MyLcdHandler) Stop() { - h.running = false -} - -// Implement a knob or touch handler (for Stream Deck Plus) -type MyKnobHandler struct{} - -func (h *MyKnobHandler) Input(knob api.KnobConfigV3, info api.StreamDeckInfoV1, event api.InputEvent) { - switch event.EventType { - case api.KNOB_CW: - // Handle clockwise rotation - case api.KNOB_CCW: - // Handle counter-clockwise rotation - case api.KNOB_PRESS: - // Handle knob press - } -} ``` ## API Documentation @@ -139,10 +107,10 @@ func (h *MyKnobHandler) Input(knob api.KnobConfigV3, info api.StreamDeckInfoV1, The API provides several interfaces for handling Stream Deck interactions: - `Handler`: Base interface for all handlers -- `IconHandler`: For handling dynamic icons/images -- `KeyHandler`: For handling key press events -- `LcdHandler`: For handling LCD displays (Stream Deck Plus) -- `KnobOrTouchHandler`: For handling knob rotations and touch events (Stream Deck Plus) +- `ForegroundHandler`: For handling dynamic icons/images +- `InputHandler`: For handling input events +- `BackgroundHandler`: For handling dynamic backgrounds for individual displays, or whole deck +- `CombinedHandler`: For handlers that can do foregrounds and handle input, if this handler is applied to the foreground and input of a specific key/lcd segment, the same instance of the struct will be used, so resources can be shared Key components: diff --git a/vendor/github.com/unix-streamdeck/api/v2/config.go b/vendor/github.com/unix-streamdeck/api/v2/config.go index 514f1de..3c21822 100644 --- a/vendor/github.com/unix-streamdeck/api/v2/config.go +++ b/vendor/github.com/unix-streamdeck/api/v2/config.go @@ -2,6 +2,7 @@ package api import ( "image" + "math" "time" ) @@ -9,8 +10,8 @@ type LcdBackgrounder interface { GetTouchPanelBackground() string GetTouchPanelBackgroundBuff() []image.Image SetTouchPanelBackgroundBuff(img []image.Image) - GetTouchPanelBackgroundHandler() TouchPanelBackgroundHandler - SetTouchPanelBackgroundHandler(handler TouchPanelBackgroundHandler) + GetTouchPanelBackgroundHandler() BackgroundHandler + SetTouchPanelBackgroundHandler(handler BackgroundHandler) GetTouchPanelBackgroundHandlerFields() map[string]any } @@ -18,11 +19,30 @@ type KeyGridBackgrounder interface { GetKeyGridBackground() string GetKeyGridBackgroundBuff() []image.Image SetKeyGridBackgroundBuff(img []image.Image) - GetKeyGridBackgroundHandler() KeyGridBackgroundHandler - SetKeyGridBackgroundHandler(handler KeyGridBackgroundHandler) + GetKeyGridBackgroundHandler() BackgroundHandler + SetKeyGridBackgroundHandler(handler BackgroundHandler) GetKeyGridBackgroundHandlerFields() map[string]any } +type InputActions interface { + GetSwitchPage() int + GetKeyBind() string + GetCommand() string + GetBrightness() int + GetUrl() string + GetObsCommand() string + GetObsCommandParams() map[string]string +} + +type ForegroundActions interface { + GetIcon() string + GetText() string + GetTextSize() int + GetTextAlignment() VerticalAlignment + GetFontFace() string + GetTextColour() string +} + type ConfigV3 struct { Modules []string `json:"modules,omitempty"` Decks []DeckV3 `json:"decks"` @@ -30,23 +50,23 @@ type ConfigV3 struct { } type DeckV3 struct { - Serial string `json:"serial"` - Pages []PageV3 `json:"pages"` - TouchPanelBackground string `json:"touch_panel_background"` - TouchPanelBackgroundBuff []image.Image `json:"-"` - TouchPanelBackgroundHandler TouchPanelBackgroundHandler `json:"-"` - TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` - KeyGridBackground string `json:"key_grid_background"` - KeyGridBackgroundBuff []image.Image `json:"-"` - KeyGridBackgroundHandler KeyGridBackgroundHandler `json:"-"` - KeyGridBackgroundHandlerFields map[string]any `json:"key_grid_background_handler_fields"` -} - -func (d *DeckV3) SetTouchPanelBackgroundHandler(handler TouchPanelBackgroundHandler) { + Serial string `json:"serial"` + Pages []PageV3 `json:"pages"` + TouchPanelBackground string `json:"touch_panel_background"` + TouchPanelBackgroundBuff []image.Image `json:"-"` + TouchPanelBackgroundHandler BackgroundHandler `json:"-"` + TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` + KeyGridBackground string `json:"key_grid_background"` + KeyGridBackgroundBuff []image.Image `json:"-"` + KeyGridBackgroundHandler BackgroundHandler `json:"-"` + KeyGridBackgroundHandlerFields map[string]any `json:"key_grid_background_handler_fields"` +} + +func (d *DeckV3) SetTouchPanelBackgroundHandler(handler BackgroundHandler) { d.TouchPanelBackgroundHandler = handler } -func (d *DeckV3) GetTouchPanelBackgroundHandler() TouchPanelBackgroundHandler { +func (d *DeckV3) GetTouchPanelBackgroundHandler() BackgroundHandler { return d.TouchPanelBackgroundHandler } @@ -78,11 +98,11 @@ func (d *DeckV3) SetKeyGridBackgroundBuff(img []image.Image) { d.KeyGridBackgroundBuff = img } -func (d *DeckV3) SetKeyGridBackgroundHandler(handler KeyGridBackgroundHandler) { +func (d *DeckV3) SetKeyGridBackgroundHandler(handler BackgroundHandler) { d.KeyGridBackgroundHandler = handler } -func (d *DeckV3) GetKeyGridBackgroundHandler() KeyGridBackgroundHandler { +func (d *DeckV3) GetKeyGridBackgroundHandler() BackgroundHandler { return d.KeyGridBackgroundHandler } @@ -91,23 +111,23 @@ func (d *DeckV3) GetKeyGridBackgroundHandlerFields() map[string]any { } type PageV3 struct { - Keys []KeyV3 `json:"keys"` - Knobs []KnobV3 `json:"knobs"` - TouchPanelBackground string `json:"touch_panel_background"` - TouchPanelBackgroundBuff []image.Image `json:"-"` - TouchPanelBackgroundHandler TouchPanelBackgroundHandler `json:"-"` - TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` - KeyGridBackground string `json:"key_grid_background"` - KeyGridBackgroundBuff []image.Image `json:"-"` - KeyGridBackgroundHandler KeyGridBackgroundHandler `json:"-"` - KeyGridBackgroundHandlerFields map[string]any `json:"key_grid_background_handler_fields"` -} - -func (p *PageV3) SetTouchPanelBackgroundHandler(handler TouchPanelBackgroundHandler) { + Keys []KeyV3 `json:"keys"` + Knobs []KnobV3 `json:"knobs"` + TouchPanelBackground string `json:"touch_panel_background"` + TouchPanelBackgroundBuff []image.Image `json:"-"` + TouchPanelBackgroundHandler BackgroundHandler `json:"-"` + TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` + KeyGridBackground string `json:"key_grid_background"` + KeyGridBackgroundBuff []image.Image `json:"-"` + KeyGridBackgroundHandler BackgroundHandler `json:"-"` + KeyGridBackgroundHandlerFields map[string]any `json:"key_grid_background_handler_fields"` +} + +func (p *PageV3) SetTouchPanelBackgroundHandler(handler BackgroundHandler) { p.TouchPanelBackgroundHandler = handler } -func (p *PageV3) GetTouchPanelBackgroundHandler() TouchPanelBackgroundHandler { +func (p *PageV3) GetTouchPanelBackgroundHandler() BackgroundHandler { return p.TouchPanelBackgroundHandler } @@ -139,11 +159,11 @@ func (p *PageV3) SetKeyGridBackgroundBuff(img []image.Image) { p.KeyGridBackgroundBuff = img } -func (p *PageV3) SetKeyGridBackgroundHandler(handler KeyGridBackgroundHandler) { +func (p *PageV3) SetKeyGridBackgroundHandler(handler BackgroundHandler) { p.KeyGridBackgroundHandler = handler } -func (p *PageV3) GetKeyGridBackgroundHandler() KeyGridBackgroundHandler { +func (p *PageV3) GetKeyGridBackgroundHandler() BackgroundHandler { return p.KeyGridBackgroundHandler } @@ -152,21 +172,66 @@ func (p *PageV3) GetKeyGridBackgroundHandlerFields() map[string]any { } type StreamDeckInfoV1 struct { - Cols int `json:"cols,omitempty"` - Rows int `json:"rows,omitempty"` - IconSize int `json:"icon_size,omitempty"` - Page int `json:"page"` - Serial string `json:"serial,omitempty"` - Name string `json:"name,omitempty"` - Connected bool `json:"connected"` - LastConnected time.Time `json:"last_connected,omitempty"` - LastDisconnected time.Time `json:"last_disconnected,omitempty"` - LcdWidth int `json:"lcd_width,omitempty"` - LcdHeight int `json:"lcd_height,omitempty"` - LcdCols int `json:"lcd_cols,omitempty"` - KnobCols int `json:"knob_cols,omitempty"` - PaddingX int `json:"padding_x"` - PaddingY int `json:"padding_y"` + Cols int `json:"cols,omitempty"` + Rows int `json:"rows,omitempty"` + IconSize int `json:"icon_size,omitempty"` + Page int `json:"page"` + Serial string `json:"serial,omitempty"` + Name string `json:"name,omitempty"` + Connected bool `json:"connected"` + LastConnected time.Time `json:"last_connected,omitempty"` + LastDisconnected time.Time `json:"last_disconnected,omitempty"` + LcdWidth int `json:"lcd_width,omitempty"` + LcdHeight int `json:"lcd_height,omitempty"` + LcdCols int `json:"lcd_cols,omitempty"` + KnobCols int `json:"knob_cols,omitempty"` + PaddingX int `json:"padding_x"` + PaddingY int `json:"padding_y"` + KeyGridBackgroundWidth int `json:"key_grid_background_width"` + KeyGridBackgroundHeight int `json:"key_grid_background_height"` + LcdBackgroundWidth int `json:"lcd_background_width"` + LcdBackgroundHeight int `json:"lcd_background_height"` +} + +func (info StreamDeckInfoV1) GetDimensions(handlerType HandlerType) (int, int) { + if handlerType == LCD { + return info.LcdWidth, info.LcdHeight + } + return info.IconSize, info.IconSize +} + +func (info StreamDeckInfoV1) GetGridDimensions(handlerType HandlerType) (int, int) { + if handlerType == LCD { + return info.LcdBackgroundWidth, info.LcdBackgroundHeight + } + return info.KeyGridBackgroundWidth, info.KeyGridBackgroundHeight +} + +func (info StreamDeckInfoV1) SplitBackgroundImage(background image.Image, handlerType HandlerType) []image.Image { + var frameArr []image.Image + + if handlerType == KEY { + for keyIndex := range info.Cols * info.Rows { + keyX := keyIndex % info.Cols + keyY := int(math.Floor(float64(keyIndex) / float64(info.Cols))) + + x0, y0 := keyX*(info.IconSize+info.PaddingX), keyY*(info.IconSize+info.PaddingY) + x1, y1 := keyX*(info.IconSize+info.PaddingX)+info.IconSize, keyY*(info.IconSize+info.PaddingY)+info.IconSize + + frameArr = append(frameArr, SubImage(background, x0, y0, x1, y1)) + } + } else { + for lcdIndex := range info.LcdCols { + x0, y0 := info.LcdWidth*lcdIndex, 0 + x1, y1 := info.LcdWidth*(lcdIndex+1), info.LcdHeight + + subImage := SubImage(background, x0, y0, x1, y1) + + frameArr = append(frameArr, subImage) + } + } + + return frameArr } type ObsConnectionInfoV2 struct { diff --git a/vendor/github.com/unix-streamdeck/api/v2/handler.go b/vendor/github.com/unix-streamdeck/api/v2/handler.go index 40db4bf..9e45be2 100644 --- a/vendor/github.com/unix-streamdeck/api/v2/handler.go +++ b/vendor/github.com/unix-streamdeck/api/v2/handler.go @@ -14,41 +14,30 @@ type VisualHandler interface { type InputHandler interface { Handler + Input(fields map[string]any, handlerType HandlerType, info StreamDeckInfoV1, event InputEvent) } -type IconHandler interface { +type ForegroundHandler interface { VisualHandler - Start(key KeyConfigV3, info StreamDeckInfoV1, callback func(image image.Image)) + Start(fields map[string]any, handlerType HandlerType, info StreamDeckInfoV1, callback func(image image.Image)) } -type KeyHandler interface { - InputHandler - Key(key KeyConfigV3, info StreamDeckInfoV1) -} - -type LcdHandler interface { +type CombinedHandler interface { VisualHandler - Start(key KnobConfigV3, info StreamDeckInfoV1, callback func(image image.Image)) -} - -type KnobOrTouchHandler interface { InputHandler - Input(key KnobConfigV3, info StreamDeckInfoV1, event InputEvent) } type BackgroundHandler interface { - VisualHandler - Start(fields map[string]any, info StreamDeckInfoV1, callback func(images []image.Image)) - StartIndividual(fields map[string]any, info StreamDeckInfoV1, callback func(img image.Image)) + ForegroundHandler + StartGrid(fields map[string]any, handlerType HandlerType, info StreamDeckInfoV1, callback func(images []image.Image)) } -type KeyGridBackgroundHandler interface { - BackgroundHandler -} +type HandlerType uint8 -type TouchPanelBackgroundHandler interface { - BackgroundHandler -} +const ( + LCD HandlerType = iota + KEY +) type InputEventType uint8 @@ -58,6 +47,9 @@ const ( KNOB_PRESS SCREEN_SHORT_TAP SCREEN_LONG_TAP + SCREEN_SWIPE + KEY_PRESS + KEY_RELEASE ) type InputEvent struct { diff --git a/vendor/github.com/unix-streamdeck/api/v2/key.go b/vendor/github.com/unix-streamdeck/api/v2/key.go index f515095..ddae2fd 100644 --- a/vendor/github.com/unix-streamdeck/api/v2/key.go +++ b/vendor/github.com/unix-streamdeck/api/v2/key.go @@ -6,21 +6,21 @@ type KeyBackgrounder interface { GetKeyBackground() string GetKeyBackgroundBuff() image.Image SetKeyBackgroundBuff(img image.Image) - GetKeyBackgroundHandler() KeyGridBackgroundHandler - SetKeyBackgroundHandler(handler KeyGridBackgroundHandler) + GetKeyBackgroundHandler() BackgroundHandler + SetKeyBackgroundHandler(handler BackgroundHandler) GetKeyBackgroundHandlerFields() map[string]any } type KeyV3 struct { - Application map[string]*KeyConfigV3 `json:"application,omitempty"` - ActiveBuff image.Image `json:"-"` - ActiveIconHandlerStruct *IconHandler `json:"-"` - ActiveKeyHandlerStruct *KeyHandler `json:"-"` - ActiveApplication string `json:"-"` - KeyBackground string `json:"background"` - KeyBackgroundBuff image.Image `json:"-"` - KeyBackgroundHandler KeyGridBackgroundHandler `json:"-"` - KeyBackgroundHandlerFields map[string]any `json:"key_background_handler_fields"` + Application map[string]*KeyConfigV3 `json:"application,omitempty"` + ActiveBuff image.Image `json:"-"` + ActiveIconHandlerStruct *ForegroundHandler `json:"-"` + ActiveKeyHandlerStruct *InputHandler `json:"-"` + ActiveApplication string `json:"-"` + KeyBackground string `json:"background"` + KeyBackgroundBuff image.Image `json:"-"` + KeyBackgroundHandler BackgroundHandler `json:"-"` + KeyBackgroundHandlerFields map[string]any `json:"key_background_handler_fields"` } func (k *KeyV3) GetKeyBackgroundHandlerFields() map[string]any { @@ -39,41 +39,92 @@ func (k *KeyV3) SetKeyBackgroundBuff(img image.Image) { k.KeyBackgroundBuff = img } -func (k *KeyV3) SetKeyBackgroundHandler(handler KeyGridBackgroundHandler) { +func (k *KeyV3) SetKeyBackgroundHandler(handler BackgroundHandler) { k.KeyBackgroundHandler = handler } -func (k *KeyV3) GetKeyBackgroundHandler() KeyGridBackgroundHandler { +func (k *KeyV3) GetKeyBackgroundHandler() BackgroundHandler { return k.KeyBackgroundHandler } type KeyConfigV3 struct { - Icon string `json:"icon,omitempty"` - SwitchPage int `json:"switch_page,omitempty"` - Text string `json:"text,omitempty"` - TextSize int `json:"text_size,omitempty"` - TextAlignment string `json:"text_alignment,omitempty"` - FontFace string `json:"font_face,omitempty"` - TextColour string `json:"text_colour,omitempty"` - Keybind string `json:"keybind,omitempty"` - Command string `json:"command,omitempty"` - Brightness int `json:"brightness,omitempty"` - Url string `json:"url,omitempty"` - KeyHold int `json:"key_hold,omitempty"` - ObsCommand string `json:"obs_command,omitempty"` - ObsCommandParams map[string]string `json:"obs_command_params,omitempty"` - IconHandler string `json:"icon_handler,omitempty"` - KeyHandler string `json:"key_handler,omitempty"` - IconHandlerFields map[string]any `json:"icon_handler_fields,omitempty"` - KeyHandlerFields map[string]any `json:"key_handler_fields,omitempty"` - SharedHandlerFields map[string]any `json:"shared_handler_fields,omitempty"` - IconHandlerStruct IconHandler `json:"-"` - KeyHandlerStruct KeyHandler `json:"-"` - SharedState map[string]any `json:"-"` - KeyBackground string `json:"background"` - KeyBackgroundBuff image.Image `json:"-"` - KeyBackgroundHandler KeyGridBackgroundHandler `json:"-"` - KeyBackgroundHandlerFields map[string]any `json:"key_background_handler_fields"` + Icon string `json:"icon,omitempty"` + Text string `json:"text,omitempty"` + TextSize int `json:"text_size,omitempty"` + TextAlignment VerticalAlignment `json:"text_alignment,omitempty"` + FontFace string `json:"font_face,omitempty"` + TextColour string `json:"text_colour,omitempty"` + SwitchPage int `json:"switch_page,omitempty"` + Keybind string `json:"keybind,omitempty"` + Command string `json:"command,omitempty"` + Brightness int `json:"brightness,omitempty"` + Url string `json:"url,omitempty"` + KeyHold int `json:"key_hold,omitempty"` + ObsCommand string `json:"obs_command,omitempty"` + ObsCommandParams map[string]string `json:"obs_command_params,omitempty"` + IconHandler string `json:"icon_handler,omitempty"` + KeyHandler string `json:"key_handler,omitempty"` + IconHandlerFields map[string]any `json:"icon_handler_fields,omitempty"` + KeyHandlerFields map[string]any `json:"key_handler_fields,omitempty"` + SharedHandlerFields map[string]any `json:"shared_handler_fields,omitempty"` + IconHandlerStruct ForegroundHandler `json:"-"` + KeyHandlerStruct InputHandler `json:"-"` + KeyBackground string `json:"background"` + KeyBackgroundBuff image.Image `json:"-"` + KeyBackgroundHandler BackgroundHandler `json:"-"` + KeyBackgroundHandlerFields map[string]any `json:"key_background_handler_fields"` +} + +func (kc *KeyConfigV3) GetIcon() string { + return kc.Icon +} + +func (kc *KeyConfigV3) GetText() string { + return kc.Text +} + +func (kc *KeyConfigV3) GetTextSize() int { + return kc.TextSize +} + +func (kc *KeyConfigV3) GetTextAlignment() VerticalAlignment { + return kc.TextAlignment +} + +func (kc *KeyConfigV3) GetFontFace() string { + return kc.FontFace +} + +func (kc *KeyConfigV3) GetTextColour() string { + return kc.TextColour +} + +func (kc *KeyConfigV3) GetSwitchPage() int { + return kc.SwitchPage +} + +func (kc *KeyConfigV3) GetKeyBind() string { + return kc.Keybind +} + +func (kc *KeyConfigV3) GetCommand() string { + return kc.Command +} + +func (kc *KeyConfigV3) GetBrightness() int { + return kc.Brightness +} + +func (kc *KeyConfigV3) GetUrl() string { + return kc.Url +} + +func (kc *KeyConfigV3) GetObsCommand() string { + return kc.ObsCommand +} + +func (kc *KeyConfigV3) GetObsCommandParams() map[string]string { + return kc.ObsCommandParams } func (kc *KeyConfigV3) GetKeyBackgroundHandlerFields() map[string]any { @@ -92,10 +143,10 @@ func (kc *KeyConfigV3) SetKeyBackgroundBuff(img image.Image) { kc.KeyBackgroundBuff = img } -func (kc *KeyConfigV3) SetKeyBackgroundHandler(handler KeyGridBackgroundHandler) { +func (kc *KeyConfigV3) SetKeyBackgroundHandler(handler BackgroundHandler) { kc.KeyBackgroundHandler = handler } -func (kc *KeyConfigV3) GetKeyBackgroundHandler() KeyGridBackgroundHandler { +func (kc *KeyConfigV3) GetKeyBackgroundHandler() BackgroundHandler { return kc.KeyBackgroundHandler } diff --git a/vendor/github.com/unix-streamdeck/api/v2/lcdknob.go b/vendor/github.com/unix-streamdeck/api/v2/lcdknob.go index 049e030..95d9f35 100644 --- a/vendor/github.com/unix-streamdeck/api/v2/lcdknob.go +++ b/vendor/github.com/unix-streamdeck/api/v2/lcdknob.go @@ -6,30 +6,30 @@ type LcdSegmentBackgrounder interface { GetTouchPanelBackground() string GetTouchPanelBackgroundBuff() image.Image SetTouchPanelBackgroundBuff(img image.Image) - GetTouchPanelBackgroundHandler() TouchPanelBackgroundHandler - SetTouchPanelBackgroundHandler(handler TouchPanelBackgroundHandler) + GetTouchPanelBackgroundHandler() ForegroundHandler + SetTouchPanelBackgroundHandler(handler ForegroundHandler) GetTouchPanelBackgroundHandlerFields() map[string]any } type KnobV3 struct { - Application map[string]*KnobConfigV3 `json:"application,omitempty"` - ActiveBuff image.Image `json:"-"` - ActiveApplication string `json:"-"` - TouchPanelBackground string `json:"touch_panel_background"` - TouchPanelBackgroundBuff image.Image `json:"-"` - TouchPanelBackgroundHandler TouchPanelBackgroundHandler `json:"-"` - TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` + Application map[string]*KnobConfigV3 `json:"application,omitempty"` + ActiveBuff image.Image `json:"-"` + ActiveApplication string `json:"-"` + TouchPanelBackground string `json:"touch_panel_background"` + TouchPanelBackgroundBuff image.Image `json:"-"` + TouchPanelBackgroundHandler ForegroundHandler `json:"-"` + TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` } func (k *KnobV3) GetTouchPanelBackgroundHandlerFields() map[string]any { return k.TouchPanelBackgroundHandlerFields } -func (k *KnobV3) SetTouchPanelBackgroundHandler(handler TouchPanelBackgroundHandler) { +func (k *KnobV3) SetTouchPanelBackgroundHandler(handler ForegroundHandler) { k.TouchPanelBackgroundHandler = handler } -func (k *KnobV3) GetTouchPanelBackgroundHandler() TouchPanelBackgroundHandler { +func (k *KnobV3) GetTouchPanelBackgroundHandler() ForegroundHandler { return k.TouchPanelBackgroundHandler } @@ -55,39 +55,90 @@ type KnobActionV3 struct { ObsCommandParams map[string]string `json:"obs_command_params,omitempty"` } +func (k *KnobActionV3) GetSwitchPage() int { + return k.SwitchPage +} + +func (k *KnobActionV3) GetKeyBind() string { + return k.Keybind +} + +func (k *KnobActionV3) GetCommand() string { + return k.Command +} + +func (k *KnobActionV3) GetBrightness() int { + return k.Brightness +} + +func (k *KnobActionV3) GetUrl() string { + return k.Url +} + +func (k *KnobActionV3) GetObsCommand() string { + return k.ObsCommand +} + +func (k *KnobActionV3) GetObsCommandParams() map[string]string { + return k.ObsCommandParams +} + type KnobConfigV3 struct { - Icon string `json:"icon,omitempty"` - Text string `json:"text,omitempty"` - TextSize int `json:"text_size,omitempty"` - TextAlignment string `json:"text_alignment,omitempty"` - FontFace string `json:"font_face,omitempty"` - TextColour string `json:"text_colour,omitempty"` - LcdHandler string `json:"lcd_handler,omitempty"` - KnobOrTouchHandler string `json:"knob_or_touch_handler,omitempty"` - LcdHandlerStruct LcdHandler `json:"-"` - KnobOrTouchHandlerStruct KnobOrTouchHandler `json:"-"` - LcdHandlerFields map[string]any `json:"lcd_handler_fields,omitempty"` - KnobOrTouchHandlerFields map[string]any `json:"knob_or_touch_handler_fields,omitempty"` - SharedHandlerFields map[string]any `json:"shared_handler_fields,omitempty"` - KnobPressAction KnobActionV3 `json:"knob_press_action,omitempty"` - KnobTurnUpAction KnobActionV3 `json:"knob_turn_up_action,omitempty"` - KnobTurnDownAction KnobActionV3 `json:"knob_turn_down_action,omitempty"` - SharedState map[string]any `json:"-"` - TouchPanelBackground string `json:"touch_panel_background"` - TouchPanelBackgroundBuff image.Image `json:"-"` - TouchPanelBackgroundHandler TouchPanelBackgroundHandler `json:"-"` - TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` + Icon string `json:"icon,omitempty"` + Text string `json:"text,omitempty"` + TextSize int `json:"text_size,omitempty"` + TextAlignment VerticalAlignment `json:"text_alignment,omitempty"` + FontFace string `json:"font_face,omitempty"` + TextColour string `json:"text_colour,omitempty"` + LcdHandler string `json:"lcd_handler,omitempty"` + KnobOrTouchHandler string `json:"knob_or_touch_handler,omitempty"` + LcdHandlerStruct ForegroundHandler `json:"-"` + KnobOrTouchHandlerStruct InputHandler `json:"-"` + LcdHandlerFields map[string]any `json:"lcd_handler_fields,omitempty"` + KnobOrTouchHandlerFields map[string]any `json:"knob_or_touch_handler_fields,omitempty"` + SharedHandlerFields map[string]any `json:"shared_handler_fields,omitempty"` + KnobPressAction KnobActionV3 `json:"knob_press_action,omitempty"` + KnobTurnUpAction KnobActionV3 `json:"knob_turn_up_action,omitempty"` + KnobTurnDownAction KnobActionV3 `json:"knob_turn_down_action,omitempty"` + TouchPanelBackground string `json:"touch_panel_background"` + TouchPanelBackgroundBuff image.Image `json:"-"` + TouchPanelBackgroundHandler ForegroundHandler `json:"-"` + TouchPanelBackgroundHandlerFields map[string]any `json:"touch_panel_background_handler_fields"` +} + +func (kc *KnobConfigV3) GetIcon() string { + return kc.Icon +} + +func (kc *KnobConfigV3) GetText() string { + return kc.Text +} + +func (kc *KnobConfigV3) GetTextSize() int { + return kc.TextSize +} + +func (kc *KnobConfigV3) GetTextAlignment() VerticalAlignment { + return kc.TextAlignment +} + +func (kc *KnobConfigV3) GetFontFace() string { + return kc.FontFace +} + +func (kc *KnobConfigV3) GetTextColour() string { + return kc.TextColour } func (kc *KnobConfigV3) GetTouchPanelBackgroundHandlerFields() map[string]any { return kc.TouchPanelBackgroundHandlerFields } -func (kc *KnobConfigV3) SetTouchPanelBackgroundHandler(handler TouchPanelBackgroundHandler) { +func (kc *KnobConfigV3) SetTouchPanelBackgroundHandler(handler ForegroundHandler) { kc.TouchPanelBackgroundHandler = handler } -func (kc *KnobConfigV3) GetTouchPanelBackgroundHandler() TouchPanelBackgroundHandler { +func (kc *KnobConfigV3) GetTouchPanelBackgroundHandler() ForegroundHandler { return kc.TouchPanelBackgroundHandler } diff --git a/vendor/github.com/unix-streamdeck/api/v2/obj.go b/vendor/github.com/unix-streamdeck/api/v2/obj.go deleted file mode 100644 index 303758d..0000000 --- a/vendor/github.com/unix-streamdeck/api/v2/obj.go +++ /dev/null @@ -1,47 +0,0 @@ -package api - -import "image" - -type StreamDeckInfo struct { - Cols int `json:"cols,omitempty"` - Rows int `json:"rows,omitempty"` - IconSize int `json:"icon_size,omitempty"` - Page int `json:"page"` - Serial string `json:"serial,omitempty"` -} - -type Page []Key - -type Deck struct { - Serial string `json:"serial"` - Pages []Page `json:"pages"` -} - -type Config struct { - Modules []string `json:"modules,omitempty"` - Decks []Deck `json:"decks"` -} - -type DepracatedConfig struct { - Modules []string `json:"modules,omitempty"` - Pages []Page `json:"pages"` -} - -type Key struct { - Icon string `json:"icon,omitempty"` - SwitchPage int `json:"switch_page,omitempty"` - Text string `json:"text,omitempty"` - TextSize int `json:"text_size,omitempty"` - TextAlignment string `json:"text_alignment,omitempty"` - Keybind string `json:"keybind,omitempty"` - Command string `json:"command,omitempty"` - Brightness int `json:"brightness,omitempty"` - Url string `json:"url,omitempty"` - IconHandler string `json:"icon_handler,omitempty"` - KeyHandler string `json:"key_handler,omitempty"` - IconHandlerFields map[string]string `json:"icon_handler_fields,omitempty"` - KeyHandlerFields map[string]string `json:"key_handler_fields,omitempty"` - Buff image.Image `json:"-"` - IconHandlerStruct IconHandler `json:"-"` - KeyHandlerStruct KeyHandler `json:"-"` -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4c4d665..810ab94 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -67,7 +67,7 @@ github.com/tklauser/go-sysconf # github.com/tklauser/numcpus v0.11.0 ## explicit; go 1.24.0 github.com/tklauser/numcpus -# github.com/unix-streamdeck/api/v2 v2.0.15 +# github.com/unix-streamdeck/api/v2 v2.0.16 ## explicit; go 1.25.7 github.com/unix-streamdeck/api/v2 # github.com/unix-streamdeck/driver v0.0.0-20260313153150-8a1327d02063