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

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

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

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

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

163
internal/hwinfo/hwinfo.go Normal file
View File

@@ -0,0 +1,163 @@
package hwinfo
/*
#include <windows.h>
#include "hwisenssm2.h"
*/
import "C"
import (
"fmt"
"log"
"time"
"unsafe"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/shmem"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/util"
)
// SharedMemory provides access to the HWiNFO shared memory
type SharedMemory struct {
data []byte
shmem C.PHWiNFO_SENSORS_SHARED_MEM2
}
// ReadSharedMem reads data from HWiNFO shared memory
// creating a copy of the data
func ReadSharedMem() (*SharedMemory, error) {
data, err := shmem.ReadBytes()
if err != nil {
return nil, err
}
return &SharedMemory{
data: append([]byte(nil), data...),
shmem: C.PHWiNFO_SENSORS_SHARED_MEM2(unsafe.Pointer(&data[0])),
}, nil
}
// Result for streamed shared memory updates
type Result struct {
Shmem *SharedMemory
Err error
}
func readAndSend(ch chan<- Result) {
shmem, err := ReadSharedMem()
ch <- Result{Shmem: shmem, Err: err}
}
// StreamSharedMem delivers shared memory hardware sensors updates
// over a channel
func StreamSharedMem() <-chan Result {
ch := make(chan Result)
go func() {
readAndSend(ch)
// TODO: don't use time.Tick, cancellable?
for range time.Tick(1 * time.Second) {
readAndSend(ch)
}
}()
return ch
}
// Signature "HWiS" if active, 'DEAD' when inactive
func (s *SharedMemory) Signature() string {
return util.DecodeCharPtr(unsafe.Pointer(&s.shmem.dwSignature), C.sizeof_DWORD)
}
// Version v1 is latest
func (s *SharedMemory) Version() int {
return int(s.shmem.dwVersion)
}
// Revision revision of version
func (s *SharedMemory) Revision() int {
return int(s.shmem.dwRevision)
}
// PollTime last polling time
func (s *SharedMemory) PollTime() uint64 {
addr := unsafe.Pointer(uintptr(unsafe.Pointer(&s.shmem.dwRevision)) + C.sizeof_DWORD)
return uint64(*(*C.__time64_t)(addr))
}
// OffsetOfSensorSection offset of the Sensor section from beginning of HWiNFO_SENSORS_SHARED_MEM2
func (s *SharedMemory) OffsetOfSensorSection() int {
return int(s.shmem.dwOffsetOfSensorSection)
}
// SizeOfSensorElement size of each sensor element = sizeof( HWiNFO_SENSORS_SENSOR_ELEMENT )
func (s *SharedMemory) SizeOfSensorElement() int {
return int(s.shmem.dwSizeOfSensorElement)
}
// NumSensorElements number of sensor elements
func (s *SharedMemory) NumSensorElements() int {
return int(s.shmem.dwNumSensorElements)
}
// OffsetOfReadingSection offset of the Reading section from beginning of HWiNFO_SENSORS_SHARED_MEM2
func (s *SharedMemory) OffsetOfReadingSection() int {
return int(s.shmem.dwOffsetOfReadingSection)
}
// SizeOfReadingElement size of each Reading element = sizeof( HWiNFO_SENSORS_READING_ELEMENT )
func (s *SharedMemory) SizeOfReadingElement() int {
return int(s.shmem.dwSizeOfReadingElement)
}
// NumReadingElements number of Reading elements
func (s *SharedMemory) NumReadingElements() int {
return int(s.shmem.dwNumReadingElements)
}
func (s *SharedMemory) dataForSensor(pos int) ([]byte, error) {
if pos >= s.NumSensorElements() {
return nil, fmt.Errorf("dataForSensor pos out of range, %d for size %d", pos, s.NumSensorElements())
}
start := s.OffsetOfSensorSection() + (pos * s.SizeOfSensorElement())
end := start + s.SizeOfSensorElement()
return s.data[start:end], nil
}
// IterSensors iterate over each sensor
func (s *SharedMemory) IterSensors() <-chan Sensor {
ch := make(chan Sensor)
go func() {
for i := 0; i < s.NumSensorElements(); i++ {
data, err := s.dataForSensor(i)
if err != nil {
log.Fatalf("TODO: failed to read dataForSensor: %v", err)
}
ch <- NewSensor(data)
}
close(ch)
}()
return ch
}
func (s *SharedMemory) dataForReading(pos int) ([]byte, error) {
if pos >= s.NumReadingElements() {
return nil, fmt.Errorf("dataForReading pos out of range, %d for size %d", pos, s.NumSensorElements())
}
start := s.OffsetOfReadingSection() + (pos * s.SizeOfReadingElement())
end := start + s.SizeOfReadingElement()
return s.data[start:end], nil
}
// IterReadings iterate over each sensor
func (s *SharedMemory) IterReadings() <-chan Reading {
ch := make(chan Reading)
go func() {
for i := 0; i < s.NumReadingElements(); i++ {
data, err := s.dataForReading(i)
if err != nil {
log.Fatalf("TODO: failed to read dataForReading: %v", err)
}
ch <- NewReading(data)
}
close(ch)
}()
return ch
}

