| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- //go:build windows
- // ============================================================================
- // 系统级鼠标提醒(Windows 原生实现)
- //
- // 需求:平时鼠标是正常样式;有消息来(通过 API 触发)时,鼠标图标闪烁,
- // 且光标下方浮出简短的半透明提示文字,可覆盖任意窗口(系统级)。
- //
- // 实现原理:
- // 1. 全局低层鼠标钩子 WH_MOUSE_LL:捕获系统级鼠标移动,获取屏幕坐标。
- // 2. 一个置顶、透明、点击穿透的覆盖层窗口(WS_EX_LAYERED|WS_EX_TRANSPARENT
- // |WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE),用 GDI UpdateLayeredWindow 在鼠标
- // 位置绘制闪烁光标环与下方半透明文字。
- // 3. 定时器驱动闪烁:普通光标样式 <-> 提醒光标样式交替。
- // 4. 提醒可自动消退(默认 8 秒),也可再调 API 停止。
- //
- // 说明:本文件仅在 Windows 编译。其他平台见 mousealert_other.go 空实现。
- // ============================================================================
- package main
- import (
- "sync"
- "syscall"
- "time"
- "unsafe"
- )
- // Windows API 常量
- const (
- whMouseLL = 14 // WH_MOUSE_LL 全局低层鼠标钩子
- wmTimer = 0x0113
- wmDestroy = 0x0002
- wmNcDestroy = 0x0082
- wndExLayered = 0x00080000 // WS_EX_LAYERED 分层窗口(支持透明)
- wndExTrans = 0x00000020 // WS_EX_TRANSPARENT 点击穿透
- wndExTool = 0x00000080 // WS_EX_TOOLWINDOW 不占任务栏
- wndExTopMost = 0x00000008 // WS_EX_TOPMOST 置顶
- wndExNoActivate = 0x08000000 // WS_EX_NOACTIVATE 不抢焦点
- swpNoActivate = 0x0010 // SWP_NOACTIVATE
- swpShowWindow = 0x0040 // SWP_SHOWWINDOW
- biRgb = 0 // BI_RGB
- timerFlash = 1 // 闪烁定时器 ID
- overlayWidth = 220
- overlayHeight = 90
- textTopY = 26 // 文字起始 Y 坐标(覆盖层内),用于区分半透明文字区
- alertDuration = 8 * time.Second
- flashIntervalMs = 250 // 闪烁周期 250ms
- )
- // 光标提示样式:普通状态 vs 提醒状态(闪烁交替)
- const (
- flashColorNormal = 0x00FFFFFF // 普通:白
- flashColorAlert = 0x00FF5050 // 提醒:红
- )
- // hwndTopMost HWND_TOPMOST = (HWND)-1
- const hwndTopMost uintptr = ^uintptr(0)
- // ------------------------------------------------------------------------
- // Windows 结构体与函数指针声明(用 syscall 直接调用,避免引入额外依赖)
- // ------------------------------------------------------------------------
- type point struct {
- X int32
- Y int32
- }
- // rect Windows RECT 结构(GetClientRect 输出)
- type rect struct {
- Left int32
- Top int32
- Right int32
- Bottom int32
- }
- type msg struct {
- HWND uintptr
- Message uint32
- WParam uintptr
- LParam uintptr
- Time uint32
- Pt point
- LPrivate uint32
- }
- type msllhookstruct struct {
- Pt point
- MouseData uint32
- Flags uint32
- Time uint32
- DWExtraInfo uintptr
- }
- type wndclassex struct {
- CbSize uint32
- Style uint32
- LpfnWndProc uintptr
- CbClsExtra int32
- CbWndExtra int32
- HInstance uintptr
- HIcon uintptr
- HCursor uintptr
- HbrBackground uintptr
- LpszMenuName *uint16
- LpszClassName *uint16
- HIconSm uintptr
- }
- type bitmapinfoheader struct {
- BiSize uint32
- BiWidth int32
- BiHeight int32
- BiPlanes uint16
- BiBitCount uint16
- BiCompression uint32
- BiSizeImage uint32
- BiXPelsPerMeter int32
- BiYPelsPerMeter int32
- BiClrUsed uint32
- BiClrImportant uint32
- }
- type blendfunction struct {
- BlendOp byte
- BlendFlags byte
- SourceConstantAlpha byte
- AlphaFormat byte
- }
- // 延迟加载的 user32.dll / gdi32.dll / kernel32.dll 函数
- var (
- user32 = syscall.NewLazyDLL("user32.dll")
- gdi32 = syscall.NewLazyDLL("gdi32.dll")
- kernel32 = syscall.NewLazyDLL("kernel32.dll")
- procSetWindowsHookEx = user32.NewProc("SetWindowsHookExW")
- procUnhookWindowsHook = user32.NewProc("UnhookWindowsHookEx")
- procGetMessage = user32.NewProc("GetMessageW")
- procTranslateMessage = user32.NewProc("TranslateMessage")
- procDispatchMessage = user32.NewProc("DispatchMessageW")
- procRegisterClassEx = user32.NewProc("RegisterClassExW")
- procCreateWindowEx = user32.NewProc("CreateWindowExW")
- procDefWindowProc = user32.NewProc("DefWindowProcW")
- procDestroyWindow = user32.NewProc("DestroyWindow")
- procSetTimer = user32.NewProc("SetTimer")
- procKillTimer = user32.NewProc("KillTimer")
- procShowWindow = user32.NewProc("ShowWindow")
- procSetWindowPos = user32.NewProc("SetWindowPos")
- procUpdateLayeredWindow = user32.NewProc("UpdateLayeredWindow")
- procGetDC = user32.NewProc("GetDC")
- procReleaseDC = user32.NewProc("ReleaseDC")
- procGetCursorPos = user32.NewProc("GetCursorPos")
- procPostMessage = user32.NewProc("PostMessageW")
- procGetClientRect = user32.NewProc("GetClientRect")
- procGetWindowRect = user32.NewProc("GetWindowRect")
- procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
- procDeleteDC = gdi32.NewProc("DeleteDC")
- procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
- procSelectObject = gdi32.NewProc("SelectObject")
- procDeleteObject = gdi32.NewProc("DeleteObject")
- procSetBkMode = gdi32.NewProc("SetBkMode")
- procSetTextColor = gdi32.NewProc("SetTextColor")
- procSetTextAlign = gdi32.NewProc("SetTextAlign")
- procTextOutW = gdi32.NewProc("TextOutW")
- procCreateFontW = gdi32.NewProc("CreateFontW")
- procSetPixel = gdi32.NewProc("SetPixel")
- procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
- )
- // ------------------------------------------------------------------------
- // 鼠标提醒运行时状态
- // ------------------------------------------------------------------------
- // mouseAlertState 鼠标提醒的运行时状态(单实例)
- type mouseAlertState struct {
- mu sync.Mutex
- started bool // 工作线程是否已启动(持久化,仅启动一次)
- hook uintptr
- overlayWnd uintptr
- active bool
- text string
- flashOn bool
- lastX, lastY int32
- stopTime time.Time
- hookProc uintptr
- wndProc uintptr
- classOK bool
- }
- var mouseAlert = &mouseAlertState{}
- // triggerMouseAlert 触发鼠标提醒:显示闪烁光标 + 光标下方透明文字。
- // 文字为空时用默认提示。重复触发会刷新提醒计时并立即显示。
- //
- // 定位策略:主动用 GetCursorPos 读取当前鼠标位置并记录为 lastX/lastY,
- // 再通过 PostMessage 唤醒窗口线程立即重绘,避免依赖全局钩子回调(钩子
- // 回调因负担重可能被系统卸载,导致窗口停留在旧位置不跟随鼠标)。
- func triggerMouseAlert(text string) {
- if text == "" {
- text = "有新消息提醒"
- }
- // 读取当前鼠标位置,供窗口重绘时定位(不依赖钩子回调)
- var pt point
- curRet, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt)))
- hasCursor := curRet != 0
- mouseAlert.mu.Lock()
- mouseAlert.text = text
- mouseAlert.active = true
- mouseAlert.flashOn = true
- mouseAlert.stopTime = time.Now().Add(alertDuration)
- if hasCursor {
- mouseAlert.lastX, mouseAlert.lastY = pt.X, pt.Y
- }
- started := mouseAlert.started
- mouseAlert.started = true
- overlay := mouseAlert.overlayWnd
- mouseAlert.mu.Unlock()
- // 首次触发:启动常驻工作线程(幂等),建窗后由线程主动定位并重绘
- if !started {
- go mouseAlertLoop()
- return
- }
- // 已启动:唤醒窗口线程立即重绘(窗口线程负责 SetWindowPos/绘制,
- // 避免跨线程操作窗口;若窗口尚在初始化,线程建窗后也会主动绘制)
- if overlay != 0 {
- procShowWindow.Call(overlay, 5) // SW_SHOW
- procPostMessage.Call(overlay, wmTimer, timerFlash, 0)
- }
- }
- // stopMouseAlert 立即停止鼠标提醒(隐藏覆盖层;钩子保持常驻)
- func stopMouseAlert() {
- mouseAlert.mu.Lock()
- mouseAlert.active = false
- overlay := mouseAlert.overlayWnd
- mouseAlert.mu.Unlock()
- if overlay != 0 {
- procShowWindow.Call(overlay, 0) // SW_HIDE
- }
- }
- // mouseAlertLoop 常驻工作线程:注册窗口类、安装全局钩子、创建覆盖层窗口,
- // 进入消息循环。低层钩子必须有消息循环,故全程运行在独立线程。
- func mouseAlertLoop() {
- hInst, _, _ := procGetModuleHandleW.Call(0)
- mouseAlert.mu.Lock()
- className, _ := syscall.UTF16PtrFromString("TengTuMouseAlertOverlay")
- wc := wndclassex{
- CbSize: uint32(unsafe.Sizeof(wndclassex{})),
- LpfnWndProc: mouseAlert.wndProcAddr(),
- HInstance: hInst,
- LpszClassName: className,
- HCursor: 0,
- }
- atom, _, _ := procRegisterClassEx.Call(uintptr(unsafe.Pointer(&wc)))
- if atom != 0 {
- mouseAlert.classOK = true
- }
- mouseAlert.mu.Unlock()
- // 安装全局低层鼠标钩子
- hookProc := mouseAlert.hookProcAddr()
- hHook, _, _ := procSetWindowsHookEx.Call(whMouseLL, hookProc, 0, 0)
- if hHook != 0 {
- mouseAlert.mu.Lock()
- mouseAlert.hook = hHook
- mouseAlert.mu.Unlock()
- }
- // 创建覆盖层窗口(初始 220x90,位置随后由钩子/定时器设置)
- style := uintptr(wndExLayered | wndExTrans | wndExTool | wndExTopMost | wndExNoActivate)
- overlay, _, _ := procCreateWindowEx.Call(
- style,
- uintptr(unsafe.Pointer(className)),
- 0,
- 0, 0, overlayWidth, overlayHeight, 0,
- 0, 0, hInst, 0,
- )
- mouseAlert.mu.Lock()
- mouseAlert.overlayWnd = overlay
- mouseAlert.mu.Unlock()
- // 启动闪烁定时器
- if overlay != 0 {
- procSetTimer.Call(overlay, timerFlash, flashIntervalMs, 0)
- // 建窗后立即用当前鼠标位置定位并重绘一次,保证首次触发即可见,
- // 不依赖全局钩子回调(钩子回调在系统线程执行,仅做转发)。
- var pt point
- curRet, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt)))
- if curRet != 0 {
- mouseAlert.mu.Lock()
- if mouseAlert.active {
- mouseAlert.lastX, mouseAlert.lastY = pt.X, pt.Y
- text := mouseAlert.text
- flashOn := mouseAlert.flashOn
- mouseAlert.mu.Unlock()
- mouseAlert.renderOverlay(overlay, text, flashOn, pt.X, pt.Y)
- } else {
- mouseAlert.mu.Unlock()
- }
- }
- }
- // 消息循环(常驻,直到线程退出)
- var m msg
- for {
- ret, _, _ := procGetMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
- if ret == 0 {
- break // WM_QUIT
- }
- procTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
- procDispatchMessage.Call(uintptr(unsafe.Pointer(&m)))
- }
- // 清理
- if overlay != 0 {
- procDestroyWindow.Call(overlay)
- }
- if hHook != 0 {
- procUnhookWindowsHook.Call(hHook)
- }
- mouseAlert.mu.Lock()
- mouseAlert.hook = 0
- mouseAlert.overlayWnd = 0
- mouseAlert.mu.Unlock()
- }
- // hookProcAddr 返回全局钩子回调地址(保持 Go 函数引用)
- func (s *mouseAlertState) hookProcAddr() uintptr {
- if s.hookProc != 0 {
- return s.hookProc
- }
- s.hookProc = syscall.NewCallback(s.hookCallback)
- return s.hookProc
- }
- // wndProcAddr 返回覆盖层窗口过程地址
- func (s *mouseAlertState) wndProcAddr() uintptr {
- if s.wndProc != 0 {
- return s.wndProc
- }
- s.wndProc = syscall.NewCallback(s.wndProcCallback)
- return s.wndProc
- }
- // hookCallback 全局低层鼠标钩子回调:仅转发给下一钩子,不访问任何 Go 状态。
- //
- // 重要:此回调在系统创建的钩子线程上执行,且 CGO 启用时 syscall.NewCallback
- // 的回调从该线程进入 Go runtime。此前的实现在此处访问 s.mu(sync.Mutex 锁)、
- // 调用 time.Now()、procShowWindow 等 Go 状态操作,会导致进程在钩子回调触发时
- // 静默退出(app.Run 返回,exit 0)。故此处只做 CallNextHookEx 转发,不做任何
- // 其他工作;鼠标位置跟踪与到期判断全部移到窗口线程的定时器(wndProcCallback
- // 的 wmTimer 分支,用 GetCursorPos 主动读取),避免在钩子线程做 Go 操作。
- func (s *mouseAlertState) hookCallback(nCode int, wParam uintptr, lParam uintptr) uintptr {
- ret, _, _ := procCallNextHookEx.Call(0, uintptr(nCode), wParam, lParam)
- return ret
- }
- // renderOverlay 定位覆盖层到鼠标位置并重绘闪烁光标 + 下方透明文字。
- // 在窗口线程内调用(mouseAlertLoop 建窗后 / wndProcCallback 定时器),
- // 窗口/DC 操作均为同线程,避免跨线程操作窗口导致绘制失败。
- func (s *mouseAlertState) renderOverlay(overlay uintptr, text string, flashOn bool, x, y int32) {
- if overlay == 0 {
- return
- }
- // 让覆盖层左上角位于鼠标位置偏左上,使光标环中心对准鼠标
- left := x - 10
- top := y - 4
- procSetWindowPos.Call(overlay, hwndTopMost,
- uintptr(int32(left)), uintptr(int32(top)),
- overlayWidth, overlayHeight, swpNoActivate|swpShowWindow)
- s.drawLayered(overlay, text, flashOn)
- }
- // drawLayered 用 32 位 DIB + UpdateLayeredWindow 绘制闪烁光标环与半透明文字。
- func (s *mouseAlertState) drawLayered(overlay uintptr, text string, flashOn bool) {
- // 用窗口实际总尺寸创建 DIB,并用 GetWindowRect 获取窗口屏幕位置,
- // 显式传给 UpdateLayeredWindow 的 pptDst/psize,确保 DIB 与窗口完全
- // 匹配(此前用 GetClientRect 且 psize=nil,当 DIB 尺寸与窗口实际尺寸
- // 不一致时 UpdateLayeredWindow 绘制失效,导致覆盖层显示为空白)。
- var wr rect
- procGetWindowRect.Call(overlay, uintptr(unsafe.Pointer(&wr)))
- w := int(wr.Right - wr.Left)
- h := int(wr.Bottom - wr.Top)
- if w < 8 || h < 8 { // 兜底:窗口尺寸异常时用默认尺寸
- w, h = overlayWidth, overlayHeight
- }
- hdcScreen, _, _ := procGetDC.Call(0)
- if hdcScreen == 0 {
- return
- }
- defer procReleaseDC.Call(0, hdcScreen)
- hdcMem, _, _ := procCreateCompatibleDC.Call(hdcScreen)
- if hdcMem == 0 {
- return
- }
- defer procDeleteDC.Call(hdcMem)
- // 32 位 DIB 位图(负高度:自顶向下)
- bi := bitmapinfoheader{}
- bi.BiSize = 40 // sizeof(BITMAPINFOHEADER)
- bi.BiWidth = int32(w)
- bi.BiHeight = int32(-h)
- bi.BiPlanes = 1
- bi.BiBitCount = 32
- bi.BiCompression = biRgb
- var bits unsafe.Pointer
- hBitmap, _, _ := procCreateDIBSection.Call(hdcScreen,
- uintptr(unsafe.Pointer(&bi)), 0, uintptr(unsafe.Pointer(&bits)), 0, 0)
- if hBitmap == 0 {
- return
- }
- defer procDeleteObject.Call(hBitmap)
- oldBmp, _, _ := procSelectObject.Call(hdcMem, hBitmap)
- // 清空为全透明
- clearPixels(bits, w, h)
- // 光标环中心放在覆盖层 (10, 4) 附近,即鼠标位置
- drawCursorRing(hdcMem, 10, 4, flashOn)
- // 光标下方半透明文字
- if text != "" {
- drawAlertText(hdcMem, text, 10, textTopY)
- }
- // GDI 绘制到 DIB 不写 alpha 通道,此处按亮度补全 alpha,
- // 使图形/文字边缘抗锯齿平滑(文字区半透明)
- setAlphaFromLuma(bits, w, h)
- procSelectObject.Call(hdcMem, oldBmp)
- // UpdateLayeredWindow:显式传 pptDst(窗口屏幕左上角)与 psize(窗口尺寸),
- // 使 DIB 内容精确定位到窗口,避免 psize=nil 时因尺寸不匹配绘制失效。
- var blend blendfunction
- blend.BlendOp = 0 // AC_SRC_OVER
- blend.AlphaFormat = 0x01 // AC_SRC_ALPHA
- blend.SourceConstantAlpha = 255
- var dstPt point
- dstPt.X = wr.Left
- dstPt.Y = wr.Top
- var szPt point
- szPt.X = int32(w)
- szPt.Y = int32(h)
- procUpdateLayeredWindow.Call(overlay, hdcScreen,
- uintptr(unsafe.Pointer(&dstPt)), // pptDst = 窗口左上角屏幕坐标
- uintptr(unsafe.Pointer(&szPt)), // psize = 窗口尺寸
- hdcMem, 0, 0, uintptr(unsafe.Pointer(&blend)), 0x00000002 /*ULW_ALPHA*/)
- }
- // clearPixels 把 DIB 位图全部置为透明(ARGB=0)
- func clearPixels(bits unsafe.Pointer, w, h int) {
- if bits == nil {
- return
- }
- total := uintptr(w) * uintptr(h) * 4
- for i := uintptr(0); i+8 <= total; i += 8 {
- *(*uint64)(unsafe.Add(bits, i)) = 0
- }
- }
- // setAlphaFromLuma 按亮度补全 alpha:非透明像素的 alpha = max(r,g,b),
- // 文字区(y >= textTopY)乘以 3/4 实现半透明。
- func setAlphaFromLuma(bits unsafe.Pointer, w, h int) {
- if bits == nil {
- return
- }
- for y := 0; y < h; y++ {
- for x := 0; x < w; x++ {
- p := (*uint32)(unsafe.Add(bits, uintptr(y*w+x)*4))
- v := *p
- b := uint8(v)
- g := uint8(v >> 8)
- r := uint8(v >> 16)
- m := uint32(r)
- if uint32(g) > m {
- m = uint32(g)
- }
- if uint32(b) > m {
- m = uint32(b)
- }
- if m == 0 {
- *p = 0 // 保持透明
- continue
- }
- if y >= textTopY {
- m = m * 3 / 4 // 文字半透明
- }
- *p = v&0x00FFFFFF | (m << 24) // 写入 alpha
- }
- }
- }
- // drawCursorRing 在覆盖层坐标 (cx,cy) 画闪烁光标环(提醒样式)
- func drawCursorRing(hdc uintptr, cx, cy int32, flashOn bool) {
- color := flashColorNormal
- if flashOn {
- color = flashColorAlert
- }
- const r = 12
- for angle := 0; angle < 360; angle += 4 {
- rad := float64(angle) * 3.14159265 / 180.0
- px := cx + int32(float64(r)*cos(rad))
- py := cy + int32(float64(r)*sin(rad))
- procSetPixel.Call(hdc, uintptr(px), uintptr(py), uintptr(color))
- }
- // 中心小点
- procSetPixel.Call(hdc, uintptr(cx), uintptr(cy), uintptr(color))
- }
- // drawAlertText 在 (x,y) 绘制白色提示文字(抗锯齿边缘由 setAlphaFromLuma 处理)
- func drawAlertText(hdc uintptr, text string, x, y int32) {
- // CreateFontW 参数顺序:nHeight,nWidth,nEscapement,nOrientation,nWeight,
- // bItalic,bUnderline,bStrikeOut,iCharSet,iOutPrecision,iClipPrecision,
- // iQuality,iPitchAndFamily,pszFaceName。nHeight=-16 表示 16 像素高。
- var height int32 = -16
- hFont, _, _ := procCreateFontW.Call(
- uintptr(height), 0, 0, 0, 400, // 高16、粗体
- 0, 0, 0, 134, // 非斜体、GB2312_CHARSET
- 0, 0, 4, 2, // 抗锯齿、DEFAULT_PITCH
- 0, // lpszFace=NULL 用系统默认字体
- )
- if hFont == 0 {
- return
- }
- defer procDeleteObject.Call(hFont)
- oldFont, _, _ := procSelectObject.Call(hdc, hFont)
- procSetBkMode.Call(hdc, 1) // TRANSPARENT
- procSetTextColor.Call(hdc, 0x00FFFFFF) // 白
- procSetTextAlign.Call(hdc, 0) // TA_LEFT
- textPtr, _ := syscall.UTF16PtrFromString(text)
- procTextOutW.Call(hdc, uintptr(x), uintptr(y), uintptr(unsafe.Pointer(textPtr)), uintptr(len([]rune(text))))
- procSelectObject.Call(hdc, oldFont)
- }
- // wndProcCallback 覆盖层窗口过程:处理定时器(闪烁)与销毁
- func (s *mouseAlertState) wndProcCallback(hwnd uintptr, msg uint32, wParam uintptr, lParam uintptr) uintptr {
- switch msg {
- case wmTimer:
- if uintptr(wParam) == timerFlash {
- // 主动用 GetCursorPos 读取当前鼠标位置(窗口线程,安全)。
- // 不再依赖全局钩子回调更新 lastX/lastY(钩子回调在系统线程
- // 执行,任何 Go 状态操作都会导致进程退出,故仅做转发)。
- var pt point
- curRet, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt)))
- s.mu.Lock()
- if s.active {
- if curRet != 0 {
- s.lastX, s.lastY = pt.X, pt.Y
- }
- // 到期判断(原先在钩子回调,现移到窗口线程)
- if !s.stopTime.IsZero() && time.Now().After(s.stopTime) {
- s.active = false
- s.mu.Unlock()
- procShowWindow.Call(hwnd, 0) // SW_HIDE
- return 0
- }
- s.flashOn = !s.flashOn
- text := s.text
- flashOn := s.flashOn
- x, y := s.lastX, s.lastY
- s.mu.Unlock()
- s.renderOverlay(hwnd, text, flashOn, x, y)
- return 0
- }
- s.mu.Unlock()
- }
- return 0
- case wmDestroy, wmNcDestroy:
- procKillTimer.Call(hwnd, timerFlash)
- return 0
- }
- ret, _, _ := procDefWindowProc.Call(hwnd, uintptr(msg), wParam, lParam)
- return ret
- }
- // procCallNextHookEx 钩子链传递
- var procCallNextHookEx = user32.NewProc("CallNextHookEx")
- // cos / sin 简单实现(避免依赖 math 的浮点精度问题)
- func cos(rad float64) float64 {
- r := rad
- for r > 3.14159265 {
- r -= 6.28318530
- }
- for r < -3.14159265 {
- r += 6.28318530
- }
- r2 := r * r
- return 1 - r2/2 + r2*r2/24 - r2*r2*r2/720
- }
- func sin(rad float64) float64 {
- r := rad
- for r > 3.14159265 {
- r -= 6.28318530
- }
- for r < -3.14159265 {
- r += 6.28318530
- }
- r2 := r * r
- return r - r2*r/6 + r2*r2*r/120 - r2*r2*r2*r/5040
- }
|