@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,1368 @@
|
|
|
1
|
+
import { WE_CMD_READ_EEPROM, WE_CMD_WRITE_EEPROM, WE_REPORT_ID, weBuildCmdPayload, wePackScalarPair, weReportChecksum, weUnpackScalarPair, } from "@openmouse/protocol/endgame-gear-we";
|
|
2
|
+
import { VENDOR_ID } from "../vendors.js";
|
|
3
|
+
import { ATK_COMPX_COMMAND, ATK_R1_BUTTONS, ATK_R1_PROFILE_COUNT, ATK_VXE_R1_ANGLE_SELECTOR, ATK_VXE_R1_DEBOUNCE_SELECTOR, ATK_VXE_R1_LOD_SELECTOR, ATK_VXE_R1_POLLING_RATES, ATK_VXE_R1_SETTINGS_REGISTER, ATK_SENSORS, ATK_LIFT_OFF_MIN_CODE, ATK_LIFT_OFF_MAX_CODE, atkBuildReceiverPairRequest, atkBuildSetCurrentProfile, atkDecodeLiftOff, atkDecodeButtonAssignment, atkDecodeCurrentProfile, atkDecodePairingStatus, atkDecodeReceiverStatus, atkDecodeVxeR1PollingCode, atkDpiOptionsForSensor, atkDpiStageLength, atkPackDpiStage, atkPackDpiStageForSensor, atkPackVxeR1LiveSetting, atkPackVxeR1PollingSetting, atkUnpackDpiStage, atkUnpackDpiStageForSensor, } from "@openmouse/protocol/atk";
|
|
4
|
+
import { ATK_COMPX_PRODUCT_IDS, ATK_PRODUCTS } from "./products.js";
|
|
5
|
+
// ATK mice (A9 family and siblings) use the same OEM framing as the Endgame
|
|
6
|
+
// Gear WE series — 16-byte EEPROM commands on report 0x08 — but carry them on
|
|
7
|
+
// output/input reports rather than feature reports.
|
|
8
|
+
const BATTERY_COMMAND = 0x04;
|
|
9
|
+
const VERSION_COMMAND = 0x12;
|
|
10
|
+
const CIDMID_COMMAND = 0x10;
|
|
11
|
+
const SET_LONG_RANGE_COMMAND = 0x16;
|
|
12
|
+
const GET_LONG_RANGE_COMMAND = 0x17;
|
|
13
|
+
const FRAME_LENGTH = 16;
|
|
14
|
+
const DATA_OFFSET = 5;
|
|
15
|
+
const MAX_DATA_LENGTH = 10;
|
|
16
|
+
const REPLY_TIMEOUT_MS = 500;
|
|
17
|
+
const WRITE_SETTLE_MS = 10;
|
|
18
|
+
const R1_LIVE_WRITE_SETTLE_MS = 250;
|
|
19
|
+
const R1_PROFILE_SWITCH_SETTLE_MS = 100;
|
|
20
|
+
const MAX_IDENTIFY_ATTEMPTS = 3;
|
|
21
|
+
// The VXE R1 SE/SE+ ships its "Wireless mouse -1k dongle" under 0x373b:0x1085
|
|
22
|
+
// (Beken MCU). It shares the A9 EEPROM map for DPI/advanced/lod, but the poll
|
|
23
|
+
// rate lives in the live-settings row; see the codec for the full story.
|
|
24
|
+
const VXE_R1_RECEIVER_PID = 0x1085;
|
|
25
|
+
const VXE_R1_PRO_MAX_RECEIVER_PID = 0xf58a;
|
|
26
|
+
const VXE_R1_PRO_MAX_MOUSE_PID = 0xf58c;
|
|
27
|
+
const VXE_R1_COMPX_RECEIVER_PID = 0xf58e;
|
|
28
|
+
const VXE_R1_COMPX_MOUSE_PID = 0xf58f;
|
|
29
|
+
const R1_SETTINGS_LENGTH = 4;
|
|
30
|
+
// Byte addresses in the mouse's configuration EEPROM.
|
|
31
|
+
const REGISTER = {
|
|
32
|
+
// 0x0000: polling rate, DPI stage count, active DPI stage (value/checksum pairs).
|
|
33
|
+
system: 0x0000,
|
|
34
|
+
liftOffDistance: 0x000a,
|
|
35
|
+
// Four bytes per DPI stage.
|
|
36
|
+
dpiBase: 0x000c,
|
|
37
|
+
paw3955DpiBase: 0x1b00,
|
|
38
|
+
dpiColorBase: 0x002c,
|
|
39
|
+
dpiLighting: 0x004c,
|
|
40
|
+
// 0x00a9: debounce, motion sync, sleep timer, linear correction, ripple control.
|
|
41
|
+
advanced: 0x00a9,
|
|
42
|
+
// 0x00bd: angle tuning degrees, then the angle-snapping enable flag.
|
|
43
|
+
angle: 0x00bd,
|
|
44
|
+
sensorPerformance: 0x00b5,
|
|
45
|
+
};
|
|
46
|
+
const SYSTEM_LENGTH = 6;
|
|
47
|
+
const R1_SYSTEM_ROW_LENGTH = 10;
|
|
48
|
+
const ADVANCED_LENGTH = 10;
|
|
49
|
+
const ANGLE_LENGTH = 4;
|
|
50
|
+
const DPI_STAGE_LENGTH = 4;
|
|
51
|
+
const MAX_DPI_STAGES = 6;
|
|
52
|
+
const R1_MAX_DPI_STAGES = 8;
|
|
53
|
+
const DPI_MIN = 50;
|
|
54
|
+
const DPI_MAX = 42000;
|
|
55
|
+
const DEBOUNCE_MAX_MS = 15;
|
|
56
|
+
// The R1 accepts 1-20 ms in its live-settings debounce entry (per OpenVXE).
|
|
57
|
+
const R1_DEBOUNCE_MAX_MS = 20;
|
|
58
|
+
const SLEEP_STEP_SECONDS = 10;
|
|
59
|
+
const SLEEP_SECONDS = [30, 60, 120, 300, 600, 1800];
|
|
60
|
+
const R1_SLEEP_SECONDS = [30, 60, 120, 180, 300, 1200, 1500, 1800];
|
|
61
|
+
const R1_DEBOUNCE_MILLISECONDS = [0, 1, 2, 4, 8, 15, 20];
|
|
62
|
+
const R1_SENSOR_PERFORMANCE_LENGTH = 6;
|
|
63
|
+
const R1_DPI_LIGHTING_LENGTH = 8;
|
|
64
|
+
const R1_DPI_COLOR_GROUP_LENGTH = 8;
|
|
65
|
+
const R1_DPI_BRIGHTNESS = [0x10, 0x80, 0xff];
|
|
66
|
+
const R1_DPI_SPEED = [1, 3, 5];
|
|
67
|
+
const POLLING_RATES = [
|
|
68
|
+
[0x08, 125],
|
|
69
|
+
[0x04, 250],
|
|
70
|
+
[0x02, 500],
|
|
71
|
+
[0x01, 1000],
|
|
72
|
+
[0x10, 2000],
|
|
73
|
+
[0x20, 4000],
|
|
74
|
+
[0x40, 8000],
|
|
75
|
+
];
|
|
76
|
+
/** Register codes are tenths of a millimetre offset by 6 (1 = 0.7 mm). */
|
|
77
|
+
const LIFT_OFF_CODES = [
|
|
78
|
+
[1, "Low"],
|
|
79
|
+
[4, "Medium"],
|
|
80
|
+
[11, "High"],
|
|
81
|
+
];
|
|
82
|
+
/** R1 lift-off levels are 1 mm and 2 mm in the live-settings entry. */
|
|
83
|
+
const R1_LIFT_OFF_CODES = [
|
|
84
|
+
[1, "Low"],
|
|
85
|
+
[2, "High"],
|
|
86
|
+
];
|
|
87
|
+
export class AtkHidClient {
|
|
88
|
+
// Sleep is stored in 10-second units with no "never" value.
|
|
89
|
+
canDisableSleep = false;
|
|
90
|
+
// Written out rather than a parameter property so node --test can strip types.
|
|
91
|
+
device;
|
|
92
|
+
queue = Promise.resolve();
|
|
93
|
+
lastStatus = null;
|
|
94
|
+
product = null;
|
|
95
|
+
identified = false;
|
|
96
|
+
identifyAttempts = 0;
|
|
97
|
+
constructor(device) {
|
|
98
|
+
this.device = device;
|
|
99
|
+
}
|
|
100
|
+
static isSupported(device) {
|
|
101
|
+
const search = (collection) => (collection.usagePage === 0xff02 && collection.usage === 0x0002)
|
|
102
|
+
|| collection.children.some(search);
|
|
103
|
+
if (!device.collections.some(search))
|
|
104
|
+
return false;
|
|
105
|
+
if (device.vendorId === VENDOR_ID.atk)
|
|
106
|
+
return true;
|
|
107
|
+
return device.vendorId === VENDOR_ID.vgn && ATK_COMPX_PRODUCT_IDS.includes(device.productId);
|
|
108
|
+
}
|
|
109
|
+
async open() {
|
|
110
|
+
if (!this.device.opened)
|
|
111
|
+
await this.device.open();
|
|
112
|
+
}
|
|
113
|
+
async close() {
|
|
114
|
+
this.lastStatus = null;
|
|
115
|
+
this.product = null;
|
|
116
|
+
this.identified = false;
|
|
117
|
+
this.identifyAttempts = 0;
|
|
118
|
+
if (this.device.opened)
|
|
119
|
+
await this.device.close();
|
|
120
|
+
}
|
|
121
|
+
/** ATK broadcasts are undocumented; the panel falls back to polling. */
|
|
122
|
+
async startNotifications() {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
displayName() {
|
|
126
|
+
if (this.product)
|
|
127
|
+
return `${this.product.brand} ${this.product.model}`;
|
|
128
|
+
const name = this.device.productName?.trim();
|
|
129
|
+
if (!name)
|
|
130
|
+
return "ATK";
|
|
131
|
+
return /^atk/i.test(name) ? name : `ATK ${name}`;
|
|
132
|
+
}
|
|
133
|
+
deviceBrand() {
|
|
134
|
+
return this.product?.brand ?? (/^vxe\b/i.test(this.device.productName || "") ? "VXE" : "ATK");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A wired A9 still reports a battery level, so the receiver is identified by
|
|
138
|
+
* its own product string instead: "ATK Nearlink Mouse Dongle" against the
|
|
139
|
+
* mouse's own "ATK A9 PLUS 2.0 NK".
|
|
140
|
+
*/
|
|
141
|
+
isWireless() {
|
|
142
|
+
return this.device.productId === VXE_R1_RECEIVER_PID
|
|
143
|
+
|| (this.device.vendorId === VENDOR_ID.vgn
|
|
144
|
+
&& (this.device.productId === VXE_R1_PRO_MAX_RECEIVER_PID
|
|
145
|
+
|| this.device.productId === VXE_R1_COMPX_RECEIVER_PID))
|
|
146
|
+
|| /receiver|dongle/i.test(this.device.productName || "");
|
|
147
|
+
}
|
|
148
|
+
/** VXE R1-family devices using the shared EEPROM command framing. */
|
|
149
|
+
isR1() {
|
|
150
|
+
return this.product?.family === "r1"
|
|
151
|
+
|| this.usesSharedR1Transport();
|
|
152
|
+
}
|
|
153
|
+
maxDpi() {
|
|
154
|
+
return this.product ? ATK_SENSORS[this.product.sensor].maxDpi : DPI_MAX;
|
|
155
|
+
}
|
|
156
|
+
getSleepOptions() {
|
|
157
|
+
return this.isR1() && !this.usesR1LiveSettings() ? R1_SLEEP_SECONDS : SLEEP_SECONDS;
|
|
158
|
+
}
|
|
159
|
+
getDebounceMaxMs() {
|
|
160
|
+
return this.isR1() ? R1_DEBOUNCE_MAX_MS : DEBOUNCE_MAX_MS;
|
|
161
|
+
}
|
|
162
|
+
getDebounceOptions() {
|
|
163
|
+
return this.isR1() && !this.usesR1LiveSettings()
|
|
164
|
+
? R1_DEBOUNCE_MILLISECONDS
|
|
165
|
+
: Array.from({ length: this.getDebounceMaxMs() + (this.usesR1LiveSettings() ? 0 : 1) }, (_, index) => index + (this.usesR1LiveSettings() ? 1 : 0));
|
|
166
|
+
}
|
|
167
|
+
getSupportedPollingRates() {
|
|
168
|
+
return this.usesR1LiveSettings()
|
|
169
|
+
? [...ATK_VXE_R1_POLLING_RATES]
|
|
170
|
+
: this.isR1()
|
|
171
|
+
? POLLING_RATES.map(([, hertz]) => hertz).filter((hertz) => hertz <= 1000)
|
|
172
|
+
: POLLING_RATES.map(([, hertz]) => hertz);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The encoding steps by 10 DPI, then 50 above 10,000 and 100 above 30,000.
|
|
176
|
+
* Models top out below 42,000; writes are confirmed by reading back.
|
|
177
|
+
*/
|
|
178
|
+
getDpiOptions() {
|
|
179
|
+
if (this.product)
|
|
180
|
+
return atkDpiOptionsForSensor(this.product.sensor);
|
|
181
|
+
const options = [];
|
|
182
|
+
for (let dpi = DPI_MIN; dpi <= 10000; dpi += 10)
|
|
183
|
+
options.push(dpi);
|
|
184
|
+
for (let dpi = 10050; dpi <= 30000; dpi += 50)
|
|
185
|
+
options.push(dpi);
|
|
186
|
+
for (let dpi = 30100; dpi <= DPI_MAX; dpi += 100)
|
|
187
|
+
options.push(dpi);
|
|
188
|
+
return options;
|
|
189
|
+
}
|
|
190
|
+
async readStatus(live = false) {
|
|
191
|
+
await this.open();
|
|
192
|
+
await this.identify();
|
|
193
|
+
const battery = await this.readBattery();
|
|
194
|
+
const system = await this.read(REGISTER.system, SYSTEM_LENGTH);
|
|
195
|
+
const stageCount = this.stageCount(system);
|
|
196
|
+
const activeDpiStage = this.stageIndex(system);
|
|
197
|
+
const dpiStages = [];
|
|
198
|
+
for (let index = 0; index < stageCount; index += 1)
|
|
199
|
+
dpiStages.push(await this.readDpiStage(index));
|
|
200
|
+
const stage = dpiStages[activeDpiStage];
|
|
201
|
+
if (live && this.lastStatus) {
|
|
202
|
+
return this.lastStatus = {
|
|
203
|
+
...this.lastStatus,
|
|
204
|
+
batteryPercent: battery?.percent ?? null,
|
|
205
|
+
batteryState: batteryState(battery),
|
|
206
|
+
batteryVoltageMv: battery?.millivolts ?? null,
|
|
207
|
+
pollingRateHz: await this.readPollingRate(system),
|
|
208
|
+
dpi: stage.x,
|
|
209
|
+
dpiY: stage.y,
|
|
210
|
+
dpiStages: dpiStages.map(({ x }) => x),
|
|
211
|
+
activeDpiStage,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const firmware = await this.readFirmware();
|
|
215
|
+
const liftOffDistance = await this.read(REGISTER.liftOffDistance, 2);
|
|
216
|
+
const advanced = await this.read(REGISTER.advanced, ADVANCED_LENGTH);
|
|
217
|
+
const angle = await this.read(REGISTER.angle, ANGLE_LENGTH).catch(() => null);
|
|
218
|
+
const r1Extras = this.usesR1ProMaxUiTransport()
|
|
219
|
+
? await this.readR1Extras(stageCount)
|
|
220
|
+
: null;
|
|
221
|
+
const stored = this.usesR1ProMaxUiTransport()
|
|
222
|
+
? await this.readR1StoredConfiguration().catch(() => null)
|
|
223
|
+
: null;
|
|
224
|
+
const receiver = this.usesR1LiveSettings()
|
|
225
|
+
? await this.readR1ReceiverInfo().catch(() => null)
|
|
226
|
+
: null;
|
|
227
|
+
const angleTuning = angle ? weUnpackScalarPair(angle[0], angle[1]) : null;
|
|
228
|
+
const angleSnapping = angle ? weUnpackScalarPair(angle[2], angle[3]) : null;
|
|
229
|
+
const sensorProfile = this.product ? ATK_SENSORS[this.product.sensor] : null;
|
|
230
|
+
return this.lastStatus = {
|
|
231
|
+
brand: this.deviceBrand(),
|
|
232
|
+
name: this.displayName(),
|
|
233
|
+
ui: {
|
|
234
|
+
family: "atk",
|
|
235
|
+
hideUnsupportedPollingRates: true,
|
|
236
|
+
forceShowBattery: battery !== null,
|
|
237
|
+
dpiStageEditor: this.usesR1ProMaxUiTransport() ? {
|
|
238
|
+
maxStages: R1_MAX_DPI_STAGES,
|
|
239
|
+
countEditable: true,
|
|
240
|
+
minDpi: sensorProfile?.minDpi ?? DPI_MIN,
|
|
241
|
+
maxDpi: sensorProfile?.maxDpi ?? DPI_MAX,
|
|
242
|
+
stepDpi: 50,
|
|
243
|
+
} : undefined,
|
|
244
|
+
dpiLighting: r1Extras ? {
|
|
245
|
+
modes: [0, 1, 2],
|
|
246
|
+
brightness: [0, 1, 2],
|
|
247
|
+
speed: [0, 1, 2],
|
|
248
|
+
} : undefined,
|
|
249
|
+
},
|
|
250
|
+
batteryPercent: battery?.percent ?? null,
|
|
251
|
+
batteryState: batteryState(battery),
|
|
252
|
+
batteryVoltageMv: battery?.millivolts ?? null,
|
|
253
|
+
dpi: stage.x,
|
|
254
|
+
dpiY: stage.y,
|
|
255
|
+
dpiStages: dpiStages.map(({ x }) => x),
|
|
256
|
+
dpiStageColors: r1Extras?.dpiStageColors,
|
|
257
|
+
activeDpiStage,
|
|
258
|
+
supportsSeparateDpiAxes: this.isR1ProMax(),
|
|
259
|
+
pollingRateHz: await this.readPollingRate(system),
|
|
260
|
+
supportedPollingRates: this.getSupportedPollingRates(),
|
|
261
|
+
activeProfile: stored?.activeProfile ?? null,
|
|
262
|
+
atkProfileCount: stored ? ATK_R1_PROFILE_COUNT : undefined,
|
|
263
|
+
atkButtonMappings: stored?.buttons,
|
|
264
|
+
atkReceiver: receiver ?? undefined,
|
|
265
|
+
connectionType: this.isWireless() ? "Wireless" : "Wired",
|
|
266
|
+
connectionDetail: this.isWireless() ? "2.4 GHz receiver" : "Wired USB",
|
|
267
|
+
debounceMs: advanced[0],
|
|
268
|
+
motionSync: advanced[2] === 1,
|
|
269
|
+
sleepTimeout: advanced[4] * SLEEP_STEP_SECONDS || null,
|
|
270
|
+
rippleControl: advanced[8] === 1,
|
|
271
|
+
performanceMode: r1Extras?.performanceMode,
|
|
272
|
+
longRangeMode: r1Extras?.longRangeMode,
|
|
273
|
+
dpiLedMode: r1Extras?.dpiLedMode,
|
|
274
|
+
dpiLedBrightness: r1Extras?.dpiLedBrightness,
|
|
275
|
+
dpiLedSpeed: r1Extras?.dpiLedSpeed,
|
|
276
|
+
angleSnapping: this.isR1()
|
|
277
|
+
? advanced[6] === 1
|
|
278
|
+
: angleSnapping === null ? null : angleSnapping === 1,
|
|
279
|
+
angleTuning: angleTuning === null ? null : this.decodeAngle(angleTuning),
|
|
280
|
+
liftOffDistance: this.decodeLiftOffDistance(liftOffDistance[0]),
|
|
281
|
+
supportedLiftOffDistances: this.isR1() ? ["Low", "High"] : undefined,
|
|
282
|
+
liftOffScale: this.supportsLiftOffScale() ? this.liftOffScale(liftOffDistance[0]) : undefined,
|
|
283
|
+
firmware,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** Read the active R1 profile and its six button records without changing either. */
|
|
287
|
+
async readR1StoredConfiguration() {
|
|
288
|
+
await this.identify();
|
|
289
|
+
if (!this.usesR1ProMaxUiTransport()) {
|
|
290
|
+
throw new Error("Stored R1 configuration inspection is not available on this connection.");
|
|
291
|
+
}
|
|
292
|
+
const activeProfile = await this.readR1CurrentProfile();
|
|
293
|
+
const buttons = [];
|
|
294
|
+
for (let group = 0; group < ATK_R1_BUTTONS.length; group += 2) {
|
|
295
|
+
const first = ATK_R1_BUTTONS[group];
|
|
296
|
+
const data = await this.read(first.address, 8);
|
|
297
|
+
for (let offset = 0; offset < 2; offset += 1) {
|
|
298
|
+
const button = ATK_R1_BUTTONS[group + offset];
|
|
299
|
+
if (!button)
|
|
300
|
+
continue;
|
|
301
|
+
const assignment = atkDecodeButtonAssignment(data.subarray(offset * 4, offset * 4 + 4));
|
|
302
|
+
if (!assignment)
|
|
303
|
+
throw new Error(`The ${button.label.toLowerCase()} assignment is truncated.`);
|
|
304
|
+
buttons.push({
|
|
305
|
+
id: button.id,
|
|
306
|
+
name: button.label,
|
|
307
|
+
address: button.address,
|
|
308
|
+
keyClass: assignment.keyClass,
|
|
309
|
+
value1: assignment.value1,
|
|
310
|
+
value2: assignment.value2,
|
|
311
|
+
checksumValid: assignment.checksumValid,
|
|
312
|
+
action: assignment.label,
|
|
313
|
+
raw: assignment.raw,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return { activeProfile: activeProfile + 1, buttons };
|
|
318
|
+
}
|
|
319
|
+
/** Select one of the four verified R1 banks and require readback. */
|
|
320
|
+
async setR1ActiveProfile(profile) {
|
|
321
|
+
await this.identify();
|
|
322
|
+
const receiver = this.usesVerifiedR1ProMaxReceiverTransport();
|
|
323
|
+
if (!this.usesVerifiedR1WiredTransport() && !receiver) {
|
|
324
|
+
throw new Error("R1 profile switching is not available on this connection.");
|
|
325
|
+
}
|
|
326
|
+
if (!Number.isInteger(profile) || profile < 1 || profile > ATK_R1_PROFILE_COUNT) {
|
|
327
|
+
throw new Error(`R1 profile must be between 1 and ${ATK_R1_PROFILE_COUNT}.`);
|
|
328
|
+
}
|
|
329
|
+
const frame = atkBuildSetCurrentProfile(profile - 1);
|
|
330
|
+
if (receiver) {
|
|
331
|
+
await this.exchange(frame, (reply) => reply.length === frame.length
|
|
332
|
+
&& reply.every((byte, index) => byte === frame[index]));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
await this.send(frame);
|
|
336
|
+
}
|
|
337
|
+
await delay(R1_PROFILE_SWITCH_SETTLE_MS);
|
|
338
|
+
const confirmed = (await this.readR1CurrentProfile()) + 1;
|
|
339
|
+
if (confirmed !== profile) {
|
|
340
|
+
throw new Error(`The mouse kept profile ${confirmed} instead of ${profile}.`);
|
|
341
|
+
}
|
|
342
|
+
this.lastStatus = null;
|
|
343
|
+
return confirmed;
|
|
344
|
+
}
|
|
345
|
+
/** Read receiver online/RF identity and pairing countdown; this never starts pairing. */
|
|
346
|
+
async readR1ReceiverInfo() {
|
|
347
|
+
await this.identify();
|
|
348
|
+
if (!this.isR1() || !this.usesR1LiveSettings()) {
|
|
349
|
+
throw new Error("R1 receiver telemetry is available only through the receiver transport.");
|
|
350
|
+
}
|
|
351
|
+
const onlineReply = await this.exchange(weBuildCmdPayload(ATK_COMPX_COMMAND.getWirelessMouseOnline), (frame) => frame[0] === ATK_COMPX_COMMAND.getWirelessMouseOnline
|
|
352
|
+
&& frame[1] === 0 && frame[4] >= 1 && this.hasValidChecksum(frame));
|
|
353
|
+
const online = atkDecodeReceiverStatus(onlineReply.subarray(DATA_OFFSET));
|
|
354
|
+
if (!online)
|
|
355
|
+
throw new Error("The receiver returned a truncated online-status reply.");
|
|
356
|
+
const pairingReply = await this.exchange(weBuildCmdPayload(ATK_COMPX_COMMAND.getWirelessDonglePairResult), (frame) => frame[0] === ATK_COMPX_COMMAND.getWirelessDonglePairResult
|
|
357
|
+
&& frame[1] === 0 && frame[4] >= 2 && this.hasValidChecksum(frame)).catch(() => null);
|
|
358
|
+
const pairing = pairingReply ? atkDecodePairingStatus(pairingReply.subarray(DATA_OFFSET)) : null;
|
|
359
|
+
return {
|
|
360
|
+
...online,
|
|
361
|
+
pairingStatus: pairing?.status ?? null,
|
|
362
|
+
pairingSecondsRemaining: pairing?.secondsRemaining ?? null,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/** Start receiver pairing for one exact CID/MID; callers must poll telemetry for completion. */
|
|
366
|
+
async startR1ReceiverPairing(cid, mid) {
|
|
367
|
+
await this.identify();
|
|
368
|
+
if (this.device.vendorId !== VENDOR_ID.vgn || this.device.productId !== VXE_R1_COMPX_RECEIVER_PID) {
|
|
369
|
+
throw new Error("R1 receiver pairing is available only through the verified VXE Mouse 1K Dongle.");
|
|
370
|
+
}
|
|
371
|
+
await this.exchange(atkBuildReceiverPairRequest(cid, mid), (frame) => frame[0] === ATK_COMPX_COMMAND.setWirelessDonglePair
|
|
372
|
+
&& frame[1] === 0 && this.hasValidChecksum(frame));
|
|
373
|
+
}
|
|
374
|
+
async setPollingRate(pollingRateHz) {
|
|
375
|
+
if (!this.usesR1LiveSettings())
|
|
376
|
+
await this.identify();
|
|
377
|
+
if (this.usesR1LiveSettings())
|
|
378
|
+
return await this.setR1PollingRate(pollingRateHz);
|
|
379
|
+
const encoded = POLLING_RATES.find(([, hertz]) => hertz === pollingRateHz);
|
|
380
|
+
if (!encoded)
|
|
381
|
+
throw new Error(`This mouse does not support ${pollingRateHz} Hz.`);
|
|
382
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
383
|
+
// ATK Hub writes the complete 10-byte system row through the F58A
|
|
384
|
+
// receiver. Preserve stage count, active stage, and the unknown tail.
|
|
385
|
+
const system = Array.from(await this.read(REGISTER.system, R1_SYSTEM_ROW_LENGTH));
|
|
386
|
+
system.splice(0, 2, ...wePackScalarPair(encoded[0]));
|
|
387
|
+
await this.writeR1ProMaxReceiverSystem(system);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
await this.write(REGISTER.system, wePackScalarPair(encoded[0]));
|
|
391
|
+
}
|
|
392
|
+
if (this.isR1())
|
|
393
|
+
await delay(R1_LIVE_WRITE_SETTLE_MS);
|
|
394
|
+
const confirmed = this.decodePollingRate((await this.read(REGISTER.system, SYSTEM_LENGTH))[0]);
|
|
395
|
+
if (confirmed !== pollingRateHz) {
|
|
396
|
+
throw new Error(`The mouse kept ${confirmed} Hz instead of ${pollingRateHz} Hz.`);
|
|
397
|
+
}
|
|
398
|
+
this.patch({ pollingRateHz: confirmed });
|
|
399
|
+
return confirmed;
|
|
400
|
+
}
|
|
401
|
+
async setDpi(dpi, dpiY = dpi) {
|
|
402
|
+
await this.identify();
|
|
403
|
+
const sensor = this.product?.sensor ?? null;
|
|
404
|
+
const options = sensor ? atkDpiOptionsForSensor(sensor) : null;
|
|
405
|
+
for (const value of [dpi, dpiY]) {
|
|
406
|
+
if (!Number.isInteger(value) || (options ? !options.includes(value) : value < DPI_MIN || value > DPI_MAX)) {
|
|
407
|
+
throw new Error(`${value.toLocaleString()} is not a supported DPI value.`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const index = this.stageIndex(await this.read(REGISTER.system, SYSTEM_LENGTH));
|
|
411
|
+
const stage = sensor ? atkPackDpiStageForSensor(sensor, dpi, dpiY) : atkPackDpiStage(dpi, dpiY);
|
|
412
|
+
if (!stage)
|
|
413
|
+
throw new Error(`${dpi.toLocaleString()} DPI is not representable by this sensor.`);
|
|
414
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
415
|
+
await this.writeR1ProMaxReceiverDpiStage(index, stage);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
await this.write(this.dpiAddress(index), stage);
|
|
419
|
+
}
|
|
420
|
+
const confirmed = await this.readDpiStage(index);
|
|
421
|
+
if (confirmed.x !== dpi || confirmed.y !== dpiY) {
|
|
422
|
+
throw new Error(`The mouse kept ${confirmed.x.toLocaleString()} DPI instead of ${dpi.toLocaleString()}.`);
|
|
423
|
+
}
|
|
424
|
+
this.patch({ dpi: confirmed.x, dpiY: confirmed.y });
|
|
425
|
+
return confirmed.x;
|
|
426
|
+
}
|
|
427
|
+
async setDpiStageCount(count) {
|
|
428
|
+
await this.identify();
|
|
429
|
+
if (!this.usesVerifiedR1WiredTransport()
|
|
430
|
+
&& !this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
431
|
+
throw new Error("R1 DPI stage count editing is not available on this connection.");
|
|
432
|
+
}
|
|
433
|
+
if (!Number.isInteger(count) || count < 1 || count > R1_MAX_DPI_STAGES) {
|
|
434
|
+
throw new Error(`DPI stage count must be between 1 and ${R1_MAX_DPI_STAGES}.`);
|
|
435
|
+
}
|
|
436
|
+
// The vendor writes the complete 10-byte system row when changing the
|
|
437
|
+
// stage count. Preserve the unknown tail and clamp the active stage in the
|
|
438
|
+
// same transaction so the row can never temporarily point past the end.
|
|
439
|
+
const system = Array.from(await this.read(REGISTER.system, 10));
|
|
440
|
+
const previousActive = Math.min(system[4], Math.max(system[2], 1) - 1);
|
|
441
|
+
const active = Math.min(previousActive, count - 1);
|
|
442
|
+
system.splice(2, 2, ...wePackScalarPair(count));
|
|
443
|
+
system.splice(4, 2, ...wePackScalarPair(active));
|
|
444
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
445
|
+
await this.writeR1ProMaxReceiverSystem(system);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
await this.write(REGISTER.system, system);
|
|
449
|
+
}
|
|
450
|
+
const confirmedSystem = await this.read(REGISTER.system, 10);
|
|
451
|
+
const confirmedCount = this.stageCount(confirmedSystem);
|
|
452
|
+
const confirmedActive = this.stageIndex(confirmedSystem);
|
|
453
|
+
if (confirmedCount !== count || confirmedActive !== active) {
|
|
454
|
+
throw new Error(`The mouse kept ${confirmedCount} DPI stages with stage ${confirmedActive + 1} active instead of ${count}.`);
|
|
455
|
+
}
|
|
456
|
+
const stages = [];
|
|
457
|
+
let activeStage = null;
|
|
458
|
+
for (let index = 0; index < confirmedCount; index += 1) {
|
|
459
|
+
const stage = await this.readDpiStage(index);
|
|
460
|
+
stages.push(stage.x);
|
|
461
|
+
if (index === confirmedActive)
|
|
462
|
+
activeStage = stage;
|
|
463
|
+
}
|
|
464
|
+
if (!activeStage)
|
|
465
|
+
throw new Error("The active DPI stage could not be read back.");
|
|
466
|
+
this.patch({
|
|
467
|
+
dpiStages: stages,
|
|
468
|
+
activeDpiStage: confirmedActive,
|
|
469
|
+
dpi: activeStage.x,
|
|
470
|
+
dpiY: activeStage.y,
|
|
471
|
+
});
|
|
472
|
+
return confirmedCount;
|
|
473
|
+
}
|
|
474
|
+
async setActiveDpiStage(index) {
|
|
475
|
+
await this.identify();
|
|
476
|
+
if (this.isR1()
|
|
477
|
+
&& !this.usesVerifiedR1WiredTransport()
|
|
478
|
+
&& !this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
479
|
+
throw new Error("R1 DPI stage selection is not available on this connection.");
|
|
480
|
+
}
|
|
481
|
+
const receiver = this.usesVerifiedR1ProMaxReceiverTransport();
|
|
482
|
+
const system = await this.read(REGISTER.system, receiver ? R1_SYSTEM_ROW_LENGTH : SYSTEM_LENGTH);
|
|
483
|
+
const count = this.stageCount(system);
|
|
484
|
+
if (!Number.isInteger(index) || index < 0 || index >= count) {
|
|
485
|
+
throw new Error(`DPI stage must be between 1 and ${count}.`);
|
|
486
|
+
}
|
|
487
|
+
let confirmed;
|
|
488
|
+
if (receiver) {
|
|
489
|
+
const row = Array.from(system);
|
|
490
|
+
row.splice(4, 2, ...wePackScalarPair(index));
|
|
491
|
+
await this.writeR1ProMaxReceiverSystem(row);
|
|
492
|
+
const confirmedSystem = await this.read(REGISTER.system, R1_SYSTEM_ROW_LENGTH);
|
|
493
|
+
confirmed = weUnpackScalarPair(confirmedSystem[4], confirmedSystem[5]);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
await this.write(REGISTER.system + 4, wePackScalarPair(index));
|
|
497
|
+
const pair = await this.read(REGISTER.system + 4, 2);
|
|
498
|
+
confirmed = weUnpackScalarPair(pair[0], pair[1]);
|
|
499
|
+
}
|
|
500
|
+
if (confirmed !== index) {
|
|
501
|
+
throw new Error(`The mouse kept DPI stage ${(confirmed ?? 0) + 1} instead of ${index + 1}.`);
|
|
502
|
+
}
|
|
503
|
+
const stage = await this.readDpiStage(index);
|
|
504
|
+
this.patch({
|
|
505
|
+
activeDpiStage: confirmed,
|
|
506
|
+
dpi: stage.x,
|
|
507
|
+
dpiY: stage.y,
|
|
508
|
+
});
|
|
509
|
+
return confirmed;
|
|
510
|
+
}
|
|
511
|
+
async setDpiStageValue(index, dpi) {
|
|
512
|
+
await this.identify();
|
|
513
|
+
if (this.isR1()
|
|
514
|
+
&& !this.usesVerifiedR1WiredTransport()
|
|
515
|
+
&& !this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
516
|
+
throw new Error("R1 DPI stage editing is not available on this connection.");
|
|
517
|
+
}
|
|
518
|
+
const system = await this.read(REGISTER.system, SYSTEM_LENGTH);
|
|
519
|
+
const count = this.stageCount(system);
|
|
520
|
+
if (!Number.isInteger(index) || index < 0 || index >= count) {
|
|
521
|
+
throw new Error(`DPI stage must be between 1 and ${count}.`);
|
|
522
|
+
}
|
|
523
|
+
const sensor = this.product?.sensor ?? null;
|
|
524
|
+
const options = sensor ? atkDpiOptionsForSensor(sensor) : this.getDpiOptions();
|
|
525
|
+
if (!Number.isInteger(dpi) || !options.includes(dpi)) {
|
|
526
|
+
throw new Error(`${dpi.toLocaleString()} is not a supported DPI value.`);
|
|
527
|
+
}
|
|
528
|
+
const packed = sensor ? atkPackDpiStageForSensor(sensor, dpi, dpi) : atkPackDpiStage(dpi, dpi);
|
|
529
|
+
if (!packed)
|
|
530
|
+
throw new Error(`${dpi.toLocaleString()} DPI is not representable by this sensor.`);
|
|
531
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
532
|
+
await this.writeR1ProMaxReceiverDpiStage(index, packed);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
await this.write(this.dpiAddress(index), packed);
|
|
536
|
+
}
|
|
537
|
+
const confirmed = await this.readDpiStage(index);
|
|
538
|
+
if (confirmed.x !== dpi || confirmed.y !== dpi) {
|
|
539
|
+
throw new Error(`The mouse kept ${confirmed.x.toLocaleString()} DPI instead of ${dpi.toLocaleString()}.`);
|
|
540
|
+
}
|
|
541
|
+
const dpiStages = this.lastStatus?.dpiStages?.slice() ?? [];
|
|
542
|
+
while (dpiStages.length < count)
|
|
543
|
+
dpiStages.push(dpiStages.at(-1) ?? dpi);
|
|
544
|
+
dpiStages[index] = confirmed.x;
|
|
545
|
+
const active = this.lastStatus?.activeDpiStage ?? this.stageIndex(system);
|
|
546
|
+
this.patch({
|
|
547
|
+
dpiStages,
|
|
548
|
+
...(active === index ? { dpi: confirmed.x, dpiY: confirmed.y } : {}),
|
|
549
|
+
});
|
|
550
|
+
return confirmed.x;
|
|
551
|
+
}
|
|
552
|
+
async setDpiStageColor(index, color) {
|
|
553
|
+
await this.identify();
|
|
554
|
+
if (!this.usesVerifiedR1WiredTransport()
|
|
555
|
+
&& !this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
556
|
+
throw new Error("DPI stage colors are not available on this connection.");
|
|
557
|
+
}
|
|
558
|
+
const rgb = parseHexColor(color);
|
|
559
|
+
const count = this.stageCount(await this.read(REGISTER.system, SYSTEM_LENGTH));
|
|
560
|
+
if (!rgb || !Number.isInteger(index) || index < 0 || index >= count) {
|
|
561
|
+
throw new Error("The DPI stage or colour is invalid.");
|
|
562
|
+
}
|
|
563
|
+
const groupAddress = REGISTER.dpiColorBase + Math.floor(index / 2) * R1_DPI_COLOR_GROUP_LENGTH;
|
|
564
|
+
const group = Array.from(await this.read(groupAddress, R1_DPI_COLOR_GROUP_LENGTH));
|
|
565
|
+
group.splice((index % 2) * 4, 4, ...packRgb(rgb));
|
|
566
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
567
|
+
await this.writeR1ProMaxReceiverDpiColorGroup(groupAddress, group);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
await this.write(groupAddress, group);
|
|
571
|
+
}
|
|
572
|
+
const confirmedGroup = await this.read(groupAddress, R1_DPI_COLOR_GROUP_LENGTH);
|
|
573
|
+
const confirmed = unpackRgb(confirmedGroup.subarray((index % 2) * 4, (index % 2 + 1) * 4));
|
|
574
|
+
if (confirmed !== color.toLowerCase())
|
|
575
|
+
throw new Error(`The mouse kept ${confirmed ?? "an invalid colour"} instead of ${color}.`);
|
|
576
|
+
const dpiStageColors = this.lastStatus?.dpiStageColors?.slice() ?? [];
|
|
577
|
+
while (dpiStageColors.length < count)
|
|
578
|
+
dpiStageColors.push("#000000");
|
|
579
|
+
dpiStageColors[index] = confirmed;
|
|
580
|
+
this.patch({ dpiStageColors });
|
|
581
|
+
return confirmed;
|
|
582
|
+
}
|
|
583
|
+
async setPerformanceMode(enabled) {
|
|
584
|
+
await this.identify();
|
|
585
|
+
const change = (block) => {
|
|
586
|
+
block.splice(4, 2, ...wePackScalarPair(enabled ? 1 : 0));
|
|
587
|
+
};
|
|
588
|
+
const confirmed = this.usesVerifiedR1ProMaxReceiverTransport()
|
|
589
|
+
? await this.writeR1ProMaxReceiverPerformanceBlock(change)
|
|
590
|
+
: await this.writeR1PerformanceBlock(change);
|
|
591
|
+
const value = weUnpackScalarPair(confirmed[4], confirmed[5]) === 1;
|
|
592
|
+
if (value !== enabled)
|
|
593
|
+
throw new Error(`The mouse left performance mode ${value ? "on" : "off"}.`);
|
|
594
|
+
this.patch({ performanceMode: value });
|
|
595
|
+
return value;
|
|
596
|
+
}
|
|
597
|
+
async setLongRangeMode(enabled) {
|
|
598
|
+
await this.identify();
|
|
599
|
+
const receiver = this.usesVerifiedR1ProMaxReceiverTransport();
|
|
600
|
+
if (!this.usesVerifiedR1WiredTransport() && !receiver) {
|
|
601
|
+
throw new Error("Long-range mode is not available on this connection.");
|
|
602
|
+
}
|
|
603
|
+
const frame = weBuildCmdPayload(SET_LONG_RANGE_COMMAND, [0, 0, 0, 10, enabled ? 1 : 0]);
|
|
604
|
+
if (receiver) {
|
|
605
|
+
await this.exchange(frame, (reply) => reply.length === frame.length
|
|
606
|
+
&& reply.every((byte, index) => byte === frame[index]));
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
await this.send(frame);
|
|
610
|
+
}
|
|
611
|
+
await delay(WRITE_SETTLE_MS);
|
|
612
|
+
const confirmed = await this.readLongRangeMode();
|
|
613
|
+
if (confirmed !== enabled) {
|
|
614
|
+
throw new Error(`The mouse left long-range mode ${confirmed ? "on" : "off"}.`);
|
|
615
|
+
}
|
|
616
|
+
this.patch({ longRangeMode: confirmed });
|
|
617
|
+
return confirmed;
|
|
618
|
+
}
|
|
619
|
+
async setDpiLighting(mode, brightness, speed) {
|
|
620
|
+
await this.identify();
|
|
621
|
+
if (!this.usesVerifiedR1WiredTransport()
|
|
622
|
+
&& !this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
623
|
+
throw new Error("DPI lighting is not available on this connection.");
|
|
624
|
+
}
|
|
625
|
+
if (![0, 1, 2].includes(mode) || ![0, 1, 2].includes(brightness) || ![0, 1, 2].includes(speed)) {
|
|
626
|
+
throw new Error("The DPI lighting setting is invalid.");
|
|
627
|
+
}
|
|
628
|
+
// R1 Pro Max uses its own DPI-lighting row encoding:
|
|
629
|
+
// Off = 00 00 | 00 00 | speed | 00 55
|
|
630
|
+
// Always On = 01 54 | brightness | speed | 01 54
|
|
631
|
+
// Breathing = 02 53 | preserve brightness | speed | 01 54
|
|
632
|
+
const block = Array.from(await this.read(REGISTER.dpiLighting, R1_DPI_LIGHTING_LENGTH));
|
|
633
|
+
const proMax = this.isR1ProMax();
|
|
634
|
+
const expectedEffect = proMax && mode === 0
|
|
635
|
+
? [0, 0]
|
|
636
|
+
: wePackScalarPair(mode === 2 ? 2 : 1);
|
|
637
|
+
const expectedBrightness = proMax && mode === 2
|
|
638
|
+
? [block[2], block[3]]
|
|
639
|
+
: mode === 0
|
|
640
|
+
? [0, 0]
|
|
641
|
+
: wePackScalarPair(R1_DPI_BRIGHTNESS[brightness]);
|
|
642
|
+
block.splice(0, 2, ...expectedEffect);
|
|
643
|
+
block.splice(2, 2, ...expectedBrightness);
|
|
644
|
+
block.splice(4, 2, ...wePackScalarPair(R1_DPI_SPEED[speed]));
|
|
645
|
+
block.splice(6, 2, ...wePackScalarPair(mode === 0 ? 0 : 1));
|
|
646
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
647
|
+
await this.writeR1ProMaxReceiverDpiLighting(block);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
await this.write(REGISTER.dpiLighting, block);
|
|
651
|
+
}
|
|
652
|
+
const raw = await this.read(REGISTER.dpiLighting, R1_DPI_LIGHTING_LENGTH);
|
|
653
|
+
const expectedSpeed = wePackScalarPair(R1_DPI_SPEED[speed]);
|
|
654
|
+
const expectedEnabled = wePackScalarPair(mode === 0 ? 0 : 1);
|
|
655
|
+
const expected = [...expectedEffect, ...expectedBrightness, ...expectedSpeed, ...expectedEnabled];
|
|
656
|
+
if (!expected.every((byte, index) => raw[index] === byte)) {
|
|
657
|
+
throw new Error("The mouse did not retain its DPI lighting settings.");
|
|
658
|
+
}
|
|
659
|
+
const confirmed = this.decodeR1DpiLighting(raw);
|
|
660
|
+
if (!confirmed || confirmed.dpiLedMode !== mode || confirmed.dpiLedSpeed !== speed
|
|
661
|
+
|| (mode === 1 && confirmed.dpiLedBrightness !== brightness)) {
|
|
662
|
+
throw new Error("The mouse returned an invalid DPI lighting state.");
|
|
663
|
+
}
|
|
664
|
+
this.patch(confirmed);
|
|
665
|
+
}
|
|
666
|
+
async setLiftOffDistance(value) {
|
|
667
|
+
if (!this.usesR1LiveSettings())
|
|
668
|
+
await this.identify();
|
|
669
|
+
if (this.usesR1LiveSettings())
|
|
670
|
+
return await this.setR1LiftOffDistance(value);
|
|
671
|
+
const encoded = (this.isR1() ? R1_LIFT_OFF_CODES : LIFT_OFF_CODES).find(([, name]) => name === value);
|
|
672
|
+
if (!encoded)
|
|
673
|
+
throw new Error(`This mouse does not support a ${value.toLowerCase()} lift-off distance.`);
|
|
674
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
675
|
+
await this.writeR1ProMaxReceiverLiftOffDistance(encoded[0]);
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
await this.write(REGISTER.liftOffDistance, wePackScalarPair(encoded[0]));
|
|
679
|
+
}
|
|
680
|
+
const confirmed = this.decodeLiftOffDistance((await this.read(REGISTER.liftOffDistance, 2))[0]);
|
|
681
|
+
if (confirmed !== value) {
|
|
682
|
+
throw new Error(`The mouse kept a ${String(confirmed).toLowerCase()} lift-off distance instead of ${value.toLowerCase()}.`);
|
|
683
|
+
}
|
|
684
|
+
this.patch({ liftOffDistance: confirmed });
|
|
685
|
+
return confirmed;
|
|
686
|
+
}
|
|
687
|
+
async setLiftOffScale(code) {
|
|
688
|
+
await this.identify();
|
|
689
|
+
if (!this.supportsLiftOffScale()) {
|
|
690
|
+
throw new Error("This mouse does not support a continuous lift-off range.");
|
|
691
|
+
}
|
|
692
|
+
if (!Number.isInteger(code) || code < ATK_LIFT_OFF_MIN_CODE || code > ATK_LIFT_OFF_MAX_CODE) {
|
|
693
|
+
throw new Error(`A lift-off code runs ${ATK_LIFT_OFF_MIN_CODE} to ${ATK_LIFT_OFF_MAX_CODE}.`);
|
|
694
|
+
}
|
|
695
|
+
await this.write(REGISTER.liftOffDistance, wePackScalarPair(code));
|
|
696
|
+
const confirmed = (await this.read(REGISTER.liftOffDistance, 2))[0];
|
|
697
|
+
if (confirmed !== code) {
|
|
698
|
+
throw new Error(`The mouse kept lift-off code ${confirmed} instead of ${code}.`);
|
|
699
|
+
}
|
|
700
|
+
this.patch({ liftOffDistance: this.decodeLiftOffDistance(confirmed), liftOffScale: this.liftOffScale(confirmed) });
|
|
701
|
+
return confirmed;
|
|
702
|
+
}
|
|
703
|
+
async setMotionSync(enabled) {
|
|
704
|
+
await this.identify();
|
|
705
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
706
|
+
const confirmed = await this.writeR1ProMaxReceiverAdvanced(2, enabled ? 1 : 0) === 1;
|
|
707
|
+
if (confirmed !== enabled) {
|
|
708
|
+
throw new Error(`The mouse left Motion Sync ${confirmed ? "on" : "off"}.`);
|
|
709
|
+
}
|
|
710
|
+
this.patch({ motionSync: confirmed });
|
|
711
|
+
return confirmed;
|
|
712
|
+
}
|
|
713
|
+
return await this.setAdvancedFlag(2, enabled, "motionSync", "Motion Sync");
|
|
714
|
+
}
|
|
715
|
+
async setRippleControl(enabled) {
|
|
716
|
+
await this.identify();
|
|
717
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
718
|
+
const confirmed = await this.writeR1ProMaxReceiverAdvanced(8, enabled ? 1 : 0) === 1;
|
|
719
|
+
if (confirmed !== enabled) {
|
|
720
|
+
throw new Error(`The mouse left ripple control ${confirmed ? "on" : "off"}.`);
|
|
721
|
+
}
|
|
722
|
+
this.patch({ rippleControl: confirmed });
|
|
723
|
+
return confirmed;
|
|
724
|
+
}
|
|
725
|
+
return await this.setAdvancedFlag(8, enabled, "rippleControl", "ripple control");
|
|
726
|
+
}
|
|
727
|
+
async setAngleSnapping(enabled) {
|
|
728
|
+
if (!this.usesR1LiveSettings())
|
|
729
|
+
await this.identify();
|
|
730
|
+
if (this.usesR1LiveSettings())
|
|
731
|
+
return await this.setR1AngleSnapping(enabled);
|
|
732
|
+
if (this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
733
|
+
const confirmed = await this.writeR1ProMaxReceiverAdvanced(6, enabled ? 1 : 0) === 1;
|
|
734
|
+
if (confirmed !== enabled) {
|
|
735
|
+
throw new Error(`The mouse left angle snapping ${confirmed ? "on" : "off"}.`);
|
|
736
|
+
}
|
|
737
|
+
this.patch({ angleSnapping: confirmed });
|
|
738
|
+
return confirmed;
|
|
739
|
+
}
|
|
740
|
+
if (this.isR1()) {
|
|
741
|
+
return await this.setAdvancedFlag(6, enabled, "angleSnapping", "straight-line correction");
|
|
742
|
+
}
|
|
743
|
+
const group = await this.read(REGISTER.angle, ANGLE_LENGTH);
|
|
744
|
+
await this.write(REGISTER.angle, [group[0], enabled ? 1 : 0].flatMap((value) => wePackScalarPair(value)));
|
|
745
|
+
const confirmed = (await this.read(REGISTER.angle, ANGLE_LENGTH))[2] === 1;
|
|
746
|
+
if (confirmed !== enabled)
|
|
747
|
+
throw new Error(`The mouse left angle snapping ${confirmed ? "on" : "off"}.`);
|
|
748
|
+
this.patch({ angleSnapping: confirmed });
|
|
749
|
+
return confirmed;
|
|
750
|
+
}
|
|
751
|
+
async setDebounceTime(milliseconds) {
|
|
752
|
+
if (!this.usesR1LiveSettings())
|
|
753
|
+
await this.identify();
|
|
754
|
+
if (this.usesR1LiveSettings())
|
|
755
|
+
return await this.setR1DebounceTime(milliseconds);
|
|
756
|
+
const options = this.getDebounceOptions();
|
|
757
|
+
if (!Number.isInteger(milliseconds) || !options.includes(milliseconds)) {
|
|
758
|
+
throw new Error(`This mouse does not support ${milliseconds} ms debounce.`);
|
|
759
|
+
}
|
|
760
|
+
const confirmed = this.usesVerifiedR1ProMaxReceiverTransport()
|
|
761
|
+
? await this.writeR1ProMaxReceiverAdvanced(0, milliseconds)
|
|
762
|
+
: await this.writeAdvanced(0, milliseconds);
|
|
763
|
+
if (confirmed !== milliseconds) {
|
|
764
|
+
throw new Error(`The mouse kept ${confirmed} ms of debounce instead of ${milliseconds} ms.`);
|
|
765
|
+
}
|
|
766
|
+
this.patch({ debounceMs: confirmed });
|
|
767
|
+
return confirmed;
|
|
768
|
+
}
|
|
769
|
+
async setSleepTimeout(seconds) {
|
|
770
|
+
if (!this.usesR1LiveSettings())
|
|
771
|
+
await this.identify();
|
|
772
|
+
const valid = this.isR1()
|
|
773
|
+
? this.getSleepOptions().includes(seconds)
|
|
774
|
+
: Number.isInteger(seconds) && seconds >= SLEEP_STEP_SECONDS
|
|
775
|
+
&& seconds <= 0xff * SLEEP_STEP_SECONDS && seconds % SLEEP_STEP_SECONDS === 0;
|
|
776
|
+
if (!valid) {
|
|
777
|
+
throw new Error(`This mouse does not support a ${seconds} second sleep timeout.`);
|
|
778
|
+
}
|
|
779
|
+
const units = Math.round(seconds / SLEEP_STEP_SECONDS);
|
|
780
|
+
const confirmedUnits = this.usesVerifiedR1ProMaxReceiverTransport()
|
|
781
|
+
? await this.writeR1ProMaxReceiverAdvanced(4, units)
|
|
782
|
+
: await this.writeAdvanced(4, units);
|
|
783
|
+
const confirmed = confirmedUnits * SLEEP_STEP_SECONDS;
|
|
784
|
+
if (confirmed !== units * SLEEP_STEP_SECONDS) {
|
|
785
|
+
throw new Error(`The mouse kept a ${confirmed} second sleep timeout instead of ${seconds} seconds.`);
|
|
786
|
+
}
|
|
787
|
+
this.patch({ sleepTimeout: confirmed });
|
|
788
|
+
return confirmed;
|
|
789
|
+
}
|
|
790
|
+
async setAdvancedFlag(offset, enabled, field, label) {
|
|
791
|
+
const confirmed = await this.writeAdvanced(offset, enabled ? 1 : 0) === 1;
|
|
792
|
+
if (confirmed !== enabled)
|
|
793
|
+
throw new Error(`The mouse left ${label} ${confirmed ? "on" : "off"}.`);
|
|
794
|
+
this.patch({ [field]: confirmed });
|
|
795
|
+
return confirmed;
|
|
796
|
+
}
|
|
797
|
+
/** The advanced block is written whole, so read it back, patch one pair, write. */
|
|
798
|
+
async writeAdvanced(offset, value) {
|
|
799
|
+
const group = Array.from(await this.read(REGISTER.advanced, ADVANCED_LENGTH));
|
|
800
|
+
group.splice(offset, 2, ...wePackScalarPair(value));
|
|
801
|
+
await this.write(REGISTER.advanced, group);
|
|
802
|
+
return (await this.read(REGISTER.advanced, ADVANCED_LENGTH))[offset];
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* F58A DPI stage colours are stored as two 4-byte colour records per
|
|
806
|
+
* 8-byte EEPROM group, matching the captured receiver write/echo path.
|
|
807
|
+
*/
|
|
808
|
+
async writeR1ProMaxReceiverDpiColorGroup(address, block) {
|
|
809
|
+
await this.identify();
|
|
810
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
811
|
+
throw new Error("DPI stage colors are not available on this connection.");
|
|
812
|
+
}
|
|
813
|
+
if (block.length !== R1_DPI_COLOR_GROUP_LENGTH) {
|
|
814
|
+
throw new Error("The DPI stage colour block has an invalid length.");
|
|
815
|
+
}
|
|
816
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [
|
|
817
|
+
0,
|
|
818
|
+
(address >> 8) & 0xff,
|
|
819
|
+
address & 0xff,
|
|
820
|
+
block.length,
|
|
821
|
+
...block,
|
|
822
|
+
]);
|
|
823
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
824
|
+
&& frame[1] === 0
|
|
825
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
826
|
+
&& frame[3] === (address & 0xff)
|
|
827
|
+
&& frame[4] === block.length
|
|
828
|
+
&& this.hasValidChecksum(frame)
|
|
829
|
+
&& block.every((byte, index) => frame[DATA_OFFSET + index] === byte));
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* F58A DPI lighting uses the same complete 8-byte 0x004c row as wired.
|
|
833
|
+
*/
|
|
834
|
+
async writeR1ProMaxReceiverDpiLighting(block) {
|
|
835
|
+
await this.identify();
|
|
836
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
837
|
+
throw new Error("DPI lighting is not available on this connection.");
|
|
838
|
+
}
|
|
839
|
+
const address = REGISTER.dpiLighting;
|
|
840
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [
|
|
841
|
+
0,
|
|
842
|
+
(address >> 8) & 0xff,
|
|
843
|
+
address & 0xff,
|
|
844
|
+
block.length,
|
|
845
|
+
...block,
|
|
846
|
+
]);
|
|
847
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
848
|
+
&& frame[1] === 0
|
|
849
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
850
|
+
&& frame[3] === (address & 0xff)
|
|
851
|
+
&& frame[4] === block.length
|
|
852
|
+
&& this.hasValidChecksum(frame)
|
|
853
|
+
&& block.every((byte, index) => frame[DATA_OFFSET + index] === byte));
|
|
854
|
+
}
|
|
855
|
+
async readR1Extras(stageCount) {
|
|
856
|
+
try {
|
|
857
|
+
const performance = await this.read(REGISTER.sensorPerformance, R1_SENSOR_PERFORMANCE_LENGTH);
|
|
858
|
+
const lighting = this.decodeR1DpiLighting(await this.read(REGISTER.dpiLighting, R1_DPI_LIGHTING_LENGTH));
|
|
859
|
+
const dpiStageColors = [];
|
|
860
|
+
for (let groupIndex = 0; groupIndex < Math.ceil(stageCount / 2); groupIndex += 1) {
|
|
861
|
+
const group = await this.read(REGISTER.dpiColorBase + groupIndex * R1_DPI_COLOR_GROUP_LENGTH, R1_DPI_COLOR_GROUP_LENGTH);
|
|
862
|
+
for (let slot = 0; slot < 2 && dpiStageColors.length < stageCount; slot += 1) {
|
|
863
|
+
const color = unpackRgb(group.subarray(slot * 4, slot * 4 + 4));
|
|
864
|
+
if (!color)
|
|
865
|
+
throw new Error("The mouse reported a DPI stage color that failed its checksum.");
|
|
866
|
+
dpiStageColors.push(color);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const performanceMode = weUnpackScalarPair(performance[4], performance[5]);
|
|
870
|
+
if ((performanceMode !== 0 && performanceMode !== 1) || !lighting) {
|
|
871
|
+
throw new Error("The mouse reported invalid R1 extended settings.");
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
dpiStageColors,
|
|
875
|
+
performanceMode: performanceMode === 1,
|
|
876
|
+
longRangeMode: await this.readLongRangeMode(),
|
|
877
|
+
...lighting,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
decodeR1DpiLighting(block) {
|
|
885
|
+
if (block.length < R1_DPI_LIGHTING_LENGTH)
|
|
886
|
+
return null;
|
|
887
|
+
const effectIsZero = block[0] === 0 && block[1] === 0;
|
|
888
|
+
const brightnessIsZero = block[2] === 0 && block[3] === 0;
|
|
889
|
+
const effect = effectIsZero ? 0 : weUnpackScalarPair(block[0], block[1]);
|
|
890
|
+
const brightness = brightnessIsZero ? null : weUnpackScalarPair(block[2], block[3]);
|
|
891
|
+
const speed = weUnpackScalarPair(block[4], block[5]);
|
|
892
|
+
const enabled = weUnpackScalarPair(block[6], block[7]);
|
|
893
|
+
const brightnessIndex = brightness === null
|
|
894
|
+
? 1 // inactive brightness is not persisted; vendor UI defaults it to Medium
|
|
895
|
+
: R1_DPI_BRIGHTNESS.indexOf(brightness);
|
|
896
|
+
const speedIndex = R1_DPI_SPEED.indexOf(speed);
|
|
897
|
+
if (brightnessIndex < 0 || speedIndex < 0 || (enabled !== 0 && enabled !== 1))
|
|
898
|
+
return null;
|
|
899
|
+
// R1 Pro Max may report the trailing flag as 0 after reconnect even when
|
|
900
|
+
// the stored effect is Breathing. Treat the effect field as authoritative.
|
|
901
|
+
// Keep effect=1 + enabled=0 as the legacy Off encoding used by other R1s.
|
|
902
|
+
if (effect === 0) {
|
|
903
|
+
return { dpiLedMode: 0, dpiLedBrightness: brightnessIndex, dpiLedSpeed: speedIndex };
|
|
904
|
+
}
|
|
905
|
+
if (effect === 2) {
|
|
906
|
+
return { dpiLedMode: 2, dpiLedBrightness: brightnessIndex, dpiLedSpeed: speedIndex };
|
|
907
|
+
}
|
|
908
|
+
if (effect === 1) {
|
|
909
|
+
return {
|
|
910
|
+
dpiLedMode: enabled === 0 ? 0 : 1,
|
|
911
|
+
dpiLedBrightness: brightnessIndex,
|
|
912
|
+
dpiLedSpeed: speedIndex,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
async readLongRangeMode() {
|
|
918
|
+
const reply = await this.exchange(weBuildCmdPayload(GET_LONG_RANGE_COMMAND), (frame) => frame[0] === GET_LONG_RANGE_COMMAND && frame[1] === 0
|
|
919
|
+
&& frame[4] >= 1 && frame[DATA_OFFSET] <= 1 && this.hasValidChecksum(frame));
|
|
920
|
+
return reply[DATA_OFFSET] === 1;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* F58A Performance Mode uses the same 6-byte 0x00b5 row as wired, but
|
|
924
|
+
* receiver writes are enabled only after their write/echo path was captured.
|
|
925
|
+
*/
|
|
926
|
+
async writeR1ProMaxReceiverPerformanceBlock(change) {
|
|
927
|
+
await this.identify();
|
|
928
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
929
|
+
throw new Error("Performance mode is not available on this connection.");
|
|
930
|
+
}
|
|
931
|
+
const address = REGISTER.sensorPerformance;
|
|
932
|
+
const block = Array.from(await this.read(address, R1_SENSOR_PERFORMANCE_LENGTH));
|
|
933
|
+
change(block);
|
|
934
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [
|
|
935
|
+
0,
|
|
936
|
+
(address >> 8) & 0xff,
|
|
937
|
+
address & 0xff,
|
|
938
|
+
block.length,
|
|
939
|
+
...block,
|
|
940
|
+
]);
|
|
941
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
942
|
+
&& frame[1] === 0
|
|
943
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
944
|
+
&& frame[3] === (address & 0xff)
|
|
945
|
+
&& frame[4] === block.length
|
|
946
|
+
&& this.hasValidChecksum(frame)
|
|
947
|
+
&& block.every((byte, index) => frame[DATA_OFFSET + index] === byte));
|
|
948
|
+
return await this.read(address, R1_SENSOR_PERFORMANCE_LENGTH);
|
|
949
|
+
}
|
|
950
|
+
async writeR1PerformanceBlock(change) {
|
|
951
|
+
await this.identify();
|
|
952
|
+
if (!this.usesVerifiedR1WiredTransport()) {
|
|
953
|
+
throw new Error("Performance mode is not available on this connection.");
|
|
954
|
+
}
|
|
955
|
+
const block = Array.from(await this.read(REGISTER.sensorPerformance, R1_SENSOR_PERFORMANCE_LENGTH));
|
|
956
|
+
change(block);
|
|
957
|
+
await this.write(REGISTER.sensorPerformance, block);
|
|
958
|
+
return await this.read(REGISTER.sensorPerformance, R1_SENSOR_PERFORMANCE_LENGTH);
|
|
959
|
+
}
|
|
960
|
+
dpiAddress(index) {
|
|
961
|
+
const sensor = this.product?.sensor ?? null;
|
|
962
|
+
if (sensor && ATK_SENSORS[sensor].family === "paw3955master") {
|
|
963
|
+
return REGISTER.paw3955DpiBase + index * atkDpiStageLength(sensor);
|
|
964
|
+
}
|
|
965
|
+
return REGISTER.dpiBase + index * DPI_STAGE_LENGTH;
|
|
966
|
+
}
|
|
967
|
+
async readDpiStage(index) {
|
|
968
|
+
const sensor = this.product?.sensor ?? null;
|
|
969
|
+
const data = await this.read(this.dpiAddress(index), atkDpiStageLength(sensor));
|
|
970
|
+
const stage = this.product
|
|
971
|
+
? atkUnpackDpiStageForSensor(this.product.sensor, data)
|
|
972
|
+
: atkUnpackDpiStage(data);
|
|
973
|
+
if (!stage)
|
|
974
|
+
throw new Error("The mouse reported a DPI stage that failed its checksum.");
|
|
975
|
+
return stage;
|
|
976
|
+
}
|
|
977
|
+
stageIndex(system) {
|
|
978
|
+
const stages = this.stageCount(system);
|
|
979
|
+
return Math.min(system[4], stages - 1);
|
|
980
|
+
}
|
|
981
|
+
stageCount(system) {
|
|
982
|
+
const max = this.isR1() ? R1_MAX_DPI_STAGES : MAX_DPI_STAGES;
|
|
983
|
+
return Math.min(Math.max(system[2], 1), max);
|
|
984
|
+
}
|
|
985
|
+
decodePollingRate(value) {
|
|
986
|
+
const match = POLLING_RATES.find(([code]) => code === value);
|
|
987
|
+
if (!match)
|
|
988
|
+
throw new Error(`The mouse reported an unknown polling-rate value 0x${value.toString(16)}.`);
|
|
989
|
+
return match[1];
|
|
990
|
+
}
|
|
991
|
+
decodeLiftOffDistance(code) {
|
|
992
|
+
if (this.isR1())
|
|
993
|
+
return R1_LIFT_OFF_CODES.find(([value]) => value === code)?.[1] ?? null;
|
|
994
|
+
const millimetres = atkDecodeLiftOff(code);
|
|
995
|
+
if (millimetres === null)
|
|
996
|
+
return null;
|
|
997
|
+
if (millimetres < 1)
|
|
998
|
+
return "Low";
|
|
999
|
+
return millimetres < 1.5 ? "Medium" : "High";
|
|
1000
|
+
}
|
|
1001
|
+
supportsLiftOffScale() {
|
|
1002
|
+
return this.product?.sensor === "PAW3950Ultra" || this.product?.sensor === "PAW3955Master";
|
|
1003
|
+
}
|
|
1004
|
+
liftOffScale(code) {
|
|
1005
|
+
const millimetres = atkDecodeLiftOff(code);
|
|
1006
|
+
if (millimetres === null)
|
|
1007
|
+
return null;
|
|
1008
|
+
return {
|
|
1009
|
+
value: code,
|
|
1010
|
+
min: ATK_LIFT_OFF_MIN_CODE,
|
|
1011
|
+
max: ATK_LIFT_OFF_MAX_CODE,
|
|
1012
|
+
millimetres,
|
|
1013
|
+
minMillimetres: atkDecodeLiftOff(ATK_LIFT_OFF_MIN_CODE),
|
|
1014
|
+
maxMillimetres: atkDecodeLiftOff(ATK_LIFT_OFF_MAX_CODE),
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
decodeAngle(byte) {
|
|
1018
|
+
return byte > 0x80 ? byte - 0x100 : byte;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* R1 polling comes from the live-settings row at 0x0070, not the A9 system
|
|
1022
|
+
* block (whose first pair is a link-mode byte on this family). A stock row
|
|
1023
|
+
* may be unpopulated, so fall back to the receiver's ceiling when unknown.
|
|
1024
|
+
*/
|
|
1025
|
+
async readPollingRate(system) {
|
|
1026
|
+
if (this.usesR1LiveSettings()) {
|
|
1027
|
+
const settings = await this.read(ATK_VXE_R1_SETTINGS_REGISTER, R1_SETTINGS_LENGTH).catch(() => null);
|
|
1028
|
+
const decoded = settings ? atkDecodeVxeR1PollingCode(settings[1]) : null;
|
|
1029
|
+
return decoded ?? 1000;
|
|
1030
|
+
}
|
|
1031
|
+
return this.decodePollingRate((system ?? await this.read(REGISTER.system, SYSTEM_LENGTH))[0]);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* The R1 applies the write through its live-settings row (OpenVXE sends it
|
|
1035
|
+
* fire-and-forget), so confirmation stays optimistic rather than erroring on
|
|
1036
|
+
* a register that may not echo what was applied.
|
|
1037
|
+
*/
|
|
1038
|
+
async setR1PollingRate(pollingRateHz) {
|
|
1039
|
+
const data = atkPackVxeR1PollingSetting(pollingRateHz);
|
|
1040
|
+
if (!data)
|
|
1041
|
+
throw new Error(`This mouse does not support ${pollingRateHz} Hz.`);
|
|
1042
|
+
await this.write(ATK_VXE_R1_SETTINGS_REGISTER, data);
|
|
1043
|
+
const confirmed = await this.readPollingRate();
|
|
1044
|
+
await delay(R1_LIVE_WRITE_SETTLE_MS);
|
|
1045
|
+
this.patch({ pollingRateHz: confirmed });
|
|
1046
|
+
return confirmed;
|
|
1047
|
+
}
|
|
1048
|
+
/** The R1's lift-off levels are 1 mm and 2 mm, applied through the live-settings row. */
|
|
1049
|
+
async setR1LiftOffDistance(value) {
|
|
1050
|
+
const encoded = R1_LIFT_OFF_CODES.find(([, name]) => name === value);
|
|
1051
|
+
if (!encoded)
|
|
1052
|
+
throw new Error(`This mouse does not support a ${value.toLowerCase()} lift-off distance.`);
|
|
1053
|
+
await this.write(ATK_VXE_R1_SETTINGS_REGISTER, atkPackVxeR1LiveSetting(ATK_VXE_R1_LOD_SELECTOR, encoded[0]));
|
|
1054
|
+
await delay(R1_LIVE_WRITE_SETTLE_MS);
|
|
1055
|
+
this.patch({ liftOffDistance: value });
|
|
1056
|
+
return value;
|
|
1057
|
+
}
|
|
1058
|
+
async setR1DebounceTime(milliseconds) {
|
|
1059
|
+
if (!Number.isInteger(milliseconds) || milliseconds < 1 || milliseconds > R1_DEBOUNCE_MAX_MS) {
|
|
1060
|
+
throw new Error(`Debounce must be a whole number of milliseconds between 1 and ${R1_DEBOUNCE_MAX_MS}.`);
|
|
1061
|
+
}
|
|
1062
|
+
await this.write(ATK_VXE_R1_SETTINGS_REGISTER, atkPackVxeR1LiveSetting(ATK_VXE_R1_DEBOUNCE_SELECTOR, milliseconds));
|
|
1063
|
+
await delay(R1_LIVE_WRITE_SETTLE_MS);
|
|
1064
|
+
this.patch({ debounceMs: milliseconds });
|
|
1065
|
+
return milliseconds;
|
|
1066
|
+
}
|
|
1067
|
+
async setR1AngleSnapping(enabled) {
|
|
1068
|
+
await this.write(ATK_VXE_R1_SETTINGS_REGISTER, atkPackVxeR1LiveSetting(ATK_VXE_R1_ANGLE_SELECTOR, enabled ? 0x10 : 0x00));
|
|
1069
|
+
await delay(R1_LIVE_WRITE_SETTLE_MS);
|
|
1070
|
+
this.patch({ angleSnapping: enabled });
|
|
1071
|
+
return enabled;
|
|
1072
|
+
}
|
|
1073
|
+
patch(changes) {
|
|
1074
|
+
if (this.lastStatus)
|
|
1075
|
+
this.lastStatus = { ...this.lastStatus, ...changes };
|
|
1076
|
+
}
|
|
1077
|
+
hasValidChecksum(frame) {
|
|
1078
|
+
return frame.length === FRAME_LENGTH
|
|
1079
|
+
&& frame[15] === weReportChecksum(WE_REPORT_ID, [...frame.subarray(0, 15)]);
|
|
1080
|
+
}
|
|
1081
|
+
async readR1CurrentProfile() {
|
|
1082
|
+
const reply = await this.exchange(weBuildCmdPayload(ATK_COMPX_COMMAND.getCurrentConfig), (frame) => frame[0] === ATK_COMPX_COMMAND.getCurrentConfig
|
|
1083
|
+
&& frame[1] === 0 && frame[4] >= 1 && this.hasValidChecksum(frame));
|
|
1084
|
+
const profile = atkDecodeCurrentProfile(reply.subarray(DATA_OFFSET));
|
|
1085
|
+
if (profile === null)
|
|
1086
|
+
throw new Error("The mouse reported an invalid active configuration bank.");
|
|
1087
|
+
return profile;
|
|
1088
|
+
}
|
|
1089
|
+
async identify() {
|
|
1090
|
+
if (this.identified)
|
|
1091
|
+
return;
|
|
1092
|
+
if (this.identifyAttempts >= MAX_IDENTIFY_ATTEMPTS) {
|
|
1093
|
+
if (this.usesSharedR1Transport()) {
|
|
1094
|
+
throw new Error("The VXE R1 did not answer CID/MID; refusing to use the fallback DPI codec.");
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
this.identifyAttempts += 1;
|
|
1099
|
+
const reply = await this.exchange(weBuildCmdPayload(CIDMID_COMMAND), (frame) => frame[0] === CIDMID_COMMAND && frame[4] >= 2).catch(() => null);
|
|
1100
|
+
if (!reply) {
|
|
1101
|
+
if (this.usesSharedR1Transport()) {
|
|
1102
|
+
throw new Error("The VXE R1 did not answer CID/MID; refusing to use the fallback DPI codec.");
|
|
1103
|
+
}
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
this.identified = true;
|
|
1107
|
+
this.product = ATK_PRODUCTS[`${reply[DATA_OFFSET]},${reply[DATA_OFFSET + 1]}`] ?? null;
|
|
1108
|
+
}
|
|
1109
|
+
usesSharedR1Transport() {
|
|
1110
|
+
return this.device.productId === VXE_R1_RECEIVER_PID
|
|
1111
|
+
|| (this.device.vendorId === VENDOR_ID.vgn && ATK_COMPX_PRODUCT_IDS.includes(this.device.productId))
|
|
1112
|
+
|| /\bvxe\s+r1(?:\s*(?:se\+?|pro\s+max))?\b/i.test(this.device.productName || "");
|
|
1113
|
+
}
|
|
1114
|
+
/** Only the SE/SE+ receiver family uses the selector-based 0x0070 live row. */
|
|
1115
|
+
usesR1LiveSettings() {
|
|
1116
|
+
return this.isR1()
|
|
1117
|
+
&& (this.device.productId === VXE_R1_RECEIVER_PID
|
|
1118
|
+
|| (this.device.vendorId === VENDOR_ID.vgn && this.device.productId === VXE_R1_COMPX_RECEIVER_PID));
|
|
1119
|
+
}
|
|
1120
|
+
isR1ProMax() {
|
|
1121
|
+
return this.product === ATK_PRODUCTS["2,27"];
|
|
1122
|
+
}
|
|
1123
|
+
usesVerifiedR1ProMaxReceiverTransport() {
|
|
1124
|
+
return this.device.vendorId === VENDOR_ID.vgn
|
|
1125
|
+
&& this.device.productId === VXE_R1_PRO_MAX_RECEIVER_PID
|
|
1126
|
+
&& this.isR1ProMax();
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Wired F58C and receiver F58A expose the same R1 Pro Max settings surface.
|
|
1130
|
+
* This predicate is for reads/UI only; write paths remain individually gated.
|
|
1131
|
+
*/
|
|
1132
|
+
usesR1ProMaxUiTransport() {
|
|
1133
|
+
return this.usesVerifiedR1WiredTransport()
|
|
1134
|
+
|| this.usesVerifiedR1ProMaxReceiverTransport();
|
|
1135
|
+
}
|
|
1136
|
+
usesVerifiedR1WiredTransport() {
|
|
1137
|
+
if (this.device.vendorId !== VENDOR_ID.vgn || this.isWireless())
|
|
1138
|
+
return false;
|
|
1139
|
+
return (this.device.productId === VXE_R1_COMPX_MOUSE_PID && this.product === ATK_PRODUCTS["2,32"])
|
|
1140
|
+
|| (this.device.productId === VXE_R1_PRO_MAX_MOUSE_PID && this.isR1ProMax());
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Command 0x12 (GetMouseVersion) reports the version as BCD, matching the
|
|
1144
|
+
* Endgame Gear siblings: an A9 Nearlink dongle answering 0x01 0x23 is 1.23.
|
|
1145
|
+
*/
|
|
1146
|
+
async readFirmware() {
|
|
1147
|
+
const reply = await this.exchange(weBuildCmdPayload(VERSION_COMMAND), (frame) => frame[0] === VERSION_COMMAND).catch(() => null);
|
|
1148
|
+
if (!reply)
|
|
1149
|
+
return [];
|
|
1150
|
+
const data = reply.subarray(DATA_OFFSET, DATA_OFFSET + Math.min(reply[4], MAX_DATA_LENGTH));
|
|
1151
|
+
if (data.length < 2)
|
|
1152
|
+
return [];
|
|
1153
|
+
const bcd = (byte) => byte.toString(16).padStart(2, "0");
|
|
1154
|
+
return [`Mouse ${Number(bcd(data[0]))}.${bcd(data[1])}`];
|
|
1155
|
+
}
|
|
1156
|
+
async readBattery() {
|
|
1157
|
+
const reply = await this.exchange(weBuildCmdPayload(BATTERY_COMMAND), (frame) => frame[0] === BATTERY_COMMAND).catch(() => null);
|
|
1158
|
+
if (!reply)
|
|
1159
|
+
return null;
|
|
1160
|
+
const length = Math.min(reply[4], MAX_DATA_LENGTH);
|
|
1161
|
+
const millivolts = length >= 4 ? (reply[DATA_OFFSET + 2] << 8) | reply[DATA_OFFSET + 3] : 0;
|
|
1162
|
+
return {
|
|
1163
|
+
percent: length >= 1 ? Math.min(reply[DATA_OFFSET], 100) : null,
|
|
1164
|
+
charging: length >= 2 ? reply[DATA_OFFSET + 1] !== 0 : null,
|
|
1165
|
+
millivolts: millivolts > 0 ? millivolts : null,
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
async read(address, length) {
|
|
1169
|
+
const reply = await this.exchange(weBuildCmdPayload(WE_CMD_READ_EEPROM, [0, (address >> 8) & 0xff, address & 0xff, length]),
|
|
1170
|
+
// Some firmware answers a read with the write command id.
|
|
1171
|
+
(frame) => (frame[0] === WE_CMD_READ_EEPROM || frame[0] === WE_CMD_WRITE_EEPROM)
|
|
1172
|
+
&& frame[1] === 0 && this.hasValidChecksum(frame)
|
|
1173
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
1174
|
+
&& frame[3] === (address & 0xff)
|
|
1175
|
+
&& frame[4] >= length);
|
|
1176
|
+
return reply.subarray(DATA_OFFSET, DATA_OFFSET + length);
|
|
1177
|
+
}
|
|
1178
|
+
async write(address, data) {
|
|
1179
|
+
if (this.isR1() && !this.usesR1LiveSettings() && !this.usesVerifiedR1WiredTransport()) {
|
|
1180
|
+
throw new Error("Persistent R1 EEPROM writes are available only over a verified wired transport.");
|
|
1181
|
+
}
|
|
1182
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [0, (address >> 8) & 0xff, address & 0xff, data.length, ...data]);
|
|
1183
|
+
await this.run(async () => {
|
|
1184
|
+
await this.open();
|
|
1185
|
+
// Copy for an ArrayBuffer-backed view, as the WebHID typings require.
|
|
1186
|
+
await this.device.sendReport(WE_REPORT_ID, new Uint8Array(payload).buffer);
|
|
1187
|
+
await delay(WRITE_SETTLE_MS);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* F58A advanced settings are written as the complete 10-byte EEPROM row.
|
|
1192
|
+
* Callers remain individually gated until each field is hardware-verified.
|
|
1193
|
+
*/
|
|
1194
|
+
async writeR1ProMaxReceiverAdvanced(offset, value) {
|
|
1195
|
+
await this.identify();
|
|
1196
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
1197
|
+
throw new Error("R1 Pro Max receiver advanced writes are not available on this connection.");
|
|
1198
|
+
}
|
|
1199
|
+
const address = REGISTER.advanced;
|
|
1200
|
+
const block = Array.from(await this.read(address, ADVANCED_LENGTH));
|
|
1201
|
+
block.splice(offset, 2, ...wePackScalarPair(value));
|
|
1202
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [
|
|
1203
|
+
0,
|
|
1204
|
+
(address >> 8) & 0xff,
|
|
1205
|
+
address & 0xff,
|
|
1206
|
+
block.length,
|
|
1207
|
+
...block,
|
|
1208
|
+
]);
|
|
1209
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
1210
|
+
&& frame[1] === 0
|
|
1211
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
1212
|
+
&& frame[3] === (address & 0xff)
|
|
1213
|
+
&& frame[4] === block.length
|
|
1214
|
+
&& this.hasValidChecksum(frame)
|
|
1215
|
+
&& block.every((byte, index) => frame[DATA_OFFSET + index] === byte));
|
|
1216
|
+
return (await this.read(address, ADVANCED_LENGTH))[offset];
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* The F58A vendor path writes LOD as one checksum-protected scalar pair at
|
|
1220
|
+
* EEPROM 0x000a and echoes the committed pair back on report 0x08.
|
|
1221
|
+
*/
|
|
1222
|
+
async writeR1ProMaxReceiverLiftOffDistance(code) {
|
|
1223
|
+
await this.identify();
|
|
1224
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
1225
|
+
throw new Error("R1 Pro Max receiver lift-off writes are not available on this connection.");
|
|
1226
|
+
}
|
|
1227
|
+
const data = wePackScalarPair(code);
|
|
1228
|
+
const address = REGISTER.liftOffDistance;
|
|
1229
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [
|
|
1230
|
+
0,
|
|
1231
|
+
(address >> 8) & 0xff,
|
|
1232
|
+
address & 0xff,
|
|
1233
|
+
data.length,
|
|
1234
|
+
...data,
|
|
1235
|
+
]);
|
|
1236
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
1237
|
+
&& frame[1] === 0
|
|
1238
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
1239
|
+
&& frame[3] === (address & 0xff)
|
|
1240
|
+
&& frame[4] === data.length
|
|
1241
|
+
&& this.hasValidChecksum(frame)
|
|
1242
|
+
&& data.every((byte, index) => frame[DATA_OFFSET + index] === byte));
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* ATK Hub writes F58A DPI records as an 8-byte pair of adjacent stages.
|
|
1246
|
+
* Preserve the neighbouring stage and require the receiver to echo the
|
|
1247
|
+
* complete group before the normal DPI readback confirms the active stage.
|
|
1248
|
+
*/
|
|
1249
|
+
async writeR1ProMaxReceiverDpiStage(index, stage) {
|
|
1250
|
+
await this.identify();
|
|
1251
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
1252
|
+
throw new Error("R1 Pro Max receiver DPI writes are not available on this connection.");
|
|
1253
|
+
}
|
|
1254
|
+
if (stage.length !== DPI_STAGE_LENGTH) {
|
|
1255
|
+
throw new Error("R1 Pro Max receiver DPI records must contain four bytes.");
|
|
1256
|
+
}
|
|
1257
|
+
const groupLength = DPI_STAGE_LENGTH * 2;
|
|
1258
|
+
const groupIndex = Math.floor(index / 2);
|
|
1259
|
+
const address = REGISTER.dpiBase + groupIndex * groupLength;
|
|
1260
|
+
const group = Array.from(await this.read(address, groupLength));
|
|
1261
|
+
group.splice((index % 2) * DPI_STAGE_LENGTH, DPI_STAGE_LENGTH, ...stage);
|
|
1262
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [
|
|
1263
|
+
0,
|
|
1264
|
+
(address >> 8) & 0xff,
|
|
1265
|
+
address & 0xff,
|
|
1266
|
+
group.length,
|
|
1267
|
+
...group,
|
|
1268
|
+
]);
|
|
1269
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
1270
|
+
&& frame[1] === 0
|
|
1271
|
+
&& frame[2] === ((address >> 8) & 0xff)
|
|
1272
|
+
&& frame[3] === (address & 0xff)
|
|
1273
|
+
&& frame[4] === group.length
|
|
1274
|
+
&& this.hasValidChecksum(frame)
|
|
1275
|
+
&& group.every((byte, offset) => frame[DATA_OFFSET + offset] === byte));
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* The verified F58A polling path writes the complete system row and echoes
|
|
1279
|
+
* the committed row back on report 0x08. No other receiver EEPROM writes
|
|
1280
|
+
* are enabled through this helper.
|
|
1281
|
+
*/
|
|
1282
|
+
async writeR1ProMaxReceiverSystem(data) {
|
|
1283
|
+
await this.identify();
|
|
1284
|
+
if (!this.usesVerifiedR1ProMaxReceiverTransport()) {
|
|
1285
|
+
throw new Error("R1 Pro Max receiver system writes are not available on this connection.");
|
|
1286
|
+
}
|
|
1287
|
+
if (data.length !== R1_SYSTEM_ROW_LENGTH) {
|
|
1288
|
+
throw new Error(`R1 Pro Max receiver system writes must preserve the complete ${R1_SYSTEM_ROW_LENGTH}-byte row.`);
|
|
1289
|
+
}
|
|
1290
|
+
const payload = weBuildCmdPayload(WE_CMD_WRITE_EEPROM, [0, 0x00, 0x00, data.length, ...data]);
|
|
1291
|
+
await this.exchange(payload, (frame) => frame[0] === WE_CMD_WRITE_EEPROM
|
|
1292
|
+
&& frame[1] === 0
|
|
1293
|
+
&& frame[2] === 0
|
|
1294
|
+
&& frame[3] === 0
|
|
1295
|
+
&& frame[4] === data.length
|
|
1296
|
+
&& this.hasValidChecksum(frame)
|
|
1297
|
+
&& data.every((byte, index) => frame[DATA_OFFSET + index] === byte));
|
|
1298
|
+
}
|
|
1299
|
+
async send(frame) {
|
|
1300
|
+
await this.run(async () => {
|
|
1301
|
+
await this.open();
|
|
1302
|
+
await this.device.sendReport(WE_REPORT_ID, new Uint8Array(frame).buffer);
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
/** Send a frame and resolve with the first input report the matcher accepts. */
|
|
1306
|
+
async exchange(frame, matches) {
|
|
1307
|
+
return await this.run(async () => {
|
|
1308
|
+
await this.open();
|
|
1309
|
+
return await new Promise((resolve, reject) => {
|
|
1310
|
+
const finish = () => {
|
|
1311
|
+
clearTimeout(timer);
|
|
1312
|
+
this.device.removeEventListener("inputreport", listener);
|
|
1313
|
+
};
|
|
1314
|
+
const timer = setTimeout(() => {
|
|
1315
|
+
finish();
|
|
1316
|
+
reject(new Error("The mouse did not answer — it may be asleep or out of range."));
|
|
1317
|
+
}, REPLY_TIMEOUT_MS);
|
|
1318
|
+
const listener = (event) => {
|
|
1319
|
+
if (event.reportId !== WE_REPORT_ID)
|
|
1320
|
+
return;
|
|
1321
|
+
const reply = copyDataView(event.data);
|
|
1322
|
+
if (reply.length < FRAME_LENGTH || !matches(reply))
|
|
1323
|
+
return;
|
|
1324
|
+
finish();
|
|
1325
|
+
resolve(reply);
|
|
1326
|
+
};
|
|
1327
|
+
this.device.addEventListener("inputreport", listener);
|
|
1328
|
+
this.device.sendReport(WE_REPORT_ID, new Uint8Array(frame).buffer).catch((error) => {
|
|
1329
|
+
finish();
|
|
1330
|
+
reject(error);
|
|
1331
|
+
});
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
async run(task) {
|
|
1336
|
+
const started = this.queue.then(task, task);
|
|
1337
|
+
this.queue = started.catch(() => undefined);
|
|
1338
|
+
return await started;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function copyDataView(view) {
|
|
1342
|
+
return new Uint8Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
|
|
1343
|
+
}
|
|
1344
|
+
function batteryState(battery) {
|
|
1345
|
+
if (!battery || battery.charging === null)
|
|
1346
|
+
return "Unknown";
|
|
1347
|
+
return battery.charging ? "Charging" : "Discharging";
|
|
1348
|
+
}
|
|
1349
|
+
function delay(milliseconds) {
|
|
1350
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1351
|
+
}
|
|
1352
|
+
function parseHexColor(color) {
|
|
1353
|
+
const match = /^#([0-9a-f]{6})$/i.exec(color);
|
|
1354
|
+
if (!match)
|
|
1355
|
+
return null;
|
|
1356
|
+
const value = Number.parseInt(match[1], 16);
|
|
1357
|
+
return [(value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff];
|
|
1358
|
+
}
|
|
1359
|
+
function packRgb(rgb) {
|
|
1360
|
+
const sum = (rgb[0] + rgb[1] + rgb[2]) & 0xff;
|
|
1361
|
+
return [...rgb, (0x55 - sum) & 0xff];
|
|
1362
|
+
}
|
|
1363
|
+
function unpackRgb(record) {
|
|
1364
|
+
if (record.length < 4 || (record[0] + record[1] + record[2] + record[3]) % 0x100 !== 0x55)
|
|
1365
|
+
return null;
|
|
1366
|
+
return `#${[record[0], record[1], record[2]].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
|
|
1367
|
+
}
|
|
1368
|
+
//# sourceMappingURL=hid.js.map
|