├── .gitignore ├── KMCounter.ahk ├── README.md ├── resouces ├── KMCounter.ico ├── KMCounter.png ├── 布局.png ├── 统计.png └── 设置.png └── screenshots ├── 1.png ├── 2.png ├── 3.gif └── 4.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | KMCounter.ini 3 | todo.txt 4 | -------------------------------------------------------------------------------- /KMCounter.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | 已知问题 3 | 更换显示器后尺寸无法自动更新 4 | */ 5 | /* 6 | 更新地址 7 | https://github.com/telppa/KMCounter 8 | https://www.autoahk.com/archives/35147 9 | */ 10 | ;编译信息 11 | ;@Ahk2Exe-Bin Unicode 32* 12 | ;@Ahk2Exe-SetName KMCounter 13 | ;@Ahk2Exe-SetVersion 3.7 14 | ;@Ahk2Exe-SetCopyright telppa (2021 - ) 15 | ;@Ahk2Exe-SetMainIcon resouces\KMCounter.ico 16 | 17 | #NoEnv 18 | #SingleInstance Force 19 | SetBatchLines, -1 20 | 21 | global APPName:="KMCounter", ver:=3.7 22 | , today:=SubStr(A_Now, 1, 8) 23 | , tomorrow:=EnvAdd(today, 1, "Days", 1, 8) 24 | , DataStorageDays, firstday 25 | , devicecaps:={}, layout:={} 26 | , hHookMouse, mouse:={} 27 | , hHookKeyboard, keyboard:={} 28 | 29 | gosub, MultiLanguage ; 多语言支持 30 | gosub, Welcome ; 首次使用时显示欢迎信息 31 | LoadData(today) ; 初始化除 hHookMouse hHookKeyboard 的全部全局变量 32 | gosub, BlockClickOnGui1 ; 因为键盘热力图是用 Edit 控件画的,所以屏蔽鼠标点击避免其造成控件外观改变 33 | gosub, CreateMenu ; 创建托盘菜单 34 | gosub, CreateGui1 ; 预先创建 GUI1 以便需要时加速显示 35 | gosub, CreateGui2 ; 预先创建 GUI2 以便需要时加速显示 36 | 37 | HookMouse() ; 鼠标钩子 38 | HookKeyboard() ; 键盘钩子 39 | 40 | SetTimer, Reload, % countdown() ; 设置一个计时器用于跨夜时重启进程以便保存当日数据并开始新的一天 41 | SetTimer, ReloadHook, % 60000*10 ; 每10分钟重载一次钩子,避免用户的按键映射型钩子的影响 42 | OnExit("ExitFunc") ; 退出时在这里卸载钩子并保存参数 43 | 44 | return 45 | 46 | Welcome: 47 | IfNotExist, KMCounter.ini 48 | OSDTIP_Pop(L_welcome_main, L_welcome_sub, -20000, "fm12 fs9 W320", "微软雅黑") 49 | return 50 | 51 | CreateGui1: 52 | ControlList:=LoadControlList(layout) ; 控件布局信息在此处创建 53 | Opt := ControlList.Opt 54 | scale := A_ScreenDPI/96 55 | Gui, -DPIScale +HwndhWin ; 禁止系统 DPI 缩放 56 | Gui, Color, % Opt.BackgroundColor, % Opt.BackgroundColor 57 | Gui, Font, % "S" Opt.FontSize//scale, % Opt.Font ; 高分屏下缩小字号 58 | for k, control in ControlList 59 | { 60 | p:="" 61 | for k1, optname in ["x", "y", "w", "h", "Hwnd"] 62 | { 63 | ; 构造控件所需参数。当 xywhHwnd 不为空,则 64 | ; p:="x+123 y+123 w123 h123 Hwndsc123 " 或 p:="xm+123 ys+123 w123 h123 Hwndsc123 " 等等 65 | ; 任意为空,则对应的项消失,例如 xy 为空,则 66 | ; p:="w123 h123 Hwndsc123 " 67 | if (control[optname]!="") 68 | p.=" " optname control[optname] 69 | } 70 | if (InStr(control.Hwnd, "sc")) 71 | { 72 | ; 增加 v变量 ,形如 “keysc123” 。 73 | ; 完全是给 WM_MOUSEMOVE 用的,因为 A_GuiControl 只显示文本或 v变量 。 74 | p.=" vkey" control.Hwnd 75 | ; 创建按键。 Edit 控件比 Text 控件多一条白色细边框,更好看一些。 76 | Gui, Add, Edit, % "C" Opt.TextColor " Center ReadOnly -WantCtrlA -TabStop -Vscroll" p, % control.Text 77 | } 78 | else if (control.Hwnd="Message") 79 | { 80 | ; 增加 v变量 ,形如 “msgMessage” 。 81 | p.=" vmsg" control.Hwnd 82 | ; 创建信息框 83 | Gui, Add, ListView, % "C" Opt.TextColor " +Grid Count10 -Hdr -HScroll" p, % L_gui1_LV标题 84 | for k1, field in [L_gui1_鼠标移动, L_gui1_键盘敲击 85 | , L_gui1_左键点击, L_gui1_右键点击, L_gui1_中键点击 86 | , L_gui1_滚轮滚动, L_gui1_滚轮横滚 87 | , L_gui1_侧键点击 88 | , L_gui1_屏幕尺寸] 89 | LV_Add("", field) 90 | 91 | LV_ModifyCol(2, "Right") ; 文本右对齐 92 | LV_ModifyCol(3, "Right") 93 | LV_ModifyCol(4, "Right") 94 | LV_ModifyCol(5, "Right") 95 | LV_ModifyCol(1, 70) ; 1 列宽度设为70 96 | LV_ModifyCol(3, 0) ; 3、4 列宽度设为0 97 | LV_ModifyCol(4, 0) 98 | LV_ModifyCol(2, (control.w-70-Ceil(22*scale))//2) ; 2、5 列平分剩下的宽度 99 | LV_ModifyCol(5, (control.w-70-Ceil(22*scale))//2) ; 即使关闭了 DPIScale 滚动条的宽度依然受影响 所以需要乘以系数 100 | } 101 | } 102 | Gui, Show, Hide 103 | return 104 | 105 | ; 统计界面下,滚轮与翻页键切换历史回忆。 106 | #If (WinActive("ahk_id " hWin)) 107 | WheelDown:: 108 | WheelUp:: 109 | PgDn:: 110 | PgUp:: 111 | Down:: 112 | Up:: 113 | Critical 114 | ; 设置默认值 115 | NonNull(history, today) 116 | loop, % DataStorageDays+1 117 | { 118 | switch, A_ThisHotkey 119 | { 120 | case, "WheelDown","PgDn","Down": history:=EnvAdd(history, -1, "Days", 1, 8) ; 前一天 121 | case, "WheelUp","PgUp","Up": history:=EnvAdd(history, 1, "Days", 1, 8) ; 后一天 122 | } 123 | ; 日期永远在今天、总计、第一天之间循环 124 | if (history > tomorrow) 125 | history := firstday 126 | if (history < firstday) 127 | history := tomorrow 128 | 129 | ; 显示全部数据 130 | if (history = tomorrow) 131 | { 132 | date := "Total" 133 | gosub, ShowHeatMap 134 | break 135 | } 136 | ; 找到历史数据,并且历史数据与当前显示数据不同,则刷新 137 | if (LoadData(history) and date!=history) 138 | { 139 | date := history 140 | gosub, ShowHeatMap 141 | break 142 | } 143 | } 144 | return 145 | #If 146 | 147 | GuiEscape: 148 | GuiClose: 149 | Gui, Hide 150 | history:="" 151 | btt() 152 | return 153 | 154 | CreateGui2: 155 | { 156 | Gui, 2:Color, 444444, 444444 157 | 158 | Gui, 2:Font, s19 Bold cEEEEEE, 微软雅黑 159 | Gui, 2:Add, Text, x16 y24 w216 h30 +0x200, %L_gui2_历史数据% 160 | Gui, 2:Font 161 | Gui, 2:Font, cEEEEEE, 微软雅黑 162 | Gui, 2:Add, Text, x16 yp+40 w216 h23, %L_gui2_sub1% 163 | 164 | Gui, 2:Add, Text, x16 yp+40 w85 h23, %L_gui2_存储%: 165 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vdsd, % DataStorageDays 166 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_天% 167 | 168 | Gui, 2:Font, s19 Bold cEEEEEE, 微软雅黑 169 | Gui, 2:Add, Text, x16 yp+48 w216 h30 +0x200, %L_gui2_屏幕尺寸% 170 | Gui, 2:Font 171 | Gui, 2:Font, cEEEEEE, 微软雅黑 172 | Gui, 2:Add, Text, x16 yp+40 w216 h23, %L_gui2_sub2% 173 | 174 | Gui, 2:Add, Text, x16 yp+40 w85 h23, %L_gui2_屏幕宽%: 175 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vdw, % devicecaps.w 176 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_毫米% 177 | 178 | Gui, 2:Add, Text, x16 yp+32 w85 h23, %L_gui2_屏幕高%: 179 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vdh, % devicecaps.h 180 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_毫米% 181 | 182 | Gui, 2:Font, s19 Bold cEEEEEE, 微软雅黑 183 | Gui, 2:Add, Text, x16 yp+48 w216 h30 +0x200, %L_gui2_键盘布局% 184 | Gui, 2:Font 185 | Gui, 2:Font, cEEEEEE, 微软雅黑 186 | Gui, 2:Add, Text, x16 yp+40 w216 h23, %L_gui2_sub3% 187 | 188 | Gui, 2:Add, Text, x16 yp+40 w85 h23, %L_gui2_键宽%: 189 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vlkw, % layout.kw 190 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_像素% 191 | 192 | Gui, 2:Add, Text, x16 yp+32 w85 h23, %L_gui2_键高%: 193 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vlkh, % layout.kh 194 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_像素% 195 | 196 | Gui, 2:Add, Text, x16 yp+32 w85 h23, %L_gui2_键间距%: 197 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vlks, % layout.ks 198 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_像素% 199 | 200 | Gui, 2:Add, Text, x16 yp+32 w85 h23, %L_gui2_区域水平间距%: 201 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vlkhs, % layout.khs 202 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_像素% 203 | 204 | Gui, 2:Add, Text, x16 yp+32 w85 h23, %L_gui2_区域垂直间距%: 205 | Gui, 2:Add, Edit, x104 yp-2 w85 h19 Number Limit -Multi vlkvs, % layout.kvs 206 | Gui, 2:Add, Text, x197 yp+2 w30 h23, %L_gui2_像素% 207 | 208 | Gui, 2:Add, Button, x16 yp+48 w80 h30 gCancelSetting hwndhBT1, %L_gui2_取消% 209 | Gui, 2:Add, Button, x140 yp+0 w80 h30 gSaveSetting hwndhBT2, %L_gui2_保存% 210 | 211 | Opt1 := [0, 0xff708090, , 0xffeeeeee, 5, 0xff444444] ; 按钮正常时候的样子 212 | Opt2 := [0, 0xffeeeeee, , 0xff708090, 5, 0xff444444] ; 鼠标在按钮上的样子 213 | Opt5 := Opt1 ; 被按过一次后的样子 214 | if !(ImageButton.Create(hBT1, Opt1, Opt2, , , Opt5)) 215 | MsgBox, 0, ImageButton Error btn1, % ImageButton.LastError 216 | if !(ImageButton.Create(hBT2, Opt1, Opt2, , , Opt5)) 217 | MsgBox, 0, ImageButton Error btn1, % ImageButton.LastError 218 | 219 | Gui, 2:Show, w235 h622 Hide 220 | } 221 | return 222 | 223 | CancelSetting: 224 | 2GuiEscape: 225 | 2GuiClose: 226 | Gui, 2:Hide 227 | return 228 | 229 | SaveSetting: 230 | Gui, 2:Submit 231 | ; 限制历史数据存储天数最小值为0,默认值为30。 232 | DataStorageDays := NonNull_Ret(dsd, 30, 0) 233 | UpdateDeviceCaps(dw, dh) 234 | UpdateLayout(lkw, lkh, lks, lkhs, lkvs) 235 | ; 直接重启以便更新设置 236 | gosub, Reload 237 | return 238 | 239 | Reload: 240 | Reload 241 | return 242 | 243 | ReloadHook: 244 | ; 如果用户的 .ahk 在我们之后运行,并且其中含有 a::b 之类的按键映射,此时就需要重载以便我们的钩子更靠前,才能获取消息。 245 | DllCall("UnhookWindowsHookEx", "UInt", hHookMouse) 246 | DllCall("UnhookWindowsHookEx", "UInt", hHookKeyboard) 247 | HookMouse() 248 | HookKeyboard() 249 | return 250 | 251 | CreateMenu: 252 | { 253 | Menu, Tray, NoStandard ; 不显示 ahk 自己的菜单 254 | Menu, Tray, Tip, %APPName% v%ver% ; 托盘提示信息 255 | Menu, Tray, Add, %L_menu_统计%, MenuHandler ; 创建新菜单项 256 | Menu, Tray, Add, %L_menu_设置%, MenuHandler 257 | Menu, Tray, Add ; 分隔符 258 | Menu, Tray, Add, %L_menu_开机启动%, MenuHandler 259 | Menu, Tray, Add, %L_menu_布局定制%, MenuHandler 260 | Menu, Tray, Add 261 | Menu, Tray, Add, %L_menu_退出%, MenuHandler 262 | Menu, Tray, Default, %L_menu_统计% ; 将统计设为默认项 263 | 264 | ico1:="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4klEQVQ4T6XToU5DQRCF4e+6VvQBeIVCQuqQYCENinoMDQkCgcJQ" 265 | ico1.="dE0FqkhEkzYoBAEErrwA8DqQSe5NNrTcbtNRK3b+nTnnbGHDKsr+AW4yWD3M0nsV4Afb+M6AnGIPZzhIAdW5jrGP9+RCa11AExMc" 266 | ico1.="4xmHdYAjXOEaH9jFPYblBA08/Afo4A13OMcFRrhMRGyHZssAW3jFI8KdyqG/DnxiZxngBXPcrnAknCtSwEk5Xry4qjnYC4AuntDH" 267 | ico1.="OCMPC4DcIAU7BPxKV8iNcjpYrDnISV/tNhVgihBxnYpP1dt4gl9sRC5dONyGKQAAAABJRU5ErkJggg==" 268 | ico1:=ImagePutHIcon(ico1) 269 | 270 | ico2:="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABVklEQVQ4T63TP0tXcRTH8ZdGTyAcXIJagxYXUQJ1qaUnELaILTqI" 271 | ico2.="o2L4BzRzaWgogqIlwbaG0jGHyEUI8gkYbQ0tgUtLfOBcuV74haBnuff757zPOZ9zvn0uaH09/MfxuXM2gf3u/S4gjrmU7wriFAts" 272 | ico2.="rXV2CmoDNjCPVdzCjQ4g6wM8wBMshdwAbuM77mILPzCHn5XBNWxiEAmUDIZx1AC26/JCq8ZJ3Kn1F+ROY+u4ickGcFgnSf8jRivS" 273 | ico2.="Ue0nw6T8FferzH4MtTWIaBEvwr3DX0wV4C2u4mEJ+g2PcdIGJPpYAV4WIDrEnhdgpgCvsNMWMSWc4Bk+pDY8wnEB0oHXpUNKWMan" 274 | ico2.="tPZ/IkaH2QK8qPobEZ/iT3TqtvEekuYvLOJ3eVyvMpLRLt4jrT2dg/ynv4m4h5FKvz2JcRjAlZrK6HQGkPV5Rnkab5paLv0x9Xic" 275 | ico2.="vbf/AUZITBFlGQzCAAAAAElFTkSuQmCC" 276 | ico2:=ImagePutHIcon(ico2) 277 | 278 | ico3.="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgElEQVQ4T2NkoBAwUqifAW6Ajo7Of1IMu3LlClgvigEwQUIGgSyj" 279 | ico3.="jQFINh9gZGRU+P///y0017jB+FhdwMjIqAdS8P//f2FGRsY+BgaGeGze+f///yW8XtDR0RFnYGBYc+XKFVtsBhAMg1EDGBjIDgNC" 280 | ico3.="KRBZHiMaSdGMrJbi3AgA8Z51EexVYzAAAAAASUVORK5CYII=" 281 | ico3:=ImagePutHIcon(ico3) 282 | 283 | if (!A_IsCompiled) 284 | Menu, Tray, Icon, resouces\%APPName%.ico ; 加载托盘图标 285 | Menu, Tray, Icon, %L_menu_统计%, HICON:%ico1% ; 加载菜单图标 286 | Menu, Tray, Icon, %L_menu_设置%, HICON:%ico2% 287 | Menu, Tray, Icon, %L_menu_布局定制%, HICON:%ico3% 288 | 289 | IfExist, %A_Startup%\%APPName%.Lnk ; 检测启动文件夹中是否有快捷方式来确定是否勾选自启 290 | Menu, Tray, Check, %L_menu_开机启动% 291 | } 292 | return 293 | 294 | MenuHandler: 295 | if (A_ThisMenuItem = L_menu_统计) 296 | { 297 | date := today 298 | gosub, ShowHeatMap 299 | } 300 | 301 | if (A_ThisMenuItem = L_menu_设置) 302 | { 303 | GuiControl, 2:, dw, % devicecaps.w 304 | GuiControl, 2:, dh, % devicecaps.h 305 | GuiControl, 2:, lkw, % layout.kw 306 | GuiControl, 2:, lkh, % layout.kh 307 | GuiControl, 2:, lks, % layout.ks 308 | GuiControl, 2:, lkhs, % layout.khs 309 | GuiControl, 2:, lkvs, % layout.kvs 310 | Gui, 2:Show, , %L_gui2_设置% 311 | } 312 | 313 | if (A_ThisMenuItem = L_menu_开机启动) 314 | { 315 | IfExist, %A_Startup%\%APPName%.Lnk 316 | { 317 | FileDelete, %A_Startup%\%APPName%.Lnk 318 | Menu, Tray, UnCheck, %L_menu_开机启动% 319 | } 320 | else 321 | { 322 | FileCreateShortcut, %A_ScriptFullPath%, %A_Startup%\%APPName%.Lnk, %A_ScriptDir% 323 | Menu, Tray, Check, %L_menu_开机启动% 324 | } 325 | } 326 | 327 | if (A_ThisMenuItem = L_menu_布局定制) 328 | { 329 | Run, https://github.com/telppa/KMCounter 330 | Run, https://www.autoahk.com/archives/35147 331 | } 332 | 333 | if (A_ThisMenuItem = L_menu_退出) 334 | ExitApp 335 | return 336 | 337 | ShowHeatMap: 338 | { 339 | ; 一定要先 Show 再设置按键颜色,否则不定时出错 340 | Gui, Show, , % Format("{1} v{2} | {3} - {4}", APPName, ver, L_gui1_当前显示数据, date) 341 | ; 先显示文字统计信息 342 | LV_Modify(1,,, Format("{:.2f} {2}", mouse[date].move, L_gui1_米),, 343 | , Format("{:.2f} {2}", mouse.total.move, L_gui1_米)) 344 | LV_Modify(2,,, Format("{1} {2}", keyboard[date].keystrokes, L_gui1_次),, 345 | , Format("{1} {2}", keyboard.total.keystrokes, L_gui1_次)) 346 | LV_Modify(3,,, Format("{1} {2}", mouse[date].lbcount, L_gui1_次),, 347 | , Format("{1} {2}", mouse.total.lbcount, L_gui1_次)) 348 | LV_Modify(4,,, Format("{1} {2}", mouse[date].rbcount, L_gui1_次),, 349 | , Format("{1} {2}", mouse.total.rbcount, L_gui1_次)) 350 | LV_Modify(5,,, Format("{1} {2}", mouse[date].mbcount, L_gui1_次),, 351 | , Format("{1} {2}", mouse.total.mbcount, L_gui1_次)) 352 | LV_Modify(6,,, Format("{1} {2}", mouse[date].wheel, L_gui1_次),, 353 | , Format("{1} {2}", mouse.total.wheel, L_gui1_次)) 354 | LV_Modify(7,,, Format("{1} {2}", mouse[date].hwheel, L_gui1_次),, 355 | , Format("{1} {2}", mouse.total.hwheel, L_gui1_次)) 356 | LV_Modify(8,,, Format("{1} {2}", mouse[date].xbcount, L_gui1_次),, 357 | , Format("{1} {2}", mouse.total.xbcount, L_gui1_次)) 358 | LV_Modify(9,,, Format("{:.1f} {2}", devicecaps.size, L_gui1_寸)) 359 | ; 有了一定的数据量后再显示图案,同时可以避免初始颜色显示错误 360 | if (keyboard[date].keystrokes >= 100) 361 | { 362 | ; 获取一组渐变色 363 | colors := getcolors(0xEEEEEE, 0xB26C65, 100) 364 | ; 将总量的1成设置为对比量 365 | maxcount := keyboard[date].keystrokes / 10 366 | for k, count in keyboard[date] 367 | { 368 | ; 按键量大于等于对比量,直接显示最深的颜色 369 | if (count >= maxcount) 370 | color := colors[100] 371 | ; 按键量小于1/100的对比量时,直接显示最浅的颜色 372 | else if (count < maxcount/100) 373 | color := colors[1] 374 | ; 根据按键量占据对比量的百分比,绘制颜色 375 | else 376 | color := colors[Floor(count/maxcount*100)] 377 | 378 | ; 设置按键颜色 379 | CtlColors.Change(%k%, color, Opt.TextColor) 380 | } 381 | } 382 | else 383 | { 384 | ; 数据量不足显示提示框 385 | for k, count in keyboard[date] 386 | CtlColors.Change(%k%, Opt.BackgroundColor, Opt.TextColor) 387 | MsgBox 0x42040, , %L_gui1_msgbox% 388 | } 389 | } 390 | return 391 | 392 | WM_MOUSEMOVE() 393 | { 394 | static init:=OnMessage(0x200, "WM_MOUSEMOVE"), IsMessageMaximized:=0 395 | global date, L_gui1_次, ControlList 396 | if (A_Gui = 1) 397 | { 398 | if (A_GuiControl = "msgMessage") 399 | { 400 | if (IsMessageMaximized = 0) 401 | { 402 | ; 鼠标移动到信息框时自动放大显示。 403 | GuiControl, Move, msgMessage, % "h" layout.kh*6+layout.khs+layout.ks*4 404 | ; 显示标题栏。 405 | GuiControl, +Hdr, msgMessage 406 | ; 此时必须隐藏按键,否则按键轮廓会穿透信息框。 407 | for k, v in ControlList.Covered 408 | GuiControl, Hide, % "key" v 409 | IsMessageMaximized := 1 410 | } 411 | } 412 | else 413 | { 414 | if (IsMessageMaximized = 1) 415 | { 416 | ; 信息框恢复最小化。 417 | GuiControl, Move, msgMessage, % "h" layout.kh 418 | ; 隐藏标题栏。 419 | GuiControl, -Hdr, msgMessage 420 | ; 显示按键。 421 | for k, v in ControlList.Covered 422 | GuiControl, Show, % "key" v 423 | IsMessageMaximized := 0 424 | } 425 | 426 | ; 鼠标移动到按键上时,显示对应按键敲击次数。 427 | key:=SubStr(A_GuiControl, 4) 428 | if (keyboard[date].HasKey(key)) 429 | btt(keyboard[date][key] " " L_gui1_次,,,,"Style2") 430 | else 431 | btt() 432 | } 433 | } 434 | else 435 | btt() 436 | } 437 | 438 | BlockClickOnGui1: 439 | { 440 | OnMessage(0x0201, "BlockClick") ; 左键按下 441 | OnMessage(0x0202, "BlockClick") ; 左键弹起 442 | OnMessage(0x0203, "BlockClick") ; 左键双击 443 | OnMessage(0x0204, "BlockClick") ; 右键按下 444 | OnMessage(0x0205, "BlockClick") ; 右键弹起 445 | OnMessage(0x0206, "BlockClick") ; 右键双击 446 | } 447 | return 448 | 449 | BlockClick(wParam, lParam, msg, hwnd) 450 | { 451 | if (A_Gui=1) 452 | return, 0 ; 必须返回0才能丢掉消息实现屏蔽的效果 453 | } 454 | 455 | ExitFunc(ExitReason, ExitCode) 456 | { 457 | DllCall("UnhookWindowsHookEx", "UInt", hHookMouse) 458 | DllCall("UnhookWindowsHookEx", "UInt", hHookKeyboard) 459 | CtlColors.Free() 460 | SaveData() 461 | } 462 | 463 | LoadData(date) 464 | { 465 | ; 获取历史数据存储天数 466 | DataStorageDays := IniRead("KMCounter.ini", "history", "storage", 30) 467 | firstday := EnvAdd(today, -DataStorageDays, "Days", 1, 8) 468 | 469 | ; 删除超时的历史数据 470 | SectionNames := StrSplit(IniRead("KMCounter.ini"), "`n", " `t`r`n`v`f") 471 | SavedSectionNames:={} 472 | for k, SectionName in SectionNames 473 | { 474 | if SectionName is integer 475 | { 476 | if (SectionName < firstday) ; 小于第一天则算超时 477 | IniDelete, KMCounter.ini, %SectionName% ; 因为要省略最后一个参数才能删除整段,所以只能用命令的形式 478 | else 479 | SavedSectionNames[SectionName]:="" 480 | } 481 | } 482 | ; 历史数据不存在则返回 false 483 | if (!SavedSectionNames.HasKey(date) and date!=today) 484 | return, false 485 | ; 历史数据已存在则返回 true 486 | if (IsObject(mouse[date]) or IsObject(keyboard[date])) 487 | return, true 488 | 489 | ; 获取屏幕信息 490 | devicecaps.w := IniRead("KMCounter.ini", "devicecaps", "w", " ") ; 传空格给最后一个参数才能让默认值变空值(空格) 491 | devicecaps.h := IniRead("KMCounter.ini", "devicecaps", "h", " ") 492 | UpdateDeviceCaps(devicecaps.w, devicecaps.h) ; 更新 devicecaps 493 | ; 获取布局信息 494 | ratio := A_ScreenWidth<1920 ? A_ScreenWidth/1920 : 1 ; 高分辨率屏幕通常有 DPIScale 设置,所以不调大小。 495 | layout.kw := IniRead("KMCounter.ini", "layout", "kw", Round(52*ratio)) ; 键宽 496 | layout.kh := IniRead("KMCounter.ini", "layout", "kh", Round(45*ratio)) ; 键高 497 | layout.ks := IniRead("KMCounter.ini", "layout", "ks", Round(2*ratio)) ; 键间距 498 | layout.khs := IniRead("KMCounter.ini", "layout", "khs", Round(10*ratio)) ; 区域水平间距 499 | layout.kvs := IniRead("KMCounter.ini", "layout", "kvs", Round(10*ratio)) ; 区域垂直间距 500 | ; 获取鼠标信息 501 | for k, v in ["lbcount", "rbcount", "mbcount", "xbcount", "wheel", "hwheel", "move"] 502 | { 503 | mouse[date, v] := IniRead("KMCounter.ini", date, v, 0) 504 | mouse["total", v] := IniRead("KMCounter.ini", "total", v, 0) 505 | } 506 | ; 获取按键信息 507 | for k, control in LoadControlList() 508 | { 509 | if (InStr(control.Hwnd, "sc")) 510 | { 511 | keyboard[date, control.Hwnd] := IniRead("KMCounter.ini", date, control.Hwnd, 0) 512 | keyboard["total", control.Hwnd] := IniRead("KMCounter.ini", "total", control.Hwnd, 0) 513 | } 514 | } 515 | keyboard[date].keystrokes := IniRead("KMCounter.ini", date, "keystrokes", 0) 516 | keyboard["total"].keystrokes := IniRead("KMCounter.ini", "total", "keystrokes", 0) 517 | 518 | return, true 519 | } 520 | 521 | SaveData() 522 | { 523 | ; 保存历史数据存储天数 524 | IniWrite(DataStorageDays, "KMCounter.ini", "history", "storage") 525 | ; 保存屏幕信息 526 | IniWrite(devicecaps.w, "KMCounter.ini", "devicecaps", "w") 527 | IniWrite(devicecaps.h, "KMCounter.ini", "devicecaps", "h") 528 | ; 保存布局信息 529 | IniWrite(layout.kw, "KMCounter.ini", "layout", "kw") 530 | IniWrite(layout.kh, "KMCounter.ini", "layout", "kh") 531 | IniWrite(layout.ks, "KMCounter.ini", "layout", "ks") 532 | IniWrite(layout.khs, "KMCounter.ini", "layout", "khs") 533 | IniWrite(layout.kvs, "KMCounter.ini", "layout", "kvs") 534 | ; 保存鼠标信息 535 | for k, v in ["lbcount", "rbcount", "mbcount", "xbcount", "wheel", "hwheel", "move"] 536 | { 537 | IniWrite(mouse[today][v], "KMCounter.ini", today, v) 538 | IniWrite(mouse["total"][v], "KMCounter.ini", "total", v) 539 | } 540 | ; 保存按键信息 541 | for k, v in keyboard[today] 542 | IniWrite(v, "KMCounter.ini", today, k) 543 | for k, v in keyboard["total"] 544 | IniWrite(v, "KMCounter.ini", "total", k) 545 | } 546 | 547 | HookMouse() 548 | { 549 | ; 全局鼠标钩子 550 | hHookMouse := DllCall("SetWindowsHookEx" . (A_IsUnicode ? "W" : "A") 551 | , "Int", WH_MOUSE_LL := 14 552 | , "Ptr", RegisterCallback("LowLevelMouseProc", "Fast", 3) 553 | , "Ptr", DllCall("GetModuleHandle", "UInt", 0, "Ptr") 554 | , "UInt", 0, "Ptr") 555 | } 556 | ; https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644986(v=vs.85) 557 | ; 非常坑爹的,在上面微软链接中没有记录 0x0208 0x020C 等值。 558 | LowLevelMouseProc(nCode, wParam, lParam) 559 | { 560 | static oldx, oldy, init:=MouseGetPos(oldx, oldy) 561 | Critical 562 | ; lParam 是一个指针,假设 &lParam = 0x123,而 lParam = 0x456 563 | ; 也就是它自身地址是 0x123 ,自身存储的地址是 0x456 564 | ; 直接 NumGet(lParam, 12, "UInt") 的话,是把 0x456 这个值按 uint 解析 565 | ; 而 NumGet(lParam+0, 12, "UInt") ,是把 0x456 这个地址里面的值按 uint 解析 566 | flags := NumGet(lParam+0, 12, "UInt") & 0x1 ; 物理按下是0,模拟是1。0x1 = 00000001 567 | if (nCode>=0 and flags=0) 568 | { 569 | switch, wParam 570 | { 571 | case, 0x0200: ; WM_MOUSEMOVE = 0x0200 572 | x := NumGet(lParam+0, 0, "Int") 573 | , y := NumGet(lParam+0, 4, "Int") 574 | , d := Sqrt((x-oldx)**2 + (y-oldy)**2) ; 勾股求斜边 575 | , d := d * devicecaps.w / A_ScreenWidth / 1000 ; 将单位 像素 转换为 米 576 | , oldx := x, oldy := y 577 | , mouse[today].move += d 578 | , mouse.total.move += d 579 | case, 0x0202: mouse[today].lbcount += 1, mouse.total.lbcount += 1 ; WM_LBUTTONUP = 0x0202 580 | case, 0x0205: mouse[today].rbcount += 1, mouse.total.rbcount += 1 ; WM_RBUTTONUP = 0x0205 581 | case, 0x0208: mouse[today].mbcount += 1, mouse.total.mbcount += 1 ; WM_MBUTTONUP = 0x0208 582 | case, 0x020C: mouse[today].xbcount += 1, mouse.total.xbcount += 1 ; WM_XBUTTONUP = 0x020C 583 | case, 0x020A: mouse[today].wheel += 1, mouse.total.wheel += 1 ; WM_MOUSEWHEEL = 0x020A 584 | case, 0x020E: mouse[today].hwheel += 1, mouse.total.hwheel += 1 ; WM_MOUSEHWHEEL = 0x020E 585 | } 586 | } 587 | ; CallNextHookEx 让其它钩子可以继续处理消息 588 | ; 返回非0值 例如1 告诉系统此消息将丢弃 589 | return, DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "UInt", wParam, "UInt", lParam) 590 | } 591 | 592 | HookKeyboard() 593 | { 594 | ; 全局键盘钩子 595 | hHookKeyboard := DllCall("SetWindowsHookEx" . (A_IsUnicode ? "W" : "A") 596 | , "Int", WH_KEYBOARD_LL := 13 597 | , "Ptr", RegisterCallback("LowLevelKeyboardProc", "Fast", 3) 598 | , "Ptr", DllCall("GetModuleHandle", "UInt", 0, "Ptr") 599 | , "UInt", 0, "Ptr") 600 | } 601 | ; https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v=vs.85) 602 | LowLevelKeyboardProc(nCode, wParam, lParam) 603 | { 604 | Critical 605 | flags := NumGet(lParam+0, 8, "UInt") & 0x10 ; 物理按下是0,模拟是非0。0x10 = 00010000 606 | if (nCode>=0 and flags=0 and (wParam = 0x0101 or wParam = 0x0105)) ; WM_KEYUP = 0x0101 WM_SYSKEYUP = 0x0105 607 | { 608 | ; vk := NumGet(lParam+0, "UInt") ; vk 不能区分数字键盘,所以用 sc 609 | Extended := NumGet(lParam+0, 8, "UInt") & 0x1 ; 扩展键(即功能键或者数字键盘上的键)是1,否则是0 610 | , sc := (Extended<<8) | NumGet(lParam+0, 4, "UInt") 611 | if (!keyboard[today].HasKey("sc" sc)) ; 即使不在布局中的按键依然初始化,使其可被记录 612 | { 613 | keyboard[today, "sc" sc] := 0 614 | keyboard["total", "sc" sc] := 0 615 | } 616 | keyboard[today, "sc" sc] += 1 617 | , keyboard["total", "sc" sc] += 1 618 | , keyboard[today, "keystrokes"] += 1 619 | , keyboard["total", "keystrokes"] += 1 620 | } 621 | ; CallNextHookEx 让其它钩子可以继续处理消息 622 | ; 返回非0值 例如1 告诉系统此消息将丢弃 623 | return, DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "UInt", wParam, "UInt", lParam) 624 | } 625 | 626 | getcolors(c1, c2, n) 627 | { 628 | ; 限制 n 的范围 629 | n := n>=2 ? n : 2 630 | ; 生成渐变色 631 | colors := [] 632 | r1 := c1 >> 16, g1 := c1 >> 8 & 0xFF, b1 := c1 & 0xFF 633 | r2 := c2 >> 16, g2 := c2 >> 8 & 0xFF, b2 := c2 & 0xFF 634 | ; (n-1) 与 (A_Index-1) 确保输出的首尾一定是 c1 和 c2 635 | rd := (r2-r1)/(n-1) 636 | , gd := (g2-g1)/(n-1) 637 | , bd := (b2-b1)/(n-1) 638 | loop, % n 639 | ; 不需要对 rd gd bd 等进行舍除 640 | ; 在这里利用 Format 进行位数限制,能更好的保留精度 641 | colors[A_Index] := Format("{:02x}{:02x}{:02x}" 642 | , r1+rd * (A_Index-1) 643 | , g1+gd * (A_Index-1) 644 | , b1+bd * (A_Index-1)) 645 | return, colors 646 | } 647 | 648 | countdown() 649 | { 650 | ; 距离明天凌晨 0:00:05 的秒数,+5秒是为了给系统时间不准留点余量 651 | return, -(EnvSub(tomorrow, A_Now, "Seconds")+5)*1000 652 | } 653 | 654 | UpdateDeviceCaps(w:="", h:="") 655 | { 656 | ; 我的屏幕使用 EDID 与 GetDeviceCaps 两种方法获取到的屏幕尺寸都是错的 657 | ; 并且 aida64 之类的软件获取到的屏幕尺寸也是错的 658 | ; 所以并不存在一种 100% 准确获取屏幕物理尺寸的方法 659 | if (w>0 and h>0) ; 传过来的值可能是空格(空值),所以用大于符号判断。 660 | { 661 | devicecaps.w := w ; 有传值过来则直接使用 662 | devicecaps.h := h 663 | } 664 | else 665 | { ; 没有传值过来则获取屏幕物理尺寸 666 | hdcScreen := DllCall("GetDC", "UPtr", 0) 667 | devicecaps.w := DllCall("GetDeviceCaps", "UPtr", hdcScreen, "Int", 4) ; 毫米 668 | devicecaps.h := DllCall("GetDeviceCaps", "UPtr", hdcScreen, "Int", 6) ; 毫米 669 | } 670 | devicecaps.size := (Sqrt(devicecaps.w**2 + devicecaps.h**2)/25.4) ; 英寸 勾股求斜边 671 | } 672 | 673 | UpdateLayout(lkw, lkh, lks, lkhs, lkvs) 674 | { 675 | layout.kw := lkw 676 | layout.kh := lkh 677 | layout.ks := lks 678 | layout.khs := lkhs 679 | layout.kvs := lkvs 680 | } 681 | 682 | IniRead(Filename, Section:="", Key:="", Default:=""){ 683 | IniRead, OutputVar, %Filename%, %Section%, %Key%, %Default% 684 | ; 不管是没找到键 亦或是 键值为空 都返回默认值 685 | return, OutputVar="" ? Default : OutputVar 686 | } 687 | IniWrite(Value, Filename, Section, Key:=""){ 688 | IniWrite, %Value%, %Filename%, %Section%, %Key% 689 | } 690 | EnvSub(Var, Value, TimeUnits){ 691 | EnvSub, Var, %Value%, %TimeUnits% 692 | return, Var 693 | } 694 | EnvAdd(Var, Value, TimeUnits, StartingPos, Length){ 695 | EnvAdd, Var, %Value%, %TimeUnits% 696 | ; 日期经过 EnvAdd 计算后位数会发生变化,所以用 SubStr 还原。 697 | return, SubStr(Var, StartingPos, Length) 698 | } 699 | MouseGetPos(ByRef OutputVarX, ByRef OutputVarY){ 700 | MouseGetPos, OutputVarX, OutputVarY 701 | } 702 | MouseGetClassNN(){ 703 | MouseGetPos,,,,OutputVarControl 704 | return, OutputVarControl 705 | } 706 | 707 | ; 此函数储存了每个按键之间的大小与距离关系,实现了自定义键盘大小。 708 | LoadControlList(layout:="") 709 | { 710 | KeyW := NonNull_Ret(layout.kw, 52, 30) ; 限制按键宽度最小值为30 711 | KeyH := NonNull_Ret(layout.kh, 45, 25) ; 限制按键高度最小值为25(可以正常显示1行文本的最小高度) 712 | KeySpacing := NonNull_Ret(layout.ks, 2, 0) ; 限制键间距 713 | HorizontalSpacing := NonNull_Ret(layout.khs, 10, 0) ; 限制区域水平间距 714 | VerticalSpacing := NonNull_Ret(layout.kvs, 10, 0) ; 限制区域垂直间距 715 | 716 | m:=[KeySpacing, "+" KeySpacing ; 普通按键间距 717 | , HorizontalSpacing, "+" HorizontalSpacing ; 区域水平间距 718 | , VerticalSpacing, "+" VerticalSpacing ; 区域垂直间距 719 | , "", ""] ; ESC-F1 间距(计算得到) 720 | 721 | , w := KeyW ; w h 不带数字的是普通按键的宽高,带数字则表示第n行特殊按键的宽高。 722 | , h := KeyH 723 | , w2 := w*2+10 ; BackSpace 724 | , w3 := (w*13 + w2 - w*12 + m.1*0)/2 ; Tab \ 725 | , w4 := (w*13 + w2 - w*11 + m.1*1)/2 ; CapsLock Enter 726 | , w5 := (w*13 + w2 - w*10 + m.1*2)/2 ; Shift 727 | , w6_1 := w3 ; Ctrl 728 | , w6_2 := w6_1-10 ; Win Alt 729 | , w6_3 := (w*13 + w2 - w6_1*2 - w6_2*4 + m.1*7) ; Space 730 | 731 | , m7 := (w*13 + w2 - w*13 + m.1*4)/3 ; ESC-F1 间距 732 | , m.7 := m7 733 | , m.8 := "+" m7 734 | 735 | list:=[] 736 | ; 第一行 737 | list.push({Hwnd:"sc1", Text:"Esc", x:"", y:"", w:w, h:h}) 738 | list.push({Hwnd:"sc59", Text:"F1", x:m.8, y:"", w:w, h:h}) 739 | list.push({Hwnd:"sc60", Text:"F2", x:m.2, y:"", w:w, h:h}) 740 | list.push({Hwnd:"sc61", Text:"F3", x:m.2, y:"", w:w, h:h}) 741 | list.push({Hwnd:"sc62", Text:"F4", x:m.2, y:"", w:w, h:h}) 742 | list.push({Hwnd:"sc63", Text:"F5", x:m.8, y:"", w:w, h:h}) 743 | list.push({Hwnd:"sc64", Text:"F6", x:m.2, y:"", w:w, h:h}) 744 | list.push({Hwnd:"sc65", Text:"F7", x:m.2, y:"", w:w, h:h}) 745 | list.push({Hwnd:"sc66", Text:"F8", x:m.2, y:"", w:w, h:h}) 746 | list.push({Hwnd:"sc67", Text:"F9", x:m.8, y:"", w:w, h:h}) 747 | list.push({Hwnd:"sc68", Text:"F10", x:m.2, y:"", w:w, h:h}) 748 | list.push({Hwnd:"sc87", Text:"F11", x:m.2, y:"", w:w, h:h}) 749 | list.push({Hwnd:"sc88", Text:"F12", x:m.2, y:"", w:w, h:h}) 750 | ; 第二行 751 | list.push({Hwnd:"sc41", Text:"``", x:"m", y:m.4, w:w, h:h}) 752 | list.push({Hwnd:"sc2", Text:"1", x:m.2, y:"", w:w, h:h}) 753 | list.push({Hwnd:"sc3", Text:"2", x:m.2, y:"", w:w, h:h}) 754 | list.push({Hwnd:"sc4", Text:"3", x:m.2, y:"", w:w, h:h}) 755 | list.push({Hwnd:"sc5", Text:"4", x:m.2, y:"", w:w, h:h}) 756 | list.push({Hwnd:"sc6", Text:"5", x:m.2, y:"", w:w, h:h}) 757 | list.push({Hwnd:"sc7", Text:"6", x:m.2, y:"", w:w, h:h}) 758 | list.push({Hwnd:"sc8", Text:"7", x:m.2, y:"", w:w, h:h}) 759 | list.push({Hwnd:"sc9", Text:"8", x:m.2, y:"", w:w, h:h}) 760 | list.push({Hwnd:"sc10", Text:"9", x:m.2, y:"", w:w, h:h}) 761 | list.push({Hwnd:"sc11", Text:"0", x:m.2, y:"", w:w, h:h}) 762 | list.push({Hwnd:"sc12", Text:"-", x:m.2, y:"", w:w, h:h}) 763 | list.push({Hwnd:"sc13", Text:"=", x:m.2, y:"", w:w, h:h}) 764 | list.push({Hwnd:"sc14", Text:"BackSpace", x:m.2, y:"", w:w2, h:h}) 765 | ; 第三行 766 | list.push({Hwnd:"sc15", Text:"Tab", x:"m", y:m.2, w:w3, h:h}) 767 | list.push({Hwnd:"sc16", Text:"q", x:m.2, y:"", w:w, h:h}) 768 | list.push({Hwnd:"sc17", Text:"w", x:m.2, y:"", w:w, h:h}) 769 | list.push({Hwnd:"sc18", Text:"e", x:m.2, y:"", w:w, h:h}) 770 | list.push({Hwnd:"sc19", Text:"r", x:m.2, y:"", w:w, h:h}) 771 | list.push({Hwnd:"sc20", Text:"t", x:m.2, y:"", w:w, h:h}) 772 | list.push({Hwnd:"sc21", Text:"y", x:m.2, y:"", w:w, h:h}) 773 | list.push({Hwnd:"sc22", Text:"u", x:m.2, y:"", w:w, h:h}) 774 | list.push({Hwnd:"sc23", Text:"i", x:m.2, y:"", w:w, h:h}) 775 | list.push({Hwnd:"sc24", Text:"o", x:m.2, y:"", w:w, h:h}) 776 | list.push({Hwnd:"sc25", Text:"p", x:m.2, y:"", w:w, h:h}) 777 | list.push({Hwnd:"sc26", Text:"[", x:m.2, y:"", w:w, h:h}) 778 | list.push({Hwnd:"sc27", Text:"]", x:m.2, y:"", w:w, h:h}) 779 | list.push({Hwnd:"sc43", Text:"\", x:m.2, y:"", w:w3, h:h}) 780 | ; 第四行 781 | list.push({Hwnd:"sc58", Text:"CapsLock", x:"m", y:m.2, w:w4, h:h}) 782 | list.push({Hwnd:"sc30", Text:"a", x:m.2, y:"", w:w, h:h}) 783 | list.push({Hwnd:"sc31", Text:"s", x:m.2, y:"", w:w, h:h}) 784 | list.push({Hwnd:"sc32", Text:"d", x:m.2, y:"", w:w, h:h}) 785 | list.push({Hwnd:"sc33", Text:"f", x:m.2, y:"", w:w, h:h}) 786 | list.push({Hwnd:"sc34", Text:"g", x:m.2, y:"", w:w, h:h}) 787 | list.push({Hwnd:"sc35", Text:"h", x:m.2, y:"", w:w, h:h}) 788 | list.push({Hwnd:"sc36", Text:"j", x:m.2, y:"", w:w, h:h}) 789 | list.push({Hwnd:"sc37", Text:"k", x:m.2, y:"", w:w, h:h}) 790 | list.push({Hwnd:"sc38", Text:"l", x:m.2, y:"", w:w, h:h}) 791 | list.push({Hwnd:"sc39", Text:";", x:m.2, y:"", w:w, h:h}) 792 | list.push({Hwnd:"sc40", Text:"'", x:m.2, y:"", w:w, h:h}) 793 | list.push({Hwnd:"sc28", Text:"Enter", x:m.2, y:"", w:w4, h:h}) 794 | ; 第五行 795 | list.push({Hwnd:"sc42", Text:"Shift", x:"m", y:m.2, w:w5, h:h}) 796 | list.push({Hwnd:"sc44", Text:"z", x:m.2, y:"", w:w, h:h}) 797 | list.push({Hwnd:"sc45", Text:"x", x:m.2, y:"", w:w, h:h}) 798 | list.push({Hwnd:"sc46", Text:"c", x:m.2, y:"", w:w, h:h}) 799 | list.push({Hwnd:"sc47", Text:"v", x:m.2, y:"", w:w, h:h}) 800 | list.push({Hwnd:"sc48", Text:"b", x:m.2, y:"", w:w, h:h}) 801 | list.push({Hwnd:"sc49", Text:"n", x:m.2, y:"", w:w, h:h}) 802 | list.push({Hwnd:"sc50", Text:"m", x:m.2, y:"", w:w, h:h}) 803 | list.push({Hwnd:"sc51", Text:",", x:m.2, y:"", w:w, h:h}) 804 | list.push({Hwnd:"sc52", Text:".", x:m.2, y:"", w:w, h:h}) 805 | list.push({Hwnd:"sc53", Text:"/", x:m.2, y:"", w:w, h:h}) 806 | list.push({Hwnd:"sc310", Text:"Shift", x:m.2, y:"", w:w5, h:h}) 807 | ; 第六行 808 | list.push({Hwnd:"sc29", Text:"Ctrl", x:"m", y:m.2, w:w6_1, h:h}) 809 | list.push({Hwnd:"sc347", Text:"Win", x:m.2, y:"", w:w6_2, h:h}) 810 | list.push({Hwnd:"sc56", Text:"Alt", x:m.2, y:"", w:w6_2, h:h}) 811 | list.push({Hwnd:"sc57", Text:"Space", x:m.2, y:"", w:w6_3, h:h}) 812 | list.push({Hwnd:"sc312", Text:"Alt", x:m.2, y:"", w:w6_2, h:h}) 813 | list.push({Hwnd:"sc348", Text:"Win", x:m.2, y:"", w:w6_2, h:h}) 814 | list.push({Hwnd:"sc285", Text:"Ctrl", x:m.2, y:"", w:w6_1, h:h}) 815 | 816 | ; 定位翻页键的区域,确保高度与第二行一致。temp1:="ym+123 Section" 817 | temp1:="m+" h+m.3 " Section" 818 | list.push({Hwnd:"sc338", Text:"Insert", x:m.6, y:temp1, w:w, h:h}) 819 | list.push({Hwnd:"sc327", Text:"Home", x:m.2, y:"", w:w, h:h}) 820 | list.push({Hwnd:"sc329", Text:"PageUp", x:m.2, y:"", w:w, h:h}) 821 | list.push({Hwnd:"sc339", Text:"Delete", x:"s", y:m.2, w:w, h:h}) 822 | list.push({Hwnd:"sc335", Text:"End", x:m.2, y:"", w:w, h:h}) 823 | list.push({Hwnd:"sc337", Text:"PageDn", x:m.2, y:"", w:w, h:h}) 824 | 825 | ; 定位方向键的区域,确保高度与第五行一致。temp1:="xs+123" temp2:="y+123" 826 | temp1:="s+" w+m.1, temp2:="+" h+2*m.1 827 | list.push({Hwnd:"sc328", Text:"▲", x:temp1, y:temp2, w:w, h:h}) 828 | list.push({Hwnd:"sc331", Text:"◀", x:"s", y:m.2, w:w, h:h}) 829 | list.push({Hwnd:"sc336", Text:"▼", x:m.2, y:"", w:w, h:h}) 830 | list.push({Hwnd:"sc333", Text:"▶", x:m.2, y:"", w:w, h:h}) 831 | 832 | ; 定位数字键盘的区域,确保高度与第二行一致。temp1:="ym+123 Section" 833 | temp1:="m+" h+m.3 " Section" 834 | list.push({Hwnd:"sc325", Text:"NumLock", x:m.6, y:temp1, w:w, h:h}) 835 | list.push({Hwnd:"sc309", Text:"/", x:m.2, y:"", w:w, h:h}) 836 | list.push({Hwnd:"sc55", Text:"*", x:m.2, y:"", w:w, h:h}) 837 | list.push({Hwnd:"sc74", Text:"-", x:m.2, y:"", w:w, h:h}) 838 | ; 数字键盘第二行 839 | list.push({Hwnd:"sc71", Text:"7", x:"s", y:m.2, w:w, h:h}) 840 | list.push({Hwnd:"sc72", Text:"8", x:m.2, y:"", w:w, h:h}) 841 | list.push({Hwnd:"sc73", Text:"9", x:m.2, y:"", w:w, h:h}) 842 | list.push({Hwnd:"sc78", Text:"+", x:m.2, y:"", w:w, h:h*2+m.1}) 843 | ; 数字键盘第三行 844 | temp1:="s+" (h+m.1)*2 845 | list.push({Hwnd:"sc75", Text:"4", x:"s", y:temp1, w:w, h:h}) 846 | list.push({Hwnd:"sc76", Text:"5", x:m.2, y:"", w:w, h:h}) 847 | list.push({Hwnd:"sc77", Text:"6", x:m.2, y:"", w:w, h:h}) 848 | ; 数字键盘第四行 849 | temp1:="s+" (h+m.1)*3 850 | list.push({Hwnd:"sc79", Text:"1", x:"s", y:temp1, w:w, h:h}) 851 | list.push({Hwnd:"sc80", Text:"2", x:m.2, y:"", w:w, h:h}) 852 | list.push({Hwnd:"sc81", Text:"3", x:m.2, y:"", w:w, h:h}) 853 | list.push({Hwnd:"sc284", Text:"Enter", x:m.2, y:"", w:w, h:h*2+m.1}) 854 | ; 数字键盘第五行 855 | temp1:="s+" (h+m.1)*4 856 | list.push({Hwnd:"sc82", Text:"0", x:"s", y:temp1, w:w*2+m.1, h:h}) 857 | list.push({Hwnd:"sc83", Text:".", x:m.2, y:"", w:w, h:h}) 858 | 859 | ; 信息框 860 | temp1:="m+" w*13+Round(m.7*3)+m.1*9+m.5 861 | , temp2:=w*7+m.1*5+m.5 ; 与 翻页键区域 + 数字键盘区域 等宽 862 | , temp3:=h ; 与 单个按键等高 863 | list.push({Hwnd:"Message", Text:"", x:temp1, y:"m", w:temp2, h:temp3}) 864 | 865 | ; 信息框放大后会被遮挡的按键列表 866 | list.Covered := ["sc338", "sc327", "sc329", "sc339", "sc335", "sc337" ; 翻页区 867 | , "sc328", "sc331", "sc336", "sc333" ; 方向区 868 | , "sc325", "sc309", "sc55", "sc74" ; 小键盘区 869 | , "sc71", "sc72", "sc73", "sc78" 870 | , "sc75", "sc76", "sc77" 871 | , "sc79", "sc80", "sc81", "sc284" 872 | , "sc82", "sc83"] 873 | 874 | ; Color 没有 0x 前缀。背景色影响 GUI 信息框 当日按键数据太少时的按键。不影响数据量足够后的按键。 875 | list.Opt := {Font:"comic sans ms", FontSize:9, BackgroundColor:"EEEEEE", TextColor:"575757"} 876 | 877 | return, list 878 | } 879 | 880 | MultiLanguage: 881 | if (A_Language="0804") 882 | { 883 | L_menu_统计:="统计" 884 | L_menu_设置:="设置" 885 | L_menu_开机启动:="开机启动" 886 | L_menu_布局定制:="布局定制" 887 | L_menu_退出:="退出" 888 | 889 | L_gui1_当前显示数据:="当前显示数据" 890 | L_gui1_LV标题:="项目|今日|本周|本月|总计" 891 | L_gui1_鼠标移动:="鼠标移动" 892 | L_gui1_键盘敲击:="键盘敲击" 893 | L_gui1_左键点击:="左键点击" 894 | L_gui1_右键点击:="右键点击" 895 | L_gui1_中键点击:="中键点击" 896 | L_gui1_滚轮滚动:="滚轮滚动" 897 | L_gui1_滚轮横滚:="滚轮横滚" 898 | L_gui1_侧键点击:="侧键点击" 899 | L_gui1_屏幕尺寸:="屏幕尺寸" 900 | L_gui1_米:="米" 901 | L_gui1_次:="次" 902 | L_gui1_寸:="寸" 903 | L_gui1_msgbox:="今日按键次数较少,故暂未生成按键热点图。" 904 | 905 | L_gui2_设置:="设置" 906 | L_gui2_历史数据:="历史数据" 907 | L_gui2_sub1:="设置历史数据保留时长。" 908 | L_gui2_存储:="存储" 909 | L_gui2_天:="天" 910 | L_gui2_屏幕尺寸:="屏幕尺寸" 911 | L_gui2_sub2:="设置显示器的真实尺寸。" 912 | L_gui2_屏幕宽:="屏幕宽" 913 | L_gui2_屏幕高:="屏幕高" 914 | L_gui2_毫米:="毫米" 915 | L_gui2_键盘布局:="键盘布局" 916 | L_gui2_sub3:="设置键盘热力图的尺寸。" 917 | L_gui2_键宽:="键宽" 918 | L_gui2_键高:="键高" 919 | L_gui2_键间距:="键间距" 920 | L_gui2_区域水平间距:="区域水平间距" 921 | L_gui2_区域垂直间距:="区域垂直间距" 922 | L_gui2_像素:="像素" 923 | L_gui2_取消:="取消" 924 | L_gui2_保存:="保存" 925 | 926 | L_welcome_main:="欢迎使用 KMCounter" 927 | L_welcome_sub:="KMCounter 将常驻托盘菜单为你统计所需信息。`n点击托盘图标即可查看统计结果。" 928 | } 929 | else 930 | { 931 | L_menu_统计:="Statistics" 932 | L_menu_设置:="Setting" 933 | L_menu_开机启动:="Start-Up" 934 | L_menu_布局定制:="Custom Layout" 935 | L_menu_退出:="Exit" 936 | 937 | L_gui1_当前显示数据:="Date" 938 | L_gui1_LV标题:="Item|Today|This Week|This Month|Total" 939 | L_gui1_鼠标移动:="MouseMove" 940 | L_gui1_键盘敲击:="Keystrokes" 941 | L_gui1_左键点击:="LButton" 942 | L_gui1_右键点击:="RButton" 943 | L_gui1_中键点击:="MButton" 944 | L_gui1_滚轮滚动:="Wheel" 945 | L_gui1_滚轮横滚:="HWheel" 946 | L_gui1_侧键点击:="XButton" 947 | L_gui1_屏幕尺寸:="Monitor" 948 | L_gui1_米:="m" 949 | L_gui1_次:=" " 950 | L_gui1_寸:="inch" 951 | L_gui1_msgbox:="The number of keystrokes is too low, keyboard heatmap not generated yet." 952 | 953 | L_gui2_设置:="Setting" 954 | L_gui2_历史数据:="History" 955 | L_gui2_sub1:="Set the storage time of history data." 956 | L_gui2_存储:="Storage" 957 | L_gui2_天:="days" 958 | L_gui2_屏幕尺寸:="Monitor" 959 | L_gui2_sub2:="Set the real size of the monitor." 960 | L_gui2_屏幕宽:="Width" 961 | L_gui2_屏幕高:="Height" 962 | L_gui2_毫米:="mm" 963 | L_gui2_键盘布局:="Layout" 964 | L_gui2_sub3:="Set the size of the heatmap." 965 | L_gui2_键宽:="Key Width" 966 | L_gui2_键高:="Key Height" 967 | L_gui2_键间距:="Key Spacing" 968 | L_gui2_区域水平间距:="HSpacing" 969 | L_gui2_区域垂直间距:="VSpacing" 970 | L_gui2_像素:="px" 971 | L_gui2_取消:="Cancel" 972 | L_gui2_保存:="Save" 973 | 974 | L_welcome_main:="Welcome to KMCounter" 975 | L_welcome_sub:="KMCounter will stay in the tray menu for statistics.`nClick the tray icon to view the results." 976 | } 977 | return 978 | 979 | #Include 980 | #Include 981 | #Include 982 | #Include 983 | #Include -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KMCounter 2 | Use heatmap to show mouse and keyboard usage. 3 | 使用热力图显示鼠标与键盘使用情况的工具。 4 | 5 | ## Usage (用法) 6 | * After running, KMCounter will stay in the tray menu for statistics, click the tray icon to view the results when needed. 7 | * 运行后,KMCounter 将常驻托盘菜单进行统计,需要时点击托盘图标查看结果。 8 | ![](https://raw.githubusercontent.com/telppa/KMCounter/main/screenshots/4.png) 9 | 10 | * On the GUI of statistics, PageUp and PageDown or Up and Down or Mouse Wheel can view the historical statistics. 11 | * 在统计界面下,使用 翻页键 或 上下键 或 鼠标滚轮 可以查看历史结果。 12 | ![](https://raw.githubusercontent.com/telppa/KMCounter/main/screenshots/3.gif) 13 | 14 | ## Features (特性) 15 | > Multi-Language 16 | > Mouse Info 17 | > Keyboard Info 18 | > Single Key Info 19 | > History Info 20 | 21 | > 支持查看鼠标信息。 22 | > 支持查看键盘信息。 23 | > 支持查看单键信息。 24 | > 支持查看历史信息。 25 | > 支持设置历史时长。 26 | > 支持设置屏幕尺寸。 27 | > 支持设置键盘布局。 28 | > 支持设置开机启动。 29 | > 支持区分真实模拟。 30 | > 支持高分辨率屏幕。 31 | > 支持低分辨率屏幕。 32 | > 更准确的统计方法。 33 | > 更完善的数据管理。 34 | > 键盘布局总是对齐。 35 | > 默认使用莫兰迪色。 36 | > 不想用了直接删除。 37 | > 不写系统不留垃圾。 38 | 39 | ## Note (注意) 40 | * KMCounter uses the hook to statistics mouse and keyboard info, so antivirus may be a false alarm risk, just add to the trust list. 41 | * KMCounter 使用钩子统计键鼠使用情况,所以杀软可能会误报风险,加白名单即可。 42 | 43 | ## Download (下载) 44 | * ver 3.7 45 | [Github Releases](https://github.com/telppa/KMCounter/releases) 46 | [蓝奏云](https://ahk.lanzoux.com/iK6zunucjqf) 47 | 48 | ## Screenshots (截图) 49 | ![](https://raw.githubusercontent.com/telppa/KMCounter/main/screenshots/3.gif) 50 | ![](https://raw.githubusercontent.com/telppa/KMCounter/main/screenshots/1.png) 51 | ![](https://raw.githubusercontent.com/telppa/KMCounter/main/screenshots/2.png) 52 | ![](https://raw.githubusercontent.com/telppa/KMCounter/main/screenshots/4.png) 53 | 54 | ## 4 ways to customize your own keyboard layout (4种方法定制键盘布局) 55 | 1. Modify the LoadControlList() in the code. 56 | 修改 LoadControlList() 相关代码。 57 | 58 | 2. Have more than a thousand people with the same needs. 59 | 有1000人以上具有相同需求。 60 | 61 | 3. I have the same keyboard. 62 | 我有一个同样的键盘。 63 | 64 | 4. Donate 100RMB or more. 65 | 捐赠 100RMB 以上。 66 | 67 | ## Thanks (感谢) 68 | [fwt](https://www.autoahk.com/archives/35133) 69 | [SKAN](https://www.autohotkey.com/boards/viewtopic.php?f=6&t=76881) 70 | [just me](https://github.com/AHK-just-me) 71 | [robodesign](https://github.com/marius-sucan) 72 | -------------------------------------------------------------------------------- /resouces/KMCounter.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/resouces/KMCounter.ico -------------------------------------------------------------------------------- /resouces/KMCounter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/resouces/KMCounter.png -------------------------------------------------------------------------------- /resouces/布局.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/resouces/布局.png -------------------------------------------------------------------------------- /resouces/统计.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/resouces/统计.png -------------------------------------------------------------------------------- /resouces/设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/resouces/设置.png -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/screenshots/3.gif -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/KMCounter/f86440f8da1bc30d129162774c7da7539229d1a9/screenshots/4.png --------------------------------------------------------------------------------