Added support for remote0
This commit is contained in:
54
internal/app/hwinfostreamdeckplugin/action_manager.go
Normal file
54
internal/app/hwinfostreamdeckplugin/action_manager.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package hwinfostreamdeckplugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type actionManager struct {
|
||||
mux sync.RWMutex
|
||||
actions map[string]*actionData
|
||||
}
|
||||
|
||||
func newActionManager() *actionManager {
|
||||
return &actionManager{actions: make(map[string]*actionData)}
|
||||
}
|
||||
|
||||
func (tm *actionManager) Run(updateTiles func(*actionData)) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
for range ticker.C {
|
||||
tm.mux.RLock()
|
||||
for _, data := range tm.actions {
|
||||
if data.settings.IsValid {
|
||||
updateTiles(data)
|
||||
}
|
||||
}
|
||||
tm.mux.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (tm *actionManager) SetAction(action, context string, settings *actionSettings) {
|
||||
tm.mux.Lock()
|
||||
tm.actions[context] = &actionData{action, context, settings}
|
||||
tm.mux.Unlock()
|
||||
}
|
||||
|
||||
func (tm *actionManager) RemoveAction(context string) {
|
||||
tm.mux.Lock()
|
||||
delete(tm.actions, context)
|
||||
tm.mux.Unlock()
|
||||
}
|
||||
|
||||
func (tm *actionManager) getSettings(context string) (actionSettings, error) {
|
||||
tm.mux.RLock()
|
||||
data, ok := tm.actions[context]
|
||||
tm.mux.RUnlock()
|
||||
if !ok {
|
||||
return actionSettings{}, fmt.Errorf("getSettings invalid key: %s", context)
|
||||
}
|
||||
// return full copy of settings, not reference to stored settings
|
||||
return *data.settings, nil
|
||||
}
|
||||
219
internal/app/hwinfostreamdeckplugin/delegate.go
Normal file
219
internal/app/hwinfostreamdeckplugin/delegate.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package hwinfostreamdeckplugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/shayne/hwinfo-streamdeck/pkg/graph"
|
||||
"github.com/shayne/hwinfo-streamdeck/pkg/streamdeck"
|
||||
)
|
||||
|
||||
const (
|
||||
tileWidth = 72
|
||||
tileHeight = 72
|
||||
)
|
||||
|
||||
// OnConnected event
|
||||
func (p *Plugin) OnConnected(c *websocket.Conn) {
|
||||
log.Println("OnConnected")
|
||||
}
|
||||
|
||||
// OnWillAppear event
|
||||
func (p *Plugin) OnWillAppear(event *streamdeck.EvWillAppear) {
|
||||
var settings actionSettings
|
||||
err := json.Unmarshal(*event.Payload.Settings, &settings)
|
||||
if err != nil {
|
||||
log.Println("OnWillAppear settings unmarshal", err)
|
||||
}
|
||||
tfSize := 10.5
|
||||
vfSize := 10.5
|
||||
var fgColor *color.RGBA
|
||||
var bgColor *color.RGBA
|
||||
var hlColor *color.RGBA
|
||||
var tColor *color.RGBA
|
||||
var vtColor *color.RGBA
|
||||
if settings.TitleFontSize != 0 {
|
||||
tfSize = settings.TitleFontSize
|
||||
}
|
||||
if settings.ValueFontSize != 0 {
|
||||
vfSize = settings.ValueFontSize
|
||||
}
|
||||
if settings.ForegroundColor == "" {
|
||||
fgColor = &color.RGBA{0, 81, 40, 255}
|
||||
} else {
|
||||
fgColor = hexToRGBA(settings.ForegroundColor)
|
||||
}
|
||||
if settings.BackgroundColor == "" {
|
||||
bgColor = &color.RGBA{0, 0, 0, 255}
|
||||
} else {
|
||||
bgColor = hexToRGBA(settings.BackgroundColor)
|
||||
}
|
||||
if settings.HighlightColor == "" {
|
||||
hlColor = &color.RGBA{0, 158, 0, 255}
|
||||
} else {
|
||||
hlColor = hexToRGBA(settings.HighlightColor)
|
||||
}
|
||||
if settings.TitleColor == "" {
|
||||
tColor = &color.RGBA{183, 183, 183, 255}
|
||||
} else {
|
||||
tColor = hexToRGBA(settings.TitleColor)
|
||||
}
|
||||
if settings.ValueTextColor == "" {
|
||||
vtColor = &color.RGBA{255, 255, 255, 255}
|
||||
} else {
|
||||
vtColor = hexToRGBA(settings.ValueTextColor)
|
||||
}
|
||||
g := graph.NewGraph(tileWidth, tileHeight, settings.Min, settings.Max, fgColor, bgColor, hlColor)
|
||||
g.SetLabel(0, "", 19, tColor)
|
||||
g.SetLabelFontSize(0, tfSize)
|
||||
g.SetLabel(1, "", 44, vtColor)
|
||||
g.SetLabelFontSize(1, vfSize)
|
||||
p.graphs[event.Context] = g
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
}
|
||||
|
||||
// OnWillDisappear event
|
||||
func (p *Plugin) OnWillDisappear(event *streamdeck.EvWillDisappear) {
|
||||
var settings actionSettings
|
||||
err := json.Unmarshal(*event.Payload.Settings, &settings)
|
||||
if err != nil {
|
||||
log.Println("OnWillAppear settings unmarshal", err)
|
||||
}
|
||||
delete(p.graphs, event.Context)
|
||||
p.am.RemoveAction(event.Context)
|
||||
}
|
||||
|
||||
// OnApplicationDidLaunch event
|
||||
func (p *Plugin) OnApplicationDidLaunch(event *streamdeck.EvApplication) {
|
||||
p.appLaunched = true
|
||||
}
|
||||
|
||||
// OnApplicationDidTerminate event
|
||||
func (p *Plugin) OnApplicationDidTerminate(event *streamdeck.EvApplication) {
|
||||
p.appLaunched = false
|
||||
}
|
||||
|
||||
// OnTitleParametersDidChange event
|
||||
func (p *Plugin) OnTitleParametersDidChange(event *streamdeck.EvTitleParametersDidChange) {
|
||||
var settings actionSettings
|
||||
err := json.Unmarshal(*event.Payload.Settings, &settings)
|
||||
if err != nil {
|
||||
log.Println("OnWillAppear settings unmarshal", err)
|
||||
}
|
||||
g, ok := p.graphs[event.Context]
|
||||
if !ok {
|
||||
log.Printf("handleSetMax no graph for context: %s\n", event.Context)
|
||||
return
|
||||
}
|
||||
g.SetLabelText(0, event.Payload.Title)
|
||||
if event.Payload.TitleParameters.TitleColor != "" {
|
||||
tClr := hexToRGBA(event.Payload.TitleParameters.TitleColor)
|
||||
g.SetLabelColor(0, tClr)
|
||||
}
|
||||
|
||||
settings.Title = event.Payload.Title
|
||||
settings.TitleColor = event.Payload.TitleParameters.TitleColor
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
log.Printf("handleSetTitle SetSettings: %v\n", err)
|
||||
return
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
}
|
||||
|
||||
// OnPropertyInspectorConnected event
|
||||
func (p *Plugin) OnPropertyInspectorConnected(event *streamdeck.EvSendToPlugin) {
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
log.Println("OnPropertyInspectorConnected getSettings", err)
|
||||
}
|
||||
sensors, err := p.hw.Sensors()
|
||||
if err != nil {
|
||||
log.Println("OnPropertyInspectorConnected Sensors", err)
|
||||
payload := evStatus{Error: true, Message: "HWiNFO Unavailable"}
|
||||
err := p.sd.SendToPropertyInspector(event.Action, event.Context, payload)
|
||||
settings.InErrorState = true
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
log.Printf("OnPropertyInspectorConnected SetSettings: %v\n", err)
|
||||
return
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
if err != nil {
|
||||
log.Println("updateTiles SendToPropertyInspector", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
evsensors := make([]*evSendSensorsPayloadSensor, 0, len(sensors))
|
||||
for _, s := range sensors {
|
||||
evsensors = append(evsensors, &evSendSensorsPayloadSensor{UID: s.ID(), Name: s.Name()})
|
||||
}
|
||||
payload := evSendSensorsPayload{Sensors: evsensors, Settings: &settings}
|
||||
err = p.sd.SendToPropertyInspector(event.Action, event.Context, payload)
|
||||
if err != nil {
|
||||
log.Println("OnPropertyInspectorConnected SendToPropertyInspector", err)
|
||||
}
|
||||
}
|
||||
|
||||
// OnSendToPlugin event
|
||||
func (p *Plugin) OnSendToPlugin(event *streamdeck.EvSendToPlugin) {
|
||||
var payload map[string]*json.RawMessage
|
||||
err := json.Unmarshal(*event.Payload, &payload)
|
||||
if err != nil {
|
||||
log.Println("OnSendToPlugin unmarshal", err)
|
||||
}
|
||||
if data, ok := payload["sdpi_collection"]; ok {
|
||||
sdpi := evSdpiCollection{}
|
||||
err = json.Unmarshal(*data, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("SDPI unmarshal", err)
|
||||
}
|
||||
switch sdpi.Key {
|
||||
case "sensorSelect":
|
||||
err = p.handleSensorSelect(event, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleSensorSelect", err)
|
||||
}
|
||||
case "readingSelect":
|
||||
err = p.handleReadingSelect(event, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleReadingSelect", err)
|
||||
}
|
||||
case "min":
|
||||
err := p.handleSetMin(event, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleSetMin", err)
|
||||
}
|
||||
case "max":
|
||||
err := p.handleSetMax(event, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleSetMax", err)
|
||||
}
|
||||
case "format":
|
||||
err := p.handleSetFormat(event, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleSetFormat", err)
|
||||
}
|
||||
case "divisor":
|
||||
err := p.handleDivisor(event, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleDivisor", err)
|
||||
}
|
||||
case "foreground", "background", "highlight", "valuetext":
|
||||
err := p.handleColorChange(event, sdpi.Key, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleColorChange", err)
|
||||
}
|
||||
case "titleFontSize", "valueFontSize":
|
||||
err := p.handleSetFontSize(event, sdpi.Key, &sdpi)
|
||||
if err != nil {
|
||||
log.Println("handleSetTitleFontSize", err)
|
||||
}
|
||||
default:
|
||||
log.Printf("Unknown sdpi key: %s\n", sdpi.Key)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
272
internal/app/hwinfostreamdeckplugin/handlers.go
Normal file
272
internal/app/hwinfostreamdeckplugin/handlers.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package hwinfostreamdeckplugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strconv"
|
||||
|
||||
hwsensorsservice "github.com/shayne/hwinfo-streamdeck/pkg/service"
|
||||
"github.com/shayne/hwinfo-streamdeck/pkg/streamdeck"
|
||||
)
|
||||
|
||||
func (p *Plugin) handleSensorSelect(event *streamdeck.EvSendToPlugin, sdpi *evSdpiCollection) error {
|
||||
sensorid := sdpi.Value
|
||||
readings, err := p.hw.ReadingsForSensorID(sensorid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSensorSelect ReadingsBySensor failed: %v", err)
|
||||
}
|
||||
evreadings := []*evSendReadingsPayloadReading{}
|
||||
for _, r := range readings {
|
||||
evreadings = append(evreadings, &evSendReadingsPayloadReading{ID: r.ID(), Label: r.Label(), Prefix: r.Unit()})
|
||||
}
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleReadingSelect getSettings: %v", err)
|
||||
}
|
||||
// only update settings if SensorUID is changing
|
||||
// this covers case where PI sends event when tile
|
||||
// selected in SD UI
|
||||
if settings.SensorUID != sensorid {
|
||||
settings.SensorUID = sensorid
|
||||
settings.ReadingID = 0
|
||||
settings.IsValid = false
|
||||
}
|
||||
payload := evSendReadingsPayload{Readings: evreadings, Settings: &settings}
|
||||
err = p.sd.SendToPropertyInspector(event.Action, event.Context, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sensorsSelect SendToPropertyInspector: %v", err)
|
||||
}
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSensorSelect SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDefaultMinMaxForReading(r hwsensorsservice.Reading) (int, int) {
|
||||
switch r.Unit() {
|
||||
case "%":
|
||||
return 0, 100
|
||||
case "Yes/No":
|
||||
return 0, 1
|
||||
}
|
||||
min := r.ValueMin()
|
||||
max := r.ValueMax()
|
||||
min -= min * .2
|
||||
if min <= 0 {
|
||||
min = 0.
|
||||
}
|
||||
max += max * .2
|
||||
return int(min), int(max)
|
||||
}
|
||||
|
||||
func (p *Plugin) handleReadingSelect(event *streamdeck.EvSendToPlugin, sdpi *evSdpiCollection) error {
|
||||
rid64, err := strconv.ParseInt(sdpi.Value, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleReadingSelect Atoi failed: %s, %v", sdpi.Value, err)
|
||||
}
|
||||
rid := int32(rid64)
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleReadingSelect getSettings: %v", err)
|
||||
}
|
||||
|
||||
// no action if reading didn't change
|
||||
if settings.ReadingID == rid {
|
||||
return nil
|
||||
}
|
||||
|
||||
settings.ReadingID = rid
|
||||
|
||||
// set default min/max
|
||||
r, err := p.getReading(settings.SensorUID, settings.ReadingID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleReadingSelect getReading: %v", err)
|
||||
}
|
||||
|
||||
g, ok := p.graphs[event.Context]
|
||||
if !ok {
|
||||
return fmt.Errorf("handleReadingSelect no graph for context: %s", event.Context)
|
||||
}
|
||||
defaultMin, defaultMax := getDefaultMinMaxForReading(r)
|
||||
settings.Min = defaultMin
|
||||
g.SetMin(settings.Min)
|
||||
settings.Max = defaultMax
|
||||
g.SetMax(settings.Max)
|
||||
settings.IsValid = true // set IsValid once we choose reading
|
||||
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleReadingSelect SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) handleSetMin(event *streamdeck.EvSendToPlugin, sdpi *evSdpiCollection) error {
|
||||
min, err := strconv.Atoi(sdpi.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetMin strconv: %v", err)
|
||||
}
|
||||
g, ok := p.graphs[event.Context]
|
||||
if !ok {
|
||||
return fmt.Errorf("handleSetMax no graph for context: %s", event.Context)
|
||||
}
|
||||
g.SetMin(min)
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetMin getSettings: %v", err)
|
||||
}
|
||||
settings.Min = min
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetMin SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) handleSetMax(event *streamdeck.EvSendToPlugin, sdpi *evSdpiCollection) error {
|
||||
max, err := strconv.Atoi(sdpi.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetMax strconv: %v", err)
|
||||
}
|
||||
g, ok := p.graphs[event.Context]
|
||||
if !ok {
|
||||
return fmt.Errorf("handleSetMax no graph for context: %s", event.Context)
|
||||
}
|
||||
g.SetMax(max)
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetMax getSettings: %v", err)
|
||||
}
|
||||
settings.Max = max
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetMax SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) handleSetFormat(event *streamdeck.EvSendToPlugin, sdpi *evSdpiCollection) error {
|
||||
format := sdpi.Value
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetFormat getSettings: %v", err)
|
||||
}
|
||||
settings.Format = format
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleSetFormat SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) handleDivisor(event *streamdeck.EvSendToPlugin, sdpi *evSdpiCollection) error {
|
||||
divisor := sdpi.Value
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleDivisor getSettings: %v", err)
|
||||
}
|
||||
settings.Divisor = divisor
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleDivisor SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
hexFormat = "#%02x%02x%02x"
|
||||
hexShortFormat = "#%1x%1x%1x"
|
||||
hexToRGBFactor = 17
|
||||
)
|
||||
|
||||
func hexToRGBA(hex string) *color.RGBA {
|
||||
var r, g, b uint8
|
||||
|
||||
if len(hex) == 4 {
|
||||
fmt.Sscanf(hex, hexShortFormat, &r, &g, &b)
|
||||
r *= hexToRGBFactor
|
||||
g *= hexToRGBFactor
|
||||
b *= hexToRGBFactor
|
||||
} else {
|
||||
fmt.Sscanf(hex, hexFormat, &r, &g, &b)
|
||||
}
|
||||
|
||||
return &color.RGBA{R: r, G: g, B: b, A: 255}
|
||||
}
|
||||
|
||||
func (p *Plugin) handleColorChange(event *streamdeck.EvSendToPlugin, key string, sdpi *evSdpiCollection) error {
|
||||
hex := sdpi.Value
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleDivisor getSettings: %v", err)
|
||||
}
|
||||
g, ok := p.graphs[event.Context]
|
||||
if !ok {
|
||||
return fmt.Errorf("handleSetMax no graph for context: %s", event.Context)
|
||||
}
|
||||
clr := hexToRGBA(hex)
|
||||
switch key {
|
||||
case "foreground":
|
||||
settings.ForegroundColor = hex
|
||||
g.SetForegroundColor(clr)
|
||||
case "background":
|
||||
settings.BackgroundColor = hex
|
||||
g.SetBackgroundColor(clr)
|
||||
case "highlight":
|
||||
settings.HighlightColor = hex
|
||||
g.SetHighlightColor(clr)
|
||||
case "valuetext":
|
||||
settings.ValueTextColor = hex
|
||||
g.SetLabelColor(1, clr)
|
||||
}
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleColorChange SetSettings: %v", err)
|
||||
}
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) handleSetFontSize(event *streamdeck.EvSendToPlugin, key string, sdpi *evSdpiCollection) error {
|
||||
sv := sdpi.Value
|
||||
size, err := strconv.ParseFloat(sv, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert value to float: %w", err)
|
||||
}
|
||||
|
||||
settings, err := p.am.getSettings(event.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getSettings failed: %w", err)
|
||||
}
|
||||
|
||||
g, ok := p.graphs[event.Context]
|
||||
if !ok {
|
||||
return fmt.Errorf("no graph for context: %s", event.Context)
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "titleFontSize":
|
||||
settings.TitleFontSize = size
|
||||
g.SetLabelFontSize(0, size)
|
||||
case "valueFontSize":
|
||||
settings.ValueFontSize = size
|
||||
g.SetLabelFontSize(1, size)
|
||||
default:
|
||||
return fmt.Errorf("invalid key: %s", sdpi.Key)
|
||||
}
|
||||
|
||||
err = p.sd.SetSettings(event.Context, &settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SetSettings failed: %w", err)
|
||||
}
|
||||
|
||||
p.am.SetAction(event.Action, event.Context, &settings)
|
||||
return nil
|
||||
}
|
||||
231
internal/app/hwinfostreamdeckplugin/plugin.go
Normal file
231
internal/app/hwinfostreamdeckplugin/plugin.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package hwinfostreamdeckplugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/shayne/go-winpeg"
|
||||
"github.com/shayne/hwinfo-streamdeck/pkg/graph"
|
||||
hwsensorsservice "github.com/shayne/hwinfo-streamdeck/pkg/service"
|
||||
"github.com/shayne/hwinfo-streamdeck/pkg/streamdeck"
|
||||
)
|
||||
|
||||
// Plugin handles information between HWiNFO and Stream Deck
|
||||
type Plugin struct {
|
||||
c *plugin.Client
|
||||
peg winpeg.ProcessExitGroup
|
||||
hw hwsensorsservice.HardwareService
|
||||
sd *streamdeck.StreamDeck
|
||||
am *actionManager
|
||||
graphs map[string]*graph.Graph
|
||||
|
||||
appLaunched bool
|
||||
}
|
||||
|
||||
func (p *Plugin) startClient() error {
|
||||
cmd := exec.Command("./hwinfo-plugin.exe")
|
||||
|
||||
// We're a host. Start by launching the plugin process.
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: hwsensorsservice.Handshake,
|
||||
Plugins: hwsensorsservice.PluginMap,
|
||||
Cmd: cmd,
|
||||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
||||
AutoMTLS: true,
|
||||
})
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g, err := winpeg.NewProcessExitGroup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := g.AddProcess(cmd.Process); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense("hwinfoplugin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.c = client
|
||||
p.peg = g
|
||||
p.hw = raw.(hwsensorsservice.HardwareService)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPlugin creates an instance and initializes the plugin
|
||||
func NewPlugin(port, uuid, event, info string) (*Plugin, error) {
|
||||
// We don't want to see the plugin logs.
|
||||
// log.SetOutput(ioutil.Discard)
|
||||
p := &Plugin{
|
||||
am: newActionManager(),
|
||||
graphs: make(map[string]*graph.Graph),
|
||||
}
|
||||
p.startClient()
|
||||
p.sd = streamdeck.NewStreamDeck(port, uuid, event, info)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// RunForever starts the plugin and waits for events, indefinitely
|
||||
func (p *Plugin) RunForever() error {
|
||||
defer func() {
|
||||
p.c.Kill()
|
||||
p.peg.Dispose()
|
||||
}()
|
||||
|
||||
p.sd.SetDelegate(p)
|
||||
p.am.Run(p.updateTiles)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if p.c.Exited() {
|
||||
p.startClient()
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
err := p.sd.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("StreamDeck Connect: %v", err)
|
||||
}
|
||||
defer p.sd.Close()
|
||||
p.sd.ListenAndWait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) getReading(suid string, rid int32) (hwsensorsservice.Reading, error) {
|
||||
rbs, err := p.hw.ReadingsForSensorID(suid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getReading ReadingsBySensor failed: %v", err)
|
||||
}
|
||||
for _, r := range rbs {
|
||||
if r.ID() == rid {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("ReadingID does not exist: %s", suid)
|
||||
}
|
||||
|
||||
func (p *Plugin) applyDefaultFormat(v float64, t hwsensorsservice.ReadingType, u string) string {
|
||||
switch t {
|
||||
case hwsensorsservice.ReadingTypeNone:
|
||||
return fmt.Sprintf("%0.f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypeTemp:
|
||||
return fmt.Sprintf("%.0f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypeVolt:
|
||||
return fmt.Sprintf("%.0f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypeFan:
|
||||
return fmt.Sprintf("%.0f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypeCurrent:
|
||||
return fmt.Sprintf("%.0f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypePower:
|
||||
return fmt.Sprintf("%0.f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypeClock:
|
||||
return fmt.Sprintf("%.0f %s", v, u)
|
||||
case hwsensorsservice.ReadingTypeUsage:
|
||||
return fmt.Sprintf("%.0f%s", v, u)
|
||||
case hwsensorsservice.ReadingTypeOther:
|
||||
return fmt.Sprintf("%.0f %s", v, u)
|
||||
}
|
||||
return "Bad Format"
|
||||
}
|
||||
|
||||
func (p *Plugin) updateTiles(data *actionData) {
|
||||
if data.action != "com.exension.hwinfor0.reading" {
|
||||
log.Printf("Unknown action updateTiles: %s\n", data.action)
|
||||
return
|
||||
}
|
||||
|
||||
g, ok := p.graphs[data.context]
|
||||
if !ok {
|
||||
log.Printf("Graph not found for context: %s\n", data.context)
|
||||
return
|
||||
}
|
||||
|
||||
if !p.appLaunched {
|
||||
if !data.settings.InErrorState {
|
||||
payload := evStatus{Error: true, Message: "HWiNFO Unavailable"}
|
||||
err := p.sd.SendToPropertyInspector("com.exension.hwinfor0.reading", data.context, payload)
|
||||
if err != nil {
|
||||
log.Println("updateTiles SendToPropertyInspector", err)
|
||||
}
|
||||
data.settings.InErrorState = true
|
||||
p.sd.SetSettings(data.context, &data.settings)
|
||||
}
|
||||
bts, err := ioutil.ReadFile("./launch-hwinfo.png")
|
||||
if err != nil {
|
||||
log.Printf("Failed to read launch-hwinfo.png: %v\n", err)
|
||||
return
|
||||
}
|
||||
err = p.sd.SetImage(data.context, bts)
|
||||
if err != nil {
|
||||
log.Printf("Failed to setImage: %v\n", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// show ui on property inspector if in error state
|
||||
if data.settings.InErrorState {
|
||||
payload := evStatus{Error: false, Message: "show_ui"}
|
||||
err := p.sd.SendToPropertyInspector("com.exension.hwinfor0.reading", data.context, payload)
|
||||
if err != nil {
|
||||
log.Println("updateTiles SendToPropertyInspector", err)
|
||||
}
|
||||
data.settings.InErrorState = false
|
||||
p.sd.SetSettings(data.context, &data.settings)
|
||||
}
|
||||
|
||||
s := data.settings
|
||||
r, err := p.getReading(s.SensorUID, s.ReadingID)
|
||||
if err != nil {
|
||||
log.Printf("getReading failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
v := r.Value()
|
||||
if s.Divisor != "" {
|
||||
fdiv := 1.
|
||||
fdiv, err := strconv.ParseFloat(s.Divisor, 64)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse float: %s\n", s.Divisor)
|
||||
return
|
||||
}
|
||||
v = r.Value() / fdiv
|
||||
}
|
||||
g.Update(v)
|
||||
var text string
|
||||
if f := s.Format; f != "" {
|
||||
text = fmt.Sprintf(f, v)
|
||||
} else {
|
||||
text = p.applyDefaultFormat(v, hwsensorsservice.ReadingType(r.TypeI()), r.Unit())
|
||||
}
|
||||
g.SetLabelText(1, text)
|
||||
|
||||
b, err := g.EncodePNG()
|
||||
if err != nil {
|
||||
log.Printf("Failed to encode graph: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = p.sd.SetImage(data.context, b)
|
||||
if err != nil {
|
||||
log.Printf("Failed to setImage: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
60
internal/app/hwinfostreamdeckplugin/types.go
Normal file
60
internal/app/hwinfostreamdeckplugin/types.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package hwinfostreamdeckplugin
|
||||
|
||||
type actionSettings struct {
|
||||
SensorUID string `json:"sensorUid"`
|
||||
ReadingID int32 `json:"readingId,string"`
|
||||
Title string `json:"title"`
|
||||
TitleFontSize float64 `json:"titleFontSize"`
|
||||
ValueFontSize float64 `json:"valueFontSize"`
|
||||
Min int `json:"min"`
|
||||
Max int `json:"max"`
|
||||
Format string `json:"format"`
|
||||
Divisor string `json:"divisor"`
|
||||
IsValid bool `json:"isValid"`
|
||||
TitleColor string `json:"titleColor"`
|
||||
ForegroundColor string `json:"foregroundColor"`
|
||||
BackgroundColor string `json:"backgroundColor"`
|
||||
HighlightColor string `json:"highlightColor"`
|
||||
ValueTextColor string `json:"valueTextColor"`
|
||||
InErrorState bool `json:"inErrorState"`
|
||||
}
|
||||
|
||||
type actionData struct {
|
||||
action string
|
||||
context string
|
||||
settings *actionSettings
|
||||
}
|
||||
|
||||
type evStatus struct {
|
||||
Error bool `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type evSendSensorsPayloadSensor struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type evSendSensorsPayload struct {
|
||||
Sensors []*evSendSensorsPayloadSensor `json:"sensors"`
|
||||
Settings *actionSettings `json:"settings"`
|
||||
}
|
||||
|
||||
type evSendReadingsPayloadReading struct {
|
||||
ID int32 `json:"id,string"`
|
||||
Label string `json:"label"`
|
||||
Prefix string `json:"prefix"`
|
||||
}
|
||||
|
||||
type evSendReadingsPayload struct {
|
||||
Readings []*evSendReadingsPayloadReading `json:"readings"`
|
||||
Settings *actionSettings `json:"settings"`
|
||||
}
|
||||
|
||||
type evSdpiCollection struct {
|
||||
Group bool `json:"group"`
|
||||
Index int `json:"index"`
|
||||
Key string `json:"key"`
|
||||
Selection []string `json:"selection"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
Reference in New Issue
Block a user