From 7fa5eabce8d531e799e74545edfc07ea96f3d088 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 16 Aug 2026 19:59:43 +1000 Subject: [PATCH] Add G703 wired HID++ support and mark onboard format 3 writable after a passing write probe. --- docs/logitech-testing.md | 39 +++++++++++++++++++ src/drivers/logitech/hidpp.test.ts | 7 ++++ src/drivers/logitech/onboard-profiles.test.ts | 9 ++++- src/drivers/logitech/onboard-profiles.ts | 13 ++++++- src/logitech/index.ts | 5 ++- 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/docs/logitech-testing.md b/docs/logitech-testing.md index a6090ae..17299da 100644 --- a/docs/logitech-testing.md +++ b/docs/logitech-testing.md @@ -14,6 +14,7 @@ Supported identifiers: - `046d:c0a8` — PRO X 2 Superstrike (USB) - `046d:c07e` — G402 / G402 Hyperion Fury (wired) - `046d:c08f` — G403 HERO (wired) +- `046d:c087` — G703 (wired) - `046d:c08b` — G502 HERO (wired) - `046d:c07d` — G502 / G502 Proteus Core (wired) - `046d:c095` — G502 X PLUS (USB cable; wireless identity `4099` uses its Lightspeed receiver) @@ -95,6 +96,44 @@ format 2 (LOGAN); DPI-stage changes still are not (the v1 format has no stage table). Record the device identifier, protocol version, and any failing setting in the issue or pull request. Do not use factory reset during initial testing. +## G703 (direct-connect, HID++ device index `0xFF`) + +The original G703 on its USB cable (`046d:c087`) takes the same direct-connect +path as the G403 HERO: legacy Adjustable DPI `0x2201` and legacy Report Rate +`0x8060`. Extended DPI `0x2202` is absent, so the lift-off/sensor card stays +hidden. Identity reports USB transport `C087` and firmware `MPM 14.02` / +`BOT 64.00` / `RQI 04.00`. + +Its onboard profile is format `3` (HEAT). A 2026-08-16 guided write probe on +this cable unit passed: name `OM_VERIFY`, default-slot DPI 1000, and 500 Hz +were stored exactly, confirmed live, and restored. Polling-rate and DPI-slot +edits therefore write the active onboard profile. The sensor advertises +50–12,000 DPI in steps of 50 and 125 / 250 / 500 / 1000 Hz. Battery is +reported from the voltage feature (`0x1001`). RGB lighting (`0x8070`) +enumerates Primary and Logo zones as write-only. + +A cable capture confirmed Wired USB, 450 DPI, 1000 Hz, charging, active +onboard profile 3, four of five DPI slots in use (450 / 800 / 1600 / 12000). +The Lightspeed dongle was not present; wireless remains untested on this unit. + +1. Confirm the sidebar and title show the mouse and the connection reads + **Wired**. +2. Confirm the firmware list and the HID++ device details section populate. +3. Confirm DPI reads back and that a staged DPI write re-reads as the value + sent (`0x2201` function 3, short request). +4. Confirm the polling-rate controls are **enabled** and that changing the rate + writes the active onboard profile's report-rate byte, then reloads as the + new rate. +5. Open a stored profile and change the DPI slot table. Confirm the physical + DPI button cycles the new values and that unused slots stay unused. +6. Confirm the sensor card (lift-off distance) is hidden. +7. Confirm battery percentage and charging state populate while the cable is + attached. +8. Confirm Primary and Logo lighting zones are offered. Writes are + write-only — the current effect is not read back. + +Do not use factory reset. Format 3 has no factory-reset image. + ## G502 HERO (direct-connect, HID++ device index `0xFF`) The wired G502 HERO follows the same direct-connect path as the G403 HERO: diff --git a/src/drivers/logitech/hidpp.test.ts b/src/drivers/logitech/hidpp.test.ts index 81780a6..4a486c3 100644 --- a/src/drivers/logitech/hidpp.test.ts +++ b/src/drivers/logitech/hidpp.test.ts @@ -26,6 +26,7 @@ import { const G402 = 0xc07e; const G403_HERO = 0xc08f; +const G703 = 0xc087; const G502 = 0xc07d; const G502_X_PLUS = 0xc095; const G502_X = 0xc099; @@ -55,6 +56,12 @@ test("the G502 family direct USB interfaces are recognized", () => { assert.equal(isDirectConnectProduct(LIGHTSPEED_RECEIVER), false); }); +test("the G703 USB cable interface is recognized as direct-connect", () => { + assert.equal(isDirectConnectProduct(G703), true); + assert.equal(isDirectConnectProduct(G403_HERO), true); + assert.equal(isWiredHidppConnection(G703, { USB: "C087" }, false), true); +}); + test("extended DPI does not imply lift-off or mode-status controls", () => { assert.equal(supportsLiveLiftOffControl(false, null), false, "0 means no LOD control"); assert.equal(supportsLiveLiftOffControl(true, "Medium"), false, "legacy DPI has no LOD field"); diff --git a/src/drivers/logitech/onboard-profiles.test.ts b/src/drivers/logitech/onboard-profiles.test.ts index f061578..6df953b 100644 --- a/src/drivers/logitech/onboard-profiles.test.ts +++ b/src/drivers/logitech/onboard-profiles.test.ts @@ -572,7 +572,7 @@ test("decodes all captured G502 LIGHTSPEED format-3 profiles", () => { test("parses the captured G502 LIGHTSPEED format-3 geometry and directory", () => { assert.deepEqual( { verified: describeProfileFormat(3).verified, writable: describeProfileFormat(3).writable }, - { verified: true, writable: false }, + { verified: true, writable: true }, ); assert.deepEqual(parseProfilesInfo(G502_LIGHTSPEED_INFO_REPLY), { memoryModelId: 1, @@ -593,6 +593,13 @@ test("parses the captured G502 LIGHTSPEED format-3 geometry and directory", () = ]); }); +test("format-3 encoders use the G703-captured scalar grid and shared rate", () => { + const capabilities = capabilitiesForFormat(3); + assert.deepEqual(capabilities.dpiStages, { maxStages: 5, minDpi: 50, maxDpi: 12000, stepDpi: 50 }); + assert.deepEqual(reportRatesFor(capabilities.reportRates, "wired"), [125, 250, 500, 1000]); + assert.equal(describeProfileFormat(3).writable, true); +}); + test("format-3 probe encoders accept limits collected live from the mouse", () => { const original = decodeOnboardProfile(G502_LIGHTSPEED_PROFILE_1, 3, { sector: 1, enabled: true }, true); const stages = original.dpiStages.map((stage) => ({ ...stage })); diff --git a/src/drivers/logitech/onboard-profiles.ts b/src/drivers/logitech/onboard-profiles.ts index 1a20612..7b1e209 100644 --- a/src/drivers/logitech/onboard-profiles.ts +++ b/src/drivers/logitech/onboard-profiles.ts @@ -54,7 +54,7 @@ const PROFILE_FORMAT_NAMES: Record = { * release — see docs/logitech-onboard-profiles.md. */ const VERIFIED_FORMATS = new Set([2, 3, 4, 7]); -const WRITABLE_FORMATS = new Set([2, 4, 7]); +const WRITABLE_FORMATS = new Set([2, 3, 4, 7]); const PROFILE_WRITE_PROBE_FORMATS = new Set([2, 3, 4]); const FACTORY_RESET_FORMATS = new Set([7]); @@ -207,6 +207,17 @@ const FORMAT_CAPABILITIES: Record = { maxNameLength: PROFILE_NAME_MAX_CHARS, bunnyHop: false, }, + // A G703 on format 3 reported four of five scalar slots, 50-12000 DPI in + // steps of 50, and 125/250/500/1000 Hz. Name, default-slot DPI, and wired + // rate writes were applied, confirmed live, and restored. + 3: { + supportedLods: [], + lodEncoding: LOD_ENCODING, + dpiStages: { maxStages: 5, minDpi: 50, maxDpi: 12000, stepDpi: 50 }, + reportRates: { wirelessMaxHz: 1000, wiredMaxHz: 1000 }, + maxNameLength: PROFILE_NAME_MAX_CHARS, + bunnyHop: false, + }, // A G102 LIGHTSYNC on format 4 reported five scalar slots, 50-8000 DPI in // steps of 50, and 125/250/500/1000 Hz through the capability collector. // Its DPI, rate, and name writes were applied, read back live, and restored. diff --git a/src/logitech/index.ts b/src/logitech/index.ts index 0260e5d..7d77f42 100644 --- a/src/logitech/index.ts +++ b/src/logitech/index.ts @@ -37,6 +37,7 @@ export const BOLT_PAIRING_SLOTS = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06] as const; * * G Pro / G502 / G403 HERO generation (HID++ 2.0, DPI feature 0x2202): * - 0xc085 — G Pro (wired, 2017) + * - 0xc087 — G703 (wired) * - 0xc08b — G502 HERO (wired) * - 0xc08c — G Pro Hero (wired) * - 0xc08e — G903 HERO (wired) @@ -55,8 +56,8 @@ export const LOGITECH_DIRECT_PRODUCT_IDS = [ 0xc084, 0xc089, 0xc092, // G303 / G402 generation 0xc07d, 0xc07e, 0xc080, - // G Pro / G502 / G403 HERO generation - 0xc085, 0xc08b, 0xc08c, 0xc08e, 0xc08f, + // G Pro / G502 / G403 HERO / G703 generation + 0xc085, 0xc087, 0xc08b, 0xc08c, 0xc08e, 0xc08f, // G Pro X Superlight generation 0xc094, // G502 X generation