View File

@@ -0,0 +1,126 @@
#ifndef _HWISENSSM2_H_INCLUDED_
#define _HWISENSSM2_H_INCLUDED_
// Name of the file mapping object that needs to be opened using OpenFileMapping Function:
#define HWiNFO_SENSORS_MAP_FILE_NAME2 "Global\\HWiNFO_SENS_SM2"
// Name of the global mutex which is acquired when accessing the Shared Memory space. Release as quick as possible !
#define HWiNFO_SENSORS_SM2_MUTEX "Global\\HWiNFO_SM2_MUTEX"
#define HWiNFO_SENSORS_STRING_LEN2 128
#define HWiNFO_UNIT_STRING_LEN 16
enum SENSOR_READING_TYPE
{
SENSOR_TYPE_NONE = 0,
SENSOR_TYPE_TEMP,
SENSOR_TYPE_VOLT,
SENSOR_TYPE_FAN,
SENSOR_TYPE_CURRENT,
SENSOR_TYPE_POWER,
SENSOR_TYPE_CLOCK,
SENSOR_TYPE_USAGE,
SENSOR_TYPE_OTHER
};
typedef enum SENSOR_READING_TYPE SENSOR_READING_TYPE;
// No alignment of structure members
#pragma pack(1)
typedef struct _HWiNFO_SENSORS_READING_ELEMENT
{
SENSOR_READING_TYPE tReading; // Type of sensor reading
DWORD dwSensorIndex; // This is the index of sensor in the Sensors[] array to which this reading belongs to
DWORD dwReadingID; // A unique ID of the reading within a particular sensor
char szLabelOrig[HWiNFO_SENSORS_STRING_LEN2]; // Original label (e.g. "Chassis2 Fan")
char szLabelUser[HWiNFO_SENSORS_STRING_LEN2]; // Label displayed, which might have been renamed by user
char szUnit[HWiNFO_UNIT_STRING_LEN]; // e.g. "RPM"
double Value;
double ValueMin;
double ValueMax;
double ValueAvg;
} HWiNFO_SENSORS_READING_ELEMENT, *PHWiNFO_SENSORS_READING_ELEMENT;
typedef struct _HWiNFO_SENSORS_SENSOR_ELEMENT
{
DWORD dwSensorID; // A unique Sensor ID
DWORD dwSensorInst; // The instance of the sensor (together with dwSensorID forms a unique ID)
char szSensorNameOrig[HWiNFO_SENSORS_STRING_LEN2]; // Original sensor name
char szSensorNameUser[HWiNFO_SENSORS_STRING_LEN2]; // Sensor name displayed, which might have been renamed by user
} HWiNFO_SENSORS_SENSOR_ELEMENT, *PHWiNFO_SENSORS_SENSOR_ELEMENT;
typedef struct _HWiNFO_SENSORS_SHARED_MEM2
{
DWORD dwSignature; // "HWiS" if active, 'DEAD' when inactive
DWORD dwVersion; // v1 is latest
DWORD dwRevision; //
__time64_t poll_time; // last polling time
// descriptors for the Sensors section
DWORD dwOffsetOfSensorSection; // Offset of the Sensor section from beginning of HWiNFO_SENSORS_SHARED_MEM2
DWORD dwSizeOfSensorElement; // Size of each sensor element = sizeof( HWiNFO_SENSORS_SENSOR_ELEMENT )
DWORD dwNumSensorElements; // Number of sensor elements
// descriptors for the Readings section
DWORD dwOffsetOfReadingSection; // Offset of the Reading section from beginning of HWiNFO_SENSORS_SHARED_MEM2
DWORD dwSizeOfReadingElement; // Size of each Reading element = sizeof( HWiNFO_SENSORS_READING_ELEMENT )
DWORD dwNumReadingElements; // Number of Reading elements
} HWiNFO_SENSORS_SHARED_MEM2, *PHWiNFO_SENSORS_SHARED_MEM2;
#pragma pack()
#endif
// ***************************************************************************************************************
// HWiNFO Shared Memory Footprint
// ***************************************************************************************************************
//
// |-----------------------------|-----------------------------------|-----------------------------------|
// Content | HWiNFO_SENSORS_SHARED_MEM2 | HWiNFO_SENSORS_SENSOR_ELEMENT[] | HWiNFO_SENSORS_READING_ELEMENT[] |
// |-----------------------------|-----------------------------------|-----------------------------------|
// Pointer |<--0 |<--dwOffsetOfSensorSection |<--dwOffsetOfReadingSection |
// |-----------------------------|-----------------------------------|-----------------------------------|
// Size | dwOffsetOfSensorSection | dwSizeOfSensorElement | dwSizeOfReadingElement |
// | | * dwNumSensorElement | * dwNumReadingElement |
// |-----------------------------|-----------------------------------|-----------------------------------|
//
// ***************************************************************************************************************
// Code Example
// ***************************************************************************************************************
/*
HANDLE hHWiNFOMemory = OpenFileMapping( FILE_MAP_READ, FALSE, HWiNFO_SENSORS_MAP_FILE_NAME2 );
if (hHWiNFOMemory)
PHWiNFO_SENSORS_SHARED_MEM2 pHWiNFOMemory =
(PHWiNFO_SENSORS_SHARED_MEM2) MapViewOfFile( hHWiNFOMemory, FILE_MAP_READ, 0, 0, 0 );
// TODO: process signature, version, revision and poll time
// loop through all available sensors
for (DWORD dwSensor = 0; dwSensor < pHWiNFOMemory->dwNumSensorElements; dwSensor++)
{
PHWiNFO_SENSORS_SENSOR_ELEMENT sensor = (PHWiNFO_SENSORS_SENSOR_ELEMENT) ((BYTE*)pHWiNFOMemory +
pHWiNFOMemory->dwOffsetOfSensorSection +
(pHWiNFOMemory->dwSizeOfSensorElement * dwSensor));
// TODO: process sensor
}
// loop through all available readings
for (DWORD dwReading = 0; dwReading < pHWiNFOMemory->dwNumReadingElements; dwReading++)
{
PHWiNFO_SENSORS_READING_ELEMENT reading = (PHWiNFO_SENSORS_READING_ELEMENT) ((BYTE*)pHWiNFOMemory +
pHWiNFOMemory->dwOffsetOfReadingSection +
(pHWiNFOMemory->dwSizeOfReadingElement * dwReading));
// TODO: process reading
}
}
*/

