Skip to content

Commit 5c2d751

Browse files
committed
Initial commit: Incott mouse HID driver
Windows system tray utility for Incott wireless mice (Ghero, G23, G24, G23V2, Zero 29, Zero 39). Supports DPI, polling rate, LOD, debounce, sleep timer, motion sync, angle snapping, ripple control, receiver LED. Includes auto-boost feature, autostart, two-level logging, and unit tests.
0 parents  commit 5c2d751

20 files changed

Lines changed: 2113 additions & 0 deletions

.github/workflows/release.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
build:
13+
runs-on: windows-latest
14+
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Go
20+
uses: actions/setup-go@v5
21+
with:
22+
go-version: '1.26'
23+
cache: true
24+
25+
- name: Run tests
26+
run: go test -v ./...
27+
28+
- name: Compile Windows resource (icon)
29+
shell: pwsh
30+
run: |
31+
$windres = (Get-Command windres -ErrorAction SilentlyContinue).Source
32+
if (-not $windres) {
33+
$windres = "C:\msys64\mingw64\bin\windres.exe"
34+
}
35+
Push-Location icons
36+
& $windres app.rc -o ../app_windows.syso
37+
Pop-Location
38+
39+
- name: Build binary
40+
env:
41+
CGO_ENABLED: 1
42+
run: go build -o IncottDriver.exe -ldflags="-H windowsgui -s -w" .
43+
44+
- name: Create release
45+
uses: softprops/action-gh-release@v2
46+
with:
47+
files: IncottDriver.exe
48+
generate_release_notes: true
49+
draft: false
50+
prerelease: false

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
IncottDriver.exe
2+
incott.log
3+
settings.json
4+
app_windows.syso
5+
docs/
6+
.claude/

CLAUDE.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Windows system tray driver for **Incott** wireless mice (Ghero, G23, G24, G23V2, Zero 29, Zero 39). Communicates with the mouse over HID to control DPI, polling rate, LOD, debounce, sleep timer, motion sync, angle snapping, ripple control, and receiver LED mode. Provides an "auto-boost" feature that switches to 8000 Hz when any of the configured target processes is detected.
8+
9+
- **Language**: Go
10+
- **Platform**: Windows only (uses `syscall`, `windows/registry`, PowerShell dialogs)
11+
- **Device IDs**: Vendor `0x093A`, Product `0x522C` (wireless) / `0x622C` (charging) — shared across all models
12+
- **Model detection**: via HID `Product` string from device firmware
13+
14+
## Build & Run
15+
16+
```bash
17+
go build -o IncottDriver.exe -ldflags="-H windowsgui" .
18+
./IncottDriver.exe
19+
```
20+
21+
The `-H windowsgui` linker flag hides the console window. Omit it during development to see stdout output. Requires CGO — the `CC` environment variable should point to a modern MinGW-w64 GCC (TDM-GCC 10.x is incompatible with Go 1.26+).
22+
23+
### Icons
24+
25+
- **Tray icon**: `tray_icon.ico` — embedded via `go:embed`, generated from `mouse.png` (resized to 16/32/48/64px ICO)
26+
- **Exe icon**: `app.ico` — compiled into `app_windows.syso` via `windres app.rc`. The `.syso` file is auto-linked by Go.
27+
- **Source image**: `mouse.png` — original high-res image of the mouse
28+
29+
To regenerate icons after changing `mouse.png`:
30+
```bash
31+
python -c "
32+
from PIL import Image
33+
img = Image.open('mouse.png').convert('RGBA')
34+
# Tray
35+
sizes = [16,32,48,64]
36+
icons = [img.resize((s,s), Image.LANCZOS) for s in sizes]
37+
icons[0].save('tray_icon.ico', format='ICO', sizes=[(s,s) for s in sizes], append_images=icons[1:])
38+
# Exe
39+
sizes = [16,32,48,64,128,256]
40+
icons = [img.resize((s,s), Image.LANCZOS) for s in sizes]
41+
icons[0].save('app.ico', format='ICO', sizes=[(s,s) for s in sizes], append_images=icons[1:])
42+
"
43+
windres app.rc -o app_windows.syso
44+
```
45+
46+
## Project Structure
47+
48+
| File | Responsibility |
49+
|---|---|
50+
| `main.go` | Entry point, tray icon via `go:embed tray_icon.ico`, `onExit` |
51+
| `config.go` | `AppConfig` struct, `loadConfig`/`saveConfig`, `setAutoStart` (registry), `promptForExe` (PowerShell dialog), `parseTargetApps`/`setTargetApps` (comma-separated app list) |
52+
| `logging.go` | `logInfo` (always writes), `logDebug` (only when debug enabled via `atomic.Bool`). Log file: `incott.log` |
53+
| `device.go` | HID constants, pre-allocated report buffers, all `apply*` functions, `mouseWorker`, `gameMonitorWorker`, `findRunningApp`, `isMouseDevice` (product name filter) |
54+
| `ui.go` | Menu structs (fixed arrays replacing maps), `onReady`, `refreshStatusText`, `updateCheckmarks`, click forwarding via goroutines |
55+
56+
## Architecture
57+
58+
Three concurrent components:
59+
60+
1. **systray UI** (`onReady` in `ui.go`) — system tray menu with DPI, Hz, LOD, Debounce, Sleep presets, toggle checkboxes (Motion Sync, Angle Snapping, Ripple Control), Receiver LED submenu, auto-boost toggle, autostart, debug logging toggle. Each submenu group uses `forwardClicks()` which spawns one goroutine per menu item, reducing the main select to ~8 cases.
61+
2. **`mouseWorker`** (`device.go`) — reconnection loop. Enumerates HID devices by vendor/product ID, filters by `isMouseDevice(info.Product)` to avoid connecting to Incott keyboards sharing the same vendor ID. Opens UsagePage `0xFF05`. On connect, reads current settings (status via `0x89`, debounce via `0x85/0x01`, LOD + motion sync via `0x84`, angle snapping via `0x84/0x03`, ripple control via `0x84/0x02`, receiver LED via `0x88`, sleep via `0x85/0x03`). Device model name is read from HID `Product` field and shown in tray tooltip. Then enters a read loop for live status updates.
62+
3. **`gameMonitorWorker`** (`device.go`) — polls every 3s via a single `CreateToolhelp32Snapshot` call. Checks all target apps (`targetAppsLower`, comma-separated in config) in one pass over the process list. Auto-boosts to 8000 Hz when any target is found, restores on exit.
63+
64+
### HID Protocol (feature reports)
65+
66+
All reports are 9 bytes, report ID `0x09`. Read commands use the set command byte OR'd with `0x80`.
67+
68+
| Action | Bytes |
69+
|---|---|
70+
| Request status | `09 89 00 00 00 00 00 00 00` |
71+
| Set polling rate | `09 01 <rate> 00 00 00 00 00 00` |
72+
| Set DPI | `09 03 06 <idx> 00 00 00 00 00` |
73+
| Set LOD | `09 04 01 <lod> 00 00 00 00 00` |
74+
| Set ripple control | `09 04 02 <0/1> 00 00 00 00 00` |
75+
| Set angle snapping | `09 04 03 <0/1> 00 00 00 00 00` |
76+
| Set motion sync | `09 04 04 <0/1> 00 00 00 00 00` |
77+
| Set debounce | `09 05 01 <ms> 00 00 00 00 00` |
78+
| Set sleep | `09 05 03 <lo> <hi> 00 00 00 00` |
79+
| Set receiver LED | `09 08 <mode> 00 00 00 00 00 00` |
80+
| Read LOD + motion sync | `09 84 00 ...` → LOD in upper nibble of byte[7], motion sync in lower nibble |
81+
| Read ripple control | `09 84 02 ...` → value in byte[3] |
82+
| Read angle snapping | `09 84 03 ...` → value in byte[3] |
83+
| Read debounce | `09 85 01 ...` → value in byte[3] |
84+
| Read sleep | `09 85 03 ...` → LE uint16 in bytes[3:5] |
85+
| Read receiver LED | `09 88 00 ...` → mode in byte[2] |
86+
87+
Rate bytes: `0x00`=1000, `0x01`=500, `0x02`=250, `0x03`=125, `0x04`=8000, `0x05`=4000, `0x06`=2000.
88+
DPI indices: `0x00`=400, `0x01`=800, `0x02`=1600, `0x03`=2400, `0x04`=3200, `0x05`=6400.
89+
LOD bytes: `0x00`=1mm, `0x01`=2mm, `0x02`=0.7mm.
90+
Receiver LED modes: `0x00`=Connect & polling rate, `0x01`=Battery status, `0x02`=Battery warning.
91+
92+
### Synchronization
93+
94+
- `mu` (`sync.Mutex`) guards `activeDevice` and `sendBuf` — used by `mouseWorker` and all `apply*` calls.
95+
- `boostMu` (`sync.Mutex`) guards `currentHz`, `savedHz`, `autoBoostEnabled`, `targetApps`, `targetAppsLower`.
96+
97+
### Logging
98+
99+
- `logInfo(format, args...)` — always written to `incott.log`. For user actions and lifecycle events.
100+
- `logDebug(format, args...)` — only when `debugEnabled` (`atomic.Bool`) is true. For HID bytes, device reads, status updates.
101+
- Config field: `"debug"` in `settings.json`.
102+
103+
### Persistence
104+
105+
- **`settings.json`** — stores `target_game_exe` (comma-separated app list), `auto_boost`, `auto_start`, `debug`. Loaded on startup, saved on setting changes.
106+
- **Windows Registry** (`HKCU\Software\Microsoft\Windows\CurrentVersion\Run`) — autostart entry under key `IncottDriver`.
107+
108+
### Performance Notes
109+
110+
- Menu items use fixed-size arrays in structs instead of `map[int]*MenuItem`.
111+
- HID report buffers (`[9]byte`, `[64]byte`) are pre-allocated and reused.
112+
- `refreshStatusText` uses `strings.Builder` + `strconv.Itoa` (zero `fmt.Sprintf`).
113+
- `targetAppsLower` is pre-computed via `parseTargetApps`, avoiding repeated `strings.ToLower`.
114+
- `ProcessEntry32` is reused across `findRunningApp` calls.
115+
- `findRunningApp` takes a single process snapshot and checks all target apps in one pass.
116+
117+
## Key Dependencies
118+
119+
- `github.com/getlantern/systray` — system tray menu
120+
- `github.com/karalabe/hid` — HID access (requires CGO, uses native Windows API)
121+
- `golang.org/x/sys/windows/registry` — registry access for autostart

Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
BINARY := IncottDriver.exe
2+
LDFLAGS := -H windowsgui
3+
4+
.PHONY: all build test vet clean run
5+
6+
all: build
7+
8+
# Build depends on test — binary is only produced if tests pass
9+
build: test
10+
go build -o $(BINARY) -ldflags="$(LDFLAGS)" .
11+
12+
test:
13+
go test -v ./...
14+
15+
vet:
16+
go vet ./...
17+
18+
clean:
19+
rm -f $(BINARY) incott.log
20+
21+
run: build
22+
./$(BINARY)

README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Incott Mouse Driver
2+
3+
<p align="center">
4+
<img src="icons/mouse.png" alt="Incott Mouse" width="120">
5+
</p>
6+
7+
Lightweight Windows system tray utility for **Incott** wireless mice. Communicates directly with the mouse over HID — no vendor software required.
8+
9+
## Features
10+
11+
**Mouse Settings** (read from device on connect, applied instantly):
12+
- **DPI** — 400, 800, 1600, 2400, 3200, 6400
13+
- **Polling Rate** — 125, 250, 500, 1000, 2000, 4000, 8000 Hz
14+
- **LOD (Lift-Off Distance)** — 0.7 mm, 1 mm, 2 mm
15+
- **Debounce** — 0–30 ms
16+
- **Sleep Timer** — 10 sec to 15 min
17+
- **Motion Sync** — on/off
18+
- **Angle Snapping** — on/off
19+
- **Ripple Control** — on/off
20+
- **Receiver LED Mode** — Battery status / Connect & polling rate / Battery warning
21+
22+
**Auto-Boost** — automatically switches polling rate to 8000 Hz when a target application is running, restores the previous value when it closes. Supports multiple apps (comma-separated, e.g. `cs2.exe, valorant.exe`).
23+
24+
**Status Bar** — shows battery level, current DPI, polling rate, and other settings at the top of the tray menu.
25+
26+
**Other:**
27+
- Start with Windows (autostart via registry)
28+
- Two-level logging (`incott.log`): INFO for user actions, DEBUG for HID protocol details
29+
30+
## Download
31+
32+
Grab the latest `IncottDriver.exe` from [Releases](../../releases) — no installation needed, just run it.
33+
34+
Releases are built automatically by GitHub Actions on every `v*` tag push.
35+
36+
## Releasing a new version (for maintainers)
37+
38+
```bash
39+
git tag v1.0.0
40+
git push origin v1.0.0
41+
```
42+
43+
The workflow at `.github/workflows/release.yml` runs tests, builds the Windows binary, and publishes it as a GitHub Release.
44+
45+
## Building from Source
46+
47+
### Prerequisites
48+
49+
- [Go 1.26+](https://go.dev/dl/)
50+
- C compiler (CGO is required by the HID library)
51+
52+
### Windows 11
53+
54+
1. Install Go from https://go.dev/dl/
55+
56+
2. Install MinGW-w64 (GCC):
57+
```powershell
58+
winget install BrechtSanders.WinLibs.POSIX.UCRT
59+
```
60+
> **Note:** TDM-GCC 10.x is incompatible with Go 1.26+. Use WinLibs GCC 15+ or MSYS2.
61+
62+
3. Set the `CC` environment variable (if WinLibs is not first in PATH):
63+
```powershell
64+
# Find the installed GCC path:
65+
Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\WinGet\Packages" -Filter "gcc.exe" -Recurse | Select -First 1
66+
67+
# Set it permanently for the current user:
68+
[System.Environment]::SetEnvironmentVariable('CC', '<path-to-gcc.exe>', 'User')
69+
```
70+
71+
4. Clone and build:
72+
```bash
73+
git clone https://github.com/anthropics/MouseIncott.git
74+
cd MouseIncott
75+
```
76+
77+
Build options:
78+
- **With Make** (recommended, runs tests first):
79+
```bash
80+
mingw32-make build
81+
```
82+
- **With batch script**:
83+
```cmd
84+
build.bat
85+
```
86+
- **Direct go build** (skips tests):
87+
```bash
88+
go build -o IncottDriver.exe -ldflags="-H windowsgui" .
89+
```
90+
91+
5. (Optional) Rebuild the exe icon after changing `icons/mouse.png`:
92+
```bash
93+
pip install Pillow
94+
python -c "
95+
from PIL import Image
96+
img = Image.open('icons/mouse.png').convert('RGBA')
97+
for name, sizes in [('icons/tray_icon.ico',[16,32,48,64]), ('icons/app.ico',[16,32,48,64,128,256])]:
98+
icons = [img.resize((s,s), Image.LANCZOS) for s in sizes]
99+
icons[0].save(name, format='ICO', sizes=[(s,s) for s in sizes], append_images=icons[1:])
100+
"
101+
windres icons/app.rc -o app_windows.syso
102+
go build -o IncottDriver.exe -ldflags="-H windowsgui" .
103+
```
104+
105+
### Docker (cross-compile)
106+
107+
Build the Windows exe from any OS using Docker:
108+
109+
```bash
110+
docker run --rm -v "$(pwd):/src" -w /src \
111+
x1unix/go-mingw:1.24 \
112+
go build -o IncottDriver.exe -ldflags="-H windowsgui" .
113+
```
114+
115+
> The `x1unix/go-mingw` image includes Go + MinGW-w64 cross-compiler for Windows. The resulting exe will not have the embedded Windows icon (`app_windows.syso` must be compiled with `windres` on Windows).
116+
117+
## Configuration
118+
119+
Settings are stored in `settings.json` (created automatically on first run):
120+
121+
```json
122+
{
123+
"target_game_exe": "cs2.exe, valorant.exe",
124+
"auto_boost": true,
125+
"auto_start": false,
126+
"debug": false
127+
}
128+
```
129+
130+
| Field | Description |
131+
|---|---|
132+
| `target_game_exe` | Comma-separated list of processes to monitor for auto-boost |
133+
| `auto_boost` | Enable automatic 8000 Hz when target app is detected |
134+
| `auto_start` | Launch on Windows startup |
135+
| `debug` | Write detailed HID protocol data to `incott.log` |
136+
137+
## Supported Devices
138+
139+
All Incott mice sharing the same chipset (Vendor ID `0x093A`) are supported:
140+
141+
| Model | Product ID | Status |
142+
|---|---|---|
143+
| Ghero | `0x522C` / `0x622C` | Supported |
144+
| G23 | `0x522C` / `0x622C` | Supported |
145+
| G24 | `0x522C` / `0x622C` | Supported |
146+
| G23V2 | `0x522C` / `0x622C` | Supported |
147+
| Zero 29 | `0x522C` / `0x622C` | Supported |
148+
| Zero 39 | `0x522C` / `0x622C` | Supported |
149+
150+
`0x522C` — wireless mode, `0x622C` — charging mode. The device model is detected automatically from the HID product name. Incott keyboards sharing the same vendor ID are automatically filtered out.
151+
152+
## License
153+
154+
MIT

build.bat

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@echo off
2+
echo Running tests...
3+
go test ./...
4+
if errorlevel 1 (
5+
echo.
6+
echo Tests FAILED. Build aborted.
7+
exit /b 1
8+
)
9+
echo.
10+
echo Tests passed. Building binary...
11+
go build -o IncottDriver.exe -ldflags="-H windowsgui" .
12+
if errorlevel 1 (
13+
echo Build FAILED.
14+
exit /b 1
15+
)
16+
echo Build successful: IncottDriver.exe

0 commit comments

Comments
 (0)