@openmouse/protocol 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +167 -0
- package/README.md +89 -0
- package/dist/asus/index.d.ts +52 -0
- package/dist/asus/index.d.ts.map +1 -0
- package/dist/asus/index.js +248 -0
- package/dist/asus/index.js.map +1 -0
- package/dist/atk/index.d.ts +200 -0
- package/dist/atk/index.d.ts.map +1 -0
- package/dist/atk/index.js +480 -0
- package/dist/atk/index.js.map +1 -0
- package/dist/bitmouse/index.d.ts +284 -0
- package/dist/bitmouse/index.d.ts.map +1 -0
- package/dist/bitmouse/index.js +396 -0
- package/dist/bitmouse/index.js.map +1 -0
- package/dist/compx/codec.d.ts +31 -0
- package/dist/compx/codec.d.ts.map +1 -0
- package/dist/compx/codec.js +50 -0
- package/dist/compx/codec.js.map +1 -0
- package/dist/corsair/index.d.ts +147 -0
- package/dist/corsair/index.d.ts.map +1 -0
- package/dist/corsair/index.js +223 -0
- package/dist/corsair/index.js.map +1 -0
- package/dist/dareu/index.d.ts +136 -0
- package/dist/dareu/index.d.ts.map +1 -0
- package/dist/dareu/index.js +344 -0
- package/dist/dareu/index.js.map +1 -0
- package/dist/drivers/asus/hid.d.ts +29 -0
- package/dist/drivers/asus/hid.d.ts.map +1 -0
- package/dist/drivers/asus/hid.js +419 -0
- package/dist/drivers/asus/hid.js.map +1 -0
- package/dist/drivers/atk/bitmouse-hid.d.ts +80 -0
- package/dist/drivers/atk/bitmouse-hid.d.ts.map +1 -0
- package/dist/drivers/atk/bitmouse-hid.js +501 -0
- package/dist/drivers/atk/bitmouse-hid.js.map +1 -0
- package/dist/drivers/atk/hid.d.ts +163 -0
- package/dist/drivers/atk/hid.d.ts.map +1 -0
- package/dist/drivers/atk/hid.js +1368 -0
- package/dist/drivers/atk/hid.js.map +1 -0
- package/dist/drivers/atk/products.d.ts +13 -0
- package/dist/drivers/atk/products.d.ts.map +1 -0
- package/dist/drivers/atk/products.js +13 -0
- package/dist/drivers/atk/products.js.map +1 -0
- package/dist/drivers/attackshark/dpi.d.ts +41 -0
- package/dist/drivers/attackshark/dpi.d.ts.map +1 -0
- package/dist/drivers/attackshark/dpi.js +215 -0
- package/dist/drivers/attackshark/dpi.js.map +1 -0
- package/dist/drivers/attackshark/hid.d.ts +124 -0
- package/dist/drivers/attackshark/hid.d.ts.map +1 -0
- package/dist/drivers/attackshark/hid.js +728 -0
- package/dist/drivers/attackshark/hid.js.map +1 -0
- package/dist/drivers/corsair/hid.d.ts +70 -0
- package/dist/drivers/corsair/hid.d.ts.map +1 -0
- package/dist/drivers/corsair/hid.js +414 -0
- package/dist/drivers/corsair/hid.js.map +1 -0
- package/dist/drivers/dareu/hid.d.ts +59 -0
- package/dist/drivers/dareu/hid.d.ts.map +1 -0
- package/dist/drivers/dareu/hid.js +478 -0
- package/dist/drivers/dareu/hid.js.map +1 -0
- package/dist/drivers/endgame/egg-op1-hid.d.ts +107 -0
- package/dist/drivers/endgame/egg-op1-hid.d.ts.map +1 -0
- package/dist/drivers/endgame/egg-op1-hid.js +605 -0
- package/dist/drivers/endgame/egg-op1-hid.js.map +1 -0
- package/dist/drivers/endgame/egg-we-control.d.ts +42 -0
- package/dist/drivers/endgame/egg-we-control.d.ts.map +1 -0
- package/dist/drivers/endgame/egg-we-control.js +87 -0
- package/dist/drivers/endgame/egg-we-control.js.map +1 -0
- package/dist/drivers/endgame/egg-we-hid.d.ts +58 -0
- package/dist/drivers/endgame/egg-we-hid.d.ts.map +1 -0
- package/dist/drivers/endgame/egg-we-hid.js +514 -0
- package/dist/drivers/endgame/egg-we-hid.js.map +1 -0
- package/dist/drivers/fantech/hid.d.ts +106 -0
- package/dist/drivers/fantech/hid.d.ts.map +1 -0
- package/dist/drivers/fantech/hid.js +295 -0
- package/dist/drivers/fantech/hid.js.map +1 -0
- package/dist/drivers/finalmouse/hid.d.ts +31 -0
- package/dist/drivers/finalmouse/hid.d.ts.map +1 -0
- package/dist/drivers/finalmouse/hid.js +181 -0
- package/dist/drivers/finalmouse/hid.js.map +1 -0
- package/dist/drivers/gearhub/hid.d.ts +191 -0
- package/dist/drivers/gearhub/hid.d.ts.map +1 -0
- package/dist/drivers/gearhub/hid.js +542 -0
- package/dist/drivers/gearhub/hid.js.map +1 -0
- package/dist/drivers/glorious/classic-hid.d.ts +62 -0
- package/dist/drivers/glorious/classic-hid.d.ts.map +1 -0
- package/dist/drivers/glorious/classic-hid.js +326 -0
- package/dist/drivers/glorious/classic-hid.js.map +1 -0
- package/dist/drivers/glorious/hid.d.ts +39 -0
- package/dist/drivers/glorious/hid.d.ts.map +1 -0
- package/dist/drivers/glorious/hid.js +203 -0
- package/dist/drivers/glorious/hid.js.map +1 -0
- package/dist/drivers/gwolves/hid.d.ts +27 -0
- package/dist/drivers/gwolves/hid.d.ts.map +1 -0
- package/dist/drivers/gwolves/hid.js +225 -0
- package/dist/drivers/gwolves/hid.js.map +1 -0
- package/dist/drivers/gwolves/products.d.ts +17 -0
- package/dist/drivers/gwolves/products.d.ts.map +1 -0
- package/dist/drivers/gwolves/products.js +58 -0
- package/dist/drivers/gwolves/products.js.map +1 -0
- package/dist/drivers/hyperx/hid.d.ts +59 -0
- package/dist/drivers/hyperx/hid.d.ts.map +1 -0
- package/dist/drivers/hyperx/hid.js +229 -0
- package/dist/drivers/hyperx/hid.js.map +1 -0
- package/dist/drivers/incott/hid.d.ts +513 -0
- package/dist/drivers/incott/hid.d.ts.map +1 -0
- package/dist/drivers/incott/hid.js +1173 -0
- package/dist/drivers/incott/hid.js.map +1 -0
- package/dist/drivers/index.d.ts +5 -0
- package/dist/drivers/index.d.ts.map +1 -0
- package/dist/drivers/index.js +5 -0
- package/dist/drivers/index.js.map +1 -0
- package/dist/drivers/keychron/m6-hid.d.ts +81 -0
- package/dist/drivers/keychron/m6-hid.d.ts.map +1 -0
- package/dist/drivers/keychron/m6-hid.js +486 -0
- package/dist/drivers/keychron/m6-hid.js.map +1 -0
- package/dist/drivers/keychron/nape-hid.d.ts +60 -0
- package/dist/drivers/keychron/nape-hid.d.ts.map +1 -0
- package/dist/drivers/keychron/nape-hid.js +432 -0
- package/dist/drivers/keychron/nape-hid.js.map +1 -0
- package/dist/drivers/ksnake/hid.d.ts +74 -0
- package/dist/drivers/ksnake/hid.d.ts.map +1 -0
- package/dist/drivers/ksnake/hid.js +368 -0
- package/dist/drivers/ksnake/hid.js.map +1 -0
- package/dist/drivers/lamzu/hid.d.ts +58 -0
- package/dist/drivers/lamzu/hid.d.ts.map +1 -0
- package/dist/drivers/lamzu/hid.js +414 -0
- package/dist/drivers/lamzu/hid.js.map +1 -0
- package/dist/drivers/lamzu-atlantis/hid.d.ts +148 -0
- package/dist/drivers/lamzu-atlantis/hid.d.ts.map +1 -0
- package/dist/drivers/lamzu-atlantis/hid.js +587 -0
- package/dist/drivers/lamzu-atlantis/hid.js.map +1 -0
- package/dist/drivers/logitech/bolt.d.ts +41 -0
- package/dist/drivers/logitech/bolt.d.ts.map +1 -0
- package/dist/drivers/logitech/bolt.js +105 -0
- package/dist/drivers/logitech/bolt.js.map +1 -0
- package/dist/drivers/logitech/hidpp.d.ts +525 -0
- package/dist/drivers/logitech/hidpp.d.ts.map +1 -0
- package/dist/drivers/logitech/hidpp.js +2774 -0
- package/dist/drivers/logitech/hidpp.js.map +1 -0
- package/dist/drivers/logitech/mode-status.d.ts +53 -0
- package/dist/drivers/logitech/mode-status.d.ts.map +1 -0
- package/dist/drivers/logitech/mode-status.js +52 -0
- package/dist/drivers/logitech/mode-status.js.map +1 -0
- package/dist/drivers/logitech/onboard-profiles.d.ts +298 -0
- package/dist/drivers/logitech/onboard-profiles.d.ts.map +1 -0
- package/dist/drivers/logitech/onboard-profiles.js +965 -0
- package/dist/drivers/logitech/onboard-profiles.js.map +1 -0
- package/dist/drivers/logitech/rgb-effects.d.ts +20 -0
- package/dist/drivers/logitech/rgb-effects.d.ts.map +1 -0
- package/dist/drivers/logitech/rgb-effects.js +93 -0
- package/dist/drivers/logitech/rgb-effects.js.map +1 -0
- package/dist/drivers/mchose/a5-gen1-hid.d.ts +36 -0
- package/dist/drivers/mchose/a5-gen1-hid.d.ts.map +1 -0
- package/dist/drivers/mchose/a5-gen1-hid.js +219 -0
- package/dist/drivers/mchose/a5-gen1-hid.js.map +1 -0
- package/dist/drivers/mchose/dock-hid.d.ts +26 -0
- package/dist/drivers/mchose/dock-hid.d.ts.map +1 -0
- package/dist/drivers/mchose/dock-hid.js +176 -0
- package/dist/drivers/mchose/dock-hid.js.map +1 -0
- package/dist/drivers/mchose/hid.d.ts +118 -0
- package/dist/drivers/mchose/hid.d.ts.map +1 -0
- package/dist/drivers/mchose/hid.js +550 -0
- package/dist/drivers/mchose/hid.js.map +1 -0
- package/dist/drivers/mchose/v3-hid.d.ts +109 -0
- package/dist/drivers/mchose/v3-hid.d.ts.map +1 -0
- package/dist/drivers/mchose/v3-hid.js +572 -0
- package/dist/drivers/mchose/v3-hid.js.map +1 -0
- package/dist/drivers/microsoft/hid.d.ts +25 -0
- package/dist/drivers/microsoft/hid.d.ts.map +1 -0
- package/dist/drivers/microsoft/hid.js +247 -0
- package/dist/drivers/microsoft/hid.js.map +1 -0
- package/dist/drivers/moddo/hid.d.ts +31 -0
- package/dist/drivers/moddo/hid.d.ts.map +1 -0
- package/dist/drivers/moddo/hid.js +154 -0
- package/dist/drivers/moddo/hid.js.map +1 -0
- package/dist/drivers/mouse-types.d.ts +388 -0
- package/dist/drivers/mouse-types.d.ts.map +1 -0
- package/dist/drivers/mouse-types.js +2 -0
- package/dist/drivers/mouse-types.js.map +1 -0
- package/dist/drivers/ninjutso/hid.d.ts +62 -0
- package/dist/drivers/ninjutso/hid.d.ts.map +1 -0
- package/dist/drivers/ninjutso/hid.js +680 -0
- package/dist/drivers/ninjutso/hid.js.map +1 -0
- package/dist/drivers/orbital/hid.d.ts +24 -0
- package/dist/drivers/orbital/hid.d.ts.map +1 -0
- package/dist/drivers/orbital/hid.js +85 -0
- package/dist/drivers/orbital/hid.js.map +1 -0
- package/dist/drivers/orbital/host-protocol.d.ts +59 -0
- package/dist/drivers/orbital/host-protocol.d.ts.map +1 -0
- package/dist/drivers/orbital/host-protocol.js +884 -0
- package/dist/drivers/orbital/host-protocol.js.map +1 -0
- package/dist/drivers/pulsar/pulsar-hid.d.ts +63 -0
- package/dist/drivers/pulsar/pulsar-hid.d.ts.map +1 -0
- package/dist/drivers/pulsar/pulsar-hid.js +381 -0
- package/dist/drivers/pulsar/pulsar-hid.js.map +1 -0
- package/dist/drivers/pulsar/pulsar-pro-hid.d.ts +46 -0
- package/dist/drivers/pulsar/pulsar-pro-hid.d.ts.map +1 -0
- package/dist/drivers/pulsar/pulsar-pro-hid.js +343 -0
- package/dist/drivers/pulsar/pulsar-pro-hid.js.map +1 -0
- package/dist/drivers/pulsar/pulsar-xs1-hid.d.ts +40 -0
- package/dist/drivers/pulsar/pulsar-xs1-hid.d.ts.map +1 -0
- package/dist/drivers/pulsar/pulsar-xs1-hid.js +251 -0
- package/dist/drivers/pulsar/pulsar-xs1-hid.js.map +1 -0
- package/dist/drivers/rawm/hid.d.ts +29 -0
- package/dist/drivers/rawm/hid.d.ts.map +1 -0
- package/dist/drivers/rawm/hid.js +210 -0
- package/dist/drivers/rawm/hid.js.map +1 -0
- package/dist/drivers/razer/cobra-hid.d.ts +63 -0
- package/dist/drivers/razer/cobra-hid.d.ts.map +1 -0
- package/dist/drivers/razer/cobra-hid.js +282 -0
- package/dist/drivers/razer/cobra-hid.js.map +1 -0
- package/dist/drivers/razer/hid-open.d.ts +13 -0
- package/dist/drivers/razer/hid-open.d.ts.map +1 -0
- package/dist/drivers/razer/hid-open.js +29 -0
- package/dist/drivers/razer/hid-open.js.map +1 -0
- package/dist/drivers/razer/hid.d.ts +231 -0
- package/dist/drivers/razer/hid.d.ts.map +1 -0
- package/dist/drivers/razer/hid.js +772 -0
- package/dist/drivers/razer/hid.js.map +1 -0
- package/dist/drivers/razer/viper-hid.d.ts +33 -0
- package/dist/drivers/razer/viper-hid.d.ts.map +1 -0
- package/dist/drivers/razer/viper-hid.js +177 -0
- package/dist/drivers/razer/viper-hid.js.map +1 -0
- package/dist/drivers/razer/viper-mini-hid.d.ts +58 -0
- package/dist/drivers/razer/viper-mini-hid.d.ts.map +1 -0
- package/dist/drivers/razer/viper-mini-hid.js +273 -0
- package/dist/drivers/razer/viper-mini-hid.js.map +1 -0
- package/dist/drivers/razer/viper-v4-pro-hid.d.ts +26 -0
- package/dist/drivers/razer/viper-v4-pro-hid.d.ts.map +1 -0
- package/dist/drivers/razer/viper-v4-pro-hid.js +154 -0
- package/dist/drivers/razer/viper-v4-pro-hid.js.map +1 -0
- package/dist/drivers/registry.d.ts +70 -0
- package/dist/drivers/registry.d.ts.map +1 -0
- package/dist/drivers/registry.js +146 -0
- package/dist/drivers/registry.js.map +1 -0
- package/dist/drivers/steelseries/aerox3-hid.d.ts +53 -0
- package/dist/drivers/steelseries/aerox3-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/aerox3-hid.js +172 -0
- package/dist/drivers/steelseries/aerox3-hid.js.map +1 -0
- package/dist/drivers/steelseries/aerox5-hid.d.ts +54 -0
- package/dist/drivers/steelseries/aerox5-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/aerox5-hid.js +173 -0
- package/dist/drivers/steelseries/aerox5-hid.js.map +1 -0
- package/dist/drivers/steelseries/aerox5-wireless-hid.d.ts +69 -0
- package/dist/drivers/steelseries/aerox5-wireless-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/aerox5-wireless-hid.js +234 -0
- package/dist/drivers/steelseries/aerox5-wireless-hid.js.map +1 -0
- package/dist/drivers/steelseries/aerox9-wireless-hid.d.ts +70 -0
- package/dist/drivers/steelseries/aerox9-wireless-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/aerox9-wireless-hid.js +227 -0
- package/dist/drivers/steelseries/aerox9-wireless-hid.js.map +1 -0
- package/dist/drivers/steelseries/hid.d.ts +48 -0
- package/dist/drivers/steelseries/hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/hid.js +157 -0
- package/dist/drivers/steelseries/hid.js.map +1 -0
- package/dist/drivers/steelseries/prime-mini-wireless-hid.d.ts +62 -0
- package/dist/drivers/steelseries/prime-mini-wireless-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/prime-mini-wireless-hid.js +215 -0
- package/dist/drivers/steelseries/prime-mini-wireless-hid.js.map +1 -0
- package/dist/drivers/steelseries/prime-plus-hid.d.ts +51 -0
- package/dist/drivers/steelseries/prime-plus-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/prime-plus-hid.js +150 -0
- package/dist/drivers/steelseries/prime-plus-hid.js.map +1 -0
- package/dist/drivers/steelseries/rival3-wireless-hid.d.ts +61 -0
- package/dist/drivers/steelseries/rival3-wireless-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/rival3-wireless-hid.js +185 -0
- package/dist/drivers/steelseries/rival3-wireless-hid.js.map +1 -0
- package/dist/drivers/steelseries/rival310-hid.d.ts +59 -0
- package/dist/drivers/steelseries/rival310-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/rival310-hid.js +182 -0
- package/dist/drivers/steelseries/rival310-hid.js.map +1 -0
- package/dist/drivers/steelseries/rival650-hid.d.ts +68 -0
- package/dist/drivers/steelseries/rival650-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/rival650-hid.js +209 -0
- package/dist/drivers/steelseries/rival650-hid.js.map +1 -0
- package/dist/drivers/steelseries/sensei-ten-hid.d.ts +62 -0
- package/dist/drivers/steelseries/sensei-ten-hid.d.ts.map +1 -0
- package/dist/drivers/steelseries/sensei-ten-hid.js +186 -0
- package/dist/drivers/steelseries/sensei-ten-hid.js.map +1 -0
- package/dist/drivers/teevolution/hid.d.ts +82 -0
- package/dist/drivers/teevolution/hid.d.ts.map +1 -0
- package/dist/drivers/teevolution/hid.js +607 -0
- package/dist/drivers/teevolution/hid.js.map +1 -0
- package/dist/drivers/vendors.d.ts +254 -0
- package/dist/drivers/vendors.d.ts.map +1 -0
- package/dist/drivers/vendors.js +587 -0
- package/dist/drivers/vendors.js.map +1 -0
- package/dist/drivers/vgn/hid.d.ts +33 -0
- package/dist/drivers/vgn/hid.d.ts.map +1 -0
- package/dist/drivers/vgn/hid.js +213 -0
- package/dist/drivers/vgn/hid.js.map +1 -0
- package/dist/drivers/wallhack/keyboard-hid.d.ts +28 -0
- package/dist/drivers/wallhack/keyboard-hid.d.ts.map +1 -0
- package/dist/drivers/wallhack/keyboard-hid.js +80 -0
- package/dist/drivers/wallhack/keyboard-hid.js.map +1 -0
- package/dist/drivers/wallhack/mouse-hid.d.ts +45 -0
- package/dist/drivers/wallhack/mouse-hid.d.ts.map +1 -0
- package/dist/drivers/wallhack/mouse-hid.js +288 -0
- package/dist/drivers/wallhack/mouse-hid.js.map +1 -0
- package/dist/drivers/webhid.d.ts +59 -0
- package/dist/drivers/webhid.d.ts.map +1 -0
- package/dist/drivers/webhid.js +2 -0
- package/dist/drivers/webhid.js.map +1 -0
- package/dist/drivers/wlmouse/hid.d.ts +81 -0
- package/dist/drivers/wlmouse/hid.d.ts.map +1 -0
- package/dist/drivers/wlmouse/hid.js +576 -0
- package/dist/drivers/wlmouse/hid.js.map +1 -0
- package/dist/drivers/wooting/hid.d.ts +86 -0
- package/dist/drivers/wooting/hid.d.ts.map +1 -0
- package/dist/drivers/wooting/hid.js +320 -0
- package/dist/drivers/wooting/hid.js.map +1 -0
- package/dist/drivers/zaunkoenig/hid.d.ts +31 -0
- package/dist/drivers/zaunkoenig/hid.d.ts.map +1 -0
- package/dist/drivers/zaunkoenig/hid.js +162 -0
- package/dist/drivers/zaunkoenig/hid.js.map +1 -0
- package/dist/endgame-gear/op1.d.ts +165 -0
- package/dist/endgame-gear/op1.d.ts.map +1 -0
- package/dist/endgame-gear/op1.js +384 -0
- package/dist/endgame-gear/op1.js.map +1 -0
- package/dist/endgame-gear/wireless.d.ts +93 -0
- package/dist/endgame-gear/wireless.d.ts.map +1 -0
- package/dist/endgame-gear/wireless.js +242 -0
- package/dist/endgame-gear/wireless.js.map +1 -0
- package/dist/fantech/index.d.ts +9 -0
- package/dist/fantech/index.d.ts.map +1 -0
- package/dist/fantech/index.js +11 -0
- package/dist/fantech/index.js.map +1 -0
- package/dist/finalmouse/index.d.ts +41 -0
- package/dist/finalmouse/index.d.ts.map +1 -0
- package/dist/finalmouse/index.js +106 -0
- package/dist/finalmouse/index.js.map +1 -0
- package/dist/gearhub/index.d.ts +210 -0
- package/dist/gearhub/index.d.ts.map +1 -0
- package/dist/gearhub/index.js +282 -0
- package/dist/gearhub/index.js.map +1 -0
- package/dist/glorious/index.d.ts +89 -0
- package/dist/glorious/index.d.ts.map +1 -0
- package/dist/glorious/index.js +236 -0
- package/dist/glorious/index.js.map +1 -0
- package/dist/glorious-classic/index.d.ts +103 -0
- package/dist/glorious-classic/index.d.ts.map +1 -0
- package/dist/glorious-classic/index.js +304 -0
- package/dist/glorious-classic/index.js.map +1 -0
- package/dist/gwolves/index.d.ts +82 -0
- package/dist/gwolves/index.d.ts.map +1 -0
- package/dist/gwolves/index.js +197 -0
- package/dist/gwolves/index.js.map +1 -0
- package/dist/hyperx/index.d.ts +181 -0
- package/dist/hyperx/index.d.ts.map +1 -0
- package/dist/hyperx/index.js +303 -0
- package/dist/hyperx/index.js.map +1 -0
- package/dist/incott/index.d.ts +1067 -0
- package/dist/incott/index.d.ts.map +1 -0
- package/dist/incott/index.js +1482 -0
- package/dist/incott/index.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/keychron/index.d.ts +194 -0
- package/dist/keychron/index.d.ts.map +1 -0
- package/dist/keychron/index.js +341 -0
- package/dist/keychron/index.js.map +1 -0
- package/dist/ksnake/index.d.ts +157 -0
- package/dist/ksnake/index.d.ts.map +1 -0
- package/dist/ksnake/index.js +314 -0
- package/dist/ksnake/index.js.map +1 -0
- package/dist/lamzu/atlantis.d.ts +237 -0
- package/dist/lamzu/atlantis.d.ts.map +1 -0
- package/dist/lamzu/atlantis.js +263 -0
- package/dist/lamzu/atlantis.js.map +1 -0
- package/dist/lamzu/index.d.ts +60 -0
- package/dist/lamzu/index.d.ts.map +1 -0
- package/dist/lamzu/index.js +86 -0
- package/dist/lamzu/index.js.map +1 -0
- package/dist/logitech/controls.d.ts +138 -0
- package/dist/logitech/controls.d.ts.map +1 -0
- package/dist/logitech/controls.js +184 -0
- package/dist/logitech/controls.js.map +1 -0
- package/dist/logitech/friendly-name.d.ts +50 -0
- package/dist/logitech/friendly-name.d.ts.map +1 -0
- package/dist/logitech/friendly-name.js +74 -0
- package/dist/logitech/friendly-name.js.map +1 -0
- package/dist/logitech/haptics.d.ts +97 -0
- package/dist/logitech/haptics.d.ts.map +1 -0
- package/dist/logitech/haptics.js +116 -0
- package/dist/logitech/haptics.js.map +1 -0
- package/dist/logitech/hosts.d.ts +55 -0
- package/dist/logitech/hosts.d.ts.map +1 -0
- package/dist/logitech/hosts.js +80 -0
- package/dist/logitech/hosts.js.map +1 -0
- package/dist/logitech/index.d.ts +127 -0
- package/dist/logitech/index.d.ts.map +1 -0
- package/dist/logitech/index.js +233 -0
- package/dist/logitech/index.js.map +1 -0
- package/dist/logitech/wheel.d.ts +117 -0
- package/dist/logitech/wheel.d.ts.map +1 -0
- package/dist/logitech/wheel.js +133 -0
- package/dist/logitech/wheel.js.map +1 -0
- package/dist/mchose/a5-gen1.d.ts +38 -0
- package/dist/mchose/a5-gen1.d.ts.map +1 -0
- package/dist/mchose/a5-gen1.js +60 -0
- package/dist/mchose/a5-gen1.js.map +1 -0
- package/dist/mchose/buttons.d.ts +66 -0
- package/dist/mchose/buttons.d.ts.map +1 -0
- package/dist/mchose/buttons.js +184 -0
- package/dist/mchose/buttons.js.map +1 -0
- package/dist/mchose/dock.d.ts +77 -0
- package/dist/mchose/dock.d.ts.map +1 -0
- package/dist/mchose/dock.js +133 -0
- package/dist/mchose/dock.js.map +1 -0
- package/dist/mchose/index.d.ts +299 -0
- package/dist/mchose/index.d.ts.map +1 -0
- package/dist/mchose/index.js +429 -0
- package/dist/mchose/index.js.map +1 -0
- package/dist/mchose/v3-buttons.d.ts +64 -0
- package/dist/mchose/v3-buttons.d.ts.map +1 -0
- package/dist/mchose/v3-buttons.js +258 -0
- package/dist/mchose/v3-buttons.js.map +1 -0
- package/dist/mchose/v3.d.ts +380 -0
- package/dist/mchose/v3.d.ts.map +1 -0
- package/dist/mchose/v3.js +608 -0
- package/dist/mchose/v3.js.map +1 -0
- package/dist/microsoft/index.d.ts +19 -0
- package/dist/microsoft/index.d.ts.map +1 -0
- package/dist/microsoft/index.js +22 -0
- package/dist/microsoft/index.js.map +1 -0
- package/dist/moddo/index.d.ts +72 -0
- package/dist/moddo/index.d.ts.map +1 -0
- package/dist/moddo/index.js +147 -0
- package/dist/moddo/index.js.map +1 -0
- package/dist/ninjutso/index.d.ts +74 -0
- package/dist/ninjutso/index.d.ts.map +1 -0
- package/dist/ninjutso/index.js +133 -0
- package/dist/ninjutso/index.js.map +1 -0
- package/dist/orbital/index.d.ts +14 -0
- package/dist/orbital/index.d.ts.map +1 -0
- package/dist/orbital/index.js +19 -0
- package/dist/orbital/index.js.map +1 -0
- package/dist/pulsar/index.d.ts +81 -0
- package/dist/pulsar/index.d.ts.map +1 -0
- package/dist/pulsar/index.js +185 -0
- package/dist/pulsar/index.js.map +1 -0
- package/dist/rawm/index.d.ts +55 -0
- package/dist/rawm/index.d.ts.map +1 -0
- package/dist/rawm/index.js +229 -0
- package/dist/rawm/index.js.map +1 -0
- package/dist/razer/codec.d.ts +504 -0
- package/dist/razer/codec.d.ts.map +1 -0
- package/dist/razer/codec.js +648 -0
- package/dist/razer/codec.js.map +1 -0
- package/dist/razer/devices.d.ts +156 -0
- package/dist/razer/devices.d.ts.map +1 -0
- package/dist/razer/devices.js +540 -0
- package/dist/razer/devices.js.map +1 -0
- package/dist/razer/index.d.ts +2 -0
- package/dist/razer/index.d.ts.map +1 -0
- package/dist/razer/index.js +2 -0
- package/dist/razer/index.js.map +1 -0
- package/dist/razer/v4.d.ts +19 -0
- package/dist/razer/v4.d.ts.map +1 -0
- package/dist/razer/v4.js +51 -0
- package/dist/razer/v4.js.map +1 -0
- package/dist/steelseries/aerox3.d.ts +236 -0
- package/dist/steelseries/aerox3.d.ts.map +1 -0
- package/dist/steelseries/aerox3.js +311 -0
- package/dist/steelseries/aerox3.js.map +1 -0
- package/dist/steelseries/aerox5-wireless.d.ts +300 -0
- package/dist/steelseries/aerox5-wireless.d.ts.map +1 -0
- package/dist/steelseries/aerox5-wireless.js +389 -0
- package/dist/steelseries/aerox5-wireless.js.map +1 -0
- package/dist/steelseries/aerox5.d.ts +228 -0
- package/dist/steelseries/aerox5.d.ts.map +1 -0
- package/dist/steelseries/aerox5.js +294 -0
- package/dist/steelseries/aerox5.js.map +1 -0
- package/dist/steelseries/aerox9-wireless.d.ts +213 -0
- package/dist/steelseries/aerox9-wireless.d.ts.map +1 -0
- package/dist/steelseries/aerox9-wireless.js +305 -0
- package/dist/steelseries/aerox9-wireless.js.map +1 -0
- package/dist/steelseries/devices.d.ts +82 -0
- package/dist/steelseries/devices.d.ts.map +1 -0
- package/dist/steelseries/devices.js +310 -0
- package/dist/steelseries/devices.js.map +1 -0
- package/dist/steelseries/index.d.ts +13 -0
- package/dist/steelseries/index.d.ts.map +1 -0
- package/dist/steelseries/index.js +13 -0
- package/dist/steelseries/index.js.map +1 -0
- package/dist/steelseries/prime-mini-wireless.d.ts +253 -0
- package/dist/steelseries/prime-mini-wireless.d.ts.map +1 -0
- package/dist/steelseries/prime-mini-wireless.js +351 -0
- package/dist/steelseries/prime-mini-wireless.js.map +1 -0
- package/dist/steelseries/prime-plus.d.ts +179 -0
- package/dist/steelseries/prime-plus.d.ts.map +1 -0
- package/dist/steelseries/prime-plus.js +275 -0
- package/dist/steelseries/prime-plus.js.map +1 -0
- package/dist/steelseries/rival3-wireless.d.ts +222 -0
- package/dist/steelseries/rival3-wireless.d.ts.map +1 -0
- package/dist/steelseries/rival3-wireless.js +308 -0
- package/dist/steelseries/rival3-wireless.js.map +1 -0
- package/dist/steelseries/rival3.d.ts +99 -0
- package/dist/steelseries/rival3.d.ts.map +1 -0
- package/dist/steelseries/rival3.js +147 -0
- package/dist/steelseries/rival3.js.map +1 -0
- package/dist/steelseries/rival310.d.ts +230 -0
- package/dist/steelseries/rival310.d.ts.map +1 -0
- package/dist/steelseries/rival310.js +322 -0
- package/dist/steelseries/rival310.js.map +1 -0
- package/dist/steelseries/rival650.d.ts +215 -0
- package/dist/steelseries/rival650.d.ts.map +1 -0
- package/dist/steelseries/rival650.js +284 -0
- package/dist/steelseries/rival650.js.map +1 -0
- package/dist/steelseries/sensei-ten.d.ts +283 -0
- package/dist/steelseries/sensei-ten.d.ts.map +1 -0
- package/dist/steelseries/sensei-ten.js +389 -0
- package/dist/steelseries/sensei-ten.js.map +1 -0
- package/dist/teevolution/index.d.ts +211 -0
- package/dist/teevolution/index.d.ts.map +1 -0
- package/dist/teevolution/index.js +458 -0
- package/dist/teevolution/index.js.map +1 -0
- package/dist/valkyrie/index.d.ts +19 -0
- package/dist/valkyrie/index.d.ts.map +1 -0
- package/dist/valkyrie/index.js +43 -0
- package/dist/valkyrie/index.js.map +1 -0
- package/dist/valkyrie/settings.d.ts +28 -0
- package/dist/valkyrie/settings.d.ts.map +1 -0
- package/dist/valkyrie/settings.js +84 -0
- package/dist/valkyrie/settings.js.map +1 -0
- package/dist/vgn/index.d.ts +59 -0
- package/dist/vgn/index.d.ts.map +1 -0
- package/dist/vgn/index.js +165 -0
- package/dist/vgn/index.js.map +1 -0
- package/dist/wallhack/index.d.ts +165 -0
- package/dist/wallhack/index.d.ts.map +1 -0
- package/dist/wallhack/index.js +226 -0
- package/dist/wallhack/index.js.map +1 -0
- package/dist/wlmouse/index.d.ts +4 -0
- package/dist/wlmouse/index.d.ts.map +1 -0
- package/dist/wlmouse/index.js +7 -0
- package/dist/wlmouse/index.js.map +1 -0
- package/dist/wooting/index.d.ts +225 -0
- package/dist/wooting/index.d.ts.map +1 -0
- package/dist/wooting/index.js +296 -0
- package/dist/wooting/index.js.map +1 -0
- package/dist/zaunkoenig/index.d.ts +49 -0
- package/dist/zaunkoenig/index.d.ts.map +1 -0
- package/dist/zaunkoenig/index.js +127 -0
- package/dist/zaunkoenig/index.js.map +1 -0
- package/package.json +193 -0
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
import { incottButtonActionCode, incottDecodeButtonBinding, incottDecodeDebounce, incottDecodeDpiStage, incottDecodeDpiStageAxis, incottDecodeDpiCycle, incottDecodeFireKey, incottDecodeIdentity, incottDpiMaxForSensor, incottDecodeInputStatus, incottDecodeLiftOffDirect, incottDecodePerformanceMode, incottDecodePollingRate, incottDecodeReceiverLed, incottDecodeSleep, incottDecodeToggle, incottEncodeMacroBuffer, incottEncodeMacroChunkHeader, incottEncodeQuery, incottEncodeQueryDpiAxis, incottEncodeSetButtonBinding, incottEncodeSetDebounce, incottEncodeSetDpi, incottEncodeSetDpiCycle, incottEncodeSetFireKey, incottEncodeSetLiftOff, incottEncodeSetPerformanceMode, incottEncodeSetPollingRate, incottEncodeSetReceiverLed, incottEncodeSetSleep, incottEncodeSetToggle, incottFrameMatches, incottIsWiredProduct, incottMacroChunks, incottLiftOffLabel, incottLiftOffTenths, incottNormalizeProductName, incottPerformanceModeFromWire, incottPerformanceModeToWire, incottValidateDpi, INCOTT_BUTTON_ACTIONS, INCOTT_BUTTON_NAMES, INCOTT_BUTTON_WIRE_INDEX, INCOTT_CMD_QUERY_BUTTON, INCOTT_CMD_QUERY_DPI_STAGE, INCOTT_CMD_QUERY_DPI_STAGE_VALUE, INCOTT_CMD_QUERY_IDENTITY, INCOTT_CMD_QUERY_POLLING, INCOTT_CMD_QUERY_RECEIVER_LED, INCOTT_CMD_QUERY_SENSOR, INCOTT_CMD_QUERY_TIMING, INCOTT_DEBOUNCE_MAX_MS, INCOTT_DPI_AXIS, INCOTT_DPI_DEFAULT_STAGE_PRESETS, INCOTT_DPI_MAX, INCOTT_DPI_MIN, INCOTT_DPI_STAGE_COUNT, INCOTT_DPI_STEP, INCOTT_INPUT_REPORT_ID, INCOTT_PERFORMANCE_MODE_NAMES, INCOTT_POLLING_STEPS_HZ, INCOTT_POLLING_STEPS_HZ_WIRED, INCOTT_PRODUCT_IDS, INCOTT_SLEEP_OPTIONS, INCOTT_REPORT_ID, INCOTT_RESPONSE_LENGTH, INCOTT_SUB_ANGLE_SNAP, INCOTT_SUB_DEBOUNCE, INCOTT_SUB_FIRE_KEY, INCOTT_SUB_LOD, INCOTT_SUB_MOTION_SYNC, INCOTT_SUB_NONE, INCOTT_SUB_PERFORMANCE, INCOTT_SUB_RIPPLE, INCOTT_SUB_SLEEP, INCOTT_USAGE_PAGE, INCOTT_VENDOR_ID, } from "../../incott/index.js";
|
|
2
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
+
/**
|
|
4
|
+
* Serializes feature-report request/response pairs for the Incott protocol.
|
|
5
|
+
*
|
|
6
|
+
* The device exposes a single shared response buffer, so overlapping requests
|
|
7
|
+
* and leftover frames both produce values that belong to a different query —
|
|
8
|
+
* probing the same query three times in separate runs returned three
|
|
9
|
+
* different payloads on real hardware. Every request therefore runs alone,
|
|
10
|
+
* discards whatever was already latched, and accepts a frame only when its
|
|
11
|
+
* command and sub-command echo the request. Ported intact from IncottHub
|
|
12
|
+
* (`src/drivers/incott/transaction.ts`), including the stale-frame regression
|
|
13
|
+
* this exists to prevent — the prior-art Go implementation (IncottHIDApp)
|
|
14
|
+
* matches on the command byte alone and is fooled by exactly this.
|
|
15
|
+
*/
|
|
16
|
+
export class IncottTransactionQueue {
|
|
17
|
+
transport;
|
|
18
|
+
settleMs;
|
|
19
|
+
attempts;
|
|
20
|
+
sleep;
|
|
21
|
+
tail = Promise.resolve();
|
|
22
|
+
constructor(transport, options = {}) {
|
|
23
|
+
this.transport = transport;
|
|
24
|
+
this.settleMs = options.settleMs ?? 50;
|
|
25
|
+
this.attempts = options.attempts ?? 10;
|
|
26
|
+
this.sleep = options.sleep ?? defaultSleep;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Sends one request and returns the matching response frame, or null when
|
|
30
|
+
* no matching frame arrives. Never rejects: a device that stops answering
|
|
31
|
+
* yields null so the caller can render an em dash instead of a stale value.
|
|
32
|
+
*/
|
|
33
|
+
request(payload, cmd, sub, axis = null) {
|
|
34
|
+
const run = this.tail.then(() => this.exchange(payload, cmd, sub, axis));
|
|
35
|
+
// Keep the chain alive even if one exchange throws.
|
|
36
|
+
this.tail = run.catch(() => undefined);
|
|
37
|
+
return run;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sends a write, which the device does not answer. Queued alongside
|
|
41
|
+
* requests so ordering holds, but it never reads: waiting for a response
|
|
42
|
+
* that will never arrive would spend the entire attempt budget per write.
|
|
43
|
+
*/
|
|
44
|
+
send(payload) {
|
|
45
|
+
const run = this.tail.then(async () => {
|
|
46
|
+
try {
|
|
47
|
+
await this.transport.sendFeatureReport(INCOTT_REPORT_ID, toArrayBuffer(payload));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// A failed write surfaces as a failed read-back in the client.
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
this.tail = run.catch(() => undefined);
|
|
54
|
+
return run;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sends a 32-byte OUTPUT report, the macro upload's data path. Queued with
|
|
58
|
+
* everything else so it cannot interleave with the 8-byte header that
|
|
59
|
+
* announces it, and followed by a settle delay because the vendor waits
|
|
60
|
+
* 80 ms between the two halves of each chunk.
|
|
61
|
+
*/
|
|
62
|
+
sendOutput(reportId, payload) {
|
|
63
|
+
const run = this.tail.then(async () => {
|
|
64
|
+
if (!this.transport.sendReport) {
|
|
65
|
+
throw new Error("This transport cannot send output reports, which the macro upload needs.");
|
|
66
|
+
}
|
|
67
|
+
await this.transport.sendReport(reportId, toArrayBuffer(payload));
|
|
68
|
+
await this.sleep(this.settleMs);
|
|
69
|
+
});
|
|
70
|
+
this.tail = run.catch(() => undefined);
|
|
71
|
+
return run;
|
|
72
|
+
}
|
|
73
|
+
async exchange(payload, cmd, sub, axis = null) {
|
|
74
|
+
try {
|
|
75
|
+
// Discard anything latched by a previous exchange before sending.
|
|
76
|
+
await this.read();
|
|
77
|
+
await this.transport.sendFeatureReport(INCOTT_REPORT_ID, toArrayBuffer(payload));
|
|
78
|
+
for (let attempt = 0; attempt < this.attempts; attempt += 1) {
|
|
79
|
+
if (this.settleMs > 0)
|
|
80
|
+
await this.sleep(this.settleMs);
|
|
81
|
+
const frame = await this.read();
|
|
82
|
+
if (frame && incottFrameMatches(frame, cmd, sub, axis))
|
|
83
|
+
return frame;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async read() {
|
|
92
|
+
try {
|
|
93
|
+
const view = await this.transport.receiveFeatureReport(INCOTT_REPORT_ID);
|
|
94
|
+
const out = new Uint8Array(INCOTT_RESPONSE_LENGTH);
|
|
95
|
+
const length = Math.min(view.byteLength, INCOTT_RESPONSE_LENGTH);
|
|
96
|
+
for (let index = 0; index < length; index += 1)
|
|
97
|
+
out[index] = view.getUint8(index);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Sends the identity query (`09 8f 00`) directly against a transport and
|
|
107
|
+
* checks for a well-formed reply — byte 0 the report id (`0x09`), byte 1 the
|
|
108
|
+
* echoed command (`0x8f`). Never throws: a transport whose `sendFeatureReport`
|
|
109
|
+
* rejects, or that answers with something else entirely, both resolve to
|
|
110
|
+
* `false` (via `IncottTransactionQueue`'s own catch-and-return-null path),
|
|
111
|
+
* exactly like a device that is not there.
|
|
112
|
+
*
|
|
113
|
+
* WHY THIS EXISTS (BUG, hardware-verified 2026-09-08): wired, the mouse
|
|
114
|
+
* exposes TWO top-level collections on usage page `0xFF05` on the same
|
|
115
|
+
* interface — identical as far as vendor/product id and usage page go — but
|
|
116
|
+
* only one of them actually answers feature reports. The other rejects every
|
|
117
|
+
* one at the OS level (`HidD_SetFeature: (0x00000001) Incorrect function` on
|
|
118
|
+
* Windows). Usage page alone cannot tell them apart; only sending something
|
|
119
|
+
* and checking for a real reply can. See `incottSelectCollection` for the
|
|
120
|
+
* ordered probe-and-pick algorithm this backs, and the comment on
|
|
121
|
+
* `IncottHidClient.open()` for why a WebHID-based client can only apply this
|
|
122
|
+
* to the single collection it was handed, not choose between two.
|
|
123
|
+
*/
|
|
124
|
+
export async function incottProbeCollection(transport, options) {
|
|
125
|
+
const queue = new IncottTransactionQueue(transport, options);
|
|
126
|
+
const frame = await queue.request(incottEncodeQuery(INCOTT_CMD_QUERY_IDENTITY, INCOTT_SUB_NONE), INCOTT_CMD_QUERY_IDENTITY, null);
|
|
127
|
+
return frame !== null && frame[0] === INCOTT_REPORT_ID && frame[1] === INCOTT_CMD_QUERY_IDENTITY;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Picks the first candidate transport whose collection actually answers the
|
|
131
|
+
* identity probe, trying candidates in the given order and moving on to the
|
|
132
|
+
* next one when a candidate throws, never replies, or replies with garbage.
|
|
133
|
+
* Returns `null` when no candidate answers.
|
|
134
|
+
*
|
|
135
|
+
* Candidates must be pre-ordered by the CALLER, most likely first — this
|
|
136
|
+
* function only probes in the order it is given, it does not itself inspect
|
|
137
|
+
* usage pages. This is the general form of the wired dead-collection fix
|
|
138
|
+
* (see `incottProbeCollection`): a transport layer that genuinely has more
|
|
139
|
+
* than one candidate to try (for example a Node-hid-based tool with access
|
|
140
|
+
* to every top-level collection's own path) can use this directly. WebHID
|
|
141
|
+
* cannot: it hands `IncottHidClient` exactly one `HIDDevice` per collection,
|
|
142
|
+
* already chosen by the browser's picker/filters before this driver ever
|
|
143
|
+
* sees it, so `IncottHidClient.open()` uses `incottProbeCollection` on
|
|
144
|
+
* itself instead of calling this with more than one candidate.
|
|
145
|
+
*/
|
|
146
|
+
export async function incottSelectCollection(candidates, options) {
|
|
147
|
+
for (const candidate of candidates) {
|
|
148
|
+
try {
|
|
149
|
+
if (await incottProbeCollection(candidate, options))
|
|
150
|
+
return candidate;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Move on to the next candidate.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Byte 2 echoes the sub-command for the DPI-stage-value, sensor and timing
|
|
160
|
+
* queries, so it must be matched there. For polling, receiver LED and
|
|
161
|
+
* identity it carries data instead, and matching it would reject valid
|
|
162
|
+
* responses (every polling rate but 1000 Hz, every LED mode but 0, and
|
|
163
|
+
* identity entirely).
|
|
164
|
+
*
|
|
165
|
+
* `0x82` (DPI stage value) was added on 2026-09-08, confirmed directly on
|
|
166
|
+
* hardware: `09 82 03` replies `09 82 03 …`. `0x81` (polling) was
|
|
167
|
+
* DELIBERATELY re-confirmed NOT to belong here on the same date: writing wire
|
|
168
|
+
* `1` then wire `0` and reading back showed byte 2 of the `0x81` reply follow
|
|
169
|
+
* the write (`0 -> 1 -> 0`) — it is data, not an echoed sub-command, and
|
|
170
|
+
* matching it as one would reject every polling rate but 1000 Hz.
|
|
171
|
+
*
|
|
172
|
+
* `0x8e` (battery) used to be registered here (added 2026-09-07 alongside the
|
|
173
|
+
* original, now-disproven DPI/battery corrections — see
|
|
174
|
+
* `INCOTT_CMD_QUERY_BATTERY`) but this driver no longer queries `0x8e` for
|
|
175
|
+
* anything: battery is read from the mouse's unsolicited input report
|
|
176
|
+
* instead (see `onInputReport` below), so there is no live query left for
|
|
177
|
+
* this list to matter to. `incottDecodeBattery`'s own frame-matching in
|
|
178
|
+
* `src/incott/index.ts` is unaffected by this list either way.
|
|
179
|
+
*
|
|
180
|
+
* `0x86` echoes the button index in the same position, and MUST stay in this
|
|
181
|
+
* list now that buttons are read: six reads go out back to back, and the
|
|
182
|
+
* device latches a single shared response buffer, so matching on the command
|
|
183
|
+
* byte alone would let button 2's reply satisfy button 3's request.
|
|
184
|
+
*
|
|
185
|
+
* `0x83` WAS in this list and has been removed. Its byte 2 is the DPI stage
|
|
186
|
+
* COUNT, not an echo — `09 83 00` answers `09 83 06 01` — so treating it as
|
|
187
|
+
* one only worked because this contributor's mouse has a six-stage cycle and
|
|
188
|
+
* the driver happened to send `06`. On a mouse configured for four stages
|
|
189
|
+
* every DPI read would have been rejected. See
|
|
190
|
+
* `INCOTT_DPI_STAGE_COUNT_DEFAULT`.
|
|
191
|
+
*/
|
|
192
|
+
const SUB_ECHOING_QUERIES = [
|
|
193
|
+
INCOTT_CMD_QUERY_DPI_STAGE_VALUE,
|
|
194
|
+
INCOTT_CMD_QUERY_SENSOR,
|
|
195
|
+
INCOTT_CMD_QUERY_TIMING,
|
|
196
|
+
INCOTT_CMD_QUERY_BUTTON,
|
|
197
|
+
];
|
|
198
|
+
function toggleWord(value) {
|
|
199
|
+
return value === null ? "an unreadable state" : value ? "on" : "off";
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* WebHID client for the Incott protocol (vendor 0x093A, products 0x522C /
|
|
203
|
+
* 0x622C). Ported from IncottHub, adapted to mouse-protocol's driver
|
|
204
|
+
* interface: the client owns `device`, `open()` and `close()` directly
|
|
205
|
+
* instead of taking an externally-managed transport, matching every other
|
|
206
|
+
* driver in this registry.
|
|
207
|
+
*
|
|
208
|
+
* FOUR bugs fixed 2026-09-08 from an owner's hardware probes in BOTH wireless
|
|
209
|
+
* and wired modes (see `docs/incott-testing.md`):
|
|
210
|
+
*
|
|
211
|
+
* 1. WIRED MODE WAS COMPLETELY BROKEN. The wired mouse exposes two
|
|
212
|
+
* identical-looking `0xFF05` collections; only one answers feature
|
|
213
|
+
* reports, and usage page alone cannot tell them apart. See `open()`'s
|
|
214
|
+
* identity probe and `incottProbeCollection`/`incottSelectCollection`.
|
|
215
|
+
* 2. `0x622C` means the connection is WIRED, not "charging" — charging is
|
|
216
|
+
* a consequence of being plugged in, not what the id encodes. See
|
|
217
|
+
* `incottIsWiredProduct` (renamed from `incottIsChargingProduct`) and
|
|
218
|
+
* `MouseStatus.connectionType` below.
|
|
219
|
+
* 3. The polling-rate ceiling depends on the connection: the full
|
|
220
|
+
* 125-8000 Hz ladder is wireless-only, and the mouse only reaches
|
|
221
|
+
* 1000 Hz over the cable — see `INCOTT_POLLING_STEPS_HZ_WIRED`.
|
|
222
|
+
* 4. The real model name ("Esports G23V2Pro") is only in the WIRED product
|
|
223
|
+
* string; wireless reports a generic name with no model in it at all.
|
|
224
|
+
* See `incottNormalizeProductName`.
|
|
225
|
+
*
|
|
226
|
+
* Polling rate (0x81) is verified readable on hardware, but `readStatus()`
|
|
227
|
+
* must not throw (see the write-a-driver doc): when it cannot be read after
|
|
228
|
+
* the transaction queue's full retry budget, the client falls back to an
|
|
229
|
+
* identity-only status — real `name`, `brand`, and whatever else was
|
|
230
|
+
* genuinely readable — with `ui.settingsReady: false` and
|
|
231
|
+
* `ui.valuesVerified: false` so the app hides the settings grid instead of
|
|
232
|
+
* rendering fabricated values. `MouseStatus.pollingRateHz` is non-nullable,
|
|
233
|
+
* so the degraded path still assigns it a number; see the comment at that
|
|
234
|
+
* assignment for why that is not a fabricated reading.
|
|
235
|
+
*
|
|
236
|
+
* DPI USED to have no known read-back at all; that changed 2026-09-08. DPI
|
|
237
|
+
* lives in a six-stage table (see `INCOTT_DPI_STAGE_COUNT` in
|
|
238
|
+
* `src/incott/index.ts`) with FOUR independent operations, not one:
|
|
239
|
+
*
|
|
240
|
+
* which stage is active read: `0x83`/`0x06` write: `0x03`/`0x06 <idx>`
|
|
241
|
+
* what a stage holds read: `0x82`/`<stage>` write: `0x02 <stage> <lo> <hi>`
|
|
242
|
+
*
|
|
243
|
+
* `readStatus()` reads which stage is active (`incottDecodeDpiStageIndex`)
|
|
244
|
+
* and every one of the six stages' stored values (`incottDecodeDpiStage`),
|
|
245
|
+
* populating `dpiStages`/`activeDpiStage` for OpenMouse's shared multi-stage
|
|
246
|
+
* DPI editor (`MouseUiHints.dpiStageEditor`) and deriving `dpi` as the active
|
|
247
|
+
* stage's own value — a genuine live read, not a write cache.
|
|
248
|
+
*
|
|
249
|
+
* `setActiveDpiStage()` and `setDpiStageValue()` map onto the select and edit
|
|
250
|
+
* operations above respectively, matching OpenMouse's existing generic
|
|
251
|
+
* contract (`requireClientMethod` in `openmouse/src/device/controller.ts`).
|
|
252
|
+
* `setDpi()` is kept for the plain preset row and is defined as "set the
|
|
253
|
+
* ACTIVE stage's value" — i.e. it is exactly `setDpiStageValue` at whichever
|
|
254
|
+
* stage is currently active — since that is the only meaning left for it now
|
|
255
|
+
* that a real select operation exists.
|
|
256
|
+
*
|
|
257
|
+
* A SECOND DPI BUG, found from an owner report and fixed the same day
|
|
258
|
+
* (2026-09-08): `setDpi()` used to be the *only* way to touch DPI, so the app
|
|
259
|
+
* called it every time the user picked a value from the plain preset row —
|
|
260
|
+
* indistinguishable, from the user's point of view, from "select the stage
|
|
261
|
+
* that already holds this value." It never did that: it read the active
|
|
262
|
+
* stage and overwrote *that* stage's stored value with whatever was picked,
|
|
263
|
+
* one stage at a time destroying the mouse's factory-programmed table.
|
|
264
|
+
* (IncottHIDApp's own `0x03`/`0x06` write — which that project mislabels "set
|
|
265
|
+
* DPI" — is actually the active-stage SELECT and never touches the table;
|
|
266
|
+
* see `INCOTT_CMD_SET_DPI_STAGE` in `src/incott/index.ts`.) Wiring
|
|
267
|
+
* `dpiStages`/`activeDpiStage`/`dpiStageEditor` here gives the app a real
|
|
268
|
+
* select control (`setActiveDpiStage`) so `setDpi` is no longer asked to do a
|
|
269
|
+
* select's job.
|
|
270
|
+
*
|
|
271
|
+
* `valuesVerified` is true once both the polling-rate query and the active
|
|
272
|
+
* stage's DPI read have answered — see `readStatus()`.
|
|
273
|
+
*
|
|
274
|
+
* A FIFTH bug, fixed 2026-09-08: battery used to be read from the `0x8e`
|
|
275
|
+
* feature-report reply, byte 6. Charging a unit through a full cycle (roughly
|
|
276
|
+
* 60% to roughly 97%) while polling that reply showed byte 6 never move at
|
|
277
|
+
* all — a constant, not a live reading, the same failure mode `0x89` byte 8
|
|
278
|
+
* suffered before it (see `INCOTT_CMD_QUERY_BATTERY` in
|
|
279
|
+
* `src/incott/index.ts`). The real battery level lives in an UNSOLICITED
|
|
280
|
+
* INPUT report the mouse emits on report id 0x09 while it is actively being
|
|
281
|
+
* used — see `onInputReport` below and `incottDecodeInputStatus`. The device
|
|
282
|
+
* only emits these while in use, so `readStatus()` reports `batteryPercent:
|
|
283
|
+
* null` / `batteryState: "Unknown"` until the first one arrives; nothing here
|
|
284
|
+
* fabricates a number or falls back to the disproven feature-report bytes.
|
|
285
|
+
* `batteryState`'s charging flag comes from the report's own high bit
|
|
286
|
+
* (`raw > 100`), not from the product id: `incottIsWiredProduct` (0x622C)
|
|
287
|
+
* still means the CONNECTION is wired, which is what `connectionType` below
|
|
288
|
+
* uses, and being wired does imply charging in practice, but the input report
|
|
289
|
+
* is the authoritative source for the charging state, not an inference from
|
|
290
|
+
* the product id.
|
|
291
|
+
*
|
|
292
|
+
* A SIXTH addition, 2026-09-10: performance mode (HP/Corded/LP) is now wired
|
|
293
|
+
* into OpenMouse's shared `powerModes`/`powerMode`/`setPowerMode` contract.
|
|
294
|
+
* The value-to-label mapping was hardware-confirmed by labelling every click
|
|
295
|
+
* in the vendor tool before recording its write — see `getPowerModes`,
|
|
296
|
+
* `setPowerMode`, and `INCOTT_SUB_PERFORMANCE` in `src/incott/index.ts` for
|
|
297
|
+
* the capture and the REVERSED-vs-UI warning (HP=2, Corded=1, LP=0). `readStatus()`
|
|
298
|
+
* populates `powerMode` from a live `0x84`/`0x05` read, leaving it `undefined`
|
|
299
|
+
* (never fabricated) when the collection is dead or the read fails.
|
|
300
|
+
*/
|
|
301
|
+
export class IncottHidClient {
|
|
302
|
+
device;
|
|
303
|
+
queue;
|
|
304
|
+
/**
|
|
305
|
+
* Cached decode of the mouse's unsolicited input report (battery plus a
|
|
306
|
+
* packed DPI-stage/polling-rate snapshot) — see `onInputReport` and the
|
|
307
|
+
* class comment's battery section. `null` until the first report arrives
|
|
308
|
+
* (the mouse only emits them while actively in use), which is exactly when
|
|
309
|
+
* `readStatus()` must report `batteryPercent: null` / `batteryState:
|
|
310
|
+
* "Unknown"` rather than a fabricated value.
|
|
311
|
+
*/
|
|
312
|
+
lastInputStatus = null;
|
|
313
|
+
/**
|
|
314
|
+
* Whether `open()`'s identity probe got a well-formed reply from the
|
|
315
|
+
* collection WebHID handed this client. `null` until `open()` runs (every
|
|
316
|
+
* unit test that skips `open()` and calls `readStatus()` directly keeps
|
|
317
|
+
* this `null`, so it behaves exactly as before this field existed);
|
|
318
|
+
* `false` means this specific collection is the wired mouse's dead
|
|
319
|
+
* look-alike (see the class comment and `open()`).
|
|
320
|
+
*/
|
|
321
|
+
collectionVerified = null;
|
|
322
|
+
constructor(device, options) {
|
|
323
|
+
this.device = device;
|
|
324
|
+
// A real HIDDevice satisfies FeatureTransport structurally.
|
|
325
|
+
this.queue = new IncottTransactionQueue(device, options);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Decodes the mouse's unsolicited input report and caches the result for
|
|
329
|
+
* `readStatus()` — see the class comment's battery section and
|
|
330
|
+
* `incottDecodeInputStatus` in `src/incott/index.ts`.
|
|
331
|
+
*
|
|
332
|
+
* BYTE-INDEX CONVENTION: WebHID's `inputreport` event carries `reportId`
|
|
333
|
+
* separately from `data`, and `data` EXCLUDES the report id — unlike this
|
|
334
|
+
* driver's feature-report frames, which include it because
|
|
335
|
+
* `receiveFeatureReport` returns it at byte 0 (see the top-of-file comment
|
|
336
|
+
* in `src/incott/index.ts` for that asymmetry, which this mirrors). So
|
|
337
|
+
* `event.data.getUint8(0)`/`getUint8(1)` are this report's bytes 1/2 in
|
|
338
|
+
* node-hid terms — node-hid's raw buffer includes the report id at index 0,
|
|
339
|
+
* which is how `captures/incott-8k-wireless/input-report-battery.hex` is
|
|
340
|
+
* indexed. Get this backwards and every field reads off by one byte.
|
|
341
|
+
*
|
|
342
|
+
* Filtered to report id `INCOTT_INPUT_REPORT_ID` (0x09): the mouse's other
|
|
343
|
+
* HID collections (mouse/keyboard boot reports, etc.) are not this vendor
|
|
344
|
+
* collection's traffic and must not be mistaken for it.
|
|
345
|
+
*/
|
|
346
|
+
onInputReport = (event) => {
|
|
347
|
+
if (event.reportId !== INCOTT_INPUT_REPORT_ID)
|
|
348
|
+
return;
|
|
349
|
+
if (event.data.byteLength < 2)
|
|
350
|
+
return;
|
|
351
|
+
const decoded = incottDecodeInputStatus(event.data.getUint8(0), event.data.getUint8(1));
|
|
352
|
+
if (decoded)
|
|
353
|
+
this.lastInputStatus = decoded;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Usage page alone cannot tell the wired mouse's two look-alike `0xFF05`
|
|
357
|
+
* collections apart (see the class comment and `open()`), so this stays
|
|
358
|
+
* permissive on purpose: it accepts ANY device on a vendor-defined usage
|
|
359
|
+
* page with a matching product id, including the one that turns out to be
|
|
360
|
+
* the dead collection. Rejecting it here would also reject the live one,
|
|
361
|
+
* since WebHID hands this method one collection at a time and both look
|
|
362
|
+
* identical from `vendorId`/`productId`/`usagePage` alone. The actual
|
|
363
|
+
* distinction is made by probing after `open()` — see `open()` and
|
|
364
|
+
* `readStatus()`'s graceful degradation when the probe fails.
|
|
365
|
+
*/
|
|
366
|
+
static isSupported(device) {
|
|
367
|
+
if (device.vendorId !== INCOTT_VENDOR_ID)
|
|
368
|
+
return false;
|
|
369
|
+
if (!INCOTT_PRODUCT_IDS.includes(device.productId))
|
|
370
|
+
return false;
|
|
371
|
+
// Require BOTH the vendor page and a declared feature report `0x09`. The
|
|
372
|
+
// mouse exposes several vendor collections and only this one speaks the
|
|
373
|
+
// protocol: enumerated on hardware 2026-09-10, page 0xFF05 declares
|
|
374
|
+
// feature report 0x09, while 0xFF00 declares 0x03 and 0xFF01 declares
|
|
375
|
+
// 0x04. Matching on the usage page alone (or on any page >= 0xFF00)
|
|
376
|
+
// claims those siblings too, so the app lists the mouse once per
|
|
377
|
+
// collection and every card but one is inert.
|
|
378
|
+
return device.collections.some((collection) => collection.usagePage === INCOTT_USAGE_PAGE &&
|
|
379
|
+
collection.featureReports.some((report) => report.reportId === INCOTT_REPORT_ID));
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* WebHID request filters for the vendor collection, one per product id —
|
|
383
|
+
* both the wireless dongle's `0x522C` (`INCOTT_PRODUCT_ID`) and the wired
|
|
384
|
+
* `0x622C` (`INCOTT_PRODUCT_ID_WIRED`), hardware-verified 2026-09-08, so
|
|
385
|
+
* the picker offers the mouse in either mode.
|
|
386
|
+
*/
|
|
387
|
+
static get filters() {
|
|
388
|
+
return INCOTT_PRODUCT_IDS.map((productId) => ({
|
|
389
|
+
vendorId: INCOTT_VENDOR_ID,
|
|
390
|
+
productId,
|
|
391
|
+
usagePage: INCOTT_USAGE_PAGE,
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The real writable range: 50-45000 DPI in steps of 50 (see `INCOTT_DPI_MIN`
|
|
396
|
+
* in `src/incott/index.ts`), returned densely so the shell's custom-DPI
|
|
397
|
+
* entry (which validates by exact membership, not by snapping to a nearby
|
|
398
|
+
* value) accepts any value in range — the same pattern already used for
|
|
399
|
+
* Razer's Viper V4 Pro (`100-50,000 in 50-DPI increments`) and for
|
|
400
|
+
* Zaunkoenig and Finalmouse. This is deliberately NOT
|
|
401
|
+
* `INCOTT_DPI_DEFAULT_STAGE_PRESETS` (the six round numbers the vendor
|
|
402
|
+
* calls default stage presets) — that list is too sparse for the shell to
|
|
403
|
+
* let a user dial in, say, 12000 DPI, which this mouse genuinely supports.
|
|
404
|
+
*/
|
|
405
|
+
getDpiOptions() {
|
|
406
|
+
const count = (INCOTT_DPI_MAX - INCOTT_DPI_MIN) / INCOTT_DPI_STEP + 1;
|
|
407
|
+
return Array.from({ length: count }, (_, index) => INCOTT_DPI_MIN + index * INCOTT_DPI_STEP);
|
|
408
|
+
}
|
|
409
|
+
/** The vendor's six default DPI stage presets — a convenience list, not the writable range. See `INCOTT_DPI_DEFAULT_STAGE_PRESETS`. */
|
|
410
|
+
getDpiDefaultStagePresets() {
|
|
411
|
+
return [...INCOTT_DPI_DEFAULT_STAGE_PRESETS];
|
|
412
|
+
}
|
|
413
|
+
/** Longest debounce the firmware accepts, in milliseconds (verified: section 8 of the IncottHub spec). */
|
|
414
|
+
getDebounceMaxMs() {
|
|
415
|
+
return INCOTT_DEBOUNCE_MAX_MS;
|
|
416
|
+
}
|
|
417
|
+
/** The firmware accepts any integer 0-30 ms, so every value in range is offered. */
|
|
418
|
+
getDebounceOptions() {
|
|
419
|
+
return Array.from({ length: INCOTT_DEBOUNCE_MAX_MS + 1 }, (_, ms) => ms);
|
|
420
|
+
}
|
|
421
|
+
/** Curated presets within the verified 1-900s sleep-timer range; see INCOTT_SLEEP_OPTIONS. */
|
|
422
|
+
getSleepOptions() {
|
|
423
|
+
return [...INCOTT_SLEEP_OPTIONS];
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Opens the device, then PROBES it with the identity query before trusting
|
|
427
|
+
* it for anything else — see `incottProbeCollection`.
|
|
428
|
+
*
|
|
429
|
+
* WIRED-MODE BUG, hardware-verified 2026-09-08: the wired mouse exposes
|
|
430
|
+
* two `0xFF05` collections on the same interface that are indistinguishable
|
|
431
|
+
* by vendor id, product id, or usage page; only one of them answers
|
|
432
|
+
* feature reports at all (the other fails every `HidD_SetFeature` call at
|
|
433
|
+
* the OS level). WebHID hands this client exactly ONE `HIDDevice`, already
|
|
434
|
+
* chosen by the browser's picker/filters before this driver ever sees
|
|
435
|
+
* it — a WebHID `HIDDevice` corresponds to a single collection, so there is
|
|
436
|
+
* no second candidate here for this method to fall back to the way
|
|
437
|
+
* `incottSelectCollection` can for a transport with real alternatives. All
|
|
438
|
+
* `open()` CAN do in the browser is find out, immediately and cheaply,
|
|
439
|
+
* whether the one collection it got is the live one.
|
|
440
|
+
*
|
|
441
|
+
* This never throws on a dead collection — it records the result in
|
|
442
|
+
* `collectionVerified` instead. `readStatus()` checks that flag and skips
|
|
443
|
+
* straight to a fully degraded, `ui.settingsReady: false` status (see
|
|
444
|
+
* there) rather than burning the full retry budget on roughly a dozen
|
|
445
|
+
* queries that a collection already known to be dead cannot answer either.
|
|
446
|
+
*
|
|
447
|
+
* Also attaches `onInputReport` (see the class comment's battery section):
|
|
448
|
+
* the mouse's battery lives only in an unsolicited input report it emits
|
|
449
|
+
* while actively in use, and WebHID delivers those solely as `inputreport`
|
|
450
|
+
* events on the device — there is no way to poll for one, so the listener
|
|
451
|
+
* must be attached here, before the device is ever read, or an in-use
|
|
452
|
+
* report could arrive and be missed before anything is listening.
|
|
453
|
+
*/
|
|
454
|
+
async open() {
|
|
455
|
+
if (!this.device.opened)
|
|
456
|
+
await this.device.open();
|
|
457
|
+
this.device.addEventListener("inputreport", this.onInputReport);
|
|
458
|
+
const identity = incottDecodeIdentity(await this.query(INCOTT_CMD_QUERY_IDENTITY, INCOTT_SUB_NONE));
|
|
459
|
+
this.collectionVerified = identity !== null;
|
|
460
|
+
}
|
|
461
|
+
async close() {
|
|
462
|
+
this.device.removeEventListener("inputreport", this.onInputReport);
|
|
463
|
+
if (this.device.opened)
|
|
464
|
+
await this.device.close();
|
|
465
|
+
}
|
|
466
|
+
async readStatus() {
|
|
467
|
+
// `connectionType`/`batteryState` derive from the product id, which is a
|
|
468
|
+
// USB descriptor field read at enumeration time — genuinely available
|
|
469
|
+
// even when the collection this client was handed turns out to be the
|
|
470
|
+
// wired mouse's dead one (see `open()`). Hardware-verified 2026-09-08:
|
|
471
|
+
// 0x622C enumerates ONLY when plugged in over USB; 0x522C is the 2.4 GHz
|
|
472
|
+
// dongle. This used to be read as a "charging" flag
|
|
473
|
+
// (`incottIsChargingProduct`) — the wrong axis: 0x622C means the
|
|
474
|
+
// CONNECTION is wired, and charging is a consequence of that, not what
|
|
475
|
+
// the id itself encodes. See `incottIsWiredProduct`.
|
|
476
|
+
const wired = incottIsWiredProduct(this.device.productId);
|
|
477
|
+
// Fallback display name only — the model read from the device wins where
|
|
478
|
+
// one is available, and `name` is settled below once the identity query
|
|
479
|
+
// has answered. The product string names a model over the cable
|
|
480
|
+
// ("incott Esports G23V2Pro mouse") but is a generic "incott 8K wireless
|
|
481
|
+
// mouse" on the dongle, so it cannot be the primary source. `this.device`
|
|
482
|
+
// stays public, so `client.device.productName` remains available as the
|
|
483
|
+
// untouched raw string for anything that wants it.
|
|
484
|
+
const rawName = this.device.productName || "Incott wireless mouse";
|
|
485
|
+
// WIRED-MODE BUG, hardware-verified 2026-09-08 (see `open()`'s class
|
|
486
|
+
// comment): when `open()`'s identity probe found this collection dead,
|
|
487
|
+
// every one of the ~12 queries below would fail too, each only after
|
|
488
|
+
// burning its own retry budget. Skip straight to the degraded values
|
|
489
|
+
// instead of paying that cost for a foregone conclusion. `dead` stays
|
|
490
|
+
// `false` (not just falsy) whenever `open()` was never called or its
|
|
491
|
+
// probe succeeded, so every existing caller that reads status without
|
|
492
|
+
// opening first is unaffected.
|
|
493
|
+
const dead = this.collectionVerified === false;
|
|
494
|
+
// DPI is a genuine live read as of 2026-09-08 (see the class comment
|
|
495
|
+
// above): read which of the six stages is active, then every stage's own
|
|
496
|
+
// value. Per the driver's graceful-degradation contract, a device that
|
|
497
|
+
// stops answering yields `dpi: null` / an omitted `dpiStages` here rather
|
|
498
|
+
// than throwing or fabricating a value.
|
|
499
|
+
// One query answers both how many stages the cycle uses and which is
|
|
500
|
+
// live — see `incottDecodeDpiCycle`. The count is NOT assumed to be six:
|
|
501
|
+
// it is written back verbatim by every stage select, and publishing six
|
|
502
|
+
// rows for a four-stage cycle would offer stages the mouse never visits.
|
|
503
|
+
const dpiCycle = dead
|
|
504
|
+
? null
|
|
505
|
+
: incottDecodeDpiCycle(await this.query(INCOTT_CMD_QUERY_DPI_STAGE, INCOTT_SUB_NONE));
|
|
506
|
+
const activeDpiStage = dpiCycle?.active ?? null;
|
|
507
|
+
// Six sequential queries, deliberately NOT parallelized: the device has a
|
|
508
|
+
// single shared response buffer (see `IncottTransactionQueue`'s class
|
|
509
|
+
// comment), and concurrent requests would corrupt each other's replies.
|
|
510
|
+
// `this.query` already serializes through that queue, so a plain
|
|
511
|
+
// sequential loop is enough.
|
|
512
|
+
const dpiStageReads = [];
|
|
513
|
+
for (let stage = 0; stage < INCOTT_DPI_STAGE_COUNT; stage += 1) {
|
|
514
|
+
dpiStageReads.push(dead ? null : incottDecodeDpiStage(await this.query(INCOTT_CMD_QUERY_DPI_STAGE_VALUE, stage), stage));
|
|
515
|
+
}
|
|
516
|
+
// `dpiStages` is populated only when every one of the six stages
|
|
517
|
+
// answered — a partial table is never fabricated with a placeholder for
|
|
518
|
+
// the stage(s) that did not. It is then trimmed to the stages the cycle
|
|
519
|
+
// actually uses: the device keeps all six stored values, but the ones
|
|
520
|
+
// past `count` are not in the rotation and must not be offered as if
|
|
521
|
+
// they were.
|
|
522
|
+
const dpiStages = dpiStageReads.every((value) => value !== null)
|
|
523
|
+
? dpiStageReads.slice(0, dpiCycle?.count ?? dpiStageReads.length)
|
|
524
|
+
: null;
|
|
525
|
+
const dpi = activeDpiStage === null ? null : dpiStageReads[activeDpiStage] ?? null;
|
|
526
|
+
// The active stage's Y axis, which the plain stage read above does not
|
|
527
|
+
// distinguish — that returns X. Published so the shared "X n · Y n DPI"
|
|
528
|
+
// summary is truthful on a mouse whose axes differ; omitted rather than
|
|
529
|
+
// mirrored from X when the read fails, so the app falls back to one
|
|
530
|
+
// number instead of claiming the axes match.
|
|
531
|
+
const dpiY = dead || activeDpiStage === null
|
|
532
|
+
? null
|
|
533
|
+
: await this.readDpiStageAxis(activeDpiStage, "y");
|
|
534
|
+
const pollingRateHz = dead
|
|
535
|
+
? null
|
|
536
|
+
: incottDecodePollingRate(await this.query(INCOTT_CMD_QUERY_POLLING, INCOTT_SUB_NONE));
|
|
537
|
+
// The polling-rate (0x81) query is verified readable on hardware, but a
|
|
538
|
+
// device that stops answering must not throw readStatus() — the app
|
|
539
|
+
// degrades to identity-only fields instead (see `ui.settingsReady`
|
|
540
|
+
// below). `valuesVerified` also requires the active DPI stage's value to
|
|
541
|
+
// have answered.
|
|
542
|
+
const valuesVerified = dpi !== null && pollingRateHz !== null;
|
|
543
|
+
// Symmetric single-purpose reads, preferred over the packed byte-7
|
|
544
|
+
// nibble trick (`incottDecodeLiftOff`/`incottDecodeMotionSync`, still
|
|
545
|
+
// exported and tested): verified on hardware 2026-09-08 that both forms
|
|
546
|
+
// agree at every step (lift-off hw 0/1/2, motion sync 0/1/0).
|
|
547
|
+
const liftOffTenths = dead
|
|
548
|
+
? null
|
|
549
|
+
: incottDecodeLiftOffDirect(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_LOD));
|
|
550
|
+
const motionSync = dead
|
|
551
|
+
? null
|
|
552
|
+
: incottDecodeToggle(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_MOTION_SYNC), INCOTT_SUB_MOTION_SYNC);
|
|
553
|
+
const rippleControl = dead
|
|
554
|
+
? null
|
|
555
|
+
: incottDecodeToggle(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_RIPPLE), INCOTT_SUB_RIPPLE);
|
|
556
|
+
const angleSnapping = dead
|
|
557
|
+
? null
|
|
558
|
+
: incottDecodeToggle(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_ANGLE_SNAP), INCOTT_SUB_ANGLE_SNAP);
|
|
559
|
+
// Performance mode (HP/Corded/LP), hardware-confirmed 2026-09-10 — see
|
|
560
|
+
// `INCOTT_SUB_PERFORMANCE` and `setPowerMode`. A live read of the raw wire
|
|
561
|
+
// value, converted to its display name through `incottPerformanceModeFromWire`
|
|
562
|
+
// (the single table the value-to-label reversal lives in). `undefined`,
|
|
563
|
+
// never fabricated, when the collection is dead or the read fails/returns
|
|
564
|
+
// an out-of-range value.
|
|
565
|
+
const powerModeWire = dead
|
|
566
|
+
? null
|
|
567
|
+
: incottDecodePerformanceMode(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_PERFORMANCE));
|
|
568
|
+
const powerMode = powerModeWire === null ? undefined : incottPerformanceModeFromWire(powerModeWire) ?? undefined;
|
|
569
|
+
const debounceMs = dead
|
|
570
|
+
? null
|
|
571
|
+
: incottDecodeDebounce(await this.query(INCOTT_CMD_QUERY_TIMING, INCOTT_SUB_DEBOUNCE));
|
|
572
|
+
const sleepTimeout = dead
|
|
573
|
+
? null
|
|
574
|
+
: incottDecodeSleep(await this.query(INCOTT_CMD_QUERY_TIMING, INCOTT_SUB_SLEEP));
|
|
575
|
+
// Battery comes from `lastInputStatus` — the mouse's unsolicited input
|
|
576
|
+
// report, cached by `onInputReport` — NOT from a feature-report query.
|
|
577
|
+
// Deliberately independent of `dead`/the feature-report collection probe:
|
|
578
|
+
// the input report arrives on the device regardless of whether this
|
|
579
|
+
// client's feature-report collection turned out to be the wired mouse's
|
|
580
|
+
// dead look-alike (see `open()`), so a dead collection must not suppress
|
|
581
|
+
// a battery reading that genuinely came in. `null` here means "no report
|
|
582
|
+
// has arrived yet" (the mouse only emits them while actively in use), not
|
|
583
|
+
// a failed read — see the class comment's battery section and
|
|
584
|
+
// `incottDecodeInputStatus` in `src/incott/index.ts`. This REPLACES the
|
|
585
|
+
// disproven `0x8e`/sub `0x01` byte-6 read (itself a replacement for the
|
|
586
|
+
// also-disproven `0x89` byte 8) — see `INCOTT_CMD_QUERY_BATTERY` and
|
|
587
|
+
// `incottDecodeBattery`, both kept only as codecs/regression coverage.
|
|
588
|
+
const batteryPercent = this.lastInputStatus?.batteryPercent ?? null;
|
|
589
|
+
// The charging bit comes from the input report itself (raw > 100), not
|
|
590
|
+
// from the product id: `wired` (0x622C) is a genuinely reliable signal
|
|
591
|
+
// that being charged is *likely* (the mouse is plugged into USB), but the
|
|
592
|
+
// report is the authoritative source for whether it actually is charging
|
|
593
|
+
// right now, so it is used here instead of inferring the state from the
|
|
594
|
+
// connection.
|
|
595
|
+
const batteryCharging = this.lastInputStatus?.charging ?? null;
|
|
596
|
+
// The identity reply carries the model, the sensor and the receiver type
|
|
597
|
+
// — see `incottDecodeIdentity` for the byte map. The raw hex is still
|
|
598
|
+
// published under `firmware` below, because the remaining bytes (7-8) are
|
|
599
|
+
// genuinely undecoded and a capture of them is what a second model's
|
|
600
|
+
// owner would need to send.
|
|
601
|
+
const identity = dead
|
|
602
|
+
? null
|
|
603
|
+
: incottDecodeIdentity(await this.query(INCOTT_CMD_QUERY_IDENTITY, INCOTT_SUB_NONE));
|
|
604
|
+
// The model READ FROM THE DEVICE is preferred over the product string:
|
|
605
|
+
// it is the only source that works on the 2.4 GHz dongle, where the
|
|
606
|
+
// product string has no model in it. All six Incott models share the
|
|
607
|
+
// same two product ids, so this is the only thing that tells them apart
|
|
608
|
+
// at all. Falls back to the tidied product string whenever the identity
|
|
609
|
+
// query failed or returned a model code outside the known table — an
|
|
610
|
+
// unrecognised model reports whatever the device called itself rather
|
|
611
|
+
// than a guess.
|
|
612
|
+
const name = identity?.displayName ?? incottNormalizeProductName(rawName);
|
|
613
|
+
// The owner confirmed on hardware that the mouse only reaches 1000 Hz
|
|
614
|
+
// over the cable — 2000/4000/8000 Hz are wireless-only (see
|
|
615
|
+
// `INCOTT_POLLING_STEPS_HZ_WIRED`). Publishing the full ladder while
|
|
616
|
+
// wired would let the shell offer a rate the device silently refuses,
|
|
617
|
+
// which `setPollingRate`'s read-back verification would then report as a
|
|
618
|
+
// failure on every attempt.
|
|
619
|
+
const supportedPollingRates = wired ? [...INCOTT_POLLING_STEPS_HZ_WIRED] : [...INCOTT_POLLING_STEPS_HZ];
|
|
620
|
+
// The receiver LED belongs to the 2.4 GHz dongle and means nothing over
|
|
621
|
+
// the cable, so it is not read at all when wired — the card then hides
|
|
622
|
+
// itself rather than offering a control that cannot do anything. See
|
|
623
|
+
// `setReceiverLed`.
|
|
624
|
+
const receiverLedMode = dead || wired
|
|
625
|
+
? null
|
|
626
|
+
: incottDecodeReceiverLed(await this.query(INCOTT_CMD_QUERY_RECEIVER_LED, INCOTT_SUB_NONE));
|
|
627
|
+
// Rapid-fire parameters, global to the device — see `INCOTT_SUB_FIRE_KEY`.
|
|
628
|
+
const fireKey = dead ? null : await this.getFireKey();
|
|
629
|
+
// All six bindings, or nothing. A partial read would render some buttons
|
|
630
|
+
// with a real assignment and the rest with a fabricated default, which is
|
|
631
|
+
// worse than hiding the remapper: the shared UI writes back whatever it
|
|
632
|
+
// shows, so a wrong reading becomes a wrong write the moment anything
|
|
633
|
+
// else on the card is changed.
|
|
634
|
+
const buttonMappings = dead ? null : await this.readButtonMappings();
|
|
635
|
+
const ui = {
|
|
636
|
+
family: "incott",
|
|
637
|
+
// The advanced section is the only place debounce, sleep, motion sync,
|
|
638
|
+
// angle snapping and ripple control render; Incott has no lighting and
|
|
639
|
+
// no onboard profiles, so those cards stay hidden on their own
|
|
640
|
+
// (nothing populates the fields that gate them).
|
|
641
|
+
showAdvancedSection: true,
|
|
642
|
+
// No command reports link quality.
|
|
643
|
+
hideSignalCard: true,
|
|
644
|
+
defaultDisplayName: name,
|
|
645
|
+
// false only when the DPI and/or polling-rate query failed above (this
|
|
646
|
+
// includes the wired dead-collection fast path, since every query is
|
|
647
|
+
// forced to `null` there too); the app hides the settings grid rather
|
|
648
|
+
// than render the placeholder numbers assigned to `dpi`/`pollingRateHz`
|
|
649
|
+
// below in that case.
|
|
650
|
+
settingsReady: valuesVerified,
|
|
651
|
+
valuesVerified,
|
|
652
|
+
// OpenMouse's device-overview card (App.tsx) hides the battery column
|
|
653
|
+
// outright for a wired connection unless either a real percent is
|
|
654
|
+
// already known OR this flag is set — a sane default for mice with no
|
|
655
|
+
// battery at all while corded, but WRONG here: this mouse has an
|
|
656
|
+
// internal battery it charges over the same USB cable, and until the
|
|
657
|
+
// first unsolicited input report arrives (see the class comment's
|
|
658
|
+
// battery section) `batteryPercent` is genuinely `null` regardless of
|
|
659
|
+
// connection. Without this flag a freshly connected wired unit would
|
|
660
|
+
// show no battery card at all instead of the em dash the app renders
|
|
661
|
+
// for a null percent; with it, the card always shows (em dash first,
|
|
662
|
+
// then the real reading once a report arrives).
|
|
663
|
+
forceShowBattery: true,
|
|
664
|
+
// The mouse cannot accept a rate outside `supportedPollingRates` for
|
|
665
|
+
// its current connection, so hide the ones it would refuse rather than
|
|
666
|
+
// let the shell offer a write that read-back verification would then
|
|
667
|
+
// report as failed.
|
|
668
|
+
hideUnsupportedPollingRates: true,
|
|
669
|
+
pollingNote: wired
|
|
670
|
+
? "Up to 1,000 Hz over the cable; 8,000 Hz needs the wireless dongle."
|
|
671
|
+
: "Up to 8,000 Hz wireless; 1,000 Hz over the cable.",
|
|
672
|
+
// Set only when open()'s probe found this specific collection dead —
|
|
673
|
+
// see the class comment on `open()`. Wired mice expose a second,
|
|
674
|
+
// identical-looking 0xFF05 collection that never answers; WebHID hands
|
|
675
|
+
// this client one collection at a time and cannot pick between them,
|
|
676
|
+
// so the only remedy today is to reconnect and hope the browser offers
|
|
677
|
+
// the other one.
|
|
678
|
+
...(dead
|
|
679
|
+
? { statusNote: "This HID collection never answered — wired mice expose a second, identical-looking collection that does not work. Try reconnecting." }
|
|
680
|
+
: {}),
|
|
681
|
+
// Six FIXED stages (`countEditable: false` hides the count picker —
|
|
682
|
+
// this hardware has no command to add or remove a stage). 45000 is the
|
|
683
|
+
// higher of the two known PixArt sensor ceilings paired in the
|
|
684
|
+
// vendor's own device definition (PAW3395: 32000, PAW3950: 45000);
|
|
685
|
+
// which one is fitted to a given unit cannot currently be read, so a
|
|
686
|
+
// PAW3395 unit is expected to have a write above 32000 refused by the
|
|
687
|
+
// existing read-back verification in `setDpi`/`setDpiStageValue`
|
|
688
|
+
// rather than this module guessing which sensor is present.
|
|
689
|
+
// `countEditable` only while the cycle actually read: the count picker
|
|
690
|
+
// writes through `setDpiStageCount`, which needs a real current count
|
|
691
|
+
// to preserve the active stage, and offering it against an unreadable
|
|
692
|
+
// one would write a guess.
|
|
693
|
+
// The ceiling follows the FITTED SENSOR, which the identity reply
|
|
694
|
+
// reports: the PAW3395 models in this family stop at 32000 in the
|
|
695
|
+
// vendor's own table where the PAW3950 reaches 45000. Falls back to the
|
|
696
|
+
// higher value when identity could not be read, since narrowing on a
|
|
697
|
+
// guess would hide DPI the mouse can actually do.
|
|
698
|
+
dpiStageEditor: { maxStages: INCOTT_DPI_STAGE_COUNT, countEditable: dpiCycle !== null, minDpi: INCOTT_DPI_MIN, maxDpi: incottDpiMaxForSensor(identity?.sensorId ?? null), stepDpi: INCOTT_DPI_STEP },
|
|
699
|
+
};
|
|
700
|
+
return {
|
|
701
|
+
brand: "Incott",
|
|
702
|
+
name,
|
|
703
|
+
ui,
|
|
704
|
+
// A genuine live read (the active stage's own value) when it
|
|
705
|
+
// succeeded. Otherwise this is inert placeholder data, not a
|
|
706
|
+
// fabricated reading: MouseStatus.dpi is non-nullable, so a number must
|
|
707
|
+
// go here, but `ui.settingsReady: false` above means the app never
|
|
708
|
+
// renders it.
|
|
709
|
+
dpi: dpi ?? 0,
|
|
710
|
+
...(dpiY !== null ? { dpiY, supportsSeparateDpiAxes: true } : {}),
|
|
711
|
+
// Omitted, not nulled, when unreadable or inapplicable: the app's
|
|
712
|
+
// Incott card renders only the fields that are present, so a wired
|
|
713
|
+
// connection simply has no receiver-LED control rather than a dead one.
|
|
714
|
+
...(receiverLedMode !== null ? { incottReceiverLedMode: receiverLedMode } : {}),
|
|
715
|
+
...(fireKey !== null
|
|
716
|
+
? { incottFireKeyTimes: fireKey.times, incottFireKeyIntervalMs: fireKey.intervalMs }
|
|
717
|
+
: {}),
|
|
718
|
+
// All six stages' stored values, only when every one of them answered
|
|
719
|
+
// — see the loop above. Omitted (not fabricated) on a partial read.
|
|
720
|
+
...(dpiStages !== null ? { dpiStages } : {}),
|
|
721
|
+
...(activeDpiStage !== null ? { activeDpiStage } : {}),
|
|
722
|
+
// Same reasoning as `dpi` above: a real reading of the polling-rate
|
|
723
|
+
// query (0x81) when it succeeded, otherwise inert placeholder data
|
|
724
|
+
// behind `ui.settingsReady: false`.
|
|
725
|
+
pollingRateHz: pollingRateHz ?? 0,
|
|
726
|
+
supportedPollingRates,
|
|
727
|
+
activeProfile: null,
|
|
728
|
+
connectionType: wired ? "Wired" : "Wireless",
|
|
729
|
+
batteryPercent,
|
|
730
|
+
// Derived from the input report's own charging bit (`batteryCharging`,
|
|
731
|
+
// see above), NOT from `wired`/the product id: `0x622C` reliably means
|
|
732
|
+
// the connection is wired, and being wired does imply charging, but the
|
|
733
|
+
// report — when one has arrived — is the authoritative source for
|
|
734
|
+
// whether the mouse is actually charging right now. "Unknown" is for
|
|
735
|
+
// when no input report has arrived yet, regardless of connection.
|
|
736
|
+
batteryState: batteryCharging === null ? "Unknown" : batteryCharging ? "Charging" : "Discharging",
|
|
737
|
+
liftOffDistance: liftOffTenths === null ? null : incottLiftOffLabel(liftOffTenths),
|
|
738
|
+
supportedLiftOffDistances: ["Low", "Medium", "High"],
|
|
739
|
+
// Both fields together, or neither: the shared remapper only renders
|
|
740
|
+
// when it has the current assignments AND the list of actions it may
|
|
741
|
+
// write back.
|
|
742
|
+
...(buttonMappings !== null
|
|
743
|
+
? { buttonMappings, buttonOptions: INCOTT_BUTTON_ACTIONS.map(([label]) => label) }
|
|
744
|
+
: {}),
|
|
745
|
+
motionSync,
|
|
746
|
+
rippleControl,
|
|
747
|
+
angleSnapping,
|
|
748
|
+
debounceMs,
|
|
749
|
+
sleepTimeout,
|
|
750
|
+
// Named power/performance modes (HP/Corded/LP) — see `powerModeWire`
|
|
751
|
+
// above. `powerModes` is only advertised alongside a real `powerMode`
|
|
752
|
+
// reading, matching MCHOSE's identical three-way mode: never claim a
|
|
753
|
+
// set of options exists on a collection that just failed to answer.
|
|
754
|
+
...(powerMode !== undefined ? { powerMode, powerModes: [...INCOTT_PERFORMANCE_MODE_NAMES] } : {}),
|
|
755
|
+
firmware: [identity ? `Identity ${identity.raw}` : "Identity unavailable"],
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Sets the ACTIVE stage's stored DPI value — the only sane meaning left for
|
|
760
|
+
* a plain "set DPI" call now that a real stage-select operation exists (see
|
|
761
|
+
* `setActiveDpiStage` below and the class comment's account of the bug this
|
|
762
|
+
* used to have). This is exactly `setDpiStageValue(activeStage, dpi)`;
|
|
763
|
+
* confirms the write by reading that stage back, the same pattern every
|
|
764
|
+
* other setter in this client uses. Range/step are validated up front,
|
|
765
|
+
* before any device round-trip, so an invalid value never costs the query
|
|
766
|
+
* needed to find the active stage.
|
|
767
|
+
*/
|
|
768
|
+
async setDpi(dpi) {
|
|
769
|
+
incottValidateDpi(dpi);
|
|
770
|
+
const stage = (await this.readDpiCycle())?.active ?? null;
|
|
771
|
+
if (stage === null)
|
|
772
|
+
throw new Error("Could not read the active DPI stage to write.");
|
|
773
|
+
await this.write(incottEncodeSetDpi(stage, dpi));
|
|
774
|
+
const got = incottDecodeDpiStage(await this.query(INCOTT_CMD_QUERY_DPI_STAGE_VALUE, stage), stage);
|
|
775
|
+
if (got !== dpi)
|
|
776
|
+
throw new Error(`The mouse kept ${got ?? "an unreadable"} DPI instead of ${dpi}.`);
|
|
777
|
+
return dpi;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Sets both DPI axes on the ACTIVE stage — the shape a generic X/Y control
|
|
781
|
+
* needs, where `setDpiStageAxis` is per-stage and per-axis.
|
|
782
|
+
*
|
|
783
|
+
* Mirrors the vendor's own `setResolution`: one write with the "both" flag
|
|
784
|
+
* when the axes match, two axis-flagged writes when they differ. Named to
|
|
785
|
+
* match the method proposed upstream for a brand-agnostic axis control; if
|
|
786
|
+
* the maintainers settle on a different name this is the one line to
|
|
787
|
+
* rename.
|
|
788
|
+
*/
|
|
789
|
+
async setAxisDpi(dpiX, dpiY) {
|
|
790
|
+
incottValidateDpi(dpiX);
|
|
791
|
+
incottValidateDpi(dpiY);
|
|
792
|
+
const stage = (await this.readDpiCycle())?.active ?? null;
|
|
793
|
+
if (stage === null)
|
|
794
|
+
throw new Error("Could not read the active DPI stage to write.");
|
|
795
|
+
if (dpiX === dpiY) {
|
|
796
|
+
await this.setDpiStageAxis(stage, dpiX, "both");
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
await this.setDpiStageAxis(stage, dpiX, "x");
|
|
800
|
+
await this.setDpiStageAxis(stage, dpiY, "y");
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Uploads one macro into an on-device buffer.
|
|
804
|
+
*
|
|
805
|
+
* Ten 32-byte chunks, each announced by an 8-byte feature report and then
|
|
806
|
+
* carried by a 32-byte OUTPUT report on the same id — the only place this
|
|
807
|
+
* protocol uses an output report at all. Captured from the vendor tool
|
|
808
|
+
* 2026-09-11; see `captures/incott-8k-wireless/macro-upload-2026-09-11.hex`.
|
|
809
|
+
*
|
|
810
|
+
* The device does not acknowledge any of it, so unlike every other setter
|
|
811
|
+
* here there is nothing to verify against: there is no macro read command.
|
|
812
|
+
* Bind a button to `Macro <n>` and press it — that is the only confirmation
|
|
813
|
+
* available.
|
|
814
|
+
*/
|
|
815
|
+
async uploadMacro(macro) {
|
|
816
|
+
const chunks = incottMacroChunks(incottEncodeMacroBuffer(macro));
|
|
817
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
818
|
+
await this.write(incottEncodeMacroChunkHeader(index, macro.bufferId));
|
|
819
|
+
await this.queue.sendOutput(INCOTT_REPORT_ID, chunk);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/** Reads the DPI cycle (stage count + active stage) in one query. */
|
|
823
|
+
async readDpiCycle() {
|
|
824
|
+
return incottDecodeDpiCycle(await this.query(INCOTT_CMD_QUERY_DPI_STAGE, INCOTT_SUB_NONE));
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* SELECTS which DPI stage is active (`09 03 <count> <idx>`) — distinct from
|
|
828
|
+
* `setDpi`/`setDpiStageValue`, which EDIT a stage's stored value. Confirmed
|
|
829
|
+
* on hardware 2026-09-08 that a select never touches any stage's stored
|
|
830
|
+
* value (see `INCOTT_CMD_SET_DPI_STAGE` in `src/incott/index.ts`), so
|
|
831
|
+
* unlike every value setter in this client this one reads back the active
|
|
832
|
+
* index rather than a DPI value.
|
|
833
|
+
*
|
|
834
|
+
* The stage count is READ FIRST and written back unchanged. It shares the
|
|
835
|
+
* write with the index, so sending a constant here would silently resize a
|
|
836
|
+
* cycle that is not six stages long — see
|
|
837
|
+
* `INCOTT_DPI_STAGE_COUNT_DEFAULT`.
|
|
838
|
+
*/
|
|
839
|
+
async setActiveDpiStage(stage) {
|
|
840
|
+
if (!Number.isInteger(stage) || stage < 0 || stage >= INCOTT_DPI_STAGE_COUNT) {
|
|
841
|
+
throw new RangeError(`DPI stage out of range: ${stage}`);
|
|
842
|
+
}
|
|
843
|
+
const cycle = await this.readDpiCycle();
|
|
844
|
+
if (cycle === null)
|
|
845
|
+
throw new Error("Could not read the DPI stage cycle to write.");
|
|
846
|
+
if (stage >= cycle.count) {
|
|
847
|
+
throw new RangeError(`DPI stage ${stage} is outside this mouse's ${cycle.count}-stage cycle.`);
|
|
848
|
+
}
|
|
849
|
+
await this.write(incottEncodeSetDpiCycle(cycle.count, stage));
|
|
850
|
+
const got = (await this.readDpiCycle())?.active ?? null;
|
|
851
|
+
if (got !== stage)
|
|
852
|
+
throw new Error(`The mouse kept DPI stage ${got ?? "an unreadable"} instead of ${stage}.`);
|
|
853
|
+
return stage;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Sets how many stages the DPI cycle rotates through (`09 03 <count>
|
|
857
|
+
* <idx>`). The stored value of every stage is left alone — stages above
|
|
858
|
+
* the new count keep their values and simply stop being visited.
|
|
859
|
+
*
|
|
860
|
+
* The active stage rides along in the same write, so it is clamped into
|
|
861
|
+
* the new cycle rather than left pointing past the end.
|
|
862
|
+
*/
|
|
863
|
+
async setDpiStageCount(count) {
|
|
864
|
+
if (!Number.isInteger(count) || count < 1 || count > INCOTT_DPI_STAGE_COUNT) {
|
|
865
|
+
throw new RangeError(`DPI stage count out of range: ${count}`);
|
|
866
|
+
}
|
|
867
|
+
const cycle = await this.readDpiCycle();
|
|
868
|
+
if (cycle === null)
|
|
869
|
+
throw new Error("Could not read the DPI stage cycle to write.");
|
|
870
|
+
const active = Math.min(cycle.active, count - 1);
|
|
871
|
+
await this.write(incottEncodeSetDpiCycle(count, active));
|
|
872
|
+
const got = await this.readDpiCycle();
|
|
873
|
+
if (got?.count !== count) {
|
|
874
|
+
throw new Error(`The mouse kept ${got?.count ?? "an unreadable"} DPI stages instead of ${count}.`);
|
|
875
|
+
}
|
|
876
|
+
return count;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* EDITS one stage's stored DPI value directly, by index — unlike `setDpi`,
|
|
880
|
+
* which always targets whichever stage happens to be active right now.
|
|
881
|
+
* This is the write OpenMouse's shared multi-stage DPI editor uses when the
|
|
882
|
+
* user edits a specific stage row rather than the active-stage preset.
|
|
883
|
+
*/
|
|
884
|
+
async setDpiStageValue(stage, dpi) {
|
|
885
|
+
if (!Number.isInteger(stage) || stage < 0 || stage >= INCOTT_DPI_STAGE_COUNT) {
|
|
886
|
+
throw new RangeError(`DPI stage out of range: ${stage}`);
|
|
887
|
+
}
|
|
888
|
+
incottValidateDpi(dpi);
|
|
889
|
+
await this.write(incottEncodeSetDpi(stage, dpi));
|
|
890
|
+
const got = incottDecodeDpiStage(await this.query(INCOTT_CMD_QUERY_DPI_STAGE_VALUE, stage), stage);
|
|
891
|
+
if (got !== dpi)
|
|
892
|
+
throw new Error(`The mouse kept ${got ?? "an unreadable"} DPI instead of ${dpi} on stage ${stage}.`);
|
|
893
|
+
return dpi;
|
|
894
|
+
}
|
|
895
|
+
async setPollingRate(hz) {
|
|
896
|
+
await this.write(incottEncodeSetPollingRate(hz));
|
|
897
|
+
const got = incottDecodePollingRate(await this.query(INCOTT_CMD_QUERY_POLLING, INCOTT_SUB_NONE));
|
|
898
|
+
if (got !== hz)
|
|
899
|
+
throw new Error(`The mouse kept ${got ?? "an unreadable"} Hz instead of ${hz} Hz.`);
|
|
900
|
+
return hz;
|
|
901
|
+
}
|
|
902
|
+
async setLiftOffDistance(level) {
|
|
903
|
+
const tenths = incottLiftOffTenths(level);
|
|
904
|
+
await this.write(incottEncodeSetLiftOff(tenths));
|
|
905
|
+
// Symmetric single-purpose read (`0x84`/`0x01`), preferred over the
|
|
906
|
+
// packed byte-7 nibble form — see the comment in `readStatus()`.
|
|
907
|
+
const gotTenths = incottDecodeLiftOffDirect(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_LOD));
|
|
908
|
+
const got = gotTenths === null ? null : incottLiftOffLabel(gotTenths);
|
|
909
|
+
if (got !== level) {
|
|
910
|
+
throw new Error(`The mouse kept a ${got ?? "unreadable"} lift-off distance instead of ${level}.`);
|
|
911
|
+
}
|
|
912
|
+
return level;
|
|
913
|
+
}
|
|
914
|
+
async setMotionSync(on) {
|
|
915
|
+
await this.write(incottEncodeSetToggle("motionSync", on));
|
|
916
|
+
// Symmetric single-purpose read (`0x84`/`0x04`), preferred over the
|
|
917
|
+
// packed byte-7 nibble form — see the comment in `readStatus()`.
|
|
918
|
+
const got = incottDecodeToggle(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_MOTION_SYNC), INCOTT_SUB_MOTION_SYNC);
|
|
919
|
+
if (got !== on)
|
|
920
|
+
throw new Error(`The mouse kept Motion Sync ${toggleWord(got)} instead of ${on ? "on" : "off"}.`);
|
|
921
|
+
return on;
|
|
922
|
+
}
|
|
923
|
+
async setAngleSnapping(on) {
|
|
924
|
+
await this.write(incottEncodeSetToggle("angleSnapping", on));
|
|
925
|
+
const got = incottDecodeToggle(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_ANGLE_SNAP), INCOTT_SUB_ANGLE_SNAP);
|
|
926
|
+
if (got !== on)
|
|
927
|
+
throw new Error(`The mouse kept angle snapping ${toggleWord(got)} instead of ${on ? "on" : "off"}.`);
|
|
928
|
+
return on;
|
|
929
|
+
}
|
|
930
|
+
async setRippleControl(on) {
|
|
931
|
+
await this.write(incottEncodeSetToggle("rippleControl", on));
|
|
932
|
+
const got = incottDecodeToggle(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_RIPPLE), INCOTT_SUB_RIPPLE);
|
|
933
|
+
if (got !== on)
|
|
934
|
+
throw new Error(`The mouse kept ripple control ${toggleWord(got)} instead of ${on ? "on" : "off"}.`);
|
|
935
|
+
return on;
|
|
936
|
+
}
|
|
937
|
+
async setDebounceTime(ms) {
|
|
938
|
+
await this.write(incottEncodeSetDebounce(ms));
|
|
939
|
+
const got = incottDecodeDebounce(await this.query(INCOTT_CMD_QUERY_TIMING, INCOTT_SUB_DEBOUNCE));
|
|
940
|
+
if (got !== ms)
|
|
941
|
+
throw new Error(`The mouse kept ${got ?? "an unreadable"} ms debounce instead of ${ms} ms.`);
|
|
942
|
+
return ms;
|
|
943
|
+
}
|
|
944
|
+
async setSleepTimeout(seconds) {
|
|
945
|
+
await this.write(incottEncodeSetSleep(seconds));
|
|
946
|
+
const got = incottDecodeSleep(await this.query(INCOTT_CMD_QUERY_TIMING, INCOTT_SUB_SLEEP));
|
|
947
|
+
if (got !== seconds) {
|
|
948
|
+
throw new Error(`The mouse kept a ${got ?? "an unreadable"} s sleep timer instead of ${seconds} s.`);
|
|
949
|
+
}
|
|
950
|
+
return seconds;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Not called generically by the app (no shared receiver-LED control exists
|
|
954
|
+
* yet), kept for protocol parity with IncottHub.
|
|
955
|
+
*
|
|
956
|
+
* NOTE for whoever wires this up: the receiver LED is a property of the
|
|
957
|
+
* 2.4 GHz dongle and is meaningless when `incottIsWiredProduct(device.productId)`
|
|
958
|
+
* is true (checked 2026-09-08) — there is no dongle in that mode. Checked
|
|
959
|
+
* `MouseUiHints` in `src/drivers/mouse-types.ts` for an existing `hide*`
|
|
960
|
+
* flag to gate this with and found none scoped to the receiver LED
|
|
961
|
+
* specifically (the closest, `hideSignalCard`, is about link-quality
|
|
962
|
+
* telemetry, a different control); a new hint was deliberately NOT added to
|
|
963
|
+
* that shared contract for a control this driver does not yet advertise
|
|
964
|
+
* anywhere. Whoever wires a shared receiver-LED control into `MouseStatus`
|
|
965
|
+
* should either add a scoped `hide*` flag there at that time, or simply
|
|
966
|
+
* omit the wiring outright when wired, matching the other wireless-only
|
|
967
|
+
* behavior in this driver (see `INCOTT_POLLING_STEPS_HZ_WIRED`).
|
|
968
|
+
*/
|
|
969
|
+
/**
|
|
970
|
+
* Reads all six button bindings, keyed by the physical button name.
|
|
971
|
+
*
|
|
972
|
+
* Returns null unless every button answered: see the call site in
|
|
973
|
+
* `readStatus` for why a partial read is not published. A binding the
|
|
974
|
+
* action table does not know (a keyboard key, a macro) reports its raw code
|
|
975
|
+
* as `Unknown (0x...)` rather than being shown as one of the offered
|
|
976
|
+
* actions — the shared remapper writes back what it displays, so labelling
|
|
977
|
+
* an unknown binding as a known action would rewrite it on the next edit.
|
|
978
|
+
*/
|
|
979
|
+
async readButtonMappings() {
|
|
980
|
+
const mappings = {};
|
|
981
|
+
for (const name of INCOTT_BUTTON_NAMES) {
|
|
982
|
+
const index = INCOTT_BUTTON_WIRE_INDEX[name];
|
|
983
|
+
const binding = incottDecodeButtonBinding(await this.query(INCOTT_CMD_QUERY_BUTTON, index), index);
|
|
984
|
+
if (binding === null)
|
|
985
|
+
return null;
|
|
986
|
+
mappings[name] = binding.label ?? `Unknown (0x${binding.code.toString(16).padStart(8, "0")})`;
|
|
987
|
+
}
|
|
988
|
+
return mappings;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Reassigns one button, then reads it back and refuses to report success
|
|
992
|
+
* unless the device actually took the value.
|
|
993
|
+
*
|
|
994
|
+
* `button` is a physical name; the wire index it maps to is NOT the same
|
|
995
|
+
* number (Forward and Back are transposed — see
|
|
996
|
+
* `INCOTT_BUTTON_WIRE_INDEX`). This is a standalone command, so it cannot
|
|
997
|
+
* disturb DPI, polling or the other five buttons.
|
|
998
|
+
*/
|
|
999
|
+
async setButtonMapping(button, actionLabel) {
|
|
1000
|
+
const name = INCOTT_BUTTON_NAMES.find((candidate) => candidate === button);
|
|
1001
|
+
if (name === undefined)
|
|
1002
|
+
throw new Error(`This mouse has no "${button}" button.`);
|
|
1003
|
+
const code = incottButtonActionCode(actionLabel);
|
|
1004
|
+
if (code === null)
|
|
1005
|
+
throw new Error(`Unknown button action "${actionLabel}".`);
|
|
1006
|
+
const index = INCOTT_BUTTON_WIRE_INDEX[name];
|
|
1007
|
+
await this.write(incottEncodeSetButtonBinding(index, code));
|
|
1008
|
+
const applied = incottDecodeButtonBinding(await this.query(INCOTT_CMD_QUERY_BUTTON, index), index);
|
|
1009
|
+
if (applied === null)
|
|
1010
|
+
throw new Error("The mouse did not confirm the button change.");
|
|
1011
|
+
if (applied.code !== code) {
|
|
1012
|
+
throw new Error(`The mouse kept ${applied.label ?? "another binding"} on ${name} instead of ${actionLabel}.`);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Reads the Fire Key (rapid-fire) parameters — how many clicks a button
|
|
1017
|
+
* bound to "Rapid fire" sends, and how far apart. Global to the device: the
|
|
1018
|
+
* command carries no button index.
|
|
1019
|
+
*/
|
|
1020
|
+
async getFireKey() {
|
|
1021
|
+
return incottDecodeFireKey(await this.query(INCOTT_CMD_QUERY_TIMING, INCOTT_SUB_FIRE_KEY));
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Writes the Fire Key parameters and verifies the read-back.
|
|
1025
|
+
*
|
|
1026
|
+
* Not advertised through `MouseStatus`: the shared contract has no
|
|
1027
|
+
* rapid-fire field, so there is no generic control to publish this behind —
|
|
1028
|
+
* the same position `setReceiverLed` is in. Kept for protocol parity and
|
|
1029
|
+
* for whoever wires a control up.
|
|
1030
|
+
*/
|
|
1031
|
+
async setFireKey(times, intervalMs) {
|
|
1032
|
+
await this.write(incottEncodeSetFireKey(times, intervalMs));
|
|
1033
|
+
const got = await this.getFireKey();
|
|
1034
|
+
if (got === null)
|
|
1035
|
+
throw new Error("The mouse did not confirm the fire key change.");
|
|
1036
|
+
if (got.times !== times || got.intervalMs !== intervalMs) {
|
|
1037
|
+
throw new Error(`The mouse kept ${got.times} clicks at ${got.intervalMs} ms instead of ${times} at ${intervalMs} ms.`);
|
|
1038
|
+
}
|
|
1039
|
+
return got;
|
|
1040
|
+
}
|
|
1041
|
+
async setReceiverLed(mode) {
|
|
1042
|
+
await this.write(incottEncodeSetReceiverLed(mode));
|
|
1043
|
+
const got = incottDecodeReceiverLed(await this.query(INCOTT_CMD_QUERY_RECEIVER_LED, INCOTT_SUB_NONE));
|
|
1044
|
+
if (got !== mode)
|
|
1045
|
+
throw new Error(`The mouse kept receiver LED mode ${got ?? "unreadable"} instead of ${mode}.`);
|
|
1046
|
+
return mode;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Low-level codec method: writes the RAW 0-2 performance-mode value. Kept
|
|
1050
|
+
* for protocol parity and as the primitive `setPowerMode` below builds on.
|
|
1051
|
+
* Most callers should use `setPowerMode(name)` instead, which validates a
|
|
1052
|
+
* name against the hardware-confirmed HP/Corded/LP mapping (see
|
|
1053
|
+
* `INCOTT_SUB_PERFORMANCE` in `src/incott/index.ts`) and requires a
|
|
1054
|
+
* matching read-back before reporting success.
|
|
1055
|
+
*
|
|
1056
|
+
* This method is intentionally more lenient than `setPowerMode`: a `null`
|
|
1057
|
+
* read-back (the device not answering) does not throw here, since a raw
|
|
1058
|
+
* numeric call has no name to validate up front and existing callers/tests
|
|
1059
|
+
* rely on this tolerance. See `setPowerMode` for the stricter contract.
|
|
1060
|
+
*/
|
|
1061
|
+
async setPerformanceMode(mode) {
|
|
1062
|
+
await this.write(incottEncodeSetPerformanceMode(mode));
|
|
1063
|
+
const got = incottDecodePerformanceMode(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_PERFORMANCE));
|
|
1064
|
+
if (got !== null && got !== mode) {
|
|
1065
|
+
throw new Error(`The mouse kept performance mode ${got} instead of ${mode}.`);
|
|
1066
|
+
}
|
|
1067
|
+
return mode;
|
|
1068
|
+
}
|
|
1069
|
+
/** See `setPerformanceMode`. Returns `null` when `0x84`/`0x05` does not answer. */
|
|
1070
|
+
async getPerformanceMode() {
|
|
1071
|
+
return incottDecodePerformanceMode(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_PERFORMANCE));
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* The named power/performance modes this mouse offers, in the vendor UI's
|
|
1075
|
+
* own left-to-right display order (HP, Corded, LP) — see
|
|
1076
|
+
* `INCOTT_PERFORMANCE_MODE_NAMES`. This order is deliberately NOT the wire
|
|
1077
|
+
* value order: see `setPowerMode` and `INCOTT_SUB_PERFORMANCE` in
|
|
1078
|
+
* `src/incott/index.ts` for the reversal this table exists to get right.
|
|
1079
|
+
*/
|
|
1080
|
+
getPowerModes() {
|
|
1081
|
+
return [...INCOTT_PERFORMANCE_MODE_NAMES];
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* OpenMouse's shared power/performance-mode contract
|
|
1085
|
+
* (`requireClientMethod("setPowerMode", …)` in
|
|
1086
|
+
* `openmouse/src/device/controller.ts`) — takes the mode NAME, not the raw
|
|
1087
|
+
* wire value. Maps name -> wire through `incottPerformanceModeToWire`
|
|
1088
|
+
* (`INCOTT_PERFORMANCE_MODE_TO_WIRE` in `src/incott/index.ts`), the single
|
|
1089
|
+
* table the HP=2/Corded=1/LP=0 reversal lives in — hardware-confirmed
|
|
1090
|
+
* 2026-09-10 by labelling each click in the vendor tool before recording
|
|
1091
|
+
* its write; see `INCOTT_SUB_PERFORMANCE`'s doc comment.
|
|
1092
|
+
*
|
|
1093
|
+
* Rejects an unknown name BEFORE writing anything (same pattern as
|
|
1094
|
+
* `incottValidateDpi`/`setDpi` above), then writes and requires a matching,
|
|
1095
|
+
* non-null read-back — unlike the lower-level `setPerformanceMode`, a
|
|
1096
|
+
* `null` read-back here is treated as a failure, not tolerated: every
|
|
1097
|
+
* setter in this driver verifies its write, and an unverifiable write must
|
|
1098
|
+
* report failure rather than claim success.
|
|
1099
|
+
*/
|
|
1100
|
+
async setPowerMode(name) {
|
|
1101
|
+
const wire = incottPerformanceModeToWire(name);
|
|
1102
|
+
if (wire === null) {
|
|
1103
|
+
throw new Error(`This mouse has no "${name}" performance mode.`);
|
|
1104
|
+
}
|
|
1105
|
+
await this.write(incottEncodeSetPerformanceMode(wire));
|
|
1106
|
+
const got = incottDecodePerformanceMode(await this.query(INCOTT_CMD_QUERY_SENSOR, INCOTT_SUB_PERFORMANCE));
|
|
1107
|
+
if (got === null) {
|
|
1108
|
+
throw new Error(`Could not read back the performance mode to confirm ${name}.`);
|
|
1109
|
+
}
|
|
1110
|
+
if (got !== wire) {
|
|
1111
|
+
throw new Error(`The mouse kept performance mode ${incottPerformanceModeFromWire(got) ?? got} instead of ${name}.`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
/** Queries return an all-zero frame on failure, which every decoder rejects. */
|
|
1115
|
+
async query(cmd, sub) {
|
|
1116
|
+
const matchSub = SUB_ECHOING_QUERIES.includes(cmd) ? sub : null;
|
|
1117
|
+
return (await this.queue.request(incottEncodeQuery(cmd, sub), cmd, matchSub)) ?? new Uint8Array(INCOTT_RESPONSE_LENGTH);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Reads ONE AXIS of one DPI stage. Separate from `query` because the axis
|
|
1121
|
+
* has to be matched in the reply as well as sent: X and Y on the same stage
|
|
1122
|
+
* are two requests with an identical command and sub-command, so the second
|
|
1123
|
+
* would otherwise accept the first's latched frame.
|
|
1124
|
+
*/
|
|
1125
|
+
async queryDpiAxis(stage, axis) {
|
|
1126
|
+
const frame = await this.queue.request(incottEncodeQueryDpiAxis(stage, axis), INCOTT_CMD_QUERY_DPI_STAGE_VALUE, stage, INCOTT_DPI_AXIS[axis]);
|
|
1127
|
+
return frame ?? new Uint8Array(INCOTT_RESPONSE_LENGTH);
|
|
1128
|
+
}
|
|
1129
|
+
/** Reads one axis of one stage, or null when the device does not answer. */
|
|
1130
|
+
async readDpiStageAxis(stage, axis) {
|
|
1131
|
+
if (!Number.isInteger(stage) || stage < 0 || stage >= INCOTT_DPI_STAGE_COUNT) {
|
|
1132
|
+
throw new RangeError(`DPI stage out of range: ${stage}`);
|
|
1133
|
+
}
|
|
1134
|
+
return incottDecodeDpiStageAxis(await this.queryDpiAxis(stage, axis), stage, axis);
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Writes one axis of one stage and verifies it by reading that axis back.
|
|
1138
|
+
*
|
|
1139
|
+
* Independent X and Y are real on this device — hardware-verified
|
|
1140
|
+
* 2026-09-11, see `INCOTT_DPI_AXIS`. Writing `"both"` is what the ordinary
|
|
1141
|
+
* `setDpiStageValue` does.
|
|
1142
|
+
*/
|
|
1143
|
+
async setDpiStageAxis(stage, dpi, axis) {
|
|
1144
|
+
if (!Number.isInteger(stage) || stage < 0 || stage >= INCOTT_DPI_STAGE_COUNT) {
|
|
1145
|
+
throw new RangeError(`DPI stage out of range: ${stage}`);
|
|
1146
|
+
}
|
|
1147
|
+
incottValidateDpi(dpi);
|
|
1148
|
+
await this.write(incottEncodeSetDpi(stage, dpi, axis));
|
|
1149
|
+
// "both" has no axis of its own to read back; X is the axis it lands on.
|
|
1150
|
+
const readAxis = axis === "both" ? "x" : axis;
|
|
1151
|
+
const got = await this.readDpiStageAxis(stage, readAxis);
|
|
1152
|
+
if (got !== dpi) {
|
|
1153
|
+
throw new Error(`The mouse kept ${got ?? "an unreadable"} ${readAxis.toUpperCase()} DPI instead of ${dpi}.`);
|
|
1154
|
+
}
|
|
1155
|
+
return dpi;
|
|
1156
|
+
}
|
|
1157
|
+
async write(payload) {
|
|
1158
|
+
await this.queue.send(payload);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* WebHID's `sendFeatureReport` types its `data` parameter as `BufferSource`.
|
|
1163
|
+
* TypeScript's DOM lib makes `Uint8Array` generic over its backing buffer
|
|
1164
|
+
* type, defaulting a bare `Uint8Array` annotation to `Uint8Array<ArrayBufferLike>`,
|
|
1165
|
+
* which is not assignable to `BufferSource` (it admits `SharedArrayBuffer`).
|
|
1166
|
+
* Copying into a fresh array-like-constructed Uint8Array narrows the backing
|
|
1167
|
+
* buffer to a concrete `ArrayBuffer`, matching the pattern already used in
|
|
1168
|
+
* `src/drivers/corsair/hid.ts` and `src/drivers/zaunkoenig/hid.ts`.
|
|
1169
|
+
*/
|
|
1170
|
+
function toArrayBuffer(payload) {
|
|
1171
|
+
return new Uint8Array(payload).buffer;
|
|
1172
|
+
}
|
|
1173
|
+
//# sourceMappingURL=hid.js.map
|