View File

@@ -0,0 +1,38 @@
package mutex
/*
#include <windows.h>
#include "../hwisenssm2.h"
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/util"
)
var ghnd C.HANDLE
var imut = sync.Mutex{}
// Lock the global mutex
func Lock() error {
imut.Lock()
lpName := C.CString(C.HWiNFO_SENSORS_SM2_MUTEX)
defer C.free(unsafe.Pointer(lpName))
ghnd = C.OpenMutex(C.READ_CONTROL, C.FALSE, lpName)
if ghnd == C.HANDLE(C.NULL) {
errstr := util.HandleLastError(uint64(C.GetLastError()))
return fmt.Errorf("failed to lock global mutex: %w", errstr)
}
return nil
}
// Unlock the global mutex
func Unlock() {
defer imut.Unlock()
C.CloseHandle(ghnd)
}

View File

@@ -0,0 +1,70 @@
package plugin
import (
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo"
hwsensorsservice "github.com/shayne/hwinfo-streamdeck/pkg/service"
)
// Plugin implementation
type Plugin struct {
Service *Service
}
// PollTime implementation for plugin
func (p *Plugin) PollTime() (uint64, error) {
shmem, err := p.Service.Shmem()
if err != nil {
return 0, err
}
return shmem.PollTime(), nil
}
// Sensors implementation for plugin
func (p *Plugin) Sensors() ([]hwsensorsservice.Sensor, error) {
shmem, err := p.Service.Shmem()
if err != nil {
return nil, err
}
var sensors []hwsensorsservice.Sensor
for s := range shmem.IterSensors() {
sensors = append(sensors, &sensor{s})
}
return sensors, nil
}
// ReadingsForSensorID implementation for plugin
func (p *Plugin) ReadingsForSensorID(id string) ([]hwsensorsservice.Reading, error) {
res, err := p.Service.ReadingsBySensorID(id)
if err != nil {
return nil, err
}
var readings []hwsensorsservice.Reading
for _, r := range res {
readings = append(readings, &reading{r})
}
return readings, nil
}
type sensor struct {
hwinfo.Sensor
}
func (s sensor) Name() string {
return s.NameOrig()
}
type reading struct {
hwinfo.Reading
}
func (r reading) Label() string {
return r.LabelOrig()
}
func (r reading) Type() string {
return r.Reading.Type().String()
}
func (r reading) TypeI() int32 {
return int32(r.Reading.Type())
}

View File

@@ -0,0 +1,128 @@
package plugin
import (
"fmt"
"sync"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo"
)
// Service wraps hwinfo shared mem streaming
// and provides convenient methods for data access
type Service struct {
streamch <-chan hwinfo.Result
mu sync.RWMutex
sensorIDByIdx []string
readingsBySensorID map[string][]hwinfo.Reading
shmem *hwinfo.SharedMemory
readingsBuilt bool
}
// Start starts the service providing updating hardware info
func StartService() *Service {
return &Service{
streamch: hwinfo.StreamSharedMem(),
}
}
func (s *Service) recvShmem(shmem *hwinfo.SharedMemory) error {
if shmem == nil {
return fmt.Errorf("shmem nil")
}
s.mu.Lock()
defer s.mu.Unlock()
s.shmem = shmem
s.sensorIDByIdx = s.sensorIDByIdx[:0]
for k, v := range s.readingsBySensorID {
s.readingsBySensorID[k] = v[:0]
}
s.readingsBuilt = false
return nil
}
// Recv receives new hardware sensor updates
func (s *Service) Recv() error {
select {
case r := <-s.streamch:
if r.Err != nil {
return r.Err
}
return s.recvShmem(r.Shmem)
}
}
// Shmem provides access to underlying hwinfo shared memory
func (s *Service) Shmem() (*hwinfo.SharedMemory, error) {
s.mu.RLock()
defer s.mu.RUnlock()
if s.shmem != nil {
return s.shmem, nil
}
return nil, fmt.Errorf("shmem nil")
}
// SensorIDByIdx returns ordered slice of sensor IDs
func (s *Service) SensorIDByIdx() ([]string, error) {
s.mu.RLock()
if len(s.sensorIDByIdx) > 0 {
defer s.mu.RUnlock()
return s.sensorIDByIdx, nil
}
s.mu.RUnlock()
s.mu.Lock()
defer s.mu.Unlock()
for sens := range s.shmem.IterSensors() {
s.sensorIDByIdx = append(s.sensorIDByIdx, sens.ID())
}
return s.sensorIDByIdx, nil
}
// ReadingsBySensorID returns slice of hwinfoReading for a given sensor ID
func (s *Service) ReadingsBySensorID(id string) ([]hwinfo.Reading, error) {
s.mu.RLock()
if s.readingsBySensorID != nil && s.readingsBuilt {
defer s.mu.RUnlock()
readings, ok := s.readingsBySensorID[id]
if !ok {
return nil, fmt.Errorf("readings for sensor id %s do not exist", id)
}
return readings, nil
}
s.mu.RUnlock()
sids, err := s.SensorIDByIdx()
if err != nil {
return nil, err
}
s.mu.Lock()
defer s.mu.Unlock()
if s.readingsBySensorID == nil {
s.readingsBySensorID = make(map[string][]hwinfo.Reading)
}
for r := range s.shmem.IterReadings() {
sidx := int(r.SensorIndex())
if sidx < len(sids) {
sid := sids[sidx]
s.readingsBySensorID[sid] = append(s.readingsBySensorID[sid], r)
} else {
return nil, fmt.Errorf("sensor at index %d out of range ", sidx)
}
}
s.readingsBuilt = true
readings, ok := s.readingsBySensorID[id]
if !ok {
return nil, fmt.Errorf("readings for sensor id %s do not exist", id)
}
return readings, nil
}

124
internal/hwinfo/reading.go Normal file
View File

@@ -0,0 +1,124 @@
package hwinfo
/*
#include <windows.h>
#include "hwisenssm2.h"
*/
import "C"
import (
"unsafe"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/util"
)
// ReadingType enum of value/unit type for reading
type ReadingType int
const (
// ReadingTypeNone no type
ReadingTypeNone ReadingType = iota
// ReadingTypeTemp temperature in celsius
ReadingTypeTemp
// ReadingTypeVolt voltage
ReadingTypeVolt
// ReadingTypeFan RPM
ReadingTypeFan
// ReadingTypeCurrent amps
ReadingTypeCurrent
// ReadingTypePower watts
ReadingTypePower
// ReadingTypeClock Mhz
ReadingTypeClock
// ReadingTypeUsage e.g. MBs
ReadingTypeUsage
// ReadingTypeOther other
ReadingTypeOther
)
func (t ReadingType) String() string {
return [...]string{"None", "Temp", "Volt", "Fan", "Current", "Power", "Clock", "Usage", "Other"}[t]
}
// Reading element (e.g. usage, power, mhz...)
type Reading struct {
cr C.PHWiNFO_SENSORS_READING_ELEMENT
}
// NewReading contructs a Reading
func NewReading(data []byte) Reading {
return Reading{
cr: C.PHWiNFO_SENSORS_READING_ELEMENT(unsafe.Pointer(&data[0])),
}
}
// ID unique ID of the reading within a particular sensor
func (r *Reading) ID() int32 {
return int32(r.cr.dwReadingID)
}
// Type of sensor reading
func (r *Reading) Type() ReadingType {
return ReadingType(r.cr.tReading)
}
// SensorIndex this is the index of sensor in the Sensors[] array to
// which this reading belongs to
func (r *Reading) SensorIndex() uint64 {
return uint64(r.cr.dwSensorIndex)
}
// ReadingID a unique ID of the reading within a particular sensor
func (r *Reading) ReadingID() uint64 {
return uint64(r.cr.dwReadingID)
}
// LabelOrig original label (e.g. "Chassis2 Fan")
func (r *Reading) LabelOrig() string {
return util.DecodeCharPtr(unsafe.Pointer(&r.cr.szLabelOrig), C.HWiNFO_SENSORS_STRING_LEN2)
}
// LabelUser label displayed, which might have been renamed by user
func (r *Reading) LabelUser() string {
return util.DecodeCharPtr(unsafe.Pointer(&r.cr.szLabelUser), C.HWiNFO_SENSORS_STRING_LEN2)
}
// Unit e.g. "RPM"
func (r *Reading) Unit() string {
return util.DecodeCharPtr(unsafe.Pointer(&r.cr.szUnit), C.HWiNFO_UNIT_STRING_LEN)
}
func (r *Reading) valuePtr() unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(&r.cr.szUnit)) + C.HWiNFO_UNIT_STRING_LEN)
}
// Value current value
func (r *Reading) Value() float64 {
return float64(*(*C.double)(r.valuePtr()))
}
func (r *Reading) valueMinPtr() unsafe.Pointer {
return unsafe.Pointer(uintptr(r.valuePtr()) + C.sizeof_double)
}
// ValueMin current value
func (r *Reading) ValueMin() float64 {
return float64(*(*C.double)(r.valueMinPtr()))
}
func (r *Reading) valueMaxPtr() unsafe.Pointer {
return unsafe.Pointer(uintptr(r.valueMinPtr()) + C.sizeof_double)
}
// ValueMax current value
func (r *Reading) ValueMax() float64 {
return float64(*(*C.double)(r.valueMaxPtr()))
}
func (r *Reading) valueAvgPtr() unsafe.Pointer {
return unsafe.Pointer(uintptr(r.valueMaxPtr()) + C.sizeof_double)
}
// ValueAvg current value
func (r *Reading) ValueAvg() float64 {
return float64(*(*C.double)(r.valueAvgPtr()))
}

