星河避难所

返回

我把每天的键盘输入,变成了一张热力图Blur image

有一天在打字的时候,我突然想到一件很小的事情:

我每天敲这么多键盘,那我到底更常用的是哪些键?

这个问题本身其实没什么用,但它有一点点“可能和直觉不一样”的感觉。

比如我一直觉得自己主要在用字母区,但如果真的统计出来,会不会空格才是第一?或者回车?

这种事情,如果只是靠感觉,其实永远不会有答案。

但一旦把它变成数据,就完全是另一种东西了。

想着想着,就干脆把这件事做出来了。

软件截图

代码跟软件都已经开源了,地址放在最后,感兴趣可以去看看


它一开始其实只是个脚本#

最初的想法很简单:

监听键盘 → 做计数 → 输出结果。

按这个思路,一个脚本就能搞定,甚至都不需要什么界面。

但写到一半就发现,这种形态不太成立。

因为它太容易“断掉”了。

只要我关了终端,或者哪天忘记开,它那一段时间的数据就直接没了。而键盘使用这种行为,本来就不是你会刻意去“开始”和“结束”的东西。

如果记录本身需要你去记得,它就有点违背这个事情的初衷。

后来就慢慢变成另一种方向了:让它自己待着,一直在那儿,不需要你管。

于是最后的形态就是一个托盘应用。


为什么会走到客户端这一步#

其实我一开始是想避免写客户端的。

不是功能做不到,而是开发体验的问题。传统 GUI 那一套我不太熟,也不太想花时间在那上面。

但“监听键盘”这件事本身就决定了,它不可能是一个网页。

由于边界问题,浏览器拿不到这些数据,所以要么放弃,要么就认真做一个本地应用。

后来是因为看到 Wails V3,才觉得这件事变得顺手了不少。

之前用过 Wails V2,它更像是一个“把网站封装成桌面应用”的壳。

单窗口、靠前端路由模拟多页面、菜单和托盘能力很弱。

而 V3 原生支持了多窗口、系统菜单、托盘、Dock & Taskbar 交互之后,整个应用终于有了“骨架”,从“套壳网页”真正变成了一款原生应用。

完美的符合了我的需求。


真正麻烦的不是展示,而是“你按了什么键”#

一开始我以为,最麻烦的会是热力图这种展示层的东西。

结果完全相反。

最绕的地方其实是:怎么知道用户按了哪个键。

监听事件我选择了 robotn/gohook,他本身不提供“监听能力”,它只是把各平台的系统级输入监听 API 做了一层统一封装。

在 Windows 中他封装的是 SetWindowsHookEx

在 Mac 中封装的是 Quartz Event Tap

因此它可以在系统层面上监听原始输入事件,比应用级监听更底层,得到按压情况与虚拟键码。

问题在于,这个“原始事件”不是你直觉里的 A、B、C,而是一串虚拟键码。

虚拟键码就是操作系统为了方便程序开发,给键盘上每个“功能”分配的一个固定的数字编号。它像是按键的身份证号,而不是座位号。

以 Windows 为例:当我们按下键盘上 A 键的那一刻,键盘电路只知道按下去的是物理位置第 0x1E 号的键,它会把 0x1E 这个扫描码发给电脑。

操作系统收到 0x1E 后,会根据当前激活的键盘布局(比如美式键盘)来查表(比如查询到这个物理位置对应的是字母 A)。

于是,它把这个位置信号转换成虚拟键码 0x41(即 VK_A)。

基于这套机制,我们开发者完全不用关心用户用的是哪种键盘硬件、按键的物理位置在哪。只要在程序里看到 0x41,就可以确信:用户按下的是 A 键。

但问题同样也出现在这里,键盘的位置信号传递给系统,而系统把他们转换成虚拟码,这个过程是系统进行的,因此 Windows 跟 Mac 之间并不统一,需要额外进行处理。

微软官网中有针对 Windows 虚拟键码的说明:Virtual-Key Codes

而 Mac 则来自 Carbon.framework 中的 Events.h,记录在《Inside Mac Volume V》第 V-191 页

这件事没有什么优雅解法,只能分开处理。

最后就是用 Go 的 //go:build 把不同平台的映射拆开,各自维护。

逻辑上统一,但实现上不强行合并。


数据怎么存,反而是一个需要控制的点#

监听拿到了,接下来就是存。

一想到这个脑子里的第一反应肯定是:按一次,写一次数据库。

但稍微再一琢磨就会明显感觉到问题。

键盘输入是一个非常高频的行为,如果每次都落盘,SQLite 的压力会有点大,而且写入冲突也比较频繁。

后来就换成了一种更“松一点”的方式:

先放在内存里攒着,到一定数量,或者过一段时间,再统一写进去。

严格来说,这会有一点点数据丢失的可能(比如程序突然退出),但换来的是整体的稳定性和更低的开销。

对这种工具来说,我更倾向于后者。


热力图反而没那么复杂#

真正开始做展示的时候,反而是最顺的一段。

键盘布局其实是有现成逻辑的,用机械键盘里常说的 Unit(u)就能很好地描述:

普通按键是 1u,空格是 6.25u,一些功能键是 2u、2.75u 这种。

把它当成一个网格去排,然后再把每个键的使用次数映射成颜色深浅,一张热力图就自然出来了。

没有太多技巧,更多是一个“把东西摆对”的过程。


后来补了一点“实时感”#

最早版本其实只是统计 + 展示。

但用的时候会觉得,它有点“太安静了”。

所以后面加了一点很轻的反馈,比如按键的时候会有一点变化。

实现上也不复杂,用 Wails 自带的事件机制,把后端的事件往前端推,Vue 那边监听一下就可以了。

这一块写起来反而最像在写前端项目。


写到这里,反而会有点疑问:这东西到底有什么用#

项目做完之后,我其实想过这个问题。

它不会帮你提高效率,也不会改变你怎么打字。

你大概率也不会因为某个键用得特别多,就去刻意优化它。

但它提供了一种很微妙的东西:

你可以“看到”自己平时完全不会注意到的行为。

有些结果是符合预期的,有些会有一点点偏差。

这种偏差不大,但挺有意思的。

更像是一种观察,而不是一个工具。


Key Heat#

最后把这个东西整理了一下,做成了一个完整的应用,叫 ​Key Heat

它会一直待在托盘里,做的事情其实很克制:

  • 只统计每个键被按了多少次
  • 不记录具体输入内容
  • 没有网络请求

数据只在本地。

如果你也对这种东西有点好奇,可以自己跑一下看看:

我把每天的键盘输入,变成了一张热力图
https://hejunjie.life/blog/cif84h2i
作者 何俊杰
发布时间 2026年4月22日
版权信息 CC BY-NC-SA 4.0
评论似乎卡住了,尝试刷新?✨