sinowealth: add types for buttons

This button mapping reading and writing support is partially based on a
previous work of @phomes and @enkore. Thank you both very much!
This commit is contained in:
Yaroslav Chvanov
2022-02-01 16:03:51 +03:00
committed by Ilya guterman
parent 94f28df134
commit e9dff40258

View File

@@ -49,6 +49,8 @@ enum sinowealth_command_id {
} __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. */
@@ -232,6 +234,100 @@ struct sinowealth_config_report {
} __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 <option> times. */
SINOWEALTH_BUTTON_MACRO_MODE_REPEAT = 0x1,
/* Repeat until any button is pressed. */
SINOWEALTH_BUTTON_MACRO_MODE_REPEAT_UNTIL_PRESSED = 0x2,
/* Repeat until released. */
SINOWEALTH_BUTTON_MACRO_MODE_REPEAT_UNTIL_RELEASED = 0x4,
/* Anything above freezes up the mouse. */
} __attribute__((packed));
_Static_assert(sizeof(enum sinowealth_button_macro_mode) == sizeof(uint8_t), "Invalid size");
struct sinowealth_button_data {
enum sinowealth_button_type type;
union {
/* In some button types, byte are bit masks of enabled buttons.
* If several bits are enabled at the same time, their corresponding buttons
* will be activated at the same time.
*/
uint8_t data[3];
struct {
/* DPI divided by 100. */
uint8_t dpi;
uint8_t padding[2];
} dpi_lock;
struct {
enum sinowealth_button_key_modifiers modifiers;
uint8_t key;
uint8_t padding;
} key;
struct {
/* Macro index starting with 1.
*
* For consistency we do it like this: `button_index + (profile_index * button_count)`.
* This may clash with macros set by official software.
*/
uint8_t index;
enum sinowealth_button_macro_mode mode;
/* Mode-specific option.
*
* For 0x1:
* Value is repeat count.
*
* Other modes don't have any options.
*/
uint8_t option;
} macro;
struct {
/* Button index. */
uint8_t index;
uint8_t delay;
/* If zero, then repeat for as long as the button is held. */
uint8_t count;
} repeated_button;
};
} __attribute__((packed));
_Static_assert(sizeof(struct sinowealth_button_data) == sizeof(uint32_t), "Invalid size");
struct sinowealth_button_report {
uint8_t report_id;
uint8_t command_id;
uint8_t unknown1;
/* 0x0 --- read.
* <size of configuration> - 8 --- write.
*/
uint8_t config_write;
uint8_t unknown2[4];
struct sinowealth_button_data buttons[20];
uint8_t padding[SINOWEALTH_CONFIG_REPORT_SIZE - SINOWEALTH_BUTTON_SIZE];
} __attribute__((packed));
_Static_assert(sizeof(struct sinowealth_button_report) == SINOWEALTH_CONFIG_REPORT_SIZE, "Invalid size");
/* Data related to mouse we store for ourselves. */
struct sinowealth_data {
/* Whether the device uses REPORT_ID_CONFIG or REPORT_ID_CONFIG_LONG. */
@@ -246,6 +342,133 @@ struct sinowealth_data {
struct sinowealth_config_report configs[SINOWEALTH_NUM_PROFILES];
};
struct sinowealth_button_mapping {
struct sinowealth_button_data data;
struct ratbag_button_action action;
};
static const struct sinowealth_button_mapping sinowealth_button_map[] = {
{ { SINOWEALTH_BUTTON_TYPE_BUTTON, { { 0x01 } } }, BUTTON_ACTION_BUTTON(1) },
{ { SINOWEALTH_BUTTON_TYPE_BUTTON, { { 0x02 } } }, BUTTON_ACTION_BUTTON(2) },
{ { SINOWEALTH_BUTTON_TYPE_BUTTON, { { 0x04 } } }, BUTTON_ACTION_BUTTON(3) },
{ { SINOWEALTH_BUTTON_TYPE_BUTTON, { { 0x08 } } }, BUTTON_ACTION_BUTTON(5) },
{ { SINOWEALTH_BUTTON_TYPE_BUTTON, { { 0x10 } } }, BUTTON_ACTION_BUTTON(4) },
/* None of the other bits do anything. */
/* First data byte is a 0-255 range. */
{ { SINOWEALTH_BUTTON_TYPE_WHEEL, { { 0x1 } } }, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP) },
{ { SINOWEALTH_BUTTON_TYPE_WHEEL, { { 0xff } } }, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN) },
/* None of the other bits do anything. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x01 } } }, BUTTON_ACTION_KEY(KEY_NEXTSONG) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x02 } } }, BUTTON_ACTION_KEY(KEY_PREVIOUSSONG) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x04 } } }, BUTTON_ACTION_KEY(KEY_STOPCD) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x08 } } }, BUTTON_ACTION_KEY(KEY_PLAYPAUSE) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x10 } } }, BUTTON_ACTION_KEY(KEY_MUTE) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x20 } } }, BUTTON_ACTION_KEY(KEY_UNKNOWN) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x40 } } }, BUTTON_ACTION_KEY(KEY_VOLUMEUP) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x80 } } }, BUTTON_ACTION_KEY(KEY_VOLUMEDOWN) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x01 } } }, BUTTON_ACTION_KEY(KEY_CONFIG) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x02 } } }, BUTTON_ACTION_KEY(KEY_FILE) },
/* 0x04 makes mouse send something, but it's not processed by Linux. Hidden. */
/* 0x08 makes mouse send something, but it's not processed by Linux. Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x10 } } }, BUTTON_ACTION_KEY(KEY_MAIL) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x20 } } }, BUTTON_ACTION_KEY(KEY_CALC) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x40 } } }, BUTTON_ACTION_KEY(KEY_UNKNOWN) }, /* Hidden. */
/* 0x80 makes mouse send something, but it's not processed by Linux. Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x2 } } }, BUTTON_ACTION_KEY(KEY_HOMEPAGE) },
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x4 } } }, BUTTON_ACTION_KEY(KEY_BACK) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x8 } } }, BUTTON_ACTION_KEY(KEY_FORWARD) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x10 } } }, BUTTON_ACTION_KEY(KEY_STOP) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x20 } } }, BUTTON_ACTION_KEY(KEY_REFRESH) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x40 } } }, BUTTON_ACTION_KEY(KEY_BOOKMARKS) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY, { { 0x0, 0x0, 0x80 } } }, BUTTON_ACTION_KEY(KEY_UNKNOWN) }, /* Hidden. */
{ { SINOWEALTH_BUTTON_TYPE_SWITCH_DPI, { { 0x0 } } }, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) },
{ { SINOWEALTH_BUTTON_TYPE_SWITCH_DPI, { { 0x1 } } }, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) },
{ { SINOWEALTH_BUTTON_TYPE_SWITCH_DPI, { { 0x2 } } }, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) },
/* None of the other bits do anything. */
{ { SINOWEALTH_BUTTON_TYPE_SPECIAL, { { 0x1 } } }, BUTTON_ACTION_NONE },
/* Disabled as we don't support second profile officially.
* Not only that, by default default second profile has no button
* mapping and by using this the user is just going to be left with
* a practically non-working mouse. To fix this we would have to
* populate the empty profile with some default mappings.
*/
/* Hidden. */
/* { { SINOWEALTH_BUTTON_TYPE_SPECIAL, { { 0x6 } } }, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP) }, */
/* This must defined after `SPECIAL` type so that correct raw data
* for action type `NONE` is used. */
{ { SINOWEALTH_BUTTON_TYPE_NONE, {} }, BUTTON_ACTION_NONE },
};
/* Check if two given button data structs are equal.
*
* @return 1 if structs are equal or 0 otherwise.
*/
static int
sinowealth_button_data_is_equal(const struct sinowealth_button_data *lhs, const struct sinowealth_button_data *rhs)
{
if (lhs->type != rhs->type)
return 0;
for (unsigned int i = 0; i < sizeof(lhs->data); ++i) {
if (lhs->data[i] != rhs->data[i]) {
return 0;
}
}
return 1;
}
/* Convert a button action to raw data using the `sinowealth_button_map`.
* NOTE: It does not contain all of the button types, as some of them are
* better made programatically. See @ref sinowealth_update_buttons_from_profile.
*
* @param data Struct to write to.
*
* @return 0 on success or 1 if such action is not in the map.
*/
static int
sinowealth_button_action_to_raw(const struct ratbag_button_action *action, struct sinowealth_button_data *data)
{
const struct sinowealth_button_mapping *mapping = NULL;
ARRAY_FOR_EACH(sinowealth_button_map, mapping) {
if (ratbag_button_action_match(&mapping->action, action)) {
memcpy(data, &mapping->data, sizeof(struct sinowealth_button_data));
return 0;
}
}
return 1;
}
/* Convert raw button data to a button action using the `sinowealth_button_map`.
* NOTE: It does not contain all of the button types, as some of them are
* better made programatically. See @ref sinowealth_update_profile_from_buttons.
*
* @return Button action or NULL if such action is not in the map. */
static const struct ratbag_button_action *
sinowealth_raw_to_button_action(const struct sinowealth_button_data *data)
{
const struct sinowealth_button_mapping *mapping = NULL;
ARRAY_FOR_EACH(sinowealth_button_map, mapping) {
if (sinowealth_button_data_is_equal(data, &mapping->data) == 0)
continue;
return &mapping->action;
}
return NULL;
}
struct sinowealth_report_rate_mapping {
uint8_t raw;
unsigned int report_rate;