Skip to content

Repository files navigation

G3M Pro 电量托盘

一个使用 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 工作,不需要管理员权限。

工作原理

1. 定位 G3M Pro HID 接口

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 接收器其次”的顺序读取。

2. 发送查询并解析响应

对匹配到的 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] 电量百分比 必须在 0100 之间
response[9] 原始状态字节 由程序转换为用户可读状态

当前观察到的状态字节转换规则如下。原始 flag 只在内部参与解析,不直接显示给用户:

原始状态 额外条件 托盘显示
0x00 普通状态
0x02 正在充电
0x01 有线连接且电量低于 100% 正在充电
0x01 有线连接且电量为 100% 已充满
0x01 其他连接方式 状态未知
其他情况 包括无法确认的组合 状态未知

其中 flag=0x01 在有线连接下表示充电状态:电量低于 100% 时显示“正在充电”,达到 100% 时显示“已充满”。其他连接方式下的 0x01 含义尚未确认,因此仍显示“状态未知”。

3. 状态发布与托盘更新

一次成功查询会形成一个统一的 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
Loading

4. 电量历史和续航估算

程序将历史数据保存到当前用户的本地缓存目录:

%LOCALAPPDATA%\G3M Battery\history.json

历史记录只保存最近 8 天。设备状态发生变化时立即记录;状态不变时最多每 5 分钟记录一次,因此不会按照 5 秒轮询频率持续写入大量重复数据。每次程序启动都会建立新的观测会话,历史计算不会把程序未运行期间跨进程连接起来。

右键菜单中的“查看电量历史”会打开应用内历史窗口。窗口会显示当前状态、最后有效采样时间、数据范围、最近 24 小时或 7 天曲线、充电和放电观测,以及断连和读取失败事件:

  • 最近 24 小时和最近 7 天的电量曲线,曲线不会跨越未观测区间连接;
  • 当前状态、最后有效采样时间和历史数据范围;
  • 最近一次完整充电时长;
  • 最近一次从 100% 降到 20% 的观测时长;
  • 最近的设备断连、设备枚举失败和电量读取失败事件;
  • 当前可用的预计剩余使用时间及数据不足时的原因说明;

预计剩余使用时间只在当前处于正常非充电状态,并且有连续有效的放电数据时计算。当前实现要求最近连续数据至少覆盖 30 分钟,且电量至少下降 2 个百分点;中间发生断连、读取失败、长时间没有采样或切换连接方式时,不会跨过间隔继续计算。

估算使用简单的线性方法:

每小时耗电率 = (较早电量 - 当前电量) / 相隔小时数
预计剩余小时 = 当前电量 / 每小时耗电率

因此该数值只能作为粗略参考。程序没有运行时无法记录电量变化,整数百分比读数、使用强度、灯光和连接方式变化也会影响结果。数据不足、正在充电或发生异常时,菜单会显示“暂无估算”。

5. 开机启动

开机启动默认关闭,用户可以通过右键菜单中的“开机启动”手工开启或关闭。启用后,程序会在当前用户的注册表中写入:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
    G3MBattery = "C:\path\to\g3m-battery.exe"

程序使用当前运行中的 exe 绝对路径作为启动命令,因此不需要安装器或管理员权限。它会在当前用户登录 Windows 后启动,而不是在登录界面之前启动。若移动了 exe 文件,需要重新关闭再开启一次“开机启动”以更新路径。

6. 图标和交互状态

  • 电量 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.mdDEPLOYMENT.md

限制

  • 这是 Windows 专用程序,源码文件通过 //go:build windows 约束为 Windows 构建;
  • 程序只识别上面列出的 G3M Pro HID 集合,其他型号或固件版本可能需要重新确认 VID、PID、Usage 或报文字段;
  • 当前实现只查询和展示状态,不会修改鼠标配置,也不会控制充电或连接模式;
  • 开机启动记录的是当前 exe 路径,移动程序文件后旧路径会被识别为失效,需要重新设置开机启动;
  • 状态字节的解释基于当前设备的协议观测结果,遇到新的原始值时会显示“状态未知”,不会静默猜测;
  • 预计剩余使用时间是基于有限历史数据的线性估算,不代表设备或厂商提供的精确续航时间;
  • 历史数据只在程序运行并成功读取设备时产生,无法补全程序未运行期间的电量变化。

About

一个使用 Go 编写的 Windows 托盘程序,用于显示漫步者 G3M Pro 鼠标当前的电量、连接方式和充电状态,不需要启动 HECATE Connect。

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages