package main import ( "log" "unsafe" "github.com/jchv/go-webview2" "golang.org/x/sys/windows" ) var ( user32 = windows.NewLazySystemDLL("user32.dll") procFindWindowW = user32.NewProc("FindWindowW") procFindWindowExW = user32.NewProc("FindWindowExW") procSendMessageTimeoutW = user32.NewProc("SendMessageTimeoutW") procSetParent = user32.NewProc("SetParent") procGetForegroundWindow = user32.NewProc("GetForegroundWindow") procGetWindowRect = user32.NewProc("GetWindowRect") procGetSystemMetrics = user32.NewProc("GetSystemMetrics") procMoveWindow = user32.NewProc("MoveWindow") procShowWindow = user32.NewProc("ShowWindow") procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware") procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") procGetWindowLongPtrW = user32.NewProc("GetWindowLongPtrW") procGetDpiForWindow = user32.NewProc("GetDpiForWindow") procGetClientRect = user32.NewProc("GetClientRect") procMessageBoxW = user32.NewProc("MessageBoxW") procGetMessageW = user32.NewProc("GetMessageW") procPostMessageW = user32.NewProc("PostMessageW") procTranslateMessage = user32.NewProc("TranslateMessage") procDispatchMessageW = user32.NewProc("DispatchMessageW") ) var wv webview2.WebView var wvHwnd uintptr var jsQueue = make(chan string, 64) var paused int32 const ( wmEvalJS = 0x0401 wmSetHtml = 0x0402 ) const ( gwlStyle = uintptr(0xFFFFFFF0) // GWL_STYLE = -16 wsPopup = uintptr(0x80000000) wsVisible = uintptr(0x10000000) wsChild = uintptr(0x02000000) wsSizebox = uintptr(0x00040000) wsMaxbox = uintptr(0x00010000) ) var htmlQueue = make(chan string, 1) var ( classProgman = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Progman"))) classShellDefView = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("SHELLDLL_DefView"))) classWorkerW = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("WorkerW"))) ) func evalJS(js string) { select { case jsQueue <- js: log.Println("evalJS queued:", js[:min(len(js), 60)]) default: log.Println("jsQueue full, dropping eval") } if wvHwnd != 0 { procPostMessageW.Call(wvHwnd, wmEvalJS, 0, 0) } } func findWorkerW() uintptr { progman, _, _ := procFindWindowW.Call( classProgman, 0) if progman == 0 { log.Println("findWorkerW: Progman not found") return 0 } // 发送 0x052C 触发 Progman 创建 WorkerW var result uintptr procSendMessageTimeoutW.Call(progman, 0x052C, 0, 0, 0x0000, 1000, uintptr(unsafe.Pointer(&result))) // 方法1: Progman 下找 SHELLDLL_DefView,再找其后的 WorkerW shellDefView, _, _ := procFindWindowExW.Call(progman, 0, classShellDefView, 0) if shellDefView != 0 { ww, _, _ := procFindWindowExW.Call(progman, shellDefView, classWorkerW, 0) if ww != 0 { log.Printf("findWorkerW: 方法1成功, WorkerW=0x%x", ww) return ww } } // 方法2: 遍历顶层 WorkerW,找到含 SHELLDLL_DefView 的那个,取它后面的 WorkerW var prev uintptr for { ww, _, _ := procFindWindowExW.Call(0, prev, classWorkerW, 0) if ww == 0 { break } child, _, _ := procFindWindowExW.Call(ww, 0, classShellDefView, 0) if child != 0 { // 这个 WorkerW 包含 SHELLDLL_DefView,找它后面的 WorkerW next, _, _ := procFindWindowExW.Call(0, ww, classWorkerW, 0) if next != 0 { log.Printf("findWorkerW: 方法2成功, WorkerW=0x%x (after 0x%x)", next, ww) return next } } prev = ww } // 方法3: 遍历顶层 WorkerW,找任意不含 SHELLDLL_DefView 的可见 WorkerW prev = 0 for { ww, _, _ := procFindWindowExW.Call(0, prev, classWorkerW, 0) if ww == 0 { break } child, _, _ := procFindWindowExW.Call(ww, 0, classShellDefView, 0) if child == 0 { log.Printf("findWorkerW: 方法3成功, WorkerW=0x%x", ww) return ww } prev = ww } // 方法4: 兜底,Progman 下的第一个 WorkerW ww, _, _ := procFindWindowExW.Call(progman, 0, classWorkerW, 0) if ww != 0 { log.Printf("findWorkerW: 方法4(兜底), WorkerW=0x%x", ww) } return ww } func getDPI(hwnd uintptr) int { dpi, _, _ := procGetDpiForWindow.Call(hwnd) if dpi == 0 { return 96 } return int(dpi) } func getScreenSize() (int32, int32) { w, _, _ := procGetSystemMetrics.Call(0) h, _, _ := procGetSystemMetrics.Call(1) return int32(w), int32(h) }