@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,965 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HID++ 0x8100 onboard profile decoding.
|
|
3
|
+
*
|
|
4
|
+
* Layouts recovered from Logitech's own registration code and verified against
|
|
5
|
+
* a Pro X Superlight 2 (profile format 7) — see
|
|
6
|
+
* docs/logitech-onboard-profiles.md. Pure functions only: the transport lives
|
|
7
|
+
* in hidpp.ts, so everything here is testable against captured bytes.
|
|
8
|
+
*/
|
|
9
|
+
/** 0x8100 function ids. Write functions are deliberately absent. */
|
|
10
|
+
export const PROFILE_FN = {
|
|
11
|
+
getInfo: 0x00,
|
|
12
|
+
setMode: 0x10,
|
|
13
|
+
getMode: 0x20,
|
|
14
|
+
setCurrentProfile: 0x30,
|
|
15
|
+
getCurrentProfile: 0x40,
|
|
16
|
+
memoryRead: 0x50,
|
|
17
|
+
memoryAddrWrite: 0x60,
|
|
18
|
+
memoryWrite: 0x70,
|
|
19
|
+
memoryWriteEnd: 0x80,
|
|
20
|
+
};
|
|
21
|
+
export const ONBOARD_MODE = { onboard: 0x01, host: 0x02 };
|
|
22
|
+
/**
|
|
23
|
+
* Names from the library's PROFILE_FORMAT enum, which stops at 6 — the
|
|
24
|
+
* dispatcher handles 8. Ids 7 and 8 are newer formats the managed wrapper was
|
|
25
|
+
* never updated for, so they are described by what they carry instead.
|
|
26
|
+
*/
|
|
27
|
+
const PROFILE_FORMAT_NAMES = {
|
|
28
|
+
1: "PROTOSS_HYJAL",
|
|
29
|
+
2: "LOGAN",
|
|
30
|
+
3: "HEAT",
|
|
31
|
+
4: "HARPY",
|
|
32
|
+
5: "HOST_LAYER",
|
|
33
|
+
6: "BAYMAX",
|
|
34
|
+
7: "FORMAT 7", // (v6 + bunny hopping)
|
|
35
|
+
8: "FORMAT 8", // (v6 + bunny hopping + analog buttons)
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Formats whose layout has been confirmed by decoding a real profile whose
|
|
39
|
+
* checksum validates. Everything else is read out of vendor code only — the v6
|
|
40
|
+
* base was wrong in two ways (DPI offset and endianness) until hardware
|
|
41
|
+
* corrected it, so treat unverified layouts as display-only.
|
|
42
|
+
*
|
|
43
|
+
* Add a format here only after a dump from that device decodes sensibly with a
|
|
44
|
+
* matching CRC.
|
|
45
|
+
*
|
|
46
|
+
* Format 2 (LOGAN) is trusted only for its report-rate field: the v1 base was
|
|
47
|
+
* recovered from vendor code, the write sequence is the same one format 7
|
|
48
|
+
* proved on hardware, and the rate byte decodes back as a polling interval. A
|
|
49
|
+
* hardware sanity check on a G502/G403-family device is still required before
|
|
50
|
+
* release — see docs/logitech-onboard-profiles.md.
|
|
51
|
+
*/
|
|
52
|
+
const VERIFIED_FORMATS = new Set([2, 3, 4, 7]);
|
|
53
|
+
const WRITABLE_FORMATS = new Set([2, 3, 4, 7]);
|
|
54
|
+
const PROFILE_WRITE_PROBE_FORMATS = new Set([2, 3, 4]);
|
|
55
|
+
const FACTORY_RESET_FORMATS = new Set([7]);
|
|
56
|
+
/** Whether profile-content writes for `profileFormatId` are trusted at all. */
|
|
57
|
+
export function isProfileWritable(profileFormatId) {
|
|
58
|
+
return profileFormatId !== null && profileFormatId !== undefined && WRITABLE_FORMATS.has(profileFormatId);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format 7's per-stage lift-off byte (and the rest of its DPI-stage triplet
|
|
62
|
+
* layout) was recovered from a Pro X Superlight 2 dump. The original G Pro X
|
|
63
|
+
* Superlight (PID 0xc094, wpid 4093 — Logitech's "PRO X Wireless" in its own
|
|
64
|
+
* tooling) reports the same format id but is an older board that predates
|
|
65
|
+
* per-stage lift-off entirely; live reports show it returning HID++ error
|
|
66
|
+
* 0x05 ("Logitech internal error") specifically when a stage's lift-off byte
|
|
67
|
+
* is written, matching an unresolved report of a differently-shifted DPI
|
|
68
|
+
* stage layout on what is likely this device. Until a dump from this
|
|
69
|
+
* specific PID confirms it has lift-off storage to write to, everything else
|
|
70
|
+
* format 7 carries — DPI x/y, report rate, angle-snap, name, buttons — stays
|
|
71
|
+
* writable, and only the lift-off byte for each stage is left untouched.
|
|
72
|
+
*/
|
|
73
|
+
const UNVERIFIED_LOD_PRODUCT_IDS = new Set([0xc094]);
|
|
74
|
+
/** Whether the per-stage lift-off byte is trusted to write on this specific device. */
|
|
75
|
+
export function isLodWritableForProduct(profileFormatId, productId) {
|
|
76
|
+
if (!isProfileWritable(profileFormatId))
|
|
77
|
+
return false;
|
|
78
|
+
if (productId !== null && productId !== undefined && UNVERIFIED_LOD_PRODUCT_IDS.has(productId))
|
|
79
|
+
return false;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
/** Whether this format has a reversible guided write probe. */
|
|
83
|
+
export function supportsProfileWriteProbe(profileFormatId) {
|
|
84
|
+
return profileFormatId !== null
|
|
85
|
+
&& profileFormatId !== undefined
|
|
86
|
+
&& PROFILE_WRITE_PROBE_FORMATS.has(profileFormatId);
|
|
87
|
+
}
|
|
88
|
+
/** Whether a complete, byte-for-byte reset image exists for this format. */
|
|
89
|
+
export function supportsFactoryReset(profileFormatId) {
|
|
90
|
+
return profileFormatId !== null
|
|
91
|
+
&& profileFormatId !== undefined
|
|
92
|
+
&& FACTORY_RESET_FORMATS.has(profileFormatId);
|
|
93
|
+
}
|
|
94
|
+
export function describeProfileFormat(profileFormatId) {
|
|
95
|
+
return {
|
|
96
|
+
id: profileFormatId,
|
|
97
|
+
name: PROFILE_FORMAT_NAMES[profileFormatId] ?? "unknown",
|
|
98
|
+
base: profileFormatId >= 6 ? "v6" : "v1",
|
|
99
|
+
supported: profileFormatId >= 1 && profileFormatId <= 8,
|
|
100
|
+
verified: VERIFIED_FORMATS.has(profileFormatId),
|
|
101
|
+
writable: WRITABLE_FORMATS.has(profileFormatId),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** The name region is 0x30 bytes of UTF-16LE: 24 units, one kept for the
|
|
105
|
+
* terminator so a full-length name still reads back with a clear end. */
|
|
106
|
+
const PROFILE_NAME_BYTES = 0x30;
|
|
107
|
+
export const PROFILE_NAME_MAX_CHARS = PROFILE_NAME_BYTES / 2 - 1;
|
|
108
|
+
/**
|
|
109
|
+
* Feature 0x2202's lift-off enum: 0 means the sensor has no lift-off control,
|
|
110
|
+
* and the levels count from one. This belongs to the feature, not to a profile
|
|
111
|
+
* format — every device that exposes 0x2202 uses the same numbering.
|
|
112
|
+
*
|
|
113
|
+
* Confirmed twice over: captured from G HUB writing a Pro X Superlight 2
|
|
114
|
+
* profile (02 -> 01 for medium to low, 01 -> 03 for low to high), and matching
|
|
115
|
+
* OpenLogi's Lod enum { NotSupported = 0, Low = 1, Medium = 2, High = 3 }.
|
|
116
|
+
*
|
|
117
|
+
* The driver previously counted from zero, which made "Low" write 0 — a value
|
|
118
|
+
* meaning "unsupported" that the mouse rejects — and reported every level one
|
|
119
|
+
* step too high.
|
|
120
|
+
*/
|
|
121
|
+
const LOD_ENCODING = { Low: 1, Medium: 2, High: 3 };
|
|
122
|
+
/**
|
|
123
|
+
* Applied when the mouse does not report a format, or reports one whose limits
|
|
124
|
+
* were never established.
|
|
125
|
+
*
|
|
126
|
+
* Only two levels are offered because no third was ever confirmed on such a
|
|
127
|
+
* device — not because Low is unreachable. That was the old off-by-one talking:
|
|
128
|
+
* Low used to write 0, which means "no lift-off control", so it always failed.
|
|
129
|
+
*/
|
|
130
|
+
const DEFAULT_FORMAT_CAPABILITIES = {
|
|
131
|
+
supportedLods: ["Medium", "High"],
|
|
132
|
+
lodEncoding: LOD_ENCODING,
|
|
133
|
+
// Base v1 has a scalar DPI table, but its sensor-specific conversion and
|
|
134
|
+
// writable range are not verified. Unknown formats therefore offer no edits.
|
|
135
|
+
dpiStages: null,
|
|
136
|
+
// Ceilings are a property of the radio and the USB interface, so they cannot
|
|
137
|
+
// be carried over from another mouse.
|
|
138
|
+
reportRates: null,
|
|
139
|
+
maxNameLength: null,
|
|
140
|
+
bunnyHop: false,
|
|
141
|
+
};
|
|
142
|
+
const FORMAT_CAPABILITIES = {
|
|
143
|
+
// Format 1 capture: five scalar slots on a 252-4032 grid in steps of 84.
|
|
144
|
+
1: {
|
|
145
|
+
supportedLods: [],
|
|
146
|
+
lodEncoding: LOD_ENCODING,
|
|
147
|
+
dpiStages: { maxStages: 5, minDpi: 252, maxDpi: 4032, stepDpi: 84 },
|
|
148
|
+
reportRates: { wirelessMaxHz: 1000, wiredMaxHz: 1000 },
|
|
149
|
+
maxNameLength: null,
|
|
150
|
+
bunnyHop: false,
|
|
151
|
+
},
|
|
152
|
+
// LOGAN is shared by G502 generations. The HERO capture advertises the
|
|
153
|
+
// widest observed grid; callers narrow it to the connected sensor's live
|
|
154
|
+
// DPI list so an older 12K G502 is never offered the HERO's 25.6K ceiling.
|
|
155
|
+
2: {
|
|
156
|
+
supportedLods: [],
|
|
157
|
+
lodEncoding: LOD_ENCODING,
|
|
158
|
+
dpiStages: { maxStages: 5, minDpi: 100, maxDpi: 25600, stepDpi: 50 },
|
|
159
|
+
reportRates: { wirelessMaxHz: 1000, wiredMaxHz: 1000 },
|
|
160
|
+
maxNameLength: PROFILE_NAME_MAX_CHARS,
|
|
161
|
+
bunnyHop: false,
|
|
162
|
+
},
|
|
163
|
+
// A G703 on format 3 reported four of five scalar slots, 50-12000 DPI in
|
|
164
|
+
// steps of 50, and 125/250/500/1000 Hz. Name, default-slot DPI, and wired
|
|
165
|
+
// rate writes were applied, confirmed live, and restored.
|
|
166
|
+
3: {
|
|
167
|
+
supportedLods: [],
|
|
168
|
+
lodEncoding: LOD_ENCODING,
|
|
169
|
+
dpiStages: { maxStages: 5, minDpi: 50, maxDpi: 12000, stepDpi: 50 },
|
|
170
|
+
reportRates: { wirelessMaxHz: 1000, wiredMaxHz: 1000 },
|
|
171
|
+
maxNameLength: PROFILE_NAME_MAX_CHARS,
|
|
172
|
+
bunnyHop: false,
|
|
173
|
+
},
|
|
174
|
+
// A G102 LIGHTSYNC on format 4 reported five scalar slots, 50-8000 DPI in
|
|
175
|
+
// steps of 50, and 125/250/500/1000 Hz through the capability collector.
|
|
176
|
+
// Its DPI, rate, and name writes were applied, read back live, and restored.
|
|
177
|
+
4: {
|
|
178
|
+
supportedLods: [],
|
|
179
|
+
lodEncoding: LOD_ENCODING,
|
|
180
|
+
dpiStages: { maxStages: 5, minDpi: 50, maxDpi: 8000, stepDpi: 50 },
|
|
181
|
+
reportRates: { wirelessMaxHz: 1000, wiredMaxHz: 1000 },
|
|
182
|
+
maxNameLength: PROFILE_NAME_MAX_CHARS,
|
|
183
|
+
bunnyHop: false,
|
|
184
|
+
},
|
|
185
|
+
// Pro X Superlight 2. Three levels, counted from one. Five slots, from the
|
|
186
|
+
// 5x5 stage table in the registration and confirmed against a real profile.
|
|
187
|
+
7: {
|
|
188
|
+
supportedLods: ["Low", "Medium", "High"],
|
|
189
|
+
lodEncoding: LOD_ENCODING,
|
|
190
|
+
dpiStages: { maxStages: 5, minDpi: 100, maxDpi: 32000, stepDpi: 50 },
|
|
191
|
+
// Reported on hardware: the Lightspeed link runs to 8 kHz, the USB cable
|
|
192
|
+
// is a 1 kHz charging connection rather than a full-rate wired mode.
|
|
193
|
+
reportRates: { wirelessMaxHz: 8000, wiredMaxHz: 1000 },
|
|
194
|
+
maxNameLength: PROFILE_NAME_MAX_CHARS,
|
|
195
|
+
bunnyHop: true,
|
|
196
|
+
},
|
|
197
|
+
// Format 8 carries the analog-button block, so it is the PRO X 2 Superstrike
|
|
198
|
+
// format. Its two levels are what the driver has always offered and were
|
|
199
|
+
// never checked against a real device; its sensor range was never captured
|
|
200
|
+
// either, so slots stay unavailable rather than being assumed to match
|
|
201
|
+
// format 7.
|
|
202
|
+
8: {
|
|
203
|
+
supportedLods: ["Low", "High"],
|
|
204
|
+
lodEncoding: LOD_ENCODING,
|
|
205
|
+
dpiStages: null,
|
|
206
|
+
// Captured behavior: the wireless link reaches 8 kHz while USB is capped
|
|
207
|
+
// at 1 kHz. Transport selection is resolved from HID++ identity data.
|
|
208
|
+
reportRates: { wirelessMaxHz: 8000, wiredMaxHz: 1000 },
|
|
209
|
+
// The name region is part of base v6, which format 8 shares.
|
|
210
|
+
maxNameLength: PROFILE_NAME_MAX_CHARS,
|
|
211
|
+
bunnyHop: true,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
function greatestCommonDivisor(left, right) {
|
|
215
|
+
let a = Math.abs(left);
|
|
216
|
+
let b = Math.abs(right);
|
|
217
|
+
while (b !== 0)
|
|
218
|
+
[a, b] = [b, a % b];
|
|
219
|
+
return a;
|
|
220
|
+
}
|
|
221
|
+
/** Narrows format storage limits to the DPI grid advertised by this sensor. */
|
|
222
|
+
export function dpiStageCapabilitiesForOptions(formatCapabilities, dpiOptions) {
|
|
223
|
+
if (formatCapabilities === null || dpiOptions.length === 0)
|
|
224
|
+
return formatCapabilities;
|
|
225
|
+
const values = [...new Set(dpiOptions)]
|
|
226
|
+
.filter((value) => Number.isInteger(value) && value > 0)
|
|
227
|
+
.sort((left, right) => left - right);
|
|
228
|
+
if (values.length === 0)
|
|
229
|
+
return formatCapabilities;
|
|
230
|
+
const stepDpi = values.reduce(greatestCommonDivisor);
|
|
231
|
+
return {
|
|
232
|
+
maxStages: formatCapabilities.maxStages,
|
|
233
|
+
minDpi: values[0],
|
|
234
|
+
maxDpi: values.at(-1),
|
|
235
|
+
stepDpi: stepDpi || formatCapabilities.stepDpi,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
export function capabilitiesForFormat(profileFormatId) {
|
|
239
|
+
if (profileFormatId === null || profileFormatId === undefined)
|
|
240
|
+
return DEFAULT_FORMAT_CAPABILITIES;
|
|
241
|
+
return FORMAT_CAPABILITIES[profileFormatId] ?? DEFAULT_FORMAT_CAPABILITIES;
|
|
242
|
+
}
|
|
243
|
+
/** Turns a raw 0x2202 lift-off byte back into a level for the given format. */
|
|
244
|
+
export function decodeLiftOffLevel(raw, capabilities) {
|
|
245
|
+
if (raw === null || raw === undefined)
|
|
246
|
+
return null;
|
|
247
|
+
const level = Object.keys(capabilities.lodEncoding)
|
|
248
|
+
.find((name) => capabilities.lodEncoding[name] === raw);
|
|
249
|
+
// 0 is the feature's "not supported" value, and anything else unmapped is
|
|
250
|
+
// simply unknown. Either way it is reported as no level rather than guessed
|
|
251
|
+
// at — reading 0 as "Low" is how the off-by-one used to hide itself.
|
|
252
|
+
return level ?? null;
|
|
253
|
+
}
|
|
254
|
+
/** Encodes the HID++ macro format used by G502 onboard-memory sectors. */
|
|
255
|
+
export function encodeMacroSector(sectorSize, steps) {
|
|
256
|
+
if (!Number.isInteger(sectorSize) || sectorSize < 6 || !steps.length) {
|
|
257
|
+
throw new Error("The macro sector geometry or sequence is invalid.");
|
|
258
|
+
}
|
|
259
|
+
const records = [];
|
|
260
|
+
for (const [index, step] of steps.entries()) {
|
|
261
|
+
if (!Number.isInteger(step.key) || step.key < 1 || step.key > 0xff
|
|
262
|
+
|| !Number.isInteger(step.modifiers) || step.modifiers < 0 || step.modifiers > 0xff
|
|
263
|
+
|| !Number.isInteger(step.delayMs) || step.delayMs < 0 || step.delayMs > 0xffff) {
|
|
264
|
+
throw new Error("The macro contains an invalid key, modifier, or delay.");
|
|
265
|
+
}
|
|
266
|
+
if (index > 0 && step.delayMs > 0)
|
|
267
|
+
records.push([0x40, step.delayMs >> 8, step.delayMs & 0xff]);
|
|
268
|
+
const modifiers = Array.from({ length: 8 }, (_, bit) => 1 << bit)
|
|
269
|
+
.filter((bit) => (step.modifiers & bit) !== 0);
|
|
270
|
+
for (const modifier of modifiers)
|
|
271
|
+
records.push([0x43, modifier, 0x00]);
|
|
272
|
+
records.push([0x43, 0x00, step.key], [0x44, 0x00, step.key]);
|
|
273
|
+
for (const modifier of modifiers.reverse())
|
|
274
|
+
records.push([0x44, modifier, 0x00]);
|
|
275
|
+
}
|
|
276
|
+
records.push([0xff, 0xff, 0xff]);
|
|
277
|
+
if (records.length * 3 > sectorSize)
|
|
278
|
+
throw new Error("That sequence is too long for one onboard macro sector.");
|
|
279
|
+
const result = new Uint8Array(sectorSize).fill(0xff);
|
|
280
|
+
records.forEach((record, index) => result.set(record, index * 3));
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
export function encodeMacroButtonAssignment(profile, profileFormatId, layer, button, macroSector) {
|
|
284
|
+
if (!Number.isInteger(macroSector) || macroSector < 1 || macroSector > 0xff) {
|
|
285
|
+
throw new Error("The macro sector cannot be represented by this profile format.");
|
|
286
|
+
}
|
|
287
|
+
const name = layer === "primary" ? "button_functions" : "g_shift_function";
|
|
288
|
+
const component = componentsForFormat(profileFormatId).find((candidate) => candidate.name === name);
|
|
289
|
+
if (!component || !Number.isInteger(button) || button < 0 || button >= component.size / 4) {
|
|
290
|
+
throw new Error("That button is outside this profile format's assignment table.");
|
|
291
|
+
}
|
|
292
|
+
const result = profile.slice();
|
|
293
|
+
result.set([0x00, button & 0xff, macroSector, 0x00], component.offset + button * 4);
|
|
294
|
+
return applyCrc(result);
|
|
295
|
+
}
|
|
296
|
+
export const LOGITECH_BUTTON_ACTIONS = [
|
|
297
|
+
"Disabled", "Left click", "Right click", "Middle click", "Back", "Forward",
|
|
298
|
+
"Tilt left", "Tilt right", "Next DPI", "Previous DPI", "Cycle DPI", "Default DPI",
|
|
299
|
+
"DPI Shift", "Next profile", "Previous profile", "Cycle profiles", "G-Shift", "Battery indicator",
|
|
300
|
+
];
|
|
301
|
+
const ACTION_RECORDS = {
|
|
302
|
+
Disabled: [0xff, 0xff, 0xff, 0xff],
|
|
303
|
+
"Left click": [0x80, 0x01, 0x00, 0x01],
|
|
304
|
+
"Right click": [0x80, 0x01, 0x00, 0x02],
|
|
305
|
+
"Middle click": [0x80, 0x01, 0x00, 0x04],
|
|
306
|
+
Back: [0x80, 0x01, 0x00, 0x08],
|
|
307
|
+
Forward: [0x80, 0x01, 0x00, 0x10],
|
|
308
|
+
"Tilt left": [0x90, 0x01, 0x00, 0x00],
|
|
309
|
+
"Tilt right": [0x90, 0x02, 0x00, 0x00],
|
|
310
|
+
"Next DPI": [0x90, 0x03, 0x00, 0x00],
|
|
311
|
+
"Previous DPI": [0x90, 0x04, 0x00, 0x00],
|
|
312
|
+
"Cycle DPI": [0x90, 0x05, 0x00, 0x00],
|
|
313
|
+
"Default DPI": [0x90, 0x06, 0x00, 0x00],
|
|
314
|
+
"DPI Shift": [0x90, 0x07, 0x00, 0x00],
|
|
315
|
+
"Next profile": [0x90, 0x08, 0x00, 0x00],
|
|
316
|
+
"Previous profile": [0x90, 0x09, 0x00, 0x00],
|
|
317
|
+
"Cycle profiles": [0x90, 0x0a, 0x00, 0x00],
|
|
318
|
+
"G-Shift": [0x90, 0x0b, 0x00, 0x00],
|
|
319
|
+
"Battery indicator": [0x90, 0x0c, 0x00, 0x00],
|
|
320
|
+
};
|
|
321
|
+
function decodeButtonAssignments(bytes, component) {
|
|
322
|
+
const result = [];
|
|
323
|
+
for (let button = 0; button < component.size / 4; button += 1) {
|
|
324
|
+
const raw = [...bytes.slice(component.offset + button * 4, component.offset + button * 4 + 4)];
|
|
325
|
+
if (raw.length < 4)
|
|
326
|
+
break;
|
|
327
|
+
const action = Object.entries(ACTION_RECORDS)
|
|
328
|
+
.find(([, record]) => record.every((value, index) => value === raw[index]))?.[0] ?? "Custom";
|
|
329
|
+
result.push({ button, action, raw });
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
export function encodeButtonAssignment(sector, profileFormatId, layer, button, binding) {
|
|
334
|
+
const name = layer === "primary" ? "button_functions" : "g_shift_function";
|
|
335
|
+
const component = componentsForFormat(profileFormatId).find((candidate) => candidate.name === name);
|
|
336
|
+
if (!component || !Number.isInteger(button) || button < 0 || button >= component.size / 4) {
|
|
337
|
+
throw new Error("That button is outside this profile format's assignment table.");
|
|
338
|
+
}
|
|
339
|
+
const normalized = typeof binding === "string"
|
|
340
|
+
? { kind: "action", action: binding }
|
|
341
|
+
: binding;
|
|
342
|
+
const record = normalized.kind === "action"
|
|
343
|
+
? ACTION_RECORDS[normalized.action]
|
|
344
|
+
: normalized.kind === "keyboard"
|
|
345
|
+
? [0x80, 0x02, normalized.modifiers & 0xff, normalized.key & 0xff]
|
|
346
|
+
: [0x80, 0x03, (normalized.usage >> 8) & 0xff, normalized.usage & 0xff];
|
|
347
|
+
const result = sector.slice();
|
|
348
|
+
result.set(record, component.offset + button * 4);
|
|
349
|
+
return applyCrc(result);
|
|
350
|
+
}
|
|
351
|
+
/** Report-rate bytes index this table, matching 0x8061's ordering. */
|
|
352
|
+
const REPORT_RATE_HZ = [125, 250, 500, 1000, 2000, 4000, 8000];
|
|
353
|
+
const LAYOUT_V1 = {
|
|
354
|
+
// Formats 1-5 store one shared report interval, regardless of transport.
|
|
355
|
+
reportRateWireless: 0x00,
|
|
356
|
+
reportRateWired: 0x00,
|
|
357
|
+
dpi: 0x01,
|
|
358
|
+
angleSnapping: 0x11,
|
|
359
|
+
powerSaveTimeout: 0x1c,
|
|
360
|
+
powerOffTimeout: 0x1e,
|
|
361
|
+
profileName: 0xa0,
|
|
362
|
+
bunnyHopping: null,
|
|
363
|
+
};
|
|
364
|
+
const LAYOUT_V6 = {
|
|
365
|
+
reportRateWireless: 0x00,
|
|
366
|
+
reportRateWired: 0x01,
|
|
367
|
+
dpi: 0x02,
|
|
368
|
+
angleSnapping: 0x22,
|
|
369
|
+
powerSaveTimeout: 0x2c,
|
|
370
|
+
powerOffTimeout: 0x2e,
|
|
371
|
+
profileName: 0xa0,
|
|
372
|
+
bunnyHopping: null,
|
|
373
|
+
};
|
|
374
|
+
export function layoutForFormat(profileFormatId) {
|
|
375
|
+
const base = profileFormatId >= 6 ? LAYOUT_V6 : LAYOUT_V1;
|
|
376
|
+
return { ...base, bunnyHopping: profileFormatId >= 7 ? 0x25 : null };
|
|
377
|
+
}
|
|
378
|
+
const COMPONENTS_V1 = [
|
|
379
|
+
{ offset: 0x00, size: 1, name: "report_rate" },
|
|
380
|
+
{ offset: 0x01, size: 0x0c, name: "dpi_v1" },
|
|
381
|
+
{ offset: 0x0d, size: 3, name: "color" },
|
|
382
|
+
{ offset: 0x10, size: 1, name: "power_mode" },
|
|
383
|
+
{ offset: 0x11, size: 1, name: "angle_snapping" },
|
|
384
|
+
{ offset: 0x1c, size: 2, name: "power_save_timeout" },
|
|
385
|
+
{ offset: 0x1e, size: 2, name: "power_off_timeout" },
|
|
386
|
+
{ offset: 0x20, size: 0x40, name: "button_functions" },
|
|
387
|
+
{ offset: 0x60, size: 0x40, name: "g_shift_function" },
|
|
388
|
+
{ offset: 0xa0, size: 0x30, name: "profile_name" },
|
|
389
|
+
];
|
|
390
|
+
const COMPONENTS_V6 = [
|
|
391
|
+
{ offset: 0x00, size: 1, name: "report_rate_wireless" },
|
|
392
|
+
{ offset: 0x01, size: 1, name: "report_rate_wired" },
|
|
393
|
+
{ offset: 0x02, size: 0x1b, name: "dpi_v6" },
|
|
394
|
+
{ offset: 0x1d, size: 4, name: "dpi_delta" },
|
|
395
|
+
{ offset: 0x21, size: 1, name: "power_mode" },
|
|
396
|
+
{ offset: 0x22, size: 1, name: "angle_snapping" },
|
|
397
|
+
{ offset: 0x23, size: 2, name: "write_counter" },
|
|
398
|
+
{ offset: 0x2c, size: 2, name: "power_save_timeout" },
|
|
399
|
+
{ offset: 0x2e, size: 2, name: "power_off_timeout" },
|
|
400
|
+
{ offset: 0x30, size: 0x30, name: "button_functions" },
|
|
401
|
+
{ offset: 0x70, size: 0x30, name: "g_shift_function" },
|
|
402
|
+
{ offset: 0xa0, size: 0x30, name: "profile_name" },
|
|
403
|
+
{ offset: 0xd0, size: 0x0b, name: "lighting cluster_0_active" },
|
|
404
|
+
{ offset: 0xdb, size: 0x0b, name: "lighting cluster_1_active" },
|
|
405
|
+
{ offset: 0xe6, size: 0x0b, name: "lighting cluster_0_passive" },
|
|
406
|
+
{ offset: 0xf1, size: 0x0b, name: "lighting cluster_1_passive" },
|
|
407
|
+
{ offset: 0xfc, size: 1, name: "lighting_flag" },
|
|
408
|
+
];
|
|
409
|
+
const BUNNY_HOPPING = { offset: 0x25, size: 1, name: "bunny_hopping" };
|
|
410
|
+
const ANALOG_BUTTON = { offset: 0x26, size: 6, name: "analog_button" };
|
|
411
|
+
export function componentsForFormat(profileFormatId) {
|
|
412
|
+
const components = profileFormatId >= 6 ? [...COMPONENTS_V6] : [...COMPONENTS_V1];
|
|
413
|
+
if (profileFormatId >= 7)
|
|
414
|
+
components.push(BUNNY_HOPPING);
|
|
415
|
+
if (profileFormatId >= 8)
|
|
416
|
+
components.push(ANALOG_BUTTON);
|
|
417
|
+
return components.sort((left, right) => left.offset - right.offset);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Names the field a byte belongs to, so a diff reads as "dpi_v6 +2" rather
|
|
421
|
+
* than a bare offset. Returns null for bytes no component claims.
|
|
422
|
+
*/
|
|
423
|
+
export function describeOffset(profileFormatId, offset, sectorSize = 255) {
|
|
424
|
+
if (offset >= sectorSize - 2)
|
|
425
|
+
return "checksum";
|
|
426
|
+
const owner = componentsForFormat(profileFormatId)
|
|
427
|
+
.find((component) => offset >= component.offset && offset < component.offset + component.size);
|
|
428
|
+
return owner ? `${owner.name} +${offset - owner.offset}` : null;
|
|
429
|
+
}
|
|
430
|
+
export function parseProfilesInfo(reply) {
|
|
431
|
+
return {
|
|
432
|
+
memoryModelId: reply[3] ?? 0,
|
|
433
|
+
profileFormatId: reply[4] ?? 0,
|
|
434
|
+
macroFormatId: reply[5] ?? 0,
|
|
435
|
+
profileCount: reply[6] ?? 0,
|
|
436
|
+
buttonCount: reply[8] ?? 0,
|
|
437
|
+
sectorCount: reply[9] ?? 0,
|
|
438
|
+
sectorSize: ((reply[10] ?? 0) << 8) | (reply[11] ?? 0),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/** Directory entries are 4 bytes: sector (big-endian), enabled, reserved. */
|
|
442
|
+
export function parseDirectory(bytes) {
|
|
443
|
+
const entries = [];
|
|
444
|
+
for (let offset = 0; offset + 4 <= bytes.length; offset += 4) {
|
|
445
|
+
const sector = ((bytes[offset] ?? 0) << 8) | (bytes[offset + 1] ?? 0);
|
|
446
|
+
if (sector === 0xffff || sector === 0x0000)
|
|
447
|
+
break;
|
|
448
|
+
entries.push({ sector, enabled: bytes[offset + 2] === 0x01 });
|
|
449
|
+
}
|
|
450
|
+
return entries;
|
|
451
|
+
}
|
|
452
|
+
/** CRC-16/CCITT-FALSE over everything but the trailing two checksum bytes. */
|
|
453
|
+
export function profileCrc(bytes) {
|
|
454
|
+
let crc = 0xffff;
|
|
455
|
+
for (let index = 0; index < bytes.length - 2; index += 1) {
|
|
456
|
+
crc ^= (bytes[index] ?? 0) << 8;
|
|
457
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
458
|
+
crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return crc;
|
|
462
|
+
}
|
|
463
|
+
export function storedCrc(bytes) {
|
|
464
|
+
return ((bytes[bytes.length - 2] ?? 0) << 8) | (bytes[bytes.length - 1] ?? 0);
|
|
465
|
+
}
|
|
466
|
+
/** Writes the checksum into the last two bytes, big-endian. */
|
|
467
|
+
export function applyCrc(bytes) {
|
|
468
|
+
const crc = profileCrc(bytes);
|
|
469
|
+
bytes[bytes.length - 2] = (crc >> 8) & 0xff;
|
|
470
|
+
bytes[bytes.length - 1] = crc & 0xff;
|
|
471
|
+
return bytes;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Complete factory profile captured after G HUB/Onboard Memory Manager's
|
|
475
|
+
* "reset all profiles" action on profile format 7. This is intentionally an
|
|
476
|
+
* exact sector image rather than a list of guessed defaults: it resets button
|
|
477
|
+
* assignments, G-Shift, lighting and currently-undecoded fields as well as the
|
|
478
|
+
* settings OpenMouse exposes in the UI.
|
|
479
|
+
*/
|
|
480
|
+
const FACTORY_PROFILE_FORMAT_7 = `
|
|
481
|
+
03 03 00 00 20 03 20 03 02 b0 04 b0 04 02 40 06
|
|
482
|
+
40 06 02 60 09 60 09 02 80 0c 80 0c 02 00 00 00
|
|
483
|
+
00 ff 00 ff ff ff ff ff ff ff ff ff 3c 00 2c 01
|
|
484
|
+
80 01 00 01 80 01 00 02 80 01 00 04 80 01 00 08
|
|
485
|
+
80 01 00 10 ff ff ff ff ff ff ff ff ff ff ff ff
|
|
486
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
487
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
488
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
489
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
490
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
491
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
492
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
493
|
+
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
|
494
|
+
03 00 00 00 00 00 1f 40 00 00 00 03 00 00 00 00
|
|
495
|
+
00 1f 40 00 00 00 03 00 00 00 00 00 1f 40 32 00
|
|
496
|
+
00 03 00 00 00 00 00 1f 40 32 00 00 03 84 db
|
|
497
|
+
`;
|
|
498
|
+
/** Returns a fresh, CRC-valid factory sector only for a captured geometry. */
|
|
499
|
+
export function factoryProfileForFormat(profileFormatId, sectorSize) {
|
|
500
|
+
if (!supportsFactoryReset(profileFormatId) || sectorSize !== 255)
|
|
501
|
+
return null;
|
|
502
|
+
return Uint8Array.from(FACTORY_PROFILE_FORMAT_7.trim().split(/\s+/), (byte) => Number.parseInt(byte, 16));
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Returns a copy of the directory sector with one profile's enabled flag
|
|
506
|
+
* changed and the checksum recomputed. Every other byte is carried through
|
|
507
|
+
* untouched, so unknown directory fields survive.
|
|
508
|
+
*/
|
|
509
|
+
export function setDirectoryEnabled(sector, profileSector, enabled) {
|
|
510
|
+
const updated = sector.slice();
|
|
511
|
+
for (let offset = 0; offset + 4 <= updated.length; offset += 4) {
|
|
512
|
+
const entry = ((updated[offset] ?? 0) << 8) | (updated[offset + 1] ?? 0);
|
|
513
|
+
if (entry === 0xffff || entry === 0x0000)
|
|
514
|
+
break;
|
|
515
|
+
if (entry === profileSector) {
|
|
516
|
+
updated[offset + 2] = enabled ? 0x01 : 0x00;
|
|
517
|
+
return applyCrc(updated);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`Profile sector ${profileSector} is not listed in the directory.`);
|
|
521
|
+
}
|
|
522
|
+
function readUint16LE(bytes, offset) {
|
|
523
|
+
const low = bytes[offset];
|
|
524
|
+
const high = bytes[offset + 1];
|
|
525
|
+
if (low === undefined || high === undefined)
|
|
526
|
+
return null;
|
|
527
|
+
const value = low | (high << 8);
|
|
528
|
+
// Unwritten flash reads as 0xffff.
|
|
529
|
+
return value === 0xffff ? null : value;
|
|
530
|
+
}
|
|
531
|
+
/** Profile names are UTF-16LE, despite the library calling them "utf8". */
|
|
532
|
+
function decodeName(bytes, offset) {
|
|
533
|
+
const slice = bytes.slice(offset, offset + 0x30);
|
|
534
|
+
if (slice.length === 0 || slice.every((byte) => byte === 0xff))
|
|
535
|
+
return null;
|
|
536
|
+
const text = new TextDecoder("utf-16le").decode(slice).split("\0")[0].trim();
|
|
537
|
+
return text.length > 0 ? text : null;
|
|
538
|
+
}
|
|
539
|
+
function decodeDpi(bytes, offset) {
|
|
540
|
+
const defaultIndex = bytes[offset] ?? null;
|
|
541
|
+
const stages = [];
|
|
542
|
+
for (let stage = 0; stage < 5; stage += 1) {
|
|
543
|
+
const base = offset + 2 + stage * 5;
|
|
544
|
+
const x = readUint16LE(bytes, base);
|
|
545
|
+
const y = readUint16LE(bytes, base + 2);
|
|
546
|
+
const lod = bytes[base + 4];
|
|
547
|
+
if (x === null || y === null || lod === undefined || x === 0)
|
|
548
|
+
continue;
|
|
549
|
+
stages.push({ x, y, lod });
|
|
550
|
+
}
|
|
551
|
+
return { stages, defaultIndex: defaultIndex === 0xff ? null : defaultIndex };
|
|
552
|
+
}
|
|
553
|
+
/** Formats 1-5 store one little-endian DPI value per slot, without X/Y or LOD. */
|
|
554
|
+
function decodeLegacyDpi(bytes, offset) {
|
|
555
|
+
const rawDefaultIndex = bytes[offset];
|
|
556
|
+
const stages = [];
|
|
557
|
+
for (let stage = 0; stage < DPI_STAGE_SLOTS; stage += 1) {
|
|
558
|
+
const dpi = readUint16LE(bytes, offset + 2 + stage * 2);
|
|
559
|
+
if (dpi === null || dpi === 0)
|
|
560
|
+
continue;
|
|
561
|
+
stages.push({ x: dpi, y: dpi, lod: 0 });
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
stages,
|
|
565
|
+
defaultIndex: rawDefaultIndex === undefined || rawDefaultIndex === 0xff ? null : rawDefaultIndex,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function decodeReportRate(bytes, offset) {
|
|
569
|
+
if (offset === null)
|
|
570
|
+
return null;
|
|
571
|
+
const raw = bytes[offset];
|
|
572
|
+
return raw === undefined || raw === 0xff ? null : REPORT_RATE_HZ[raw] ?? null;
|
|
573
|
+
}
|
|
574
|
+
/** Formats 1-5 store the USB polling interval in milliseconds. */
|
|
575
|
+
function decodeLegacyReportRate(bytes, offset) {
|
|
576
|
+
if (offset === null)
|
|
577
|
+
return null;
|
|
578
|
+
const intervalMs = bytes[offset];
|
|
579
|
+
if (intervalMs === undefined || intervalMs === 0xff || intervalMs === 0)
|
|
580
|
+
return null;
|
|
581
|
+
const hz = 1000 / intervalMs;
|
|
582
|
+
return Number.isInteger(hz) ? hz : null;
|
|
583
|
+
}
|
|
584
|
+
/** Rates the byte can index, filtered to the ceiling for that connection. */
|
|
585
|
+
export function reportRatesFor(capabilities, link) {
|
|
586
|
+
if (!capabilities)
|
|
587
|
+
return [];
|
|
588
|
+
const ceiling = link === "wired" ? capabilities.wiredMaxHz : capabilities.wirelessMaxHz;
|
|
589
|
+
return REPORT_RATE_HZ.filter((rate) => rate <= ceiling);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Intersects format storage support with the rates advertised by the active
|
|
593
|
+
* hardware link. Legacy formats share one interval byte, so their live list
|
|
594
|
+
* applies to the whole profile; v6 formats retain the captured limit for the
|
|
595
|
+
* inactive link because the mouse cannot advertise that link while disconnected.
|
|
596
|
+
*/
|
|
597
|
+
export function reportRatesForDevice(capabilities, link, liveRates, activeLink, sharedInterval) {
|
|
598
|
+
const storedRates = reportRatesFor(capabilities, link);
|
|
599
|
+
if (!sharedInterval && link !== activeLink)
|
|
600
|
+
return storedRates;
|
|
601
|
+
return storedRates.filter((rate) => liveRates.includes(rate));
|
|
602
|
+
}
|
|
603
|
+
export function validateReportRate(hz, capabilities, link) {
|
|
604
|
+
const allowed = reportRatesFor(capabilities, link);
|
|
605
|
+
if (allowed.length === 0)
|
|
606
|
+
return "Report rates are not known for this profile format.";
|
|
607
|
+
if (!allowed.includes(hz)) {
|
|
608
|
+
return `${link === "wired" ? "Wired" : "Wireless"} report rate must be one of ${allowed.join(", ")} Hz.`;
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Returns a copy of the sector with one connection's report rate replaced and
|
|
614
|
+
* the checksum reapplied. The two links are stored separately, so setting one
|
|
615
|
+
* leaves the other alone.
|
|
616
|
+
*/
|
|
617
|
+
export function encodeReportRate(sector, profileFormatId, link, hz, capabilitiesOverride) {
|
|
618
|
+
const capabilities = capabilitiesOverride === undefined
|
|
619
|
+
? capabilitiesForFormat(profileFormatId).reportRates
|
|
620
|
+
: capabilitiesOverride;
|
|
621
|
+
const invalid = validateReportRate(hz, capabilities, link);
|
|
622
|
+
if (invalid)
|
|
623
|
+
throw new Error(invalid);
|
|
624
|
+
const layout = layoutForFormat(profileFormatId);
|
|
625
|
+
const offset = link === "wired" ? layout.reportRateWired : layout.reportRateWireless;
|
|
626
|
+
if (offset === null)
|
|
627
|
+
throw new Error("This profile format has no report-rate setting.");
|
|
628
|
+
const result = sector.slice();
|
|
629
|
+
// Base v1 (formats 1-5) stores the USB polling interval in milliseconds, the
|
|
630
|
+
// same encoding legacy 0x8060 uses; base v6 and later index the rate table
|
|
631
|
+
// 0x8061 shares. Writing the wrong one would read back as another rate.
|
|
632
|
+
result[offset] = profileFormatId < 6
|
|
633
|
+
? Math.round(1000 / hz)
|
|
634
|
+
: REPORT_RATE_HZ.indexOf(hz);
|
|
635
|
+
return applyCrc(result);
|
|
636
|
+
}
|
|
637
|
+
/** Returns why a profile name cannot be stored, or null when it fits. */
|
|
638
|
+
export function validateProfileName(name, maxLength) {
|
|
639
|
+
if (maxLength === null)
|
|
640
|
+
return "This profile format has no name field.";
|
|
641
|
+
const trimmed = name.trim();
|
|
642
|
+
if (trimmed.length === 0)
|
|
643
|
+
return "Enter a profile name.";
|
|
644
|
+
if (trimmed.length > maxLength)
|
|
645
|
+
return `Profile names are at most ${maxLength} characters.`;
|
|
646
|
+
// The region is UTF-16 code units, so anything outside the BMP takes two of
|
|
647
|
+
// them and would be cut in half by the length check above.
|
|
648
|
+
if ([...trimmed].some((character) => (character.codePointAt(0) ?? 0) > 0xffff)) {
|
|
649
|
+
return "Profile names cannot contain emoji or other characters outside the basic range.";
|
|
650
|
+
}
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Returns a copy of the sector with the profile name replaced and the checksum
|
|
655
|
+
* reapplied. The region is zero-filled first so a shorter name cannot leave the
|
|
656
|
+
* tail of the previous one behind it.
|
|
657
|
+
*/
|
|
658
|
+
export function encodeProfileName(sector, profileFormatId, name, maxLengthOverride) {
|
|
659
|
+
const maxLength = maxLengthOverride === undefined
|
|
660
|
+
? capabilitiesForFormat(profileFormatId).maxNameLength
|
|
661
|
+
: maxLengthOverride;
|
|
662
|
+
const invalid = validateProfileName(name, maxLength);
|
|
663
|
+
if (invalid)
|
|
664
|
+
throw new Error(invalid);
|
|
665
|
+
const offset = layoutForFormat(profileFormatId).profileName;
|
|
666
|
+
const result = sector.slice();
|
|
667
|
+
result.fill(0, offset, offset + PROFILE_NAME_BYTES);
|
|
668
|
+
const trimmed = name.trim();
|
|
669
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
670
|
+
const code = trimmed.charCodeAt(index);
|
|
671
|
+
result[offset + index * 2] = code & 0xff;
|
|
672
|
+
result[offset + index * 2 + 1] = (code >> 8) & 0xff;
|
|
673
|
+
}
|
|
674
|
+
return applyCrc(result);
|
|
675
|
+
}
|
|
676
|
+
export const BUNNY_HOP_LIMITS = { minMs: 100, maxMs: 1000, stepMs: 10 };
|
|
677
|
+
/**
|
|
678
|
+
* Returns why a bunny-hop time is invalid, or null when it is acceptable.
|
|
679
|
+
* The byte stores ms / 10, so anything off-step cannot be represented.
|
|
680
|
+
*/
|
|
681
|
+
export function validateBunnyHoppingMs(milliseconds) {
|
|
682
|
+
if (!Number.isFinite(milliseconds) || !Number.isInteger(milliseconds)) {
|
|
683
|
+
return "Enter a whole number of milliseconds.";
|
|
684
|
+
}
|
|
685
|
+
if (milliseconds % BUNNY_HOP_LIMITS.stepMs !== 0) {
|
|
686
|
+
return `Bunny hop time must be a multiple of ${BUNNY_HOP_LIMITS.stepMs} ms.`;
|
|
687
|
+
}
|
|
688
|
+
if (milliseconds === 0)
|
|
689
|
+
return null; // off
|
|
690
|
+
if (milliseconds < BUNNY_HOP_LIMITS.minMs || milliseconds > BUNNY_HOP_LIMITS.maxMs) {
|
|
691
|
+
return `Bunny hop time must be between ${BUNNY_HOP_LIMITS.minMs} and ${BUNNY_HOP_LIMITS.maxMs} ms, or off.`;
|
|
692
|
+
}
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
/** The stage table always has room for five records; how many a format allows
|
|
696
|
+
* is a per-format limit, so this is only the size of the region to clear. */
|
|
697
|
+
const DPI_STAGE_SLOTS = 5;
|
|
698
|
+
/**
|
|
699
|
+
* Returns why a stage plan cannot be written to this format, or null when it
|
|
700
|
+
* is writable. Every limit comes from the format, so an older mouse holding
|
|
701
|
+
* fewer slots or a narrower DPI range is checked against its own numbers.
|
|
702
|
+
*/
|
|
703
|
+
export function validateDpiStagePlan(plan, capabilities, legacyScalar = false) {
|
|
704
|
+
if (capabilities === null) {
|
|
705
|
+
return "DPI slots are not known for this profile format.";
|
|
706
|
+
}
|
|
707
|
+
const { maxStages, minDpi, maxDpi, stepDpi } = capabilities;
|
|
708
|
+
if (plan.stages.length < 1 || plan.stages.length > maxStages) {
|
|
709
|
+
return `This profile holds 1 to ${maxStages} DPI slots.`;
|
|
710
|
+
}
|
|
711
|
+
if (!Number.isInteger(plan.defaultIndex) || plan.defaultIndex < 0 || plan.defaultIndex >= plan.stages.length) {
|
|
712
|
+
return "The default slot must be one of the slots in use.";
|
|
713
|
+
}
|
|
714
|
+
for (const [index, stage] of plan.stages.entries()) {
|
|
715
|
+
for (const [axis, value] of [["X", stage.x], ["Y", stage.y]]) {
|
|
716
|
+
if (!Number.isInteger(value) || value < minDpi || value > maxDpi) {
|
|
717
|
+
return `Slot ${index + 1} ${axis} DPI must be between ${minDpi} and ${maxDpi}.`;
|
|
718
|
+
}
|
|
719
|
+
if (value % stepDpi !== 0) {
|
|
720
|
+
return `Slot ${index + 1} ${axis} DPI must be a multiple of ${stepDpi}.`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (legacyScalar) {
|
|
724
|
+
if (stage.x !== stage.y)
|
|
725
|
+
return `Slot ${index + 1} must use the same X and Y DPI.`;
|
|
726
|
+
if (stage.lod !== 0)
|
|
727
|
+
return `Slot ${index + 1} cannot store lift-off distance.`;
|
|
728
|
+
}
|
|
729
|
+
else if (!Number.isInteger(stage.lod) || stage.lod < 1 || stage.lod > 3) {
|
|
730
|
+
// 0 is the "unused stage" marker on formats with per-stage lift-off.
|
|
731
|
+
return `Slot ${index + 1} has an invalid lift-off level.`;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Levels as stored inside a stage record, where 0 means the stage is unused.
|
|
738
|
+
* This is a property of the record itself, so it does not vary with whatever
|
|
739
|
+
* encoding feature 0x2202 happens to use on a given format.
|
|
740
|
+
*/
|
|
741
|
+
export const PROFILE_STAGE_LOD = { Low: 1, Medium: 2, High: 3 };
|
|
742
|
+
export function stageLodLevel(lod) {
|
|
743
|
+
return Object.keys(PROFILE_STAGE_LOD)
|
|
744
|
+
.find((level) => PROFILE_STAGE_LOD[level] === lod) ?? null;
|
|
745
|
+
}
|
|
746
|
+
/** Snaps a typed DPI onto the step and into the range the format allows. */
|
|
747
|
+
export function clampDpi(value, capabilities) {
|
|
748
|
+
const { minDpi, maxDpi, stepDpi } = capabilities;
|
|
749
|
+
if (!Number.isFinite(value))
|
|
750
|
+
return minDpi;
|
|
751
|
+
const stepped = Math.round(value / stepDpi) * stepDpi;
|
|
752
|
+
return Math.min(maxDpi, Math.max(minDpi, stepped));
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Returns a copy of the sector with the DPI stage table replaced and the
|
|
756
|
+
* checksum reapplied. Slots past the plan are zeroed, which is how the vendor
|
|
757
|
+
* marks a stage unused, so the slot count is simply how many stages are set.
|
|
758
|
+
*/
|
|
759
|
+
export function encodeDpiStages(sector, profileFormatId, plan, capabilitiesOverride,
|
|
760
|
+
/**
|
|
761
|
+
* False on hardware where the per-stage lift-off byte is unverified (see
|
|
762
|
+
* isLodWritableForProduct): the stage's x/y still get the plan's values,
|
|
763
|
+
* but its lift-off byte is left exactly as read rather than overwritten,
|
|
764
|
+
* since this format may not have lift-off storage there at all.
|
|
765
|
+
*/
|
|
766
|
+
writeLod = true) {
|
|
767
|
+
const legacyScalar = profileFormatId < 6;
|
|
768
|
+
const capabilities = capabilitiesOverride === undefined
|
|
769
|
+
? capabilitiesForFormat(profileFormatId).dpiStages
|
|
770
|
+
: capabilitiesOverride;
|
|
771
|
+
const invalid = validateDpiStagePlan(plan, capabilities, legacyScalar);
|
|
772
|
+
if (invalid)
|
|
773
|
+
throw new Error(invalid);
|
|
774
|
+
const layout = layoutForFormat(profileFormatId);
|
|
775
|
+
if (layout.dpi === null)
|
|
776
|
+
throw new Error("This profile format has no DPI stages.");
|
|
777
|
+
const result = sector.slice();
|
|
778
|
+
result[layout.dpi] = plan.defaultIndex;
|
|
779
|
+
// 0x03 is the g-shift index. It is left alone unless it now points past the
|
|
780
|
+
// slots in use, in which case it would select a zeroed stage.
|
|
781
|
+
const gShift = result[layout.dpi + 1] ?? 0;
|
|
782
|
+
if (gShift >= plan.stages.length)
|
|
783
|
+
result[layout.dpi + 1] = plan.defaultIndex;
|
|
784
|
+
if (legacyScalar) {
|
|
785
|
+
for (let stage = 0; stage < DPI_STAGE_SLOTS; stage += 1) {
|
|
786
|
+
writeUint16LE(result, layout.dpi + 2 + stage * 2, plan.stages[stage]?.x ?? 0);
|
|
787
|
+
}
|
|
788
|
+
return applyCrc(result);
|
|
789
|
+
}
|
|
790
|
+
for (let stage = 0; stage < DPI_STAGE_SLOTS; stage += 1) {
|
|
791
|
+
const base = layout.dpi + 2 + stage * 5;
|
|
792
|
+
const entry = plan.stages[stage];
|
|
793
|
+
if (!entry) {
|
|
794
|
+
result[base] = 0;
|
|
795
|
+
result[base + 1] = 0;
|
|
796
|
+
result[base + 2] = 0;
|
|
797
|
+
result[base + 3] = 0;
|
|
798
|
+
if (writeLod)
|
|
799
|
+
result[base + 4] = 0;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
writeUint16LE(result, base, entry.x);
|
|
803
|
+
writeUint16LE(result, base + 2, entry.y);
|
|
804
|
+
if (writeLod)
|
|
805
|
+
result[base + 4] = entry.lod;
|
|
806
|
+
}
|
|
807
|
+
return applyCrc(result);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Snaps a typed bunny-hop time into the range and step the byte can hold.
|
|
811
|
+
* A number field only enforces min/max/step for its spinner, so a typed value
|
|
812
|
+
* has to be corrected here instead of being rejected.
|
|
813
|
+
*/
|
|
814
|
+
export function clampBunnyHopMs(milliseconds) {
|
|
815
|
+
const { minMs, maxMs, stepMs } = BUNNY_HOP_LIMITS;
|
|
816
|
+
if (!Number.isFinite(milliseconds))
|
|
817
|
+
return minMs;
|
|
818
|
+
const stepped = Math.round(milliseconds / stepMs) * stepMs;
|
|
819
|
+
return Math.min(maxMs, Math.max(minMs, stepped));
|
|
820
|
+
}
|
|
821
|
+
/** Stored as ms / 10: G HUB's 100-1000 ms range is 0x0a-0x64. */
|
|
822
|
+
function decodeBunnyHoppingMs(bytes, offset) {
|
|
823
|
+
if (offset === null)
|
|
824
|
+
return null;
|
|
825
|
+
const raw = bytes[offset];
|
|
826
|
+
return raw === undefined || raw === 0xff ? null : raw * 10;
|
|
827
|
+
}
|
|
828
|
+
function writeUint16LE(bytes, offset, value) {
|
|
829
|
+
bytes[offset] = value & 0xff;
|
|
830
|
+
bytes[offset + 1] = (value >> 8) & 0xff;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Rebuilds `before` with every field we can encode set to the value found in
|
|
834
|
+
* `after`, then fixes the checksum.
|
|
835
|
+
*
|
|
836
|
+
* This is how a write path is proven without writing: if the result is
|
|
837
|
+
* byte-identical to what the vendor software actually produced, our encoding,
|
|
838
|
+
* offsets and CRC are all correct. Any byte that differs is something we either
|
|
839
|
+
* model wrongly or cannot write yet.
|
|
840
|
+
*/
|
|
841
|
+
export function reproduceProfile(before, after, profileFormatId) {
|
|
842
|
+
const layout = layoutForFormat(profileFormatId);
|
|
843
|
+
const result = before.slice();
|
|
844
|
+
const legacyLayout = profileFormatId < 6;
|
|
845
|
+
const isErased = (offset, size) => after.slice(offset, offset + size).every((byte) => byte === 0xff);
|
|
846
|
+
const copyRate = (offset) => {
|
|
847
|
+
if (offset === null)
|
|
848
|
+
return;
|
|
849
|
+
const hz = legacyLayout ? decodeLegacyReportRate(after, offset) : decodeReportRate(after, offset);
|
|
850
|
+
if (hz === null)
|
|
851
|
+
return;
|
|
852
|
+
if (legacyLayout)
|
|
853
|
+
result[offset] = 1000 / hz;
|
|
854
|
+
else {
|
|
855
|
+
const index = REPORT_RATE_HZ.indexOf(hz);
|
|
856
|
+
if (index >= 0)
|
|
857
|
+
result[offset] = index;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
copyRate(layout.reportRateWireless);
|
|
861
|
+
copyRate(layout.reportRateWired);
|
|
862
|
+
if (layout.dpi !== null) {
|
|
863
|
+
const dpi = legacyLayout ? decodeLegacyDpi(after, layout.dpi) : decodeDpi(after, layout.dpi);
|
|
864
|
+
if (dpi.defaultIndex !== null)
|
|
865
|
+
result[layout.dpi] = dpi.defaultIndex;
|
|
866
|
+
result[layout.dpi + 1] = after[layout.dpi + 1];
|
|
867
|
+
// Stages are re-encoded from decoded values, not copied, so their
|
|
868
|
+
// little-endian representation is what is actually under test.
|
|
869
|
+
let stage = 0;
|
|
870
|
+
for (const { x, y, lod } of dpi.stages) {
|
|
871
|
+
const base = layout.dpi + 2 + stage * (legacyLayout ? 2 : 5);
|
|
872
|
+
writeUint16LE(result, base, x);
|
|
873
|
+
if (!legacyLayout) {
|
|
874
|
+
writeUint16LE(result, base + 2, y);
|
|
875
|
+
result[base + 4] = lod;
|
|
876
|
+
}
|
|
877
|
+
stage += 1;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
const angleSnapping = after[layout.angleSnapping];
|
|
881
|
+
if (angleSnapping !== undefined)
|
|
882
|
+
result[layout.angleSnapping] = angleSnapping;
|
|
883
|
+
if (layout.bunnyHopping !== null) {
|
|
884
|
+
const milliseconds = decodeBunnyHoppingMs(after, layout.bunnyHopping);
|
|
885
|
+
if (milliseconds !== null)
|
|
886
|
+
result[layout.bunnyHopping] = Math.round(milliseconds / 10);
|
|
887
|
+
// Factory reset returns the optional component to erased flash rather than
|
|
888
|
+
// encoding "off" as zero. That distinction is visible in a vendor diff.
|
|
889
|
+
else if (after[layout.bunnyHopping] === 0xff)
|
|
890
|
+
result[layout.bunnyHopping] = 0xff;
|
|
891
|
+
}
|
|
892
|
+
for (const offset of [layout.powerSaveTimeout, layout.powerOffTimeout]) {
|
|
893
|
+
if (offset === null)
|
|
894
|
+
continue;
|
|
895
|
+
const seconds = readUint16LE(after, offset);
|
|
896
|
+
if (seconds !== null)
|
|
897
|
+
writeUint16LE(result, offset, seconds);
|
|
898
|
+
}
|
|
899
|
+
const name = profileFormatId === 1 ? null : decodeName(after, layout.profileName);
|
|
900
|
+
if (name !== null) {
|
|
901
|
+
const encoded = new Uint8Array(0x30);
|
|
902
|
+
encoded.set(after.slice(layout.profileName, layout.profileName + 0x30).map(() => 0));
|
|
903
|
+
for (let index = 0; index < name.length && index * 2 + 1 < 0x30; index += 1) {
|
|
904
|
+
const code = name.charCodeAt(index);
|
|
905
|
+
encoded[index * 2] = code & 0xff;
|
|
906
|
+
encoded[index * 2 + 1] = (code >> 8) & 0xff;
|
|
907
|
+
}
|
|
908
|
+
result.set(encoded, layout.profileName);
|
|
909
|
+
}
|
|
910
|
+
else if (isErased(layout.profileName, PROFILE_NAME_BYTES)) {
|
|
911
|
+
// An unnamed factory profile uses erased bytes, not a zero-filled empty
|
|
912
|
+
// UTF-16 string. Reproduce that representation exactly so its CRC matches.
|
|
913
|
+
result.fill(0xff, layout.profileName, layout.profileName + PROFILE_NAME_BYTES);
|
|
914
|
+
}
|
|
915
|
+
// Reset-to-factory erases the complete G-Shift assignment component. We do
|
|
916
|
+
// not claim to encode individual assignments yet, but an all-0xff target is
|
|
917
|
+
// unambiguous and safe to reproduce for capture verification.
|
|
918
|
+
const gShift = componentsForFormat(profileFormatId).find((component) => component.name === "g_shift_function");
|
|
919
|
+
if (gShift && isErased(gShift.offset, gShift.size)) {
|
|
920
|
+
result.fill(0xff, gShift.offset, gShift.offset + gShift.size);
|
|
921
|
+
}
|
|
922
|
+
return applyCrc(result);
|
|
923
|
+
}
|
|
924
|
+
export function decodeOnboardProfile(bytes, profileFormatId, entry, isCurrent) {
|
|
925
|
+
const layout = layoutForFormat(profileFormatId);
|
|
926
|
+
const legacyLayout = profileFormatId < 6;
|
|
927
|
+
const dpi = layout.dpi === null
|
|
928
|
+
? { stages: [], defaultIndex: null }
|
|
929
|
+
: legacyLayout ? decodeLegacyDpi(bytes, layout.dpi) : decodeDpi(bytes, layout.dpi);
|
|
930
|
+
const angleSnappingByte = bytes[layout.angleSnapping];
|
|
931
|
+
const components = componentsForFormat(profileFormatId);
|
|
932
|
+
const buttons = components.find((component) => component.name === "button_functions");
|
|
933
|
+
const gShift = components.find((component) => component.name === "g_shift_function");
|
|
934
|
+
return {
|
|
935
|
+
sector: entry.sector,
|
|
936
|
+
enabled: entry.enabled,
|
|
937
|
+
isCurrent,
|
|
938
|
+
// The v1 region is byte text, not UTF-16. This G402 capture contains
|
|
939
|
+
// non-text device data there, so do not manufacture a mojibake name.
|
|
940
|
+
name: profileFormatId === 1 ? null : decodeName(bytes, layout.profileName),
|
|
941
|
+
dpiStages: dpi.stages,
|
|
942
|
+
defaultDpiIndex: dpi.defaultIndex,
|
|
943
|
+
reportRateWireless: legacyLayout
|
|
944
|
+
? decodeLegacyReportRate(bytes, layout.reportRateWireless)
|
|
945
|
+
: decodeReportRate(bytes, layout.reportRateWireless),
|
|
946
|
+
reportRateWired: legacyLayout
|
|
947
|
+
? decodeLegacyReportRate(bytes, layout.reportRateWired)
|
|
948
|
+
: decodeReportRate(bytes, layout.reportRateWired),
|
|
949
|
+
angleSnapping: angleSnappingByte === undefined || angleSnappingByte === 0xff
|
|
950
|
+
? null
|
|
951
|
+
: angleSnappingByte !== 0,
|
|
952
|
+
powerSaveTimeoutSeconds: layout.powerSaveTimeout === null
|
|
953
|
+
? null
|
|
954
|
+
: readUint16LE(bytes, layout.powerSaveTimeout),
|
|
955
|
+
powerOffTimeoutSeconds: layout.powerOffTimeout === null
|
|
956
|
+
? null
|
|
957
|
+
: readUint16LE(bytes, layout.powerOffTimeout),
|
|
958
|
+
bunnyHoppingMs: decodeBunnyHoppingMs(bytes, layout.bunnyHopping),
|
|
959
|
+
buttonAssignments: decodeButtonAssignments(bytes, buttons),
|
|
960
|
+
gShiftAssignments: decodeButtonAssignments(bytes, gShift),
|
|
961
|
+
crcValid: bytes.length > 2 && profileCrc(bytes) === storedCrc(bytes),
|
|
962
|
+
raw: bytes,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
//# sourceMappingURL=onboard-profiles.js.map
|