Files
qiweimanager-master/wecom_history_sync_windows.go

189 lines
5.0 KiB
Go

//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}}
}