/* * Copyright © 2020 Marian Beermann * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "shared-macro.h" enum sinowealth_report_id { SINOWEALTH_REPORT_ID_CONFIG = 0x4, SINOWEALTH_REPORT_ID_CMD = 0x5, SINOWEALTH_REPORT_ID_CONFIG_LONG = 0x6, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_report_id) == sizeof(uint8_t), "Invalid size"); enum sinowealth_command_id { SINOWEALTH_CMD_FIRMWARE_VERSION = 0x1, SINOWEALTH_CMD_PROFILE = 0x2, SINOWEALTH_CMD_GET_CONFIG = 0x11, SINOWEALTH_CMD_GET_BUTTONS = 0x12, /* Doesn't work on devices with shorter configuration data (123 instead of 137). */ SINOWEALTH_CMD_DEBOUNCE = 0x1a, /* Only works on devices that use CONFIG_LONG report ID. */ SINOWEALTH_CMD_LONG_ANGLESNAPPING_AND_LOD = 0x1b, /* Same as GET_CONFIG but for the second profile. */ SINOWEALTH_CMD_GET_CONFIG2 = 0x21, /* Same as GET_CONFIG but for the second profile. */ SINOWEALTH_CMD_GET_BUTTONS2 = 0x22, SINOWEALTH_CMD_MACRO = 0x30, /* Puts the device into DFU mode. * To reset re-plug the mouse or do a clean reboot. */ SINOWEALTH_CMD_DFU = 0x75, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_command_id) == sizeof(uint8_t), "Invalid size"); #define SINOWEALTH_BUTTON_SIZE 88 #define SINOWEALTH_CMD_SIZE 6 /* Report length commands that get configuration data should use. */ #define SINOWEALTH_CONFIG_REPORT_SIZE 520 #define SINOWEALTH_CONFIG_SIZE_MAX 167 #define SINOWEALTH_CONFIG_SIZE_MIN 123 #define SINOWEALTH_MACRO_SIZE 515 /* The PC software only goes down to 400, but the PMW3360 doesn't care */ #define SINOWEALTH_DPI_MIN 100 #define SINOWEALTH_DPI_STEP 100 /* Arbitrary, but I think every sensor supports this DPI, and this is * about as high as most people would ever like to go anyway. */ #define SINOWEALTH_DPI_FALLBACK 2000 /* Different software expose different amount of DPI slots: * Glorious - 6; * G-Wolves - 7. * But in fact fact there are eight slots. */ #define SINOWEALTH_NUM_DPIS 8 /* Technically SinoWealth mice support second profile, but there isn't * a single configuration software that exposes it. */ #define SINOWEALTH_NUM_PROFILES 1 /* Maximum amount of real events in a macro. */ #define SINOWEALTH_MACRO_LENGTH_MAX 168 /* Bit mask for @ref sinowealth_config_report.config. * * This naming may be incorrect as it's not actually known what the other bits do. */ enum sinowealth_config_data_mask { SINOWEALTH_XY_INDEPENDENT = 0b1000, }; /* Color data the way mouse stores it. * * @ref sinowealth_raw_to_color. * * @ref sinowealth_color_to_raw. * * @ref sinowealth_led_format. */ struct sinowealth_color { /* May be in either RGB or RBG format depending on the device. * See the comment above this struct. */ uint8_t data[3]; } __attribute__((packed)); _Static_assert(sizeof(struct sinowealth_color) == 3, "Invalid size"); /* Sensor IDs used in SinoWealth firmware and software. */ enum sinowealth_sensor { SINOWEALTH_SENSOR_NONE, SINOWEALTH_SENSOR_PMW3360 = 0x06, SINOWEALTH_SENSOR_PMW3327 = 0x0e, SINOWEALTH_SENSOR_PMW3389 = 0x0f, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_sensor) == sizeof(uint8_t), "Invalid sensor enum size"); enum sinowealth_rgb_effect { RGB_OFF = 0, RGB_GLORIOUS = 0x1, /* unicorn mode */ RGB_SINGLE = 0x2, /* single constant color */ RGB_BREATHING7 = 0x3, /* breathing with seven user-defined colors */ RGB_TAIL = 0x4, RGB_BREATHING = 0x5, /* Full RGB breathing. */ RGB_CONSTANT = 0x6, /* Each LED gets its own static color. Not available in Glorious software. */ RGB_RAVE = 0x7, RGB_RANDOM = 0x8, /* Randomly change colors. Not available in Glorious software. */ RGB_WAVE = 0x9, /* Single color breathing. * Not available on some mice, for example Genesis Xenon 770 and * DreamMachines DM5 (both are 0027 mice). On them RGB_BREATHING7 * with one color should be used instead. */ RGB_BREATHING1 = 0xa, /* The value mice with no LEDs have. * Unreliable as non-constant. * Do **not** overwrite it. */ RGB_NOT_SUPPORTED = 0xff, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_rgb_effect) == sizeof(uint8_t), "Invalid size"); struct sinowealth_rgb_mode { /* 0x1/2/3. * @ref sinowealth_duration_to_rgb_mode. * @ref sinowealth_rgb_mode_to_duration. */ uint8_t speed:4; /* 0x1/2/3/4. * @ref sinowealth_brightness_to_rgb_mode. * @ref sinowealth_rgb_mode_to_brightness. */ uint8_t brightness:4; }; _Static_assert(sizeof(struct sinowealth_rgb_mode) == sizeof(uint8_t), "Invalid size"); struct sinowealth_xy_independent_dpi { uint8_t x; uint8_t y; }; _Static_assert(sizeof(struct sinowealth_xy_independent_dpi) == sizeof(uint8_t[2]), "Invalid size"); /* DPI/CPI is encoded in the way the PMW3360 and PWM3327 sensors * accept it: * value = (DPI - 100) / 100; * or the way the PMW3389 sensor accepts it: * value = DPI / 100; * * @ref sinowealth_raw_to_dpi * @ref sinowealth_dpi_to_raw */ union sinowealth_dpis { /* You MUST use this field if no resolutions have separate X and Y. */ uint8_t dpis[8]; /* You MUST use this field if at least one resolution has separate * X and Y. */ struct sinowealth_xy_independent_dpi independent[8]; }; _Static_assert(sizeof(union sinowealth_dpis) == sizeof(uint8_t[16]), "Invalid size"); enum sinowealth_led_format { LED_NONE, LED_RBG, LED_RGB, }; /* Configuration data the way it's stored in mouse memory. * When we want to change a setting, we basically copy the entire mouse * configuration, modify it and send it back. */ struct sinowealth_config_report { enum sinowealth_report_id report_id; enum sinowealth_command_id command_id; uint8_t unknown1; /* 0x0 - read. * CONFIG_SIZE-8 - write. */ uint8_t config_write; uint8_t unknown2[2]; enum sinowealth_sensor sensor_type; uint8_t unknown6[3]; /* @ref sinowealth_report_rate_map. */ uint8_t report_rate:4; /* 0b1000 - make DPI axes independent. */ uint8_t config_flags:4; uint8_t dpi_count:4; /* Starting from 1 counting only active slots. */ uint8_t active_dpi:4; /* bit set: disabled, unset: enabled */ uint8_t disabled_dpi_slots; union sinowealth_dpis dpis; struct sinowealth_color dpi_color[8]; enum sinowealth_rgb_effect rgb_effect; struct sinowealth_rgb_mode glorious_mode; uint8_t glorious_direction; struct sinowealth_rgb_mode single_mode; struct sinowealth_color single_color; struct sinowealth_rgb_mode breathing7_mode; uint8_t breathing7_colorcount; struct sinowealth_color breathing7_colors[7]; struct sinowealth_rgb_mode tail_mode; struct sinowealth_rgb_mode breathing_mode; struct sinowealth_rgb_mode constant_color_mode; struct sinowealth_color constant_color_colors[6]; uint8_t unknown3[12]; struct sinowealth_rgb_mode rave_mode; struct sinowealth_color rave_colors[2]; /* From here onward goes the data not available in short mice. * .. judging by the size of this struct. The data in them may * actually be different, we didn't test this yet. */ struct sinowealth_rgb_mode random_mode; struct sinowealth_rgb_mode wave_mode; struct sinowealth_rgb_mode breathing1_mode; struct sinowealth_color breathing1_color; /* 0x1 - 2 mm. * 0x2 - 3 mm. * 0xff - indicates that lift off distance is changed with a dedicated command. Not constant, so do **NOT** overwrite it. */ uint8_t lift_off_distance; uint8_t unknown4; /* From here onward goes the data only available in long mice. */ uint8_t unknown5[36]; uint8_t padding[SINOWEALTH_CONFIG_REPORT_SIZE - SINOWEALTH_CONFIG_SIZE_MAX]; } __attribute__((packed)); _Static_assert(sizeof(struct sinowealth_config_report) == SINOWEALTH_CONFIG_REPORT_SIZE, "Invalid size"); enum sinowealth_button_type { SINOWEALTH_BUTTON_TYPE_NONE = 0, /* This value might appear on broken configurations. */ SINOWEALTH_BUTTON_TYPE_BUTTON = 0x11, SINOWEALTH_BUTTON_TYPE_WHEEL = 0x12, SINOWEALTH_BUTTON_TYPE_KEY = 0x21, SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY = 0x22, SINOWEALTH_BUTTON_TYPE_REPEATED = 0x31, SINOWEALTH_BUTTON_TYPE_SWITCH_DPI = 0x41, SINOWEALTH_BUTTON_TYPE_DPI_LOCK = 0x42, SINOWEALTH_BUTTON_TYPE_SPECIAL = 0x50, SINOWEALTH_BUTTON_TYPE_MACRO = 0x70, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_button_type) == sizeof(uint8_t), "Invalid size"); /* Bit masks. */ enum sinowealth_button_key_modifiers { SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTCTRL = 0x01, SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTSHIFT = 0x02, SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTALT = 0x04, SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTMETA = 0x08, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_button_key_modifiers) == sizeof(uint8_t), "Invalid size"); enum sinowealth_button_macro_mode { /* Repeat