Added support for remote0

This commit is contained in:
mikx
2023-10-31 20:16:41 -04:00
commit 5c6e496688
88 changed files with 7833 additions and 0 deletions

View 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
}

124
pkg/streamdeck/types.go Normal file
View File

@@ -0,0 +1,124 @@
package streamdeck
import "encoding/json"
type evRegister struct {
Event string `json:"event"`
UUID string `json:"uuid"`
}
// EvCoordinates is the coordinates structure from events
type EvCoordinates struct {
Column int `json:"column"`
Row int `json:"row"`
}
// EvWillAppearPayload is the Payload structure from the willAppear event
type EvWillAppearPayload struct {
Settings *json.RawMessage `json:"settings"`
Coordinates EvCoordinates `json:"coordinates"`
Device string `json:"device"`
State int `json:"state"`
IsInMultiAction bool `json:"isInMultiAction"`
}
// EvWillAppear is the payload from the willAppear event
type EvWillAppear struct {
Action string `json:"action"`
Event string `json:"event"`
Context string `json:"context"`
Device string `json:"device"`
Payload EvWillAppearPayload `json:"payload"`
}
// EvWillDisappearPayload is the Payload structure from willDisappear event
type EvWillDisappearPayload struct {
EvWillAppearPayload
}
// EvWillDisappear is the payload from the willDisappear event
type EvWillDisappear struct {
EvWillAppear
}
// EvApplicationPayload is the sub-strcture from the EvApplication struct
type EvApplicationPayload struct {
Application string `json:"application"`
}
// EvApplication is the payload from the applicatioDidLaunch/Terminate events
type EvApplication struct {
Payload EvApplicationPayload `json:"payload"`
}
// EvTitleParameters is sub-structure from EvTitleParametersDidChangePayload
type EvTitleParameters struct {
FontFamily string `json:"fontFamily"`
FontSize int `json:"fontSize"`
FontStyle string `json:"fontStyle"`
FontUnderline bool `json:"fontUnderline"`
ShowTitle bool `json:"showTitle"`
TitleAlignment string `json:"titleAlignment"`
TitleColor string `json:"titleColor"`
}
// EvTitleParametersDidChangePayload is the payload structure of EvTitleParametersDidChange
type EvTitleParametersDidChangePayload struct {
Coordinates EvCoordinates `json:"coordinates"`
Settings *json.RawMessage `json:"settings"`
TitleParameters EvTitleParameters `json:"titleParameters"`
Title string `json:"title"`
State int `json:"state"`
}
// EvTitleParametersDidChange is the payload from the titleParametersDidChange event
type EvTitleParametersDidChange struct {
Action string `json:"action"`
Event string `json:"event"`
Context string `json:"context"`
Device string `json:"device"`
Payload EvTitleParametersDidChangePayload `json:"payload"`
}
// EvSendToPlugin is received from the Property Inspector
type EvSendToPlugin struct {
Action string `json:"action"`
Event string `json:"event"`
Context string `json:"context"`
Payload *json.RawMessage `json:"payload"`
}
type evSendToPropertyInspector struct {
Action string `json:"action"`
Event string `json:"event"`
Context string `json:"context"`
Payload interface{} `json:"payload"`
}
type evSetTitlePayload struct {
Title string `json:"title"`
Target int `json:"target"`
}
type evSetTitle struct {
Event string `json:"event"`
Context string `json:"context"`
Payload evSetTitlePayload `json:"payload"`
}
type evSetSettings struct {
Event string `json:"event"`
Context string `json:"context"`
Payload interface{} `json:"payload"`
}
type evSetImagePayload struct {
Image string `json:"image"`
Target int `json:"target"`
}
type evSetImage struct {
Event string `json:"event"`
Context string `json:"context"`
Payload evSetImagePayload `json:"payload"`
}