常驻 macOS 菜单栏的按键计数工具:按前台应用统计键盘敲击次数,按天累计,只存本地。
这个工具需要「辅助功能」权限来监听全局键盘事件,所以你应该先知道它到底看了什么:
- 只数次数,不看内容。 事件回调拿到
CGEvent后直接丢弃,从不读取 keycode、字符或修饰键——见KeyEventTap.swift:74-79,回调的三个参数全部是_。 - 落盘的数据只有三样东西:日期、应用 bundle ID、当天次数。数据结构就是
[日期: [bundleID: 次数]],见PersistenceStore.swift:4-11。 - 不联网。 代码中没有任何网络请求,数据只写到你自己的磁盘。
- 只监听按下(
keyDown),且是listenOnly模式——无法修改或拦截你的输入。
不放心的话,需要审查的核心文件只有两个,加起来不到 140 行。
- 菜单栏图标,左键点开当天各应用的敲击排行。
- 右键 → 「统计…」打开统计窗口,含每日 / 每周 / 总计三个视图与堆叠柱状趋势图。
- 每 5 秒增量落盘,退出时 flush,不丢数据。
- 热路径(按键回调)只做一次原子自增,不分配内存、不加锁、不做 I/O,对输入零阻塞。
- macOS 13 Ventura 或更高(统计图表依赖系统
Charts框架)。 - 从源码构建还需要 Xcode 15+ 或对应的 Swift 5.9+ 工具链(
swift --version可查)。 - Intel 与 Apple Silicon 都支持,Release 产物是通用二进制。
-
从 Releases 下载
KeyboardHitCounter.zip。 -
解压,把
KeyboardHitCounter.app拖进「应用程序」。 -
解除 Gatekeeper 隔离——本项目是 ad-hoc 签名、未经 Apple 公证,从网上下载的 app 会被系统直接拦下并提示「已损坏」或「无法验证开发者」:
xattr -dr com.apple.quarantine /Applications/KeyboardHitCounter.app
这一步是必需的,不是可选优化。跳过它 app 打不开。
git clone https://github.com/wudanyang6/keyboard-hit-counter.git
cd keyboard-hit-counter
make run自己构建出来的 app 不带隔离标记,不需要上面的 xattr 步骤。
启动后菜单栏会出现图标,并弹出「需要「辅助功能」权限」提示:
- 点提示里的「打开系统设置」,或手动进入 系统设置 → 隐私与安全性 → 辅助功能。
- 勾选
KeyboardHitCounter。 - 不用重启 app——它每秒检查一次授权状态,勾选后会自动接上事件监听并开始计数。
如果列表里没有它,点 + 手动添加 /Applications/KeyboardHitCounter.app。
计数文件在:
~/Library/Application Support/KeyboardHitCounter/counts.json
完整卸载(删 app + 删数据 + 撤权限):
rm -rf /Applications/KeyboardHitCounter.app
rm -rf ~/Library/Application\ Support/KeyboardHitCounter然后在「系统设置 → 隐私与安全性 → 辅助功能」里把它移出列表。
make build # 只构建宿主架构,开发迭代用,比通用构建快
make build-universal # Intel + Apple Silicon 通用二进制
make app # 组装 build/KeyboardHitCounter.app(通用二进制 + ad-hoc 签名)
make zip # 打出可分发的 build/KeyboardHitCounter.zip
make run # app + 启动
make test # swift test
make icon # 从 Scripts/render_icon.swift 重新生成 .icns
make clean跑单个测试:swift test --filter StatsAggregatorTests 或 swift test --filter StatsAggregatorTests/testTodayWeekTotalSums。
swift run 会绕过 Info.plist 直接跑裸可执行文件,缺少 LSUIElement 和 bundle ID,权限相关的行为不对——涉及权限时请用 make run。
严格的热 / 温 / 冷三层切分:
| 层 | 组件 | 职责 |
|---|---|---|
| 热 | KeyEventTap → CounterStore |
按键回调,单次原子自增,无锁无分配无 I/O |
| 温 | FrontmostAppTracker + AppSlotRegistry |
应用切换时把 bundle ID 映射为槽位并原子写入 |
| 冷 | PersistenceWorker + PersistenceStore |
后台队列 5 秒一次增量落盘 |
可单测的逻辑全部在 KeyboardHitCounterCore,可执行 target 只是 UI 装配层(SwiftPM 的测试 target 无法 import 可执行 target)。
设计原理与取舍见 docs/superpowers/specs/2026-08-18-keyboard-hit-counter-design.md,需要维持的不变量见 CLAUDE.md。