52
internal/hwinfo/sensor.go Normal file
View File

@@ -0,0 +1,52 @@
package hwinfo
/*
#include <windows.h>
#include "hwisenssm2.h"
*/
import "C"
import (
"strconv"
"unsafe"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/util"
)
// Sensor element (e.g. motherboard, cpu, gpu...)
type Sensor struct {
cs C.PHWiNFO_SENSORS_SENSOR_ELEMENT
}
// NewSensor constructs a Sensor
func NewSensor(data []byte) Sensor {
return Sensor{
cs: C.PHWiNFO_SENSORS_SENSOR_ELEMENT(unsafe.Pointer(&data[0])),
}
}
// SensorID a unique Sensor ID
func (s *Sensor) SensorID() uint64 {
return uint64(s.cs.dwSensorID)
}
// SensorInst the instance of the sensor (together with SensorID forms a unique ID)
func (s *Sensor) SensorInst() uint64 {
return uint64(s.cs.dwSensorInst)
}
// ID a unique ID combining SensorID and SensorInst
func (s *Sensor) ID() string {
// keeping old method used in legacy steam deck plugin
return strconv.FormatUint(s.SensorID()*100+s.SensorInst(), 10)
}
// NameOrig original name of sensor
func (s *Sensor) NameOrig() string {
return util.DecodeCharPtr(unsafe.Pointer(&s.cs.szSensorNameOrig), C.HWiNFO_SENSORS_STRING_LEN2)
}
// NameUser sensor name displayed, which might have been renamed by user
func (s *Sensor) NameUser() string {
return util.DecodeCharPtr(unsafe.Pointer(&s.cs.szSensorNameUser), C.HWiNFO_SENSORS_STRING_LEN2)
}

