优化: 知识卡片交互+deepseek模型+鼠标钩子
This commit is contained in:
144
win32.go
144
win32.go
@@ -30,6 +30,14 @@ var (
|
||||
procTranslateMessage = user32.NewProc("TranslateMessage")
|
||||
procDispatchMessageW = user32.NewProc("DispatchMessageW")
|
||||
procGetParent = user32.NewProc("GetParent")
|
||||
procCallWindowProcW = user32.NewProc("CallWindowProcW")
|
||||
procSetForegroundWindow = user32.NewProc("SetForegroundWindow")
|
||||
procScreenToClient = user32.NewProc("ScreenToClient")
|
||||
procEnumChildWindows = user32.NewProc("EnumChildWindows")
|
||||
procGetWindow = user32.NewProc("GetWindow")
|
||||
procSetWindowsHookExW = user32.NewProc("SetWindowsHookExW")
|
||||
procCallNextHookEx = user32.NewProc("CallNextHookEx")
|
||||
procWindowFromPoint = user32.NewProc("WindowFromPoint")
|
||||
)
|
||||
|
||||
var wv webview2.WebView
|
||||
@@ -52,14 +60,149 @@ const (
|
||||
wsMaxbox = uintptr(0x00010000)
|
||||
)
|
||||
|
||||
const gwlpWndProc = ^uintptr(3) // GWLP_WNDPROC = -4
|
||||
|
||||
const (
|
||||
wsExTransparent = uintptr(0x00000020)
|
||||
wsExLayered = uintptr(0x00080000)
|
||||
wsExToolwindow = uintptr(0x00000080)
|
||||
)
|
||||
|
||||
const (
|
||||
gwChild = 5 // GW_CHILD
|
||||
gwHwndNext = 2 // GW_HWNDNEXT
|
||||
)
|
||||
|
||||
var htmlQueue = make(chan string, 1)
|
||||
|
||||
// overlay 窗口子类化:动态 hit test
|
||||
var (
|
||||
overlayOldWndProc uintptr
|
||||
overlayWndProcCb uintptr
|
||||
overlayHitRect struct{ Left, Top, Right, Bottom int32 }
|
||||
overlayHasHitRect bool
|
||||
childOldWndProcs = make(map[uintptr]uintptr) // 子窗口 hwnd -> 旧 wndproc
|
||||
)
|
||||
|
||||
// overlayWndProc 拦截 WM_NCHITTEST:交互区域返回 HTCLIENT,其余 HTTRANSPARENT(穿透)
|
||||
// 注意:由于 overlay 在桌面图标层之下,此 wndproc 主要作为备用
|
||||
func overlayWndProc(hwnd, msg, wp, lp uintptr) uintptr {
|
||||
if msg == 0x0084 && overlayHasHitRect { // WM_NCHITTEST
|
||||
x := int32(int16(lp & 0xFFFF))
|
||||
y := int32(int16(lp >> 16))
|
||||
pt := struct{ X, Y int32 }{x, y}
|
||||
procScreenToClient.Call(wvHwnd, uintptr(unsafe.Pointer(&pt)))
|
||||
if pt.X >= overlayHitRect.Left && pt.X <= overlayHitRect.Right &&
|
||||
pt.Y >= overlayHitRect.Top && pt.Y <= overlayHitRect.Bottom {
|
||||
return uintptr(1) // HTCLIENT
|
||||
}
|
||||
return 0xFFFFFFFF // HTTRANSPARENT
|
||||
}
|
||||
// 查找该窗口的旧 wndproc
|
||||
oldProc := overlayOldWndProc
|
||||
if v, ok := childOldWndProcs[hwnd]; ok {
|
||||
oldProc = v
|
||||
}
|
||||
r, _, _ := procCallWindowProcW.Call(oldProc, hwnd, msg, wp, lp)
|
||||
return r
|
||||
}
|
||||
|
||||
// installMouseHook 安装 WH_MOUSE_LL 全局钩子拦截刷新按钮点击
|
||||
// overlay 在 WorkerW 中,位于桌面图标层之下,WM_NCHITTEST 无法到达
|
||||
// 因此用低级鼠标钩子直接在 Go 层处理点击
|
||||
var llMouseHook uintptr
|
||||
var llMouseHookCb uintptr // 防止 GC 回收回调函数
|
||||
var progmanHwnd uintptr // Progman 句柄,用于判断点击是否在桌面
|
||||
var btnHoverActive bool // 按钮悬停状态,避免重复推送 JS
|
||||
|
||||
// isMouseInBtnRect 检查鼠标是否在按钮 hit rect 内(屏幕坐标转 wvHwnd 客户端坐标)
|
||||
func isMouseInBtnRect(screenX, screenY int32) bool {
|
||||
if !overlayHasHitRect {
|
||||
return false
|
||||
}
|
||||
pt := struct{ X, Y int32 }{screenX, screenY}
|
||||
procScreenToClient.Call(wvHwnd, uintptr(unsafe.Pointer(&pt)))
|
||||
return pt.X >= overlayHitRect.Left && pt.X <= overlayHitRect.Right &&
|
||||
pt.Y >= overlayHitRect.Top && pt.Y <= overlayHitRect.Bottom
|
||||
}
|
||||
|
||||
// isDesktopAtPoint 检查屏幕坐标处是否为桌面背景(无其他窗口遮挡)
|
||||
func isDesktopAtPoint(screenX, screenY int32) bool {
|
||||
ptPacked := uintptr(screenX) | (uintptr(uint32(screenY)) << 32)
|
||||
hwndAt, _, _ := procWindowFromPoint.Call(ptPacked)
|
||||
for h := hwndAt; h != 0; h, _, _ = procGetParent.Call(h) {
|
||||
if h == progmanHwnd {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func installMouseHook() {
|
||||
llMouseHookCb = windows.NewCallback(func(nCode, wp, lp uintptr) uintptr {
|
||||
if int32(nCode) >= 0 {
|
||||
type msll struct {
|
||||
X, Y int32
|
||||
MouseData uint32
|
||||
Flags uint32
|
||||
Time uint32
|
||||
DwExtraInfo uintptr
|
||||
}
|
||||
s := (*msll)(unsafe.Pointer(lp))
|
||||
|
||||
switch wp {
|
||||
case 0x0200: // WM_MOUSEMOVE — 悬停检测
|
||||
inBtn := isMouseInBtnRect(s.X, s.Y)
|
||||
if inBtn && !btnHoverActive {
|
||||
btnHoverActive = true
|
||||
evalJS("document.querySelector('.knowledge-refresh-btn')?.classList.add('hover-active')")
|
||||
} else if !inBtn && btnHoverActive {
|
||||
btnHoverActive = false
|
||||
evalJS("document.querySelector('.knowledge-refresh-btn')?.classList.remove('hover-active')")
|
||||
}
|
||||
|
||||
case 0x0201: // WM_LBUTTONDOWN — 点击
|
||||
if !isDesktopAtPoint(s.X, s.Y) {
|
||||
break
|
||||
}
|
||||
if isMouseInBtnRect(s.X, s.Y) {
|
||||
pushKnowledgeLoading("")
|
||||
go fetchAndPushKnowledge()
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
r, _, _ := procCallNextHookEx.Call(llMouseHook, nCode, wp, lp)
|
||||
return r
|
||||
})
|
||||
var llMouseHookVal uintptr
|
||||
llMouseHookVal, _, _ = procSetWindowsHookExW.Call(
|
||||
14, // WH_MOUSE_LL
|
||||
llMouseHookCb, 0, 0,
|
||||
)
|
||||
llMouseHook = llMouseHookVal
|
||||
log.Printf("mouse hook installed, progman=0x%x, hook=0x%x", progmanHwnd, llMouseHookVal)
|
||||
}
|
||||
|
||||
// subclassOverlayWindows 子类化 WebView2 父窗口及其所有子窗口
|
||||
func subclassOverlayWindows() {
|
||||
overlayWndProcCb = windows.NewCallback(overlayWndProc)
|
||||
oldProc, _, _ := procSetWindowLongPtrW.Call(wvHwnd, gwlpWndProc, overlayWndProcCb)
|
||||
overlayOldWndProc = oldProc
|
||||
subclassChildTree(wvHwnd)
|
||||
}
|
||||
|
||||
// subclassChildTree 递归子类化 parent 下的所有子窗口
|
||||
func subclassChildTree(parent uintptr) {
|
||||
child, _, _ := procGetWindow.Call(parent, uintptr(gwChild))
|
||||
for child != 0 {
|
||||
old, _, _ := procSetWindowLongPtrW.Call(child, gwlpWndProc, overlayWndProcCb)
|
||||
childOldWndProcs[child] = old
|
||||
subclassChildTree(child)
|
||||
child, _, _ = procGetWindow.Call(child, uintptr(gwHwndNext))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
classProgman = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Progman")))
|
||||
classShellDefView = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("SHELLDLL_DefView")))
|
||||
@@ -85,6 +228,7 @@ func findWorkerW() uintptr {
|
||||
log.Println("findWorkerW: Progman not found")
|
||||
return 0
|
||||
}
|
||||
progmanHwnd = progman // 保存 Progman 句柄供鼠标钩子判断
|
||||
// 发送 0x052C 触发 Progman 创建 WorkerW
|
||||
var result uintptr
|
||||
procSendMessageTimeoutW.Call(progman, 0x052C, 0, 0, 0x0000, 1000, uintptr(unsafe.Pointer(&result)))
|
||||
|
||||
Reference in New Issue
Block a user