一个使用 Go 编写的 Windows 托盘程序,用于实时显示漫步者 G3M Pro 鼠标的电量、连接方式和充电状态。程序直接访问鼠标或 2.4G 接收器暴露的 HID 接口,不需要启动 HECATE Connect,也不需要安装额外的专用驱动。
程序启动后会立即查询一次设备,之后默认每 5 秒查询一次。查询结果会同时反映到托盘图标、鼠标悬停提示和右键菜单中;右键菜单还提供开机启动、立即刷新、查看电量历史、预计剩余使用时间、退出和当前软件版本。
- 支持 USB 有线连接和 2.4G 接收器连接;
- 同时发现两种连接时优先使用有线设备;有线设备读取失败时再尝试其他候选设备;
- 托盘图标根据电量显示绿色、黄色或红色,并在充电时叠加闪电标识;
- 将设备原始状态字节转换为“普通状态”“正在充电”“已充满”和“状态未知”等文字;
- 监听 G3M Pro HID 设备的插入和移除,并在连接变化后立即刷新;
- 电量低于
20%且未处于充电状态时发送一次低电量通知,避免重复打扰; - 持久化记录最近 8 天的电量采样、充电状态变化、设备断连和读取失败事件;
- 在右键菜单中显示基于近期放电速度计算的预计剩余使用时间;
- 在应用内历史窗口查看最近 24 小时和 7 天电量曲线、充电时长及历史事件;
- 可在右键菜单中切换当前用户的 Windows 开机启动;
- Tooltip 和右键菜单显示电量、连接方式、充电状态及错误信息;
- 通过 Windows 内置 HID、SetupAPI、Shell API 工作,不需要管理员权限。
G3M Pro 会暴露一个厂商自定义 HID 集合。程序使用 Windows SetupAPI 枚举当前存在的 HID 设备接口,然后打开每个候选设备,依次检查 HID 属性、预解析数据和报告能力,只有同时满足以下条件的接口才会被保留:
| 项目 | 值 | 作用 |
|---|---|---|
| Vendor ID | 0x320F |
识别 HECATE/G3M Pro 设备 |
| Wired Product ID | 0x706B |
标识有线连接 |
| Receiver Product ID | 0x706E |
标识 2.4G 接收器 |
| Usage Page | 0xFF1C |
匹配厂商自定义 HID 页面 |
| Usage | 0x0092 |
匹配电量查询使用的 HID 集合 |
| 输入/输出报告长度 | 至少 64 字节 | 确保能够承载完整查询报文 |
因此,程序不会因为发现普通键盘、鼠标或其他 HID 设备就误把它们当成 G3M Pro。若有多个候选设备,程序按照“有线优先、2.4G 接收器其次”的顺序读取。
对匹配到的 HID 设备,程序通过 CreateFileW 打开设备路径,并使用 WriteFile 发送固定长度为 64 字节的查询报文。报文前 5 个字节包含当前协议使用的命令,其余字节补零:
04 20 00 1A 06 00 00 00 00 00 00 00 ...
随后使用 ReadFile 读取设备响应。程序会检查响应长度、报文头和电量范围,避免把不完整或无效的数据更新到托盘。响应字段按照 Go 的 0-based 下标解释如下:
| 响应偏移 | 含义 | 处理方式 |
|---|---|---|
response[0] |
报文类型 | 必须为 0x04 |
response[1] |
命令族 | 必须为 0x20 |
response[3] |
子命令 | 必须为 0x1A |
response[7] |
数据块标记 | 0xFF 表示无效数据块 |
response[8] |
电量百分比 | 必须在 0 到 100 之间 |
response[9] |
原始状态字节 | 由程序转换为用户可读状态 |
当前观察到的状态字节转换规则如下。原始 flag 只在内部参与解析,不直接显示给用户:
| 原始状态 | 额外条件 | 托盘显示 |
|---|---|---|
0x00 |
无 | 普通状态 |
0x02 |
无 | 正在充电 |
0x01 |
有线连接且电量低于 100% |
正在充电 |
0x01 |
有线连接且电量为 100% |
已充满 |
0x01 |
其他连接方式 | 状态未知 |
| 其他情况 | 包括无法确认的组合 | 状态未知 |
其中 flag=0x01 在有线连接下表示充电状态:电量低于 100% 时显示“正在充电”,达到 100% 时显示“已充满”。其他连接方式下的 0x01 含义尚未确认,因此仍显示“状态未知”。
一次成功查询会形成一个统一的 BatteryState,包含电量、连接方式、标准化后的充电状态、更新时间和必要的错误信息。后台监控循环负责查询和发布状态,Windows 托盘窗口线程收到状态更新消息后,重新绘制图标并更新 Tooltip;右键菜单读取同一份状态,因此三处显示保持一致。
除了每 5 秒执行一次的定时轮询,程序还向 Windows 注册 G3M Pro HID 设备接口通知。收到设备到达或移除事件后,程序会复用同一个刷新 channel 重新枚举设备,因此连接接收器、拔出接收器或切换到有线连接时不需要等待下一次定时轮询。
当一次有效查询得到的电量低于 20%,且设备没有处于“正在充电”或“已充满”状态时,程序通过托盘通知发送一次低电量提醒。同一轮低电量状态不会每 5 秒重复通知;电量恢复到 20% 或以上后,下一次再次降到阈值以下时会重新提醒。
flowchart TD
A[启动托盘程序] --> B[枚举当前 Windows HID 接口]
B --> C{找到匹配的 G3M Pro HID 集合?}
C -- 否 --> E[发布错误状态<br/>未找到 G3M Pro HID 厂商集合]
C -- 是 --> D[候选设备排序<br/>有线优先,2.4G 接收器其次]
D --> F[CreateFileW 打开设备]
F --> G[WriteFile<br/>发送 64 字节查询报文]
G --> H[ReadFile<br/>读取响应报文]
H --> I{响应长度、报文头和电量有效?}
I -- 否且还有候选 --> D
I -- 否且没有候选 --> K[发布错误状态<br/>读取电量失败]
I -- 是 --> L["解析 response[8] 电量<br/>解析 response[9] 状态字节"]
L --> M[PID 映射连接方式<br/>有线或 2.4G 接收器]
M --> N[标准化充电状态<br/>普通、充电、已充满或未知]
N --> O[发布 BatteryState]
E --> O
K --> O
O --> P[托盘窗口线程处理更新消息]
P --> Q[按状态重绘电池图标]
P --> R[更新 Tooltip 和右键菜单]
S[每 5 秒定时器] --> B
T[右键“立即刷新”] --> B
U[HID 设备到达或移除] --> B
程序将历史数据保存到当前用户的本地缓存目录:
%LOCALAPPDATA%\G3M Battery\history.json
历史记录只保存最近 8 天。设备状态发生变化时立即记录;状态不变时最多每 5 分钟记录一次,因此不会按照 5 秒轮询频率持续写入大量重复数据。每次程序启动都会建立新的观测会话,历史计算不会把程序未运行期间跨进程连接起来。
右键菜单中的“查看电量历史”会打开应用内历史窗口。窗口会显示当前状态、最后有效采样时间、数据范围、最近 24 小时或 7 天曲线、充电和放电观测,以及断连和读取失败事件:
- 最近 24 小时和最近 7 天的电量曲线,曲线不会跨越未观测区间连接;
- 当前状态、最后有效采样时间和历史数据范围;
- 最近一次完整充电时长;
- 最近一次从 100% 降到 20% 的观测时长;
- 最近的设备断连、设备枚举失败和电量读取失败事件;
- 当前可用的预计剩余使用时间及数据不足时的原因说明;
预计剩余使用时间只在当前处于正常非充电状态,并且有连续有效的放电数据时计算。当前实现要求最近连续数据至少覆盖 30 分钟,且电量至少下降 2 个百分点;中间发生断连、读取失败、长时间没有采样或切换连接方式时,不会跨过间隔继续计算。
估算使用简单的线性方法:
每小时耗电率 = (较早电量 - 当前电量) / 相隔小时数
预计剩余小时 = 当前电量 / 每小时耗电率
因此该数值只能作为粗略参考。程序没有运行时无法记录电量变化,整数百分比读数、使用强度、灯光和连接方式变化也会影响结果。数据不足、正在充电或发生异常时,菜单会显示“暂无估算”。
开机启动默认关闭,用户可以通过右键菜单中的“开机启动”手工开启或关闭。启用后,程序会在当前用户的注册表中写入:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
G3MBattery = "C:\path\to\g3m-battery.exe"
程序使用当前运行中的 exe 绝对路径作为启动命令,因此不需要安装器或管理员权限。它会在当前用户登录 Windows 后启动,而不是在登录界面之前启动。若移动了 exe 文件,需要重新关闭再开启一次“开机启动”以更新路径。
- 电量
50%及以上使用绿色填充;20%到49%使用黄色;低于20%使用红色; - 正在充电时在电池图标上绘制白色闪电;
- 未找到设备或读取失败时显示灰色边框和叉号,并在 Tooltip/菜单中保留错误原因;
- 鼠标悬停时显示简短 Tooltip;右键可以查看详细状态、预计剩余使用时间、打开应用内电量历史、立即刷新或退出程序;
- 轮询和手工刷新使用同一套查询、解析和发布流程,不会产生两种不一致的状态解释。
| 文件 | 作用 |
|---|---|
cmd/g3m-battery/hid_windows.go |
枚举 HID 接口、筛选设备、发送查询并读取响应 |
cmd/g3m-battery/battery_state_windows.go |
保存查询结果、映射连接方式、标准化充电状态和错误类型 |
cmd/g3m-battery/history_windows.go |
历史数据结构、采样持久化和历史窗口入口 |
cmd/g3m-battery/history_metrics_windows.go |
历史连续性、充电/放电时长和续航估算 |
cmd/g3m-battery/history_window_windows.go |
应用内历史窗口生命周期、消息处理和交互 |
cmd/g3m-battery/history_chart_windows.go / history_render_windows.go |
历史图表和窗口绘制辅助函数 |
cmd/g3m-battery/main_windows.go |
启动托盘、定时轮询、记录历史并协调刷新消息 |
cmd/g3m-battery/tray_windows.go |
创建 Windows 隐藏窗口、托盘图标、Tooltip 和消息分发 |
cmd/g3m-battery/tray_menu_windows.go / tray_notification_windows.go |
托盘菜单和通知 |
cmd/g3m-battery/icon_windows.go |
根据 BatteryState 动态绘制电池图标 |
cmd/g3m-battery/startup_windows.go |
读写当前用户的开机启动注册表项 |
cmd/g3m-battery/version_windows.go / cmd/g3m-battery/VERSION |
嵌入并显示软件版本号 |
cmd/g3m-battery/logo_windows_*.syso |
将 assets/logo.ico 作为 Windows 程序资源编入不同架构的可执行文件 |
项目当前仅支持 Windows,使用 Go 1.25 或兼容版本。在 Windows 上执行:
go build -trimpath -ldflags="-H=windowsgui" -o g3m-battery.exe ./cmd/g3m-battery
生成的 g3m-battery.exe 可以直接运行。程序不需要管理员权限,也不需要安装 HECATE Connect。
GitHub Actions 会在推送符合 v*.*.* 格式的标签时自动运行,分别构建 Windows amd64 和 arm64 版本,并将以下产物发布到 GitHub Release:
g3m-battery-windows-amd64.exe
g3m-battery-windows-arm64.exe
例如:
git tag -a v1.5.0 -m "Release v1.5.0"
git push origin main v1.5.0
发布前的测试和部署约定见 TESTING.md 与 DEPLOYMENT.md。
- 这是 Windows 专用程序,源码文件通过
//go:build windows约束为 Windows 构建; - 程序只识别上面列出的 G3M Pro HID 集合,其他型号或固件版本可能需要重新确认 VID、PID、Usage 或报文字段;
- 当前实现只查询和展示状态,不会修改鼠标配置,也不会控制充电或连接模式;
- 开机启动记录的是当前 exe 路径,移动程序文件后旧路径会被识别为失效,需要重新设置开机启动;
- 状态字节的解释基于当前设备的协议观测结果,遇到新的原始值时会显示“状态未知”,不会静默猜测;
- 预计剩余使用时间是基于有限历史数据的线性估算,不代表设备或厂商提供的精确续航时间;
- 历史数据只在程序运行并成功读取设备时产生,无法补全程序未运行期间的电量变化。