View File

@@ -0,0 +1,95 @@
package shmem
/*
#include <windows.h>
#include "../hwisenssm2.h"
*/
import "C"
import (
"fmt"
"reflect"
"syscall"
"unsafe"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/mutex"
"github.com/shayne/hwinfo-streamdeck/internal/hwinfo/util"
"golang.org/x/sys/windows"
)
var buf = make([]byte, 200000)
func copyBytes(addr uintptr) []byte {
headerLen := C.sizeof_HWiNFO_SENSORS_SHARED_MEM2
var d []byte
dh := (*reflect.SliceHeader)(unsafe.Pointer(&d))
dh.Data = addr
dh.Len, dh.Cap = headerLen, headerLen
cheader := C.PHWiNFO_SENSORS_SHARED_MEM2(unsafe.Pointer(&d[0]))
fullLen := int(cheader.dwOffsetOfReadingSection + (cheader.dwSizeOfReadingElement * cheader.dwNumReadingElements))
if fullLen > cap(buf) {
buf = append(buf, make([]byte, fullLen-cap(buf))...)
}
dh.Len, dh.Cap = fullLen, fullLen
copy(buf, d)
return buf[:fullLen]
}
// ReadBytes copies bytes from global shared memory
func ReadBytes() ([]byte, error) {
err := mutex.Lock()
defer mutex.Unlock()
if err != nil {
return nil, err
}
hnd, err := openFileMapping()
if err != nil {
return nil, err
}
addr, err := mapViewOfFile(hnd)
if err != nil {
return nil, err
}
defer unmapViewOfFile(addr)
defer windows.CloseHandle(windows.Handle(unsafe.Pointer(hnd)))
return copyBytes(addr), nil
}
func openFileMapping() (C.HANDLE, error) {
lpName := C.CString("Global\\HWiNFO_SENS_SM2_REMOTE_0")
defer C.free(unsafe.Pointer(lpName))
hnd := C.OpenFileMapping(syscall.FILE_MAP_READ, 0, lpName)
if hnd == C.HANDLE(C.NULL) {
errstr := util.HandleLastError(uint64(C.GetLastError()))
return nil, fmt.Errorf("OpenFileMapping: %w", errstr)
}
return hnd, nil
}
func mapViewOfFile(hnd C.HANDLE) (uintptr, error) {
addr, err := windows.MapViewOfFile(windows.Handle(unsafe.Pointer(hnd)), C.FILE_MAP_READ, 0, 0, 0)
if err != nil {
return 0, fmt.Errorf("MapViewOfFile: %w", err)
}
return addr, nil
}
func unmapViewOfFile(ptr uintptr) error {
err := windows.UnmapViewOfFile(ptr)
if err != nil {
return fmt.Errorf("UnmapViewOfFile: %w", err)
}
return nil
}

