{"id":51388343,"url":"https://github.com/hampta/luom-g10-config","last_synced_at":"2026-07-03T21:35:22.890Z","repository":{"id":361815015,"uuid":"1255955175","full_name":"hampta/luom-g10-config","owner":"hampta","description":"Vibe coded driver for shitty mouses. Work in progress.","archived":false,"fork":false,"pushed_at":"2026-06-01T11:47:18.000Z","size":815,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-01T12:22:43.611Z","etag":null,"topics":["hator","libratbag","linux","luom-g10","mouse-driver"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hampta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T10:24:06.000Z","updated_at":"2026-06-01T11:47:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hampta/luom-g10-config","commit_stats":null,"previous_names":["hampta/luom-g10-config"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/hampta/luom-g10-config","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hampta%2Fluom-g10-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hampta%2Fluom-g10-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hampta%2Fluom-g10-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hampta%2Fluom-g10-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hampta","download_url":"https://codeload.github.com/hampta/luom-g10-config/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hampta%2Fluom-g10-config/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35102741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-03T02:00:05.635Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hator","libratbag","linux","luom-g10","mouse-driver"],"created_at":"2026-07-03T21:35:22.276Z","updated_at":"2026-07-03T21:35:22.884Z","avatar_url":"https://github.com/hampta.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LUOM G10 Configuration Tool\n\nReverse-engineered CLI configurator for the **LUOM G10** (Hator Pulsar 2 Pro) mouse (Holtek `0x04D9:0xA09F`).  \nProtocol fully decoded from USBPcap captures. No Windows driver or proprietary software needed.\n\n---\n\n## Status\n\n- [x] DPI — 6 slots (×50, ≤12800 CPI)\n- [x] Active DPI slot (0–6)\n- [x] DPI slot count (1–7)\n- [x] Polling rate (125 / 250 / 500 / 1000 Hz)\n- [x] Lift-off distance (3 levels)\n- [x] Key debounce (1–10 / 20 / 100 ms)\n- [x] Light modes (12 effects)\n- [x] Standard color variant (`multicolor`, `white`, `red`, `green`, `blue`)\n- [x] RGB solid color customization (`--color RRGGBB`) via multicolor palette hack\n- [x] Button remapping (`--remap`) for standard keys + DPI\n- [x] Config persistence (`~/.config/luom_g10.json`)\n- [x] Factory defaults (`--default`)\n- [ ] `mode13`, `mode14` — captured from pcap, effect unidentified\n- [ ] Multimedia / Macro button mapping (requires Windows pcap captures)\n- [ ] Windows / macOS support\n- [x] Firmware read-back (verified: hardware does not support reading)\n\n---\n\n\n\n## Supported Devices\n\n| Product | VID | PID |\n|---------|-----|-----|\n| LUOM G10 | `0x04D9` | `0xA09F` |\n| Hator Pulsar 2 Pro (rebrand) | `0x04D9` | `0xA09F` |\n\n---\n\n## Requirements\n\n- Python **3.8+**\n- [`pyusb`](https://github.com/pyusb/pyusb)\n- `libusb-1.0` system library\n- Linux: `sudo` or a udev rule for raw USB access\n\n---\n\n## Installation\n\n### 1. Install system dependency\n\n```bash\n# Debian / Ubuntu / Mint\nsudo apt install libusb-1.0-0\n\n# Arch / Manjaro\nsudo pacman -S libusb\n\n# Fedora / RHEL\nsudo dnf install libusb1\n```\n\n### 2. Install Python package\n\n```bash\npip install pyusb\n```\n\nOr inside a virtual environment:\n\n```bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install pyusb\n```\n\n### 3. Clone the repository\n\n```bash\ngit clone https://github.com/hampta/luom-g10-config.git\ncd luom-g10-config\n```\n\n### 4. USB access — choose one option\n\n#### Option A — run with sudo (quick)\n\nPrefix every command with `sudo`.\n\n#### Option B — udev rule (recommended, passwordless)\n\nCreate `/etc/udev/rules.d/99-luom-g10.rules`:\n\n```\nSUBSYSTEM==\"usb\", ATTRS{idVendor}==\"04d9\", ATTRS{idProduct}==\"a09f\", MODE=\"0666\", GROUP=\"plugdev\"\n```\n\nReload rules and replug the mouse:\n\n```bash\nsudo udevadm control --reload-rules\nsudo udevadm trigger\n```\n\nAdd yourself to the `plugdev` group if needed:\n\n```bash\nsudo usermod -aG plugdev $USER\n# Log out and back in for the group to take effect\n```\n\n---\n\n## Usage\n\n### Read last applied config (no USB needed)\n\n```bash\npython3 luom_config.py --get\n```\n\nExample output:\n\n```\nActive DPI slot : 1  (index 0)\nEnabled slots   : 4 of 7\nCPI per slot    : [400, 800, 1600, 3200, 6400, 12800]\nActive CPI      : 400\nKey response    : 4 ms  (level 3, 0=1ms fastest, 11=100ms default)\nPolling rate    : 1000 Hz\nLift-off dist   : LOD 2  (1=low, 3=high)\nLight mode      : breathing\nButton map      : ['left', 'right', 'middle', 'forward', 'backward', 'dpi']\n```\n\n### Apply factory defaults\n\n```bash\nsudo python3 luom_config.py --default\n```\n\n### Full example\n\n```bash\nsudo python3 luom_config.py \\\n  --polling-rate 1000 \\\n  --lift-off 2 \\\n  --key-response 4 \\\n  --light-mode breathing \\\n  --dpi 400 800 1600 3200 6400 12800 \\\n  --dpi-count 4 \\\n  --active-dpi 1\n```\n\n### Force all DPI slots to one value\n\n```bash\nsudo python3 luom_config.py --force-dpi 1600\n```\n\n### Set static LED color and adjust brightness\n\nThe mouse firmware only has fixed preset colors. To achieve an arbitrary static RGB color, this tool uses a clever hack: it sets the mouse to \"multicolor\" (rainbow) mode, but fills the entire 9-slot rainbow palette with your chosen color.\n\n```bash\n# Bright Orange\nsudo python3 luom_config.py --color FF6600\n\n# Dim Orange (brightness is controlled by the RGB hex value itself!)\nsudo python3 luom_config.py --color 401A00\n\n# Built-in firmware red\nsudo python3 luom_config.py --standard-color red\n```\n\n### Button Remapping\n\nYou can remap the 6 physical buttons (Left, Right, Middle, Forward, Backward, DPI) to any of the supported actions. Provide the actions in physical button order (1 through 6).\n\n**Supported actions:** `left`, `right`, `middle`, `forward`, `backward`, `dpi`, `disabled`.\n\n```bash\n# Example: Swap Left and Right clicks (Southpaw mode)\nsudo python3 luom_config.py --remap right left middle forward backward dpi\n\n# Example: Make the DPI button act as a \"Forward\" button\nsudo python3 luom_config.py --remap left right middle forward backward forward\n\n# Example: Disable the side buttons (Forward/Backward)\nsudo python3 luom_config.py --remap left right middle disabled disabled dpi\n```\n\n---\n\n## Options Reference\n\n| Flag | Type | Allowed values | Default | Description |\n|------|------|----------------|---------|-------------|\n| `--get` | flag | — | — | Print last saved config (no USB required) |\n| `--default` | flag | — | — | Apply factory defaults |\n| `--dpi` | 6 × int | multiples of 50, ≤ 12800 | 300 500 900 1400 2400 4800 | CPI for each of the 6 slots |\n| `--force-dpi` | int | multiple of 50, ≤ 12800 | — | Set all 6 slots to the same CPI |\n| `--active-dpi` | int | 0 – 6 | 0 | Active DPI slot index (0-based) |\n| `--dpi-count` | int | 1 – 7 | 7 | How many slots the DPI button cycles through |\n| `--polling-rate` | int | 125, 250, 500, 1000 | 1000 | Report rate in Hz |\n| `--lift-off` | int | 1, 2, 3 | 2 | Lift-off distance (1 = low, 3 = high) |\n| `--key-response` | int | 1–10, 20, 100 | 100 | Button debounce time in ms |\n| `--light-mode` | str | see below | `standard` | LED lighting effect |\n| `--standard-color` | str | `multicolor`, `white`, `red`, `green`, `blue` | `multicolor` | Use a built-in firmware color preset for standard mode |\n| `--color` | hex | `RRGGBB` (e.g., `FF0000`) | — | Set a custom static RGB color (and adjust brightness) using the multicolor hack |\n| `--remap` | list | see below | (default map) | Remap physical buttons 1-6 in order. Allowed: `left`, `right`, `middle`, `forward`, `backward`, `dpi`, `disabled` |\n\n### Light modes\n\n| Value | Effect |\n|-------|--------|\n| `standard` | Static rainbow (default) |\n| `off` | LED off |\n| `breathing` | Slow breathing |\n| `neon` | Neon pulse |\n| `wave` | Color wave |\n| `key-reaction` | Reacts to button clicks |\n| `trailing` | Trail on movement |\n| `drag` | Drag back-and-forth |\n| `slide` | Slide effect |\n| `yo-yo` | Yo-yo bounce |\n| `marbles` | Marbles pattern |\n| `flying-star` | Flying star |\n\n\u003e `mode13` and `mode14` are captured from pcap but unidentified — commented out pending verification.\n\n---\n\n## State Persistence\n\nConfig is written to `~/.config/luom_g10.json` after every successful apply.  \n`--get` reads from this file — **the device is write-only over USB** and cannot be queried back.\n\nExample saved state:\n\n```json\n{\n  \"active_slot\": 0,\n  \"cpi\": [400, 800, 1600, 3200, 6400, 12800],\n  \"button_map\": [\"left\", \"right\", \"middle\", \"forward\", \"backward\", \"dpi\"],\n  \"dpi_count\": 4,\n  \"key_response\": 3,\n  \"polling_rate\": 1000,\n  \"lift_off\": 2,\n  \"light_mode\": \"breathing\"\n}\n```\n\n---\n\n## Protocol Notes\n\n| Property | Value |\n|----------|-------|\n| VID / PID | `0x04D9` / `0xA09F` (Holtek Semiconductor) |\n| Config channel | EP3 OUT — 8 × 32-byte interrupt-out burst |\n| Control packets | 16 EP0 HID class `SET_REPORT` packets |\n| HID reports | EP1 IN, 7 bytes @ 125–1000 Hz |\n| DPI encoding | `reg = (cpi // 50) - 1` |\n| Checksum | `(b2 + b4) \u0026 0xFF` per control packet |\n\nKey protocol quirks:\n\n- **LOD 1** — adds `+0x08` offset to `ctrl#13 b2`, sets `b4 = 0xE8`\n- **LOD 3** — uses a different preamble byte in `ctrl#1` (`0xb5` vs `0x75`)\n- **Light mode** — encoded in `ctrl#5` + `ctrl#6`; EP3 color data is identical across all modes\n- **Active DPI slot** (`ctrl#5`) and **DPI count** (`ctrl#9`) share `byte[4]`\n\n---\n\n## Files\n\n| File | Description |\n|------|-------------|\n| `luom_config.py` | Main CLI configuration tool |\n| `analyze_light.py` | Developer tool: parse and analyze USBPcap `.pcapng` captures |\n| `pcap/` | Raw pcap captures used for protocol reverse engineering |\n\n### analyze_light.py\n\nDeveloper/research tool. Parses `pcap/luom_g10_light.pcapng` and prints:\n\n- Endpoint breakdown (EP1 HID vs EP3 lighting frames)\n- Mouse click / movement / scroll events\n- Lighting sessions grouped by 500 ms gap, with frame type identification\n- Color table extracted from EP3 interrupt-out frames\n\n```bash\npython3 analyze_light.py\n```\n\n\u003e Requires the pcap file. Not needed for normal mouse configuration.\n\n---\n\n## Troubleshooting\n\n| Problem | Fix |\n|---------|-----|\n| `Error: LUOM G10 ... not found` | Mouse not plugged in, or wrong VID/PID. Verify with `lsusb \\| grep 04d9`. |\n| `USBError: [Errno 13] Access denied` | Run with `sudo` or apply the udev rule (see §4). |\n| `ModuleNotFoundError: usb` | Run `pip install pyusb`. |\n| `libusb not found` | Install `libusb-1.0-0` (apt) / `libusb` (pacman) / `libusb1` (dnf). |\n| Settings lost after reboot | Expected — re-apply with `luom_config.py`. Mouse stores config internally on write. |\n| `--get` shows stale values | State file is from a previous session. Re-apply config to sync. |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhampta%2Fluom-g10-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhampta%2Fluom-g10-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhampta%2Fluom-g10-config/lists"}