mousealert_windows.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. //go:build windows
  2. // ============================================================================
  3. // 系统级鼠标提醒(Windows 原生实现)
  4. //
  5. // 需求:平时鼠标是正常样式;有消息来(通过 API 触发)时,鼠标图标闪烁,
  6. // 且光标下方浮出简短的半透明提示文字,可覆盖任意窗口(系统级)。
  7. //
  8. // 实现原理:
  9. // 1. 全局低层鼠标钩子 WH_MOUSE_LL:捕获系统级鼠标移动,获取屏幕坐标。
  10. // 2. 一个置顶、透明、点击穿透的覆盖层窗口(WS_EX_LAYERED|WS_EX_TRANSPARENT
  11. // |WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE),用 GDI UpdateLayeredWindow 在鼠标
  12. // 位置绘制闪烁光标环与下方半透明文字。
  13. // 3. 定时器驱动闪烁:普通光标样式 <-> 提醒光标样式交替。
  14. // 4. 提醒可自动消退(默认 8 秒),也可再调 API 停止。
  15. //
  16. // 说明:本文件仅在 Windows 编译。其他平台见 mousealert_other.go 空实现。
  17. // ============================================================================
  18. package main
  19. import (
  20. "sync"
  21. "syscall"
  22. "time"
  23. "unsafe"
  24. )
  25. // Windows API 常量
  26. const (
  27. whMouseLL = 14 // WH_MOUSE_LL 全局低层鼠标钩子
  28. wmTimer = 0x0113
  29. wmDestroy = 0x0002
  30. wmNcDestroy = 0x0082
  31. wndExLayered = 0x00080000 // WS_EX_LAYERED 分层窗口(支持透明)
  32. wndExTrans = 0x00000020 // WS_EX_TRANSPARENT 点击穿透
  33. wndExTool = 0x00000080 // WS_EX_TOOLWINDOW 不占任务栏
  34. wndExTopMost = 0x00000008 // WS_EX_TOPMOST 置顶
  35. wndExNoActivate = 0x08000000 // WS_EX_NOACTIVATE 不抢焦点
  36. swpNoActivate = 0x0010 // SWP_NOACTIVATE
  37. swpShowWindow = 0x0040 // SWP_SHOWWINDOW
  38. biRgb = 0 // BI_RGB
  39. timerFlash = 1 // 闪烁定时器 ID
  40. overlayWidth = 220
  41. overlayHeight = 90
  42. textTopY = 26 // 文字起始 Y 坐标(覆盖层内),用于区分半透明文字区
  43. alertDuration = 8 * time.Second
  44. flashIntervalMs = 250 // 闪烁周期 250ms
  45. )
  46. // 光标提示样式:普通状态 vs 提醒状态(闪烁交替)
  47. const (
  48. flashColorNormal = 0x00FFFFFF // 普通:白
  49. flashColorAlert = 0x00FF5050 // 提醒:红
  50. )
  51. // hwndTopMost HWND_TOPMOST = (HWND)-1
  52. const hwndTopMost uintptr = ^uintptr(0)
  53. // ------------------------------------------------------------------------
  54. // Windows 结构体与函数指针声明(用 syscall 直接调用,避免引入额外依赖)
  55. // ------------------------------------------------------------------------
  56. type point struct {
  57. X int32
  58. Y int32
  59. }
  60. type msg struct {
  61. HWND uintptr
  62. Message uint32
  63. WParam uintptr
  64. LParam uintptr
  65. Time uint32
  66. Pt point
  67. LPrivate uint32
  68. }
  69. type msllhookstruct struct {
  70. Pt point
  71. MouseData uint32
  72. Flags uint32
  73. Time uint32
  74. DWExtraInfo uintptr
  75. }
  76. type wndclassex struct {
  77. CbSize uint32
  78. Style uint32
  79. LpfnWndProc uintptr
  80. CbClsExtra int32
  81. CbWndExtra int32
  82. HInstance uintptr
  83. HIcon uintptr
  84. HCursor uintptr
  85. HbrBackground uintptr
  86. LpszMenuName *uint16
  87. LpszClassName *uint16
  88. HIconSm uintptr
  89. }
  90. type bitmapinfoheader struct {
  91. BiSize uint32
  92. BiWidth int32
  93. BiHeight int32
  94. BiPlanes uint16
  95. BiBitCount uint16
  96. BiCompression uint32
  97. BiSizeImage uint32
  98. BiXPelsPerMeter int32
  99. BiYPelsPerMeter int32
  100. BiClrUsed uint32
  101. BiClrImportant uint32
  102. }
  103. type blendfunction struct {
  104. BlendOp byte
  105. BlendFlags byte
  106. SourceConstantAlpha byte
  107. AlphaFormat byte
  108. }
  109. // 延迟加载的 user32.dll / gdi32.dll / kernel32.dll 函数
  110. var (
  111. user32 = syscall.NewLazyDLL("user32.dll")
  112. gdi32 = syscall.NewLazyDLL("gdi32.dll")
  113. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  114. procSetWindowsHookEx = user32.NewProc("SetWindowsHookExW")
  115. procUnhookWindowsHook = user32.NewProc("UnhookWindowsHookEx")
  116. procGetMessage = user32.NewProc("GetMessageW")
  117. procTranslateMessage = user32.NewProc("TranslateMessage")
  118. procDispatchMessage = user32.NewProc("DispatchMessageW")
  119. procRegisterClassEx = user32.NewProc("RegisterClassExW")
  120. procCreateWindowEx = user32.NewProc("CreateWindowExW")
  121. procDefWindowProc = user32.NewProc("DefWindowProcW")
  122. procDestroyWindow = user32.NewProc("DestroyWindow")
  123. procSetTimer = user32.NewProc("SetTimer")
  124. procKillTimer = user32.NewProc("KillTimer")
  125. procShowWindow = user32.NewProc("ShowWindow")
  126. procSetWindowPos = user32.NewProc("SetWindowPos")
  127. procUpdateLayeredWindow = user32.NewProc("UpdateLayeredWindow")
  128. procGetDC = user32.NewProc("GetDC")
  129. procReleaseDC = user32.NewProc("ReleaseDC")
  130. procGetCursorPos = user32.NewProc("GetCursorPos")
  131. procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
  132. procDeleteDC = gdi32.NewProc("DeleteDC")
  133. procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
  134. procSelectObject = gdi32.NewProc("SelectObject")
  135. procDeleteObject = gdi32.NewProc("DeleteObject")
  136. procSetBkMode = gdi32.NewProc("SetBkMode")
  137. procSetTextColor = gdi32.NewProc("SetTextColor")
  138. procSetTextAlign = gdi32.NewProc("SetTextAlign")
  139. procTextOutW = gdi32.NewProc("TextOutW")
  140. procCreateFontW = gdi32.NewProc("CreateFontW")
  141. procSetPixel = gdi32.NewProc("SetPixel")
  142. procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
  143. )
  144. // ------------------------------------------------------------------------
  145. // 鼠标提醒运行时状态
  146. // ------------------------------------------------------------------------
  147. // mouseAlertState 鼠标提醒的运行时状态(单实例)
  148. type mouseAlertState struct {
  149. mu sync.Mutex
  150. started bool // 工作线程是否已启动(持久化,仅启动一次)
  151. hook uintptr
  152. overlayWnd uintptr
  153. active bool
  154. text string
  155. flashOn bool
  156. lastX, lastY int32
  157. stopTime time.Time
  158. hookProc uintptr
  159. wndProc uintptr
  160. classOK bool
  161. }
  162. var mouseAlert = &mouseAlertState{}
  163. // triggerMouseAlert 触发鼠标提醒:显示闪烁光标 + 光标下方透明文字。
  164. // 文字为空时用默认提示。重复触发会刷新提醒计时并立即显示。
  165. func triggerMouseAlert(text string) {
  166. mouseAlert.mu.Lock()
  167. if text == "" {
  168. text = "有新消息提醒"
  169. }
  170. mouseAlert.text = text
  171. mouseAlert.active = true
  172. mouseAlert.flashOn = true
  173. mouseAlert.stopTime = time.Now().Add(alertDuration)
  174. started := mouseAlert.started
  175. mouseAlert.started = true
  176. overlay := mouseAlert.overlayWnd
  177. mouseAlert.mu.Unlock()
  178. // 首次触发:启动常驻工作线程(幂等,之后不再重复创建)
  179. if !started {
  180. go mouseAlertLoop()
  181. return
  182. }
  183. // 已启动:立即显示窗口(位置由钩子回调或定时器补充)
  184. if overlay != 0 {
  185. procShowWindow.Call(overlay, 5) // SW_SHOW
  186. }
  187. }
  188. // stopMouseAlert 立即停止鼠标提醒(隐藏覆盖层;钩子保持常驻)
  189. func stopMouseAlert() {
  190. mouseAlert.mu.Lock()
  191. mouseAlert.active = false
  192. overlay := mouseAlert.overlayWnd
  193. mouseAlert.mu.Unlock()
  194. if overlay != 0 {
  195. procShowWindow.Call(overlay, 0) // SW_HIDE
  196. }
  197. }
  198. // mouseAlertLoop 常驻工作线程:注册窗口类、安装全局钩子、创建覆盖层窗口,
  199. // 进入消息循环。低层钩子必须有消息循环,故全程运行在独立线程。
  200. func mouseAlertLoop() {
  201. hInst, _, _ := procGetModuleHandleW.Call(0)
  202. mouseAlert.mu.Lock()
  203. className, _ := syscall.UTF16PtrFromString("TengTuMouseAlertOverlay")
  204. wc := wndclassex{
  205. CbSize: uint32(unsafe.Sizeof(wndclassex{})),
  206. LpfnWndProc: mouseAlert.wndProcAddr(),
  207. HInstance: hInst,
  208. LpszClassName: className,
  209. HCursor: 0,
  210. }
  211. atom, _, _ := procRegisterClassEx.Call(uintptr(unsafe.Pointer(&wc)))
  212. if atom != 0 {
  213. mouseAlert.classOK = true
  214. }
  215. mouseAlert.mu.Unlock()
  216. // 安装全局低层鼠标钩子
  217. hookProc := mouseAlert.hookProcAddr()
  218. hHook, _, _ := procSetWindowsHookEx.Call(whMouseLL, hookProc, 0, 0)
  219. if hHook != 0 {
  220. mouseAlert.mu.Lock()
  221. mouseAlert.hook = hHook
  222. mouseAlert.mu.Unlock()
  223. }
  224. // 创建覆盖层窗口(初始 220x90,位置随后由钩子/定时器设置)
  225. style := uintptr(wndExLayered | wndExTrans | wndExTool | wndExTopMost | wndExNoActivate)
  226. overlay, _, _ := procCreateWindowEx.Call(
  227. style,
  228. uintptr(unsafe.Pointer(className)),
  229. 0,
  230. 0, 0, overlayWidth, overlayHeight, 0,
  231. 0, 0, hInst, 0,
  232. )
  233. mouseAlert.mu.Lock()
  234. mouseAlert.overlayWnd = overlay
  235. mouseAlert.mu.Unlock()
  236. // 启动闪烁定时器
  237. if overlay != 0 {
  238. procSetTimer.Call(overlay, timerFlash, flashIntervalMs, 0)
  239. }
  240. // 消息循环(常驻,直到线程退出)
  241. var m msg
  242. for {
  243. ret, _, _ := procGetMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
  244. if ret == 0 {
  245. break // WM_QUIT
  246. }
  247. procTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
  248. procDispatchMessage.Call(uintptr(unsafe.Pointer(&m)))
  249. }
  250. // 清理
  251. if overlay != 0 {
  252. procDestroyWindow.Call(overlay)
  253. }
  254. if hHook != 0 {
  255. procUnhookWindowsHook.Call(hHook)
  256. }
  257. mouseAlert.mu.Lock()
  258. mouseAlert.hook = 0
  259. mouseAlert.overlayWnd = 0
  260. mouseAlert.mu.Unlock()
  261. }
  262. // hookProcAddr 返回全局钩子回调地址(保持 Go 函数引用)
  263. func (s *mouseAlertState) hookProcAddr() uintptr {
  264. if s.hookProc != 0 {
  265. return s.hookProc
  266. }
  267. s.hookProc = syscall.NewCallback(s.hookCallback)
  268. return s.hookProc
  269. }
  270. // wndProcAddr 返回覆盖层窗口过程地址
  271. func (s *mouseAlertState) wndProcAddr() uintptr {
  272. if s.wndProc != 0 {
  273. return s.wndProc
  274. }
  275. s.wndProc = syscall.NewCallback(s.wndProcCallback)
  276. return s.wndProc
  277. }
  278. // hookCallback 全局低层鼠标钩子回调:跟踪鼠标位置、驱动自动消退与重绘。
  279. // 注意:此回调在钩子线程上下文执行,必须快速返回。
  280. func (s *mouseAlertState) hookCallback(nCode int, wParam uintptr, lParam uintptr) uintptr {
  281. if nCode >= 0 {
  282. info := (*msllhookstruct)(unsafe.Pointer(lParam))
  283. s.mu.Lock()
  284. if s.active {
  285. s.lastX, s.lastY = info.Pt.X, info.Pt.Y
  286. expired := !s.stopTime.IsZero() && time.Now().After(s.stopTime)
  287. if expired {
  288. s.active = false
  289. }
  290. overlay := s.overlayWnd
  291. text := s.text
  292. flashOn := s.flashOn
  293. x, y := s.lastX, s.lastY
  294. s.mu.Unlock()
  295. if expired {
  296. if overlay != 0 {
  297. procShowWindow.Call(overlay, 0) // SW_HIDE
  298. }
  299. } else {
  300. s.renderOverlay(overlay, text, flashOn, x, y)
  301. }
  302. } else {
  303. s.mu.Unlock()
  304. }
  305. }
  306. ret, _, _ := procCallNextHookEx.Call(0, uintptr(nCode), wParam, lParam)
  307. return ret
  308. }
  309. // renderOverlay 定位覆盖层到鼠标位置并重绘闪烁光标 + 下方透明文字。
  310. // 在钩子线程内调用,窗口/DC 操作均为同线程。
  311. func (s *mouseAlertState) renderOverlay(overlay uintptr, text string, flashOn bool, x, y int32) {
  312. if overlay == 0 {
  313. return
  314. }
  315. // 让覆盖层左上角位于鼠标位置偏左上,使光标环中心对准鼠标
  316. left := x - 10
  317. top := y - 4
  318. procSetWindowPos.Call(overlay, hwndTopMost,
  319. uintptr(int32(left)), uintptr(int32(top)),
  320. overlayWidth, overlayHeight, swpNoActivate|swpShowWindow)
  321. s.drawLayered(overlay, text, flashOn)
  322. }
  323. // drawLayered 用 32 位 DIB + UpdateLayeredWindow 绘制闪烁光标环与半透明文字。
  324. func (s *mouseAlertState) drawLayered(overlay uintptr, text string, flashOn bool) {
  325. const w = overlayWidth
  326. const h = overlayHeight
  327. hdcScreen, _, _ := procGetDC.Call(0)
  328. if hdcScreen == 0 {
  329. return
  330. }
  331. defer procReleaseDC.Call(0, hdcScreen)
  332. hdcMem, _, _ := procCreateCompatibleDC.Call(hdcScreen)
  333. if hdcMem == 0 {
  334. return
  335. }
  336. defer procDeleteDC.Call(hdcMem)
  337. // 32 位 DIB 位图(负高度:自顶向下)
  338. bi := bitmapinfoheader{}
  339. bi.BiSize = 40 // sizeof(BITMAPINFOHEADER)
  340. bi.BiWidth = w
  341. bi.BiHeight = -h
  342. bi.BiPlanes = 1
  343. bi.BiBitCount = 32
  344. bi.BiCompression = biRgb
  345. var bits uintptr
  346. hBitmap, _, _ := procCreateDIBSection.Call(hdcScreen,
  347. uintptr(unsafe.Pointer(&bi)), 0, uintptr(unsafe.Pointer(&bits)), 0, 0)
  348. if hBitmap == 0 {
  349. return
  350. }
  351. defer procDeleteObject.Call(hBitmap)
  352. oldBmp, _, _ := procSelectObject.Call(hdcMem, hBitmap)
  353. // 清空为全透明
  354. clearPixels(bits, w, h)
  355. // 光标环中心放在覆盖层 (10, 4) 附近,即鼠标位置
  356. drawCursorRing(hdcMem, 10, 4, flashOn)
  357. // 光标下方半透明文字
  358. if text != "" {
  359. drawAlertText(hdcMem, text, 10, textTopY)
  360. }
  361. // GDI 绘制到 DIB 不写 alpha 通道,此处按亮度补全 alpha,
  362. // 使图形/文字边缘抗锯齿平滑(文字区半透明)
  363. setAlphaFromLuma(bits, w, h)
  364. procSelectObject.Call(hdcMem, oldBmp)
  365. // UpdateLayeredWindow:pptDst 传 nil 保持 SetWindowPos 设定的位置
  366. var blend blendfunction
  367. blend.BlendOp = 0 // AC_SRC_OVER
  368. blend.AlphaFormat = 0x01 // AC_SRC_ALPHA
  369. blend.SourceConstantAlpha = 255
  370. procUpdateLayeredWindow.Call(overlay, hdcScreen,
  371. 0, 0, // pptDst=nil, psize=nil
  372. hdcMem, 0, 0, uintptr(unsafe.Pointer(&blend)), 0x00000002 /*ULW_ALPHA*/)
  373. }
  374. // clearPixels 把 DIB 位图全部置为透明(ARGB=0)
  375. func clearPixels(bits uintptr, w, h int) {
  376. if bits == 0 {
  377. return
  378. }
  379. total := uintptr(w) * uintptr(h) * 4
  380. for i := uintptr(0); i+8 <= total; i += 8 {
  381. *(*uint64)(unsafe.Pointer(bits + i)) = 0
  382. }
  383. }
  384. // setAlphaFromLuma 按亮度补全 alpha:非透明像素的 alpha = max(r,g,b),
  385. // 文字区(y >= textTopY)乘以 3/4 实现半透明。
  386. func setAlphaFromLuma(bits uintptr, w, h int) {
  387. if bits == 0 {
  388. return
  389. }
  390. for y := 0; y < h; y++ {
  391. for x := 0; x < w; x++ {
  392. p := (*uint32)(unsafe.Pointer(bits + uintptr(y*w+x)*4))
  393. v := *p
  394. b := uint8(v)
  395. g := uint8(v >> 8)
  396. r := uint8(v >> 16)
  397. m := uint32(r)
  398. if uint32(g) > m {
  399. m = uint32(g)
  400. }
  401. if uint32(b) > m {
  402. m = uint32(b)
  403. }
  404. if m == 0 {
  405. *p = 0 // 保持透明
  406. continue
  407. }
  408. if y >= textTopY {
  409. m = m * 3 / 4 // 文字半透明
  410. }
  411. *p = v&0x00FFFFFF | (m << 24) // 写入 alpha
  412. }
  413. }
  414. }
  415. // drawCursorRing 在覆盖层坐标 (cx,cy) 画闪烁光标环(提醒样式)
  416. func drawCursorRing(hdc uintptr, cx, cy int32, flashOn bool) {
  417. color := flashColorNormal
  418. if flashOn {
  419. color = flashColorAlert
  420. }
  421. const r = 12
  422. for angle := 0; angle < 360; angle += 4 {
  423. rad := float64(angle) * 3.14159265 / 180.0
  424. px := cx + int32(float64(r)*cos(rad))
  425. py := cy + int32(float64(r)*sin(rad))
  426. procSetPixel.Call(hdc, uintptr(px), uintptr(py), uintptr(color))
  427. }
  428. // 中心小点
  429. procSetPixel.Call(hdc, uintptr(cx), uintptr(cy), uintptr(color))
  430. }
  431. // drawAlertText 在 (x,y) 绘制白色提示文字(抗锯齿边缘由 setAlphaFromLuma 处理)
  432. func drawAlertText(hdc uintptr, text string, x, y int32) {
  433. // CreateFontW 参数顺序:nHeight,nWidth,nEscapement,nOrientation,nWeight,
  434. // bItalic,bUnderline,bStrikeOut,iCharSet,iOutPrecision,iClipPrecision,
  435. // iQuality,iPitchAndFamily,pszFaceName。nHeight=-16 表示 16 像素高。
  436. var height int32 = -16
  437. hFont, _, _ := procCreateFontW.Call(
  438. uintptr(height), 0, 0, 0, 400, // 高16、粗体
  439. 0, 0, 0, 134, // 非斜体、GB2312_CHARSET
  440. 0, 0, 4, 2, // 抗锯齿、DEFAULT_PITCH
  441. 0, // lpszFace=NULL 用系统默认字体
  442. )
  443. if hFont == 0 {
  444. return
  445. }
  446. defer procDeleteObject.Call(hFont)
  447. oldFont, _, _ := procSelectObject.Call(hdc, hFont)
  448. procSetBkMode.Call(hdc, 1) // TRANSPARENT
  449. procSetTextColor.Call(hdc, 0x00FFFFFF) // 白
  450. procSetTextAlign.Call(hdc, 0) // TA_LEFT
  451. textPtr, _ := syscall.UTF16PtrFromString(text)
  452. procTextOutW.Call(hdc, uintptr(x), uintptr(y), uintptr(unsafe.Pointer(textPtr)), uintptr(len([]rune(text))))
  453. procSelectObject.Call(hdc, oldFont)
  454. }
  455. // wndProcCallback 覆盖层窗口过程:处理定时器(闪烁)与销毁
  456. func (s *mouseAlertState) wndProcCallback(hwnd uintptr, msg uint32, wParam uintptr, lParam uintptr) uintptr {
  457. switch msg {
  458. case wmTimer:
  459. if uintptr(wParam) == timerFlash {
  460. s.mu.Lock()
  461. if s.active {
  462. s.flashOn = !s.flashOn
  463. text := s.text
  464. flashOn := s.flashOn
  465. x, y := s.lastX, s.lastY
  466. s.mu.Unlock()
  467. s.renderOverlay(hwnd, text, flashOn, x, y)
  468. return 0
  469. }
  470. s.mu.Unlock()
  471. }
  472. return 0
  473. case wmDestroy, wmNcDestroy:
  474. procKillTimer.Call(hwnd, timerFlash)
  475. return 0
  476. }
  477. ret, _, _ := procDefWindowProc.Call(hwnd, uintptr(msg), wParam, lParam)
  478. return ret
  479. }
  480. // procCallNextHookEx 钩子链传递
  481. var procCallNextHookEx = user32.NewProc("CallNextHookEx")
  482. // cos / sin 简单实现(避免依赖 math 的浮点精度问题)
  483. func cos(rad float64) float64 {
  484. r := rad
  485. for r > 3.14159265 {
  486. r -= 6.28318530
  487. }
  488. for r < -3.14159265 {
  489. r += 6.28318530
  490. }
  491. r2 := r * r
  492. return 1 - r2/2 + r2*r2/24 - r2*r2*r2/720
  493. }
  494. func sin(rad float64) float64 {
  495. r := rad
  496. for r > 3.14159265 {
  497. r -= 6.28318530
  498. }
  499. for r < -3.14159265 {
  500. r += 6.28318530
  501. }
  502. r2 := r * r
  503. return r - r2*r/6 + r2*r2*r/120 - r2*r2*r2*r/5040
  504. }