Compare commits

..

7 Commits

Author SHA1 Message Date
Ophestra Umiker 0a1213edd5
rpcfetch: implement Windows system stats fetching
Unfortunately I could not find any loadavg equivalent on Windows, so it returns a fixed value for now.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 03:01:21 +09:00
Ophestra Umiker 1d28743f31
library: io: implement Windows platform support
Discord on Windows makes use of Windows named pipes for the socket instead of UNIX domain sockets.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 03:00:02 +09:00
Ophestra Umiker 50e3f3a03f
library: rpc: do not validate nonce of initial response
Yet another inconsistency between arRPC and Discord RPC, Discord RPC does not send a nonce at all. Now we internally treat "initial-ready" as a magic string to bypass nonce validation.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 02:57:42 +09:00
Ophestra Umiker 5bb6f9f1a8
library: io: move ErrAgain to shared and errPipe value to platform-specific
ErrAgain is not platform specific however EPIPE equivalent is different on Windows.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 02:56:15 +09:00
Ophestra Umiker c23e6120c8
rpcfetch: fix profile activity label pending text
Typo, instead of setting profileStatusT the second statement was supposed to set profileActT.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 02:54:31 +09:00
Ophestra Umiker 34304119ca
rpcfetch: retry nonce errors
Nonce errors shouldn't be fatal, sometimes weird stuff happens and retrying could fix it.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 02:52:48 +09:00
Ophestra Umiker f0e6395a3e
library: rpc: fix SET_ACTIVITY response type
Had a bad response type, did not affect usage with arRPC, however did not work with proper Discord RPC.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-06-30 02:51:40 +09:00
10 changed files with 100 additions and 9 deletions

View File

