//go:build windows package main import ( "fmt" "strings" "syscall" "time" "unsafe" "golang.org/x/sys/windows" ) var ( user32 = windowsLazyDLL("user32.dll") procEnumWindows = user32.NewProc("EnumWindows") procGetWindowTextW = user32.NewProc("GetWindowTextW") procGetWindowTextLengthW = user32.NewProc("GetWindowTextLengthW") procIsWindowVisible = user32.NewProc("IsWindowVisible") procSetForegroundWindow = user32.NewProc("SetForegroundWindow") procShowWindow = user32.NewProc("ShowWindow") procGetForegroundWindow = user32.NewProc("GetForegroundWindow") procGetWindowThreadProcess = user32.NewProc("GetWindowThreadProcessId") procAttachThreadInput = user32.NewProc("AttachThreadInput") procGetCurrentThreadID = user32.NewProc("GetCurrentThreadId") procSendInput = user32.NewProc("SendInput") ) const ( swRestore = 9 inputKeyboard = 1 keyeventfKeyUp = 0x0002 vkControl = 0x11 vkA = 0x41 vkC = 0x43 ) type keyboardInput struct { Type uint32 Ki keyboardInputData } type keyboardInputData struct { Vk uint16 Scan uint16 Flags uint32 Time uint32 ExtraInfo uintptr Padding [8]byte } func (a *App) prepareWeComHistoryCopy() (bool, string) { hwnd, title := findWeComWindow() if hwnd == 0 { return false, "没有找到企业微信窗口。请先打开企业微信并进入目标群聊。" } if err := activateWindow(hwnd); err != nil { return false, err.Error() } time.Sleep(250 * time.Millisecond) sendCtrlKey(vkA) time.Sleep(120 * time.Millisecond) sendCtrlKey(vkC) if strings.TrimSpace(title) == "" { title = "企业微信" } return true, fmt.Sprintf("已向企业微信窗口 %q 发送复制命令", title) } func windowsLazyDLL(name string) *syscall.LazyDLL { return syscall.NewLazyDLL(name) } func findWeComWindow() (uintptr, string) { type candidate struct { hwnd uintptr title string score int } var best candidate cb := syscall.NewCallback(func(hwnd uintptr, lparam uintptr) uintptr { if isWindowVisible(hwnd) == 0 { return 1 } title := windowTitle(hwnd) lower := strings.ToLower(title) score := 0 switch { case strings.Contains(title, "企业微信"): score = 100 case strings.Contains(lower, "wxwork"): score = 80 case strings.Contains(lower, "wecom"): score = 70 } if score == 0 && windowProcessName(hwnd) == "wxwork.exe" { score = 90 if title == "" { title = "企业微信" } } if score > best.score { best = candidate{hwnd: hwnd, title: title, score: score} } return 1 }) procEnumWindows.Call(cb, 0) return best.hwnd, best.title } func windowTitle(hwnd uintptr) string { length, _, _ := procGetWindowTextLengthW.Call(hwnd) if length == 0 { return "" } buf := make([]uint16, int(length)+1) procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) return syscall.UTF16ToString(buf) } func windowProcessName(hwnd uintptr) string { var pid uint32 procGetWindowThreadProcess.Call(hwnd, uintptr(unsafe.Pointer(&pid))) if pid == 0 { return "" } handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) if err != nil { return "" } defer windows.CloseHandle(handle) buf := make([]uint16, windows.MAX_PATH) size := uint32(len(buf)) if err := windows.QueryFullProcessImageName(handle, 0, &buf[0], &size); err != nil { return "" } path := strings.ToLower(syscall.UTF16ToString(buf[:size])) idx := strings.LastIndexAny(path, `\/`) if idx >= 0 { return path[idx+1:] } return path } func isWindowVisible(hwnd uintptr) uintptr { ret, _, _ := procIsWindowVisible.Call(hwnd) return ret } func activateWindow(hwnd uintptr) error { procShowWindow.Call(hwnd, swRestore) foreground, _, _ := procGetForegroundWindow.Call() currentThread, _, _ := procGetCurrentThreadID.Call() foregroundThread, _, _ := procGetWindowThreadProcess.Call(foreground, 0) targetThread, _, _ := procGetWindowThreadProcess.Call(hwnd, 0) if foregroundThread != 0 { procAttachThreadInput.Call(currentThread, foregroundThread, 1) defer procAttachThreadInput.Call(currentThread, foregroundThread, 0) } if targetThread != 0 && targetThread != foregroundThread { procAttachThreadInput.Call(currentThread, targetThread, 1) defer procAttachThreadInput.Call(currentThread, targetThread, 0) } ret, _, _ := procSetForegroundWindow.Call(hwnd) if ret == 0 { return fmt.Errorf("无法激活企业微信窗口,请手动切到目标群聊后重试。") } return nil } func sendCtrlKey(vk uint16) { inputs := []keyboardInput{ keyDown(vkControl), keyDown(vk), keyUp(vk), keyUp(vkControl), } procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0])) } func keyDown(vk uint16) keyboardInput { return keyboardInput{Type: inputKeyboard, Ki: keyboardInputData{Vk: vk}} } func keyUp(vk uint16) keyboardInput { return keyboardInput{Type: inputKeyboard, Ki: keyboardInputData{Vk: vk, Flags: keyeventfKeyUp}} }