Added support for remote0
This commit is contained in:
295
pkg/streamdeck/streamdeck.go
Normal file
295
pkg/streamdeck/streamdeck.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package streamdeck
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// EventDelegate receives callbacks for Stream Deck SDK events
|
||||
type EventDelegate interface {
|
||||
OnConnected(*websocket.Conn)
|
||||
OnWillAppear(*EvWillAppear)
|
||||
OnTitleParametersDidChange(*EvTitleParametersDidChange)
|
||||
OnPropertyInspectorConnected(*EvSendToPlugin)
|
||||
OnSendToPlugin(*EvSendToPlugin)
|
||||
OnApplicationDidLaunch(*EvApplication)
|
||||
OnApplicationDidTerminate(*EvApplication)
|
||||
}
|
||||
|
||||
// StreamDeck SDK APIs
|
||||
type StreamDeck struct {
|
||||
Port string
|
||||
PluginUUID string
|
||||
RegisterEvent string
|
||||
Info string
|
||||
delegate EventDelegate
|
||||
conn *websocket.Conn
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewStreamDeck prepares StreamDeck struct
|
||||
func NewStreamDeck(port, pluginUUID, registerEvent, info string) *StreamDeck {
|
||||
return &StreamDeck{
|
||||
Port: port,
|
||||
PluginUUID: pluginUUID,
|
||||
RegisterEvent: registerEvent,
|
||||
Info: info,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// SetDelegate sets the delegate for receiving Stream Deck SDK event callbacks
|
||||
func (sd *StreamDeck) SetDelegate(ed EventDelegate) {
|
||||
sd.delegate = ed
|
||||
}
|
||||
|
||||
func (sd *StreamDeck) register() error {
|
||||
reg := evRegister{Event: sd.RegisterEvent, UUID: sd.PluginUUID}
|
||||
data, err := json.Marshal(reg)
|
||||
log.Println(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = sd.conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect establishes WebSocket connection to StreamDeck software
|
||||
func (sd *StreamDeck) Connect() error {
|
||||
u := url.URL{Scheme: "ws", Host: fmt.Sprintf("127.0.0.1:%s", sd.Port)}
|
||||
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sd.conn = c
|
||||
|
||||
err = sd.register()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed register: %v", err)
|
||||
}
|
||||
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnConnected(sd.conn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the websocket connection, defer after Connect
|
||||
func (sd *StreamDeck) Close() {
|
||||
sd.conn.Close()
|
||||
}
|
||||
|
||||
func (sd *StreamDeck) onPropertyInspectorMessage(value string, ev *EvSendToPlugin) error {
|
||||
switch value {
|
||||
case "propertyInspectorConnected":
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnPropertyInspectorConnected(ev)
|
||||
}
|
||||
default:
|
||||
log.Printf("Unknown property_inspector value: %s\n", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sd *StreamDeck) onSendToPlugin(ev *EvSendToPlugin) error {
|
||||
payload := make(map[string]*json.RawMessage)
|
||||
err := json.Unmarshal(*ev.Payload, &payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("onSendToPlugin payload unmarshal: %v", err)
|
||||
}
|
||||
if raw, ok := payload["property_inspector"]; ok {
|
||||
var value string
|
||||
err := json.Unmarshal(*raw, &value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("onSendToPlugin unmarshal property_inspector value: %v", err)
|
||||
}
|
||||
err = sd.onPropertyInspectorMessage(value, ev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("onPropertyInspectorMessage: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnSendToPlugin(ev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sd *StreamDeck) spawnMessageReader() {
|
||||
defer close(sd.done)
|
||||
for {
|
||||
_, message, err := sd.conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
return
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
|
||||
var objmap map[string]*json.RawMessage
|
||||
err = json.Unmarshal(message, &objmap)
|
||||
if err != nil {
|
||||
log.Fatal("message unmarshal", err)
|
||||
}
|
||||
var event string
|
||||
err = json.Unmarshal(*objmap["event"], &event)
|
||||
if err != nil {
|
||||
log.Fatal("event unmarshal", err)
|
||||
}
|
||||
switch event {
|
||||
case "willAppear":
|
||||
var ev EvWillAppear
|
||||
err := json.Unmarshal(message, &ev)
|
||||
if err != nil {
|
||||
log.Fatal("willAppear unmarshal", err)
|
||||
}
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnWillAppear(&ev)
|
||||
}
|
||||
case "titleParametersDidChange":
|
||||
var ev EvTitleParametersDidChange
|
||||
err := json.Unmarshal(message, &ev)
|
||||
if err != nil {
|
||||
log.Fatal("titleParametersDidChange unmarshal", err)
|
||||
}
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnTitleParametersDidChange(&ev)
|
||||
}
|
||||
case "sendToPlugin":
|
||||
var ev EvSendToPlugin
|
||||
err := json.Unmarshal(message, &ev)
|
||||
if err != nil {
|
||||
log.Fatal("onSendToPlugin event unmarshal", err)
|
||||
}
|
||||
err = sd.onSendToPlugin(&ev)
|
||||
if err != nil {
|
||||
log.Fatal("onSendToPlugin", err)
|
||||
}
|
||||
case "applicationDidLaunch":
|
||||
var ev EvApplication
|
||||
err := json.Unmarshal(message, &ev)
|
||||
if err != nil {
|
||||
log.Fatal("applicationDidLaunch unmarshal", err)
|
||||
}
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnApplicationDidLaunch(&ev)
|
||||
}
|
||||
case "applicationDidTerminate":
|
||||
var ev EvApplication
|
||||
err := json.Unmarshal(message, &ev)
|
||||
if err != nil {
|
||||
log.Fatal("applicationDidTerminate unmarshal", err)
|
||||
}
|
||||
if sd.delegate != nil {
|
||||
sd.delegate.OnApplicationDidTerminate(&ev)
|
||||
}
|
||||
default:
|
||||
log.Printf("Unknown event: %s\n", event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndWait processes messages and waits until closed
|
||||
func (sd *StreamDeck) ListenAndWait() {
|
||||
go sd.spawnMessageReader()
|
||||
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-sd.done:
|
||||
return
|
||||
case <-interrupt:
|
||||
log.Println("interrupt")
|
||||
|
||||
// Cleanly close the connection by sending a close message and then
|
||||
// waiting (with timeout) for the server to close the connection.
|
||||
err := sd.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
log.Println("write close:", err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-sd.done:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendToPropertyInspector sends a payload to the Property Inspector
|
||||
func (sd *StreamDeck) SendToPropertyInspector(action, context string, payload interface{}) error {
|
||||
event := evSendToPropertyInspector{Action: action, Event: "sendToPropertyInspector",
|
||||
Context: context, Payload: payload}
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sendToPropertyInspector: %v", err)
|
||||
}
|
||||
err = sd.conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setTitle write: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTitle dynamically changes the title displayed by an instance of an action
|
||||
func (sd *StreamDeck) SetTitle(context, title string) error {
|
||||
event := evSetTitle{Event: "setTitle", Context: context, Payload: evSetTitlePayload{
|
||||
Title: title,
|
||||
Target: 0,
|
||||
}}
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setTitle: %v", err)
|
||||
}
|
||||
err = sd.conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setTitle write: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSettings saves persistent data for the action's instance
|
||||
func (sd *StreamDeck) SetSettings(context string, payload interface{}) error {
|
||||
event := evSetSettings{Event: "setSettings", Context: context, Payload: payload}
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setSettings: %v", err)
|
||||
}
|
||||
err = sd.conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setSettings write: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetImage dynamically changes the image displayed by an instance of an action
|
||||
func (sd *StreamDeck) SetImage(context string, bts []byte) error {
|
||||
b64 := base64.StdEncoding.EncodeToString(bts)
|
||||
event := evSetImage{Event: "setImage", Context: context, Payload: evSetImagePayload{
|
||||
Image: fmt.Sprintf("data:image/png;base64, %s", b64),
|
||||
Target: 0,
|
||||
}}
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setImage: %v", err)
|
||||
}
|
||||
err = sd.conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setImage write: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user