View File

@@ -0,0 +1,61 @@
package util
import "C"
import (
"errors"
"fmt"
"log"
"strings"
"unsafe"
"golang.org/x/text/encoding/charmap"
)
// ErrFileNotFound Windows error
var ErrFileNotFound = errors.New("file not found")
// ErrInvalidHandle Windows error
var ErrInvalidHandle = errors.New("invalid handle")
// UnknownError unhandled Windows error
type UnknownError struct {
Code uint64
}
func (e UnknownError) Error() string {
return fmt.Sprintf("unknown error code: %d", e.Code)
}
// HandleLastError converts C.GetLastError() to golang error
func HandleLastError(code uint64) error {
switch code {
case 2: // ERROR_FILE_NOT_FOUND
return ErrFileNotFound
case 6: // ERROR_INVALID_HANDLE
return ErrInvalidHandle
default:
return UnknownError{Code: code}
}
}
func goStringFromPtr(ptr unsafe.Pointer, len int) string {
s := C.GoStringN((*C.char)(ptr), C.int(len))
return s[:strings.IndexByte(s, 0)]
}
// DecodeCharPtr decodes ISO8859_1 string to UTF-8
func DecodeCharPtr(ptr unsafe.Pointer, len int) string {
s := goStringFromPtr(ptr, len)
ds, err := decodeISO8859_1(s)
if err != nil {
log.Fatalf("TODO: failed to decode: %v", err)
}
return ds
}
var isodecoder = charmap.ISO8859_1.NewDecoder()
func decodeISO8859_1(in string) (string, error) {
return isodecoder.String(in)
}