hwinfo-streamdeck/internal/app/hwinfostreamdeckplugin/plugin.go

232 lines
5.5 KiB
Go

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