@ -69,7 +69,7 @@ func (d *Client) SetActivity(act *Activity) (string, error) {
nonce := uuid.New().String() nonce := uuid.New().String()
_, _, err := validateRaw[Response[[]byte]](Heartbeat, "", "SET_ACTIVITY", nonce)( _, _, err := validateRaw[Response[any]](Heartbeat, "", "SET_ACTIVITY", nonce)(
d, Heartbeat, Command[activityArgs]{ d, Heartbeat, Command[activityArgs]{
Arguments: activityArgs{ Arguments: activityArgs{
PID: os.Getpid(), PID: os.Getpid(),

View File

@ -105,7 +105,7 @@ func apply() {
func retry(act *rpcfetch.Activity, s *applyState) (string, error) { func retry(act *rpcfetch.Activity, s *applyState) (string, error) {
try: try:
nonce, err := d.SetActivity(act) nonce, err := d.SetActivity(act)
if errors.Is(err, rpcfetch.ErrAgain) { if errors.Is(err, rpcfetch.ErrAgain) || errors.Is(err, rpcfetch.ErrNonce) {
failureState(false) failureState(false)
log.Println("retrying in 5 seconds...") log.Println("retrying in 5 seconds...")
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)

54
fetch/apply_windows.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"log"
"os"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
globalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx")
)
type globalMemoryStatusExT struct {
dwLength uint32
dwMemoryLoad uint32
ullTotalPhys uint64
ullAvailPhys uint64
unused [5]uint64
}
func init() {
if h, err := os.Hostname(); err != nil {
log.Fatalf("error reading hostname: %s", err)
} else {
hostname = h
}
}
func (s *applyState) populateLoadavg() {
if s.loadavg != nil {
return
}
// loadavg is unsupported on Windows
s.loadavg = &[3]string{"0.00", "0.00", "0.00"}
}
func (s *applyState) populateMem() {
if s.mem != nil {
return
}
// failure defaults
s.mem = &[2]int{-1, -1}
v := new(globalMemoryStatusExT)
v.dwLength = 64
r1, _, err := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(v)))
log.Printf("call to globalMemoryStatusEx, r1=%d, %s", r1, err)
s.mem[0] = int(v.ullAvailPhys / (1 << 10))
s.mem[1] = int(v.ullTotalPhys / (1 << 10))
}

View File

@ -412,7 +412,7 @@ func failureState(ok bool) {
statusText.SetText("Retrying in 5 seconds...") statusText.SetText("Retrying in 5 seconds...")
profileStatusB.FillColor = colornames.Darkred profileStatusB.FillColor = colornames.Darkred
profileStatusT.SetText("Disconnected") profileStatusT.SetText("Disconnected")
profileStatusT.SetText("Pending...") profileActT.SetText("Pending...")
profileT.Hide() profileT.Hide()
} }
} }

3
go.mod
View File

@ -4,7 +4,9 @@ go 1.22
require ( require (
fyne.io/fyne/v2 v2.4.5 fyne.io/fyne/v2 v2.4.5
github.com/Microsoft/go-winio v0.6.2
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
golang.org/x/image v0.17.0
) )
require ( require (
@ -28,7 +30,6 @@ require (
github.com/stretchr/testify v1.8.4 // indirect github.com/stretchr/testify v1.8.4 // indirect
github.com/tevino/abool v1.2.0 // indirect github.com/tevino/abool v1.2.0 // indirect
github.com/yuin/goldmark v1.5.5 // indirect github.com/yuin/goldmark v1.5.5 // indirect
golang.org/x/image v0.17.0 // indirect
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect

2
go.sum
View File

@ -43,6 +43,8 @@ fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzm
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=

7
io.go
View File

@ -7,7 +7,10 @@ import (
"net" "net"
"os" "os"
"strconv" "strconv"
"syscall" )
var (
ErrAgain = errors.New("operation not performed")
) )
type Client struct { type Client struct {
@ -62,7 +65,7 @@ func Raw[T any](d *Client, opcode uint32, payload any) (uint32, T, error) {
// Raw writes a raw payload to Discord and returns the response opcode and payload // Raw writes a raw payload to Discord and returns the response opcode and payload
func (d *Client) Raw(opcode uint32, payload any) (uint32, []byte, error) { func (d *Client) Raw(opcode uint32, payload any) (uint32, []byte, error) {
opcodeResp, payloadResp, err := d.raw(opcode, payload) opcodeResp, payloadResp, err := d.raw(opcode, payload)
if errors.Is(err, syscall.EPIPE) { if errors.Is(err, errPipe) {
// clean up as much as possible // clean up as much as possible
_ = d.Close() _ = d.Close()
// advise retry // advise retry

View File

@ -1,15 +1,16 @@
//go:build !windows
package rpcfetch package rpcfetch
import ( import (
"errors" "errors"
"io/fs" "io/fs"
"net" "net"
"syscall"
"time" "time"
) )
var ( var errPipe = syscall.EPIPE
ErrAgain = errors.New("operation not performed")
)
func (d *Client) dial() error { func (d *Client) dial() error {
if d.dialed { if d.dialed {

27
io_windows.go Normal file
View File

@ -0,0 +1,27 @@
package rpcfetch
import (
"errors"
"github.com/Microsoft/go-winio"
"io/fs"
"syscall"
)
var errPipe = syscall.Errno(232)
func (d *Client) dial() error {
if d.dialed {
panic("attempted to dial on open client")
}
if conn, err := winio.DialPipe(`\\.\pipe\discord-ipc-0`, nil); err != nil {
if errors.Is(err, winio.ErrTimeout) || errors.Is(err, fs.ErrNotExist) {
return ErrAgain
}
return err
} else {
d.conn = conn
d.dialed = true
}
return nil
}

3
rpc.go
View File

@ -68,6 +68,9 @@ func (p payload) traceCommand(command string) *string {
} }
func (p payload) nonce(n string) bool { func (p payload) nonce(n string) bool {
if n == "initial-ready" {
return true
}
return p.Nonce == n return p.Nonce == n
} }