|
| 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 |
0 commit comments