diff --git a/dbus.go b/dbus.go index 4efccf6..4c7da8e 100644 --- a/dbus.go +++ b/dbus.go @@ -4,19 +4,16 @@ import ( "encoding/json" "errors" "github.com/godbus/dbus/v5" + "github.com/unix-streamdeck/api" "log" ) - var conn *dbus.Conn -var s *StreamDeckDBus +var sDbus *StreamDeckDBus +var sDInfo api.StreamDeckInfo type StreamDeckDBus struct { - Cols int `json:"cols,omitempty"` - Rows int `json:"rows,omitempty"` - IconSize int `json:"icon_size,omitempty"` - Page int `json:"page"` } func (s StreamDeckDBus) GetDeckInfo() (string, *dbus.Error) { @@ -43,8 +40,8 @@ func (StreamDeckDBus) ReloadConfig() *dbus.Error { return nil } -func (StreamDeckDBus) SetPage(page int) *dbus.Error { - SetPage(config, page, dev) +func (StreamDeckDBus) SetPage(page int) *dbus.Error { + SetPage(config, page) return nil } @@ -73,13 +70,11 @@ func InitDBUS() error { } defer conn.Close() - s = &StreamDeckDBus{ - Cols: int(dev.Columns), - Rows: int(dev.Rows), - IconSize: int(dev.Pixels), + sDbus = &StreamDeckDBus{} + sDInfo = api.StreamDeckInfo{ Page: p, } - conn.ExportAll(s, "/com/unixstreamdeck/streamdeckd", "com.unixstreamdeck.streamdeckd") + conn.ExportAll(sDbus, "/com/unixstreamdeck/streamdeckd", "com.unixstreamdeck.streamdeckd") reply, err := conn.RequestName("com.unixstreamdeck.streamdeckd", dbus.NameFlagDoNotQueue) if err != nil { @@ -89,14 +84,14 @@ func InitDBUS() error { if reply != dbus.RequestNameReplyPrimaryOwner { return errors.New("DBus: Name already taken") } - select {} + select {} } func EmitPage(page int) { if conn != nil { conn.Emit("/com/unixstreamdeck/streamdeckd", "com.unixstreamdeck.streamdeckd.Page", page) } - if s != nil { - s.Page = page + if sDbus != nil { + sDInfo.Page = page } -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index 0d6bdc3..b5898cf 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,8 @@ require ( github.com/godbus/dbus/v5 v5.0.4-0.20200513180336-df5ef3eb7cca github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/unix-streamdeck/api v0.0.0-20200818180846-6942d99617b2 + github.com/unix-streamdeck/api v0.0.0-20200828000516-c734a9f6cec5 github.com/unix-streamdeck/driver v0.0.0-20200817173808-cdaf123c076b golang.org/x/image v0.0.0-20200119044424-58c23975cae1 + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 ) diff --git a/go.sum b/go.sum index c887392..5dc5f37 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/unix-streamdeck/api v0.0.0-20200818180846-6942d99617b2 h1:du+OoTUOp1mf/VRdQ8xQMWdR6JrOGbZvDG1nb9BBxHM= -github.com/unix-streamdeck/api v0.0.0-20200818180846-6942d99617b2/go.mod h1:rweAXRgCWdCACCuVhmleidq7HnJEO38zBGDe8uQqZ0w= +github.com/unix-streamdeck/api v0.0.0-20200828000516-c734a9f6cec5 h1:8WlgvTJMHvfxqmQYyTbfM9TL/BYIm/WfrbDhyA8ihQo= +github.com/unix-streamdeck/api v0.0.0-20200828000516-c734a9f6cec5/go.mod h1:rweAXRgCWdCACCuVhmleidq7HnJEO38zBGDe8uQqZ0w= github.com/unix-streamdeck/driver v0.0.0-20200817173808-cdaf123c076b h1:27gVti9+OevmBC2BnWlKC0dQ0eiIHh7PvYTWxt4vb6A= github.com/unix-streamdeck/driver v0.0.0-20200817173808-cdaf123c076b/go.mod h1:i3Eg6kJBslgUk2VIPJ3Cclta2fpV1KJrOnOnR8gnVKY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -115,6 +115,7 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/handlers/counter.go b/handlers/counter.go index d1e543e..a0c1e59 100644 --- a/handlers/counter.go +++ b/handlers/counter.go @@ -3,12 +3,16 @@ package handlers import ( "github.com/fogleman/gg" "github.com/unix-streamdeck/api" - "github.com/unix-streamdeck/driver" "golang.org/x/image/font/inconsolata" + "image" "strconv" + "time" ) -func (c *CounterIconHandler) Icon(page int, index int, key *api.Key, dev streamdeck.Device) { +func (c *CounterIconHandler) Start(_ api.Key, _ api.StreamDeckInfo, callback func(image image.Image)) { + if c.Callback == nil { + c.Callback = callback + } if c.Running { img := gg.NewContext(72, 72) img.SetRGB(0, 0, 0) @@ -18,22 +22,32 @@ func (c *CounterIconHandler) Icon(page int, index int, key *api.Key, dev streamd Count := strconv.Itoa(c.Count) img.DrawStringAnchored(Count, 72/2, 72/2, 0.5, 0.5) img.Clip() - c.OnSetImage(img.Image(), index, page, dev) - key.Buff = img.Image() + callback(img.Image()) + time.Sleep(250 * time.Millisecond) } } +func (c *CounterIconHandler) IsRunning() bool { + return c.Running +} + +func (c *CounterIconHandler) SetRunning(running bool) { + c.Running = running +} + func (c CounterIconHandler) Stop() { c.Running = false } type CounterKeyHandler struct{} -func (CounterKeyHandler) Key(page int, index int, key *api.Key, dev streamdeck.Device) { +func (CounterKeyHandler) Key(key api.Key, info api.StreamDeckInfo) { if key.IconHandler != "Counter" { return } handler := key.IconHandlerStruct.(*CounterIconHandler) handler.Count += 1 - handler.Icon(page, index, key, dev) + if handler.Callback != nil { + handler.Start(key, info, handler.Callback) + } } diff --git a/handlers/gif.go b/handlers/gif.go index c264fef..d0ff4b4 100644 --- a/handlers/gif.go +++ b/handlers/gif.go @@ -1,16 +1,16 @@ package handlers import ( - "github.com/unix-streamdeck/api" - "github.com/unix-streamdeck/driver" "github.com/nfnt/resize" + "github.com/unix-streamdeck/api" + "image" "image/gif" "log" "os" "time" ) -func (s *GifIconHandler) Icon(page int, index int, key *api.Key, dev streamdeck.Device) { +func (s *GifIconHandler) Start(key api.Key, info api.StreamDeckInfo, callback func(image image.Image)) { s.Running = true f, err := os.Open(key.Icon) if err != nil { @@ -18,24 +18,39 @@ func (s *GifIconHandler) Icon(page int, index int, key *api.Key, dev streamdeck. return } gifs, err := gif.DecodeAll(f) + if err != nil { + log.Println(err) + return + } timeDelay := gifs.Delay[0] - gifIndex := 0 - go loop(gifs, gifIndex, timeDelay, page, index, dev, key, s) + frames := make([]image.Image, len(gifs.Image)) + for i, frame := range gifs.Image { + frames[i] = resize.Resize(uint(info.IconSize), uint(info.IconSize), frame, resize.Lanczos3) + } + go loop(frames, timeDelay, callback, s) +} + +func (s *GifIconHandler) IsRunning() bool { + return s.Running +} + +func (s *GifIconHandler) SetRunning(running bool) { + s.Running = running } func (s *GifIconHandler) Stop() { s.Running = false } -func loop(gifs *gif.GIF, gifIndex int, timeDelay int, page int, index int, dev streamdeck.Device, key *api.Key, s *GifIconHandler) { +func loop(frames []image.Image, timeDelay int, callback func(image image.Image), s *GifIconHandler) { + gifIndex := 0 for s.Running { - img := resize.Resize(dev.Pixels, dev.Pixels, gifs.Image[gifIndex], resize.Lanczos3) - s.OnSetImage(img, index, page, dev) - key.Buff = img + img := frames[gifIndex] + callback(img) gifIndex++ - if gifIndex >= len(gifs.Image) { + if gifIndex >= len(frames) { gifIndex = 0 } time.Sleep(time.Duration(timeDelay * 10000000)) } -} \ No newline at end of file +} diff --git a/handlers/handlers.go b/handlers/handlers.go index 567ec13..d7a3f22 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -1,23 +1,17 @@ package handlers -import ( - "image" - "github.com/unix-streamdeck/driver" -) +import "image" type CounterIconHandler struct { - Count int - Running bool - OnSetImage func(img image.Image, i int, page int, dev streamdeck.Device) + Count int + Running bool + Callback func(image image.Image) } type GifIconHandler struct { Running bool - OnSetImage func(img image.Image, i int, page int, dev streamdeck.Device) } -type TimeIconHandler struct{ +type TimeIconHandler struct { Running bool - OnSetImage func(img image.Image, i int, page int, dev streamdeck.Device) } - diff --git a/handlers/time.go b/handlers/time.go index 81e9180..a7595eb 100644 --- a/handlers/time.go +++ b/handlers/time.go @@ -3,43 +3,41 @@ package handlers import ( "github.com/fogleman/gg" "github.com/unix-streamdeck/api" - "github.com/unix-streamdeck/driver" "golang.org/x/image/font/inconsolata" - "strconv" + "image" "time" ) -func (t *TimeIconHandler) Icon(page int, index int, key *api.Key, dev streamdeck.Device) { +func (t *TimeIconHandler) Start(_ api.Key, info api.StreamDeckInfo, callback func(image image.Image)) { t.Running = true - go timeLoop(page, index, dev, key, t) + go timeLoop(info, callback, t) +} + +func (t *TimeIconHandler) IsRunning() bool { + return t.Running +} + +func (t *TimeIconHandler) SetRunning(running bool) { + t.Running = running } func (t *TimeIconHandler) Stop() { t.Running = false } -func timeLoop(page int, index int, dev streamdeck.Device, key *api.Key, handler *TimeIconHandler) { +func timeLoop(info api.StreamDeckInfo, callback func(image image.Image), handler *TimeIconHandler) { for handler.Running { - img := gg.NewContext(72, 72) + img := gg.NewContext(info.IconSize, info.IconSize) img.SetRGB(0, 0, 0) img.Clear() img.SetRGB(1, 1, 1) img.SetFontFace(inconsolata.Regular8x16) t := time.Now() tString := t.Format("15:04:05") - img.DrawStringAnchored(tString, 72/2, 72/2, 0.5, 0.5) + img.DrawStringAnchored(tString, float64(info.IconSize)/2, float64(info.IconSize)/2, 0.5, 0.5) img.Clip() - handler.OnSetImage(img.Image(), index, page, dev) - key.Buff = img.Image() + callback(img.Image()) time.Sleep(time.Second) } -} - -func zeroes(i int) (string) { - if i < 10 { - return "0" + strconv.Itoa(i) - } else { - return strconv.Itoa(i) - } } \ No newline at end of file diff --git a/interface.go b/interface.go index 21b08c0..b977f07 100644 --- a/interface.go +++ b/interface.go @@ -1,20 +1,23 @@ package main import ( + "context" "github.com/fogleman/gg" "github.com/nfnt/resize" "github.com/unix-streamdeck/api" - "github.com/unix-streamdeck/driver" - "golang.org/x/image/font/inconsolata" "github.com/unix-streamdeck/streamdeckd/handlers" + "golang.org/x/image/font/inconsolata" + "golang.org/x/sync/semaphore" "image" "image/color" "image/draw" "log" "os" + "strings" ) var p int +var sem = semaphore.NewWeighted(int64(1)) func LoadImage(path string) (image.Image, error) { f, err := os.Open(path) @@ -35,13 +38,27 @@ func ResizeImage(img image.Image) image.Image { return resize.Resize(dev.Pixels, dev.Pixels, img, resize.Lanczos3) } -func SetImage(img image.Image, i int, page int, dev streamdeck.Device) { - if p == page { - dev.SetImage(uint8(i), img) +func SetImage(img image.Image, i int, page int) { + ctx := context.Background() + err := sem.Acquire(ctx, 1) + if err != nil { + log.Println(err) + return + } + defer sem.Release(1) + if p == page && isOpen { + err := dev.SetImage(uint8(i), img) + if err != nil { + if strings.Contains(err.Error(), "hidapi") { + disconnect() + } else { + log.Println(err) + } + } } } -func SetKey(currentKey *api.Key, i int) { +func SetKeyImage(currentKey *api.Key, i int) { if currentKey.Buff == nil { if currentKey.Icon == "" { img := image.NewRGBA(image.Rect(0, 0, int(dev.Pixels), int(dev.Pixels))) @@ -65,42 +82,56 @@ func SetKey(currentKey *api.Key, i int) { } } if currentKey.Buff != nil { - SetImage(currentKey.Buff, i, p, dev) + SetImage(currentKey.Buff, i, p) } } -func SetPage(config *api.Config, page int, dev streamdeck.Device) { +func SetPage(config *api.Config, page int) { p = page currentPage := config.Pages[page] for i := 0; i < len(currentPage); i++ { currentKey := ¤tPage[i] - if currentKey.Buff == nil { - if currentKey.IconHandler == "" { - SetKey(currentKey, i) + go SetKey(currentKey, i, page) + } + EmitPage(p) +} - } else if currentKey.IconHandlerStruct == nil { - var handler api.IconHandler - if currentKey.IconHandler == "Gif" { - handler = &handlers.GifIconHandler{Running:true, OnSetImage: SetImage} - } else if currentKey.IconHandler == "Counter" { - handler = &handlers.CounterIconHandler{Count:0, Running: true, OnSetImage: SetImage} - } else if currentKey.IconHandler == "Time" { - handler = &handlers.TimeIconHandler{Running:true, OnSetImage: SetImage} - } - if handler == nil { - continue - } - handler.Icon(page, i, currentKey, dev) - currentKey.IconHandlerStruct = handler +func SetKey(currentKey *api.Key, i int, page int) { + if currentKey.Buff == nil { + if currentKey.IconHandler == "" { + SetKeyImage(currentKey, i) + + } else if currentKey.IconHandlerStruct == nil { + var handler api.IconHandler + if currentKey.IconHandler == "Gif" { + handler = &handlers.GifIconHandler{Running:true} + } else if currentKey.IconHandler == "Counter" { + handler = &handlers.CounterIconHandler{Count:0, Running: true} + } else if currentKey.IconHandler == "Time" { + handler = &handlers.TimeIconHandler{Running:true} } - } else { - SetImage(currentKey.Buff, i, p, dev) + if handler == nil { + return + } + handler.Start(*currentKey, sDInfo, func(image image.Image) { + SetImage(image, i, page) + currentKey.Buff = image + }) + currentKey.IconHandlerStruct = handler } + } else { + SetImage(currentKey.Buff, i, p) + } + if currentKey.IconHandlerStruct != nil && !currentKey.IconHandlerStruct.IsRunning() { + currentKey.IconHandlerStruct.SetRunning(true) + currentKey.IconHandlerStruct.Start(*currentKey, sDInfo, func(image image.Image) { + SetImage(image, i, page) + currentKey.Buff = image + }) } - EmitPage(p) } -func HandleInput(key *api.Key, page int, index int, dev streamdeck.Device) { +func HandleInput(key *api.Key, page int) { if key.Command != "" { runCommand(key.Command) } @@ -109,10 +140,13 @@ func HandleInput(key *api.Key, page int, index int, dev streamdeck.Device) { } if key.SwitchPage != 0 { page = key.SwitchPage - 1 - SetPage(config, page, dev) + SetPage(config, page) } if key.Brightness != 0 { - _ = dev.SetBrightness(uint8(key.Brightness)) + err := dev.SetBrightness(uint8(key.Brightness)) + if err != nil { + log.Println(err) + } } if key.Url != "" { runCommand("xdg-open " + key.Url) @@ -128,7 +162,7 @@ func HandleInput(key *api.Key, page int, index int, dev streamdeck.Device) { } key.KeyHandlerStruct = handler } - key.KeyHandlerStruct.Key(page, index, key, dev) + key.KeyHandlerStruct.Key(*key, sDInfo) } } @@ -137,19 +171,16 @@ func Listen() { if err != nil { log.Println(err) } - for { + for isOpen { select { case k, ok := <-kch: if !ok { - err = dev.Open() - if err != nil { - log.Println(err) - } - continue + disconnect() + return } if k.Pressed == true { if len(config.Pages)-1 >= p && len(config.Pages[p])-1 >= int(k.Index) { - HandleInput(&config.Pages[p][k.Index], p, int(k.Index), dev) + HandleInput(&config.Pages[p][k.Index], p) } } } diff --git a/main.go b/main.go index 6e8a72d..54c99da 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,13 @@ package main import ( + "context" "encoding/json" - "github.com/unix-streamdeck/driver" + "errors" + "fmt" "github.com/unix-streamdeck/api" + "github.com/unix-streamdeck/driver" + "golang.org/x/sync/semaphore" _ "image/gif" _ "image/jpeg" _ "image/png" @@ -17,30 +21,21 @@ import ( var dev streamdeck.Device var config *api.Config -var configPath = os.Getenv("HOME") + "/.streamdeck-config.json" +var configPath = os.Getenv("HOME") + string(os.PathSeparator) + ".streamdeck-config.json" +var isOpen = false +var disconnectSem = semaphore.NewWeighted(1) +var connectSem = semaphore.NewWeighted(1) var basicConfig = api.Config{ Pages: []api.Page{ { - api.Key{ - }, + api.Key{}, }, }, } func main() { - d, err := streamdeck.Devices() - if err != nil { - log.Println(err) - } - if len(d) == 0 { - log.Println("No Stream Deck devices found.") - } - dev = d[0] - err = dev.Open() - if err != nil { - log.Println(err) - } + var err error config, err = readConfig() if err != nil && !os.IsNotExist(err) { log.Println(err) @@ -63,9 +58,63 @@ func main() { config.Pages = append(config.Pages, api.Page{}) } cleanupHook() - SetPage(config, 0, dev) go InitDBUS() - Listen() + attemptConnection() +} + +func attemptConnection() { + for !isOpen { + _ = openDevice() + if isOpen { + SetPage(config, p) + if sDbus != nil { + sDInfo.IconSize = int(dev.Pixels) + sDInfo.Rows = int(dev.Rows) + sDInfo.Cols = int(dev.Columns) + } + Listen() + } + } +} + +func disconnect() { + ctx := context.Background() + err := disconnectSem.Acquire(ctx, 1) + if err != nil { + return + } + defer disconnectSem.Release(1) + if !isOpen { + return + } + log.Println("Device disconnected") + _ = dev.Close() + isOpen = false + unmountHandlers() +} + +func openDevice() error { + ctx := context.Background() + err := connectSem.Acquire(ctx, 1) + if err != nil { + return err + } + defer connectSem.Release(1) + d, err := streamdeck.Devices() + if err != nil { + return err + } + if len(d) == 0 { + return errors.New("No streamdeck devices found") + } + err = d[0].Open() + if err != nil { + return err + } + dev = d[0] + isOpen = true + fmt.Println("Device (" + dev.Serial + ") connected") + return nil } func readConfig() (*api.Config, error) { @@ -81,7 +130,6 @@ func readConfig() (*api.Config, error) { return &config, nil } - func runCommand(command string) { //args := strings.Split(command, " ") c := exec.Command("/bin/sh", "-c", command) @@ -115,7 +163,7 @@ func SetConfig(configString string) error { if len(config.Pages) == 0 { config.Pages = append(config.Pages, api.Page{}) } - SetPage(config, p, dev) + SetPage(config, p) return nil } @@ -129,7 +177,7 @@ func ReloadConfig() error { if len(config.Pages) == 0 { config.Pages = append(config.Pages, api.Page{}) } - SetPage(config, p, dev) + SetPage(config, p) return nil } @@ -158,11 +206,11 @@ func SaveConfig() error { func unmountHandlers() { for i := range config.Pages { page := config.Pages[i] - for i2 := range page { - key := page[i2] + for i2 := 0; i2 < len(page); i2++ { + key := &page[i2] if key.IconHandlerStruct != nil { key.IconHandlerStruct.Stop() } } } -} \ No newline at end of file +}