From 02ec2442d3007f985be1afecf0575792b203322b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Mon, 29 Jul 2019 13:15:20 +0100 Subject: [PATCH] hidpp20: add support for RGB Effects feature (0x8071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe LaĆ­ns --- src/driver-hidpp20.c | 150 +++++++++++++++++++++++++++++++++---- src/hidpp-generic.c | 4 +- src/hidpp-generic.h | 4 +- src/hidpp20.c | 173 ++++++++++++++++++++++++++++++++++++++++++- src/hidpp20.h | 87 ++++++++++++++++++++-- 5 files changed, 393 insertions(+), 25 deletions(-) diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c index 07ac151f..4deb7f38 100644 --- a/src/driver-hidpp20.c +++ b/src/driver-hidpp20.c @@ -57,6 +57,7 @@ #define HIDPP_CAP_LED_SW_CONTROL_1300 (1 << 7) #define HIDPP_CAP_ADJUSTIBLE_REPORT_RATE_8060 (1 << 8) #define HIDPP_CAP_BATTERY_VOLTAGE_1001 (1 << 9) +#define HIDPP_CAP_RGB_EFFECTS_8071 (1 << 10) #define HIDPP_HIDDEN_FEATURE (1 << 6) @@ -338,7 +339,7 @@ hidpp20drv_read_led_8070(struct ratbag_led *led, struct hidpp20drv_data* drv_dat struct hidpp20_color_led_info info; int rc; - led_info = &drv_data->led_infos.color_leds[led->index]; + led_info = &drv_data->led_infos.color_leds_8070[led->index]; profile = &drv_data->profiles->profiles[led->profile->index]; h_led = &profile->leds[led->index]; @@ -359,7 +360,7 @@ hidpp20drv_read_led_8070(struct ratbag_led *led, struct hidpp20drv_data* drv_dat /* pre-filled, only override if unknown */ if (led->type == RATBAG_LED_TYPE_UNKNOWN) - led->type = hidpp20_8070_get_location_mapping(led_info->location); + led->type = hidpp20_led_get_location_mapping(led_info->location); led->color.red = h_led->color.red; led->color.green = h_led->color.green; led->color.blue = h_led->color.blue; @@ -405,6 +406,81 @@ hidpp20drv_read_led_8070(struct ratbag_led *led, struct hidpp20drv_data* drv_dat } } +static void +hidpp20drv_read_led_8071(struct ratbag_led *led, struct hidpp20drv_data* drv_data) +{ + struct hidpp20_profile *profile; + struct hidpp20_led *h_led; + struct hidpp20_rgb_device_info device_info; + struct hidpp20_rgb_cluster_info *cluster_info; + int rc; + + hidpp20_rgb_effects_get_device_info(drv_data->dev, &device_info); + cluster_info = &drv_data->led_infos.color_leds_8071[led->index]; + profile = &drv_data->profiles->profiles[led->profile->index]; + h_led = &profile->leds[led->index]; + + switch (h_led->mode) { + case HIDPP20_LED_ON: + led->mode = RATBAG_LED_ON; + break; + case HIDPP20_LED_CYCLE: + led->mode = RATBAG_LED_CYCLE; + break; + case HIDPP20_LED_BREATHING: + led->mode = RATBAG_LED_BREATHING; + break; + default: + led->mode = RATBAG_LED_OFF; + break; + } + + /* pre-filled, only override if unknown */ + if (led->type == RATBAG_LED_TYPE_UNKNOWN) + led->type = hidpp20_led_get_location_mapping(cluster_info->location); + led->color.red = h_led->color.red; + led->color.green = h_led->color.green; + led->color.blue = h_led->color.blue; + led->ms = h_led->period; + led->brightness = h_led->brightness * 255 / 100; + + if (device_info.ext_caps & HIDPP20_COLOR_LED_INFO_EXT_CAP_MONOCHROME_ONLY) + led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME; + else + led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; + + for (int i = 0; i < cluster_info->num_effects; i++) { + struct hidpp20_rgb_effect_info ei; + rc = hidpp20_rgb_effects_get_effect_info(drv_data->dev, + cluster_info->index, + i, &ei); + if (rc < 0) + break; + + switch (ei.effect_id) { + case HIDPP20_COLOR_LED_ZONE_EFFECT_DISABLED: + ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); + break; + case HIDPP20_COLOR_LED_ZONE_EFFECT_FIXED: + ratbag_led_set_mode_capability(led, RATBAG_LED_ON); + break; + case HIDPP20_COLOR_LED_ZONE_EFFECT_CYCLING: + ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE); + break; + case HIDPP20_COLOR_LED_ZONE_EFFECT_WAVE: + case HIDPP20_COLOR_LED_ZONE_EFFECT_BREATHING: + ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); + break; + default: + log_bug_libratbag(led->profile->device->ratbag, + "%s: Unknown effect id %d\n", + led->profile->device->name, + ei.effect_id); + break; + } + } +} + static void hidpp20drv_read_led(struct ratbag_led *led) { @@ -413,6 +489,8 @@ hidpp20drv_read_led(struct ratbag_led *led) if (drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) hidpp20drv_read_led_8070(led, drv_data); + else if (drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) + hidpp20drv_read_led_8071(led, drv_data); else if (drv_data->capabilities & HIDPP_CAP_LED_SW_CONTROL_1300) hidpp20drv_read_led_1300(led, drv_data); } @@ -609,8 +687,8 @@ hidpp20drv_update_led_1300(struct ratbag_led *led, struct hidpp20drv_data *data) } static int -hidpp20drv_update_led_8070(struct ratbag_led *led, struct ratbag_profile* profile, - struct hidpp20drv_data *drv_data) +hidpp20drv_update_led_8070_8071(struct ratbag_led *led, struct ratbag_profile* profile, + struct hidpp20drv_data *drv_data) { struct hidpp20_profile *h_profile; struct hidpp20_led *h_led; @@ -652,8 +730,9 @@ hidpp20drv_update_led(struct ratbag_led *led) struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); - if (drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) - return hidpp20drv_update_led_8070(led, profile, drv_data); + if ((drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) | + (drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071)) + return hidpp20drv_update_led_8070_8071(led, profile, drv_data); if (drv_data->capabilities & HIDPP_CAP_LED_SW_CONTROL_1300) return hidpp20drv_update_led_1300(led, drv_data); @@ -971,19 +1050,15 @@ hidpp20drv_read_kbd_reprogrammable_key(struct ratbag_device *device) } static int -hidpp20drv_read_color_leds(struct ratbag_device *device) +hidpp20drv_read_color_leds_8070(struct hidpp20drv_data *drv_data) { - struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; - if (!(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070)) - return 0; - - free(drv_data->led_infos.color_leds); - drv_data->led_infos.color_leds = NULL; + free(drv_data->led_infos.color_leds_8070); + drv_data->led_infos.color_leds_8070 = NULL; drv_data->num_leds = 0; - rc = hidpp20_color_led_effects_get_zone_infos(drv_data->dev, &drv_data->led_infos.color_leds); + rc = hidpp20_color_led_effects_get_zone_infos(drv_data->dev, &drv_data->led_infos.color_leds_8070); if (rc > 0) { drv_data->num_leds = rc; @@ -993,6 +1068,38 @@ hidpp20drv_read_color_leds(struct ratbag_device *device) return rc; } +static int +hidpp20drv_read_color_leds_8071(struct hidpp20drv_data *drv_data) +{ + int rc; + + free(drv_data->led_infos.color_leds_8071); + drv_data->led_infos.color_leds_8071 = NULL; + drv_data->num_leds = 0; + + rc = hidpp20_rgb_effects_get_cluster_infos(drv_data->dev, &drv_data->led_infos.color_leds_8071); + + if (rc > 0) { + drv_data->num_leds = rc; + rc = 0; + } + + return rc; +} + +static int +hidpp20drv_read_color_leds(struct ratbag_device *device) +{ + struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); + + if(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) + return hidpp20drv_read_color_leds_8070(drv_data); + else if (drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) + return hidpp20drv_read_color_leds_8071(drv_data); + + return 0; +} + static int hidpp20drv_read_leds(struct ratbag_device *device) { @@ -1206,6 +1313,18 @@ hidpp20drv_init_feature(struct ratbag_device *device, uint16_t feature) device->num_leds = drv_data->num_leds; break; } + case HIDPP_PAGE_RGB_EFFECTS: { + log_debug(ratbag, "device has color effects\n"); + drv_data->capabilities |= HIDPP_CAP_RGB_EFFECTS_8071; + + /* we read the profile once to get the correct number of + * supported leds. */ + if (hidpp20drv_read_color_leds(device)) + return 0; + + device->num_leds = drv_data->num_leds; + break; + } case HIDPP_PAGE_LED_SW_CONTROL: { log_debug(ratbag, "device has non-rgb leds\n"); drv_data->capabilities |= HIDPP_CAP_LED_SW_CONTROL_1300; @@ -1380,7 +1499,8 @@ hidpp20drv_remove(struct ratbag_device *device) if (drv_data->profiles) hidpp20_onboard_profiles_destroy(drv_data->profiles); - free(drv_data->led_infos.color_leds); + free(drv_data->led_infos.color_leds_8070); + free(drv_data->led_infos.color_leds_8071); free(drv_data->controls); free(drv_data->sensors); if (drv_data->dev) diff --git a/src/hidpp-generic.c b/src/hidpp-generic.c index 9ae583e2..8977be8e 100644 --- a/src/hidpp-generic.c +++ b/src/hidpp-generic.c @@ -234,7 +234,7 @@ hidpp20_1b04_get_physical_mapping_name(uint16_t value) } enum ratbag_led_type -hidpp20_8070_get_location_mapping(uint16_t value) +hidpp20_led_get_location_mapping(uint16_t value) { const struct hidpp20_8070_location_mapping *map; @@ -247,7 +247,7 @@ hidpp20_8070_get_location_mapping(uint16_t value) } const char * -hidpp20_8070_get_location_mapping_name(uint16_t value) +hidpp20_led_get_location_mapping_name(uint16_t value) { const struct hidpp20_8070_location_mapping *map; diff --git a/src/hidpp-generic.h b/src/hidpp-generic.h index 6879930d..113dbc36 100644 --- a/src/hidpp-generic.h +++ b/src/hidpp-generic.h @@ -120,10 +120,10 @@ enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value); enum ratbag_led_type -hidpp20_8070_get_location_mapping(uint16_t value); +hidpp20_led_get_location_mapping(uint16_t value); const char * -hidpp20_8070_get_location_mapping_name(uint16_t value); +hidpp20_led_get_location_mapping_name(uint16_t value); const char * hidpp20_1b04_get_logical_mapping_name(uint16_t value); diff --git a/src/hidpp20.c b/src/hidpp20.c index db5f7d46..b115a1c8 100644 --- a/src/hidpp20.c +++ b/src/hidpp20.c @@ -66,6 +66,7 @@ hidpp20_feature_get_name(uint16_t feature) CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_DPI); CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_REPORT_RATE); CASE_RETURN_STRING(HIDPP_PAGE_COLOR_LED_EFFECTS); + CASE_RETURN_STRING(HIDPP_PAGE_RGB_EFFECTS); CASE_RETURN_STRING(HIDPP_PAGE_ONBOARD_PROFILES); CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_BUTTON_SPY); default: @@ -942,7 +943,7 @@ hidpp20_color_led_effects_get_zone_infos(struct hidpp20_device *device, "led_info %d: location: %d type %s num_effects: %d persistency_caps: 0x%02x\n", info->index, info->location, - hidpp20_8070_get_location_mapping_name(info->location), + hidpp20_led_get_location_mapping_name(info->location), info->num_effects, info->persistency_caps); } @@ -991,6 +992,176 @@ hidpp20_color_led_effect_get_zone_effect_info(struct hidpp20_device *device, return 0; } +/* -------------------------------------------------------------------------- */ +/* 0x8071: RGB Effects */ +/* -------------------------------------------------------------------------- */ + +#define CMD_RGB_EFFECTS_GET_INFO 0x00 +#define CMD_RGB_EFFECTS_SET_RGB_CLUSTER_EFFECT 0x10 +#define CMD_RGB_EFFECTS_SET_MULTI_LED_RGB_CLUSTER_PATTERN 0x20 +#define CMD_RGB_EFFECTS_MANAGE_NV_CONFIG 0x30 +#define CMD_RGB_EFFECTS_MANAGE_RGB_LED_BIN_INFO 0x40 +#define CMD_RGB_EFFECTS_MANAGE_SW_CONTROL 0x50 +#define CMD_RGB_EFFECTS_SET_EFFECT_SYNC_CORRECTION 0x60 +#define CMD_RGB_EFFECTS_MANAGE_RGB_POWER_MODE_CONFIG 0x70 +#define CMD_RGB_EFFECTS_MANAGE_RGB_POWER_MODE 0x80 + +int +hidpp20_rgb_effects_get_device_info(struct hidpp20_device *device, + struct hidpp20_rgb_device_info *info) +{ + int rc; + union hidpp20_message msg = { + .msg.report_id = REPORT_ID_LONG, + .msg.device_idx = device->index, + .msg.address = CMD_RGB_EFFECTS_GET_INFO, + .msg.parameters[0] = HIDPP20_RGB_EFFECTS_INDEX_ALL, + .msg.parameters[1] = HIDPP20_RGB_EFFECTS_INDEX_ALL, + .msg.parameters[2] = HIDPP20_RGB_EFFECTS_TOI_GENERAL, + }; + uint8_t feature_index; + + feature_index = hidpp_root_get_feature_idx(device, + HIDPP_PAGE_RGB_EFFECTS); + if (feature_index == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_index; + + rc = hidpp20_request_command(device, &msg); + if (rc) + return rc; + + info->cluster_index = msg.msg.parameters[0]; + info->effect_index = msg.msg.parameters[1]; + info->cluster_count = msg.msg.parameters[2]; + + info->nv_caps = get_unaligned_be_u16(&msg.msg.parameters[3]); + info->ext_caps = get_unaligned_be_u16(&msg.msg.parameters[4]); + + return 0; +} + +int +hidpp20_rgb_effects_get_cluster_info(struct hidpp20_device *device, + uint8_t cluster_index, + struct hidpp20_rgb_cluster_info *info) +{ + int rc; + union hidpp20_message msg = { + .msg.report_id = REPORT_ID_LONG, + .msg.device_idx = device->index, + .msg.address = CMD_RGB_EFFECTS_GET_INFO, + .msg.parameters[0] = cluster_index, + .msg.parameters[1] = HIDPP20_RGB_EFFECTS_INDEX_ALL, + .msg.parameters[2] = HIDPP20_RGB_EFFECTS_TOI_GENERAL, + }; + uint8_t feature_index; + + feature_index = hidpp_root_get_feature_idx(device, + HIDPP_PAGE_RGB_EFFECTS); + if (feature_index == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_index; + + rc = hidpp20_request_command(device, &msg); + if (rc) + return rc; + + info->index = msg.msg.parameters[0]; + info->effect_index = msg.msg.parameters[1]; + + info->location = get_unaligned_be_u16(&msg.msg.parameters[2]); + info->num_effects = get_unaligned_be_u16(&msg.msg.parameters[4]); + info->persistency_caps = get_unaligned_be_u16(&msg.msg.parameters[6]); + + return 0; +} + +int +hidpp20_rgb_effects_get_cluster_infos(struct hidpp20_device *device, + struct hidpp20_rgb_cluster_info **infos_list) +{ + struct hidpp20_rgb_cluster_info *i_list, *info; + struct hidpp20_rgb_device_info device_info = {0}; + uint8_t num_infos; + unsigned i; + int rc; + + rc = hidpp20_rgb_effects_get_device_info(device, &device_info); + if (rc < 0) + return rc; + + num_infos = device_info.cluster_count; + if (num_infos == 0) { + *infos_list = NULL; + return 0; + } + + i_list = zalloc(num_infos * sizeof(struct hidpp20_rgb_cluster_info)); + + for (i = 0; i < num_infos; i++) { + info = &i_list[i]; + info->index = i; + rc = hidpp20_rgb_effects_get_cluster_info(device, i, info); + if (rc) + goto err; + + hidpp_log_raw(&device->base, + "cluster_info %d: location: %d type %s num_effects: %d persistency_caps: 0x%02x\n", + info->index, + info->location, + hidpp20_led_get_location_mapping_name(info->location), + info->num_effects, + info->persistency_caps); + } + + *infos_list = i_list; + return num_infos; +err: + free(i_list); + return rc; +} + +int +hidpp20_rgb_effects_get_effect_info(struct hidpp20_device *device, + uint8_t cluster_index, + uint8_t effect_index, + struct hidpp20_rgb_effect_info *info) +{ + int rc; + union hidpp20_message msg = { + .msg.report_id = REPORT_ID_LONG, + .msg.device_idx = device->index, + .msg.address = CMD_RGB_EFFECTS_GET_INFO, + .msg.parameters[0] = cluster_index, + .msg.parameters[1] = effect_index, + .msg.parameters[2] = HIDPP20_RGB_EFFECTS_TOI_GENERAL, + }; + uint8_t feature_index; + + feature_index = hidpp_root_get_feature_idx(device, + HIDPP_PAGE_RGB_EFFECTS); + if (feature_index == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_index; + + rc = hidpp20_request_command(device, &msg); + if (rc) + return rc; + + info->cluster_index = msg.msg.parameters[0]; + info->effect_index = msg.msg.parameters[1]; + + info->effect_id = get_unaligned_be_u16(&msg.msg.parameters[2]); + info->capabilities = get_unaligned_be_u16(&msg.msg.parameters[4]); + info->effect_period = get_unaligned_be_u16(&msg.msg.parameters[6]); + + return 0; +} + /* -------------------------------------------------------------------------- */ /* 0x1b04: Special keys and mouse buttons */ /* -------------------------------------------------------------------------- */ diff --git a/src/hidpp20.h b/src/hidpp20.h index ca5df092..d625c7c8 100644 --- a/src/hidpp20.h +++ b/src/hidpp20.h @@ -458,6 +458,31 @@ hidpp20_color_led_effect_get_zone_effect_info(struct hidpp20_device *device, uint8_t zone_effect_index, struct hidpp20_color_led_zone_effect_info *info); +struct hidpp20_rgb_device_info; + +int +hidpp20_rgb_effects_get_device_info(struct hidpp20_device *device, + struct hidpp20_rgb_device_info *info); + +struct hidpp20_rgb_cluster_info; + +int +hidpp20_rgb_effects_get_cluster_info(struct hidpp20_device *device, + uint8_t cluster_index, + struct hidpp20_rgb_cluster_info *info); + +int +hidpp20_rgb_effects_get_cluster_infos(struct hidpp20_device *device, + struct hidpp20_rgb_cluster_info **infos_list); + +struct hidpp20_rgb_effect_info; + +int +hidpp20_rgb_effects_get_effect_info(struct hidpp20_device *device, + uint8_t cluster_index, + uint8_t effect_index, + struct hidpp20_rgb_effect_info *info); + enum hidpp20_color_led_location { HIDPP20_COLOR_LED_LOCATION_UNDEFINED = 0, HIDPP20_COLOR_LED_LOCATION_PRIMARY, @@ -491,8 +516,30 @@ enum hidpp20_color_led_zone_effect { HIDPP20_COLOR_LED_ZONE_EFFECT_BOOT_UP = 8, HIDPP20_COLOR_LED_ZONE_EFFECT_DEMO_MODE = 8, HIDPP20_COLOR_LED_ZONE_EFFECT_BREATHING = 10, + HIDPP20_COLOR_LED_ZONE_EFFECT_RIPPLE = 11, + HIDPP20_COLOR_LED_ZONE_EFFECT_CUSTOM = 12, }; +/* -------------------------------------------------------------------------- */ +/* 0x8071 - RGB Effects */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_RGB_EFFECTS 0x8071 + +#define HIDPP20_RGB_EFFECTS_INDEX_ALL 0xFF + +/* type of info */ +#define HIDPP20_RGB_EFFECTS_TOI_GENERAL 0x00 +#define HIDPP20_RGB_EFFECTS_TOI_EFFECT 0x01 + +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_STATE 0x00 +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_DEFAULTS 0x01 +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_UUID_0_10 0x02 +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_UUID_11_16 0x03 +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_EFCT_NAME_0_10 0x04 +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_EFCT_NAME_11_21 0x05 +#define HIDPP20_RGB_EFFECTS_SLOT_INFO_EFCT_NAME_21_31 0x06 + /* -------------------------------------------------------------------------- */ /* 0x8100 - Onboard Profiles */ /* -------------------------------------------------------------------------- */ @@ -578,11 +625,6 @@ struct hidpp20_color_led_zone_info { } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_zone_info) == 5, "Invalid size"); -union hidpp20_generic_led_zone_info { - struct hidpp20_led_sw_ctrl_led_info* leds; - struct hidpp20_color_led_zone_info* color_leds; -}; - struct hidpp20_color_led_zone_effect_info { uint8_t zone_index; uint8_t zone_effect_index; @@ -592,6 +634,41 @@ struct hidpp20_color_led_zone_effect_info { } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_zone_effect_info) == 8, "Invalid size"); +struct hidpp20_rgb_device_info { + uint8_t cluster_index; + uint8_t effect_index; + uint8_t cluster_count; + /* we don't care about NV capabilities for libratbag, they just + * indicate sale demo effects */ + uint16_t nv_caps; + uint16_t ext_caps; +} __attribute__((packed)); +_Static_assert(sizeof(struct hidpp20_rgb_device_info) == 7, "Invalid size"); + +struct hidpp20_rgb_cluster_info { + uint8_t index; + uint8_t effect_index; + uint16_t location; + uint16_t num_effects; + uint16_t persistency_caps; +} __attribute__((packed)); +_Static_assert(sizeof(struct hidpp20_rgb_cluster_info) == 8, "Invalid size"); + +struct hidpp20_rgb_effect_info { + uint8_t cluster_index; + uint8_t effect_index; + uint16_t effect_id; + uint16_t capabilities; + uint16_t effect_period; +} __attribute__((packed)); +_Static_assert(sizeof(struct hidpp20_rgb_effect_info) == 8, "Invalid size"); + +union hidpp20_generic_led_zone_info { + struct hidpp20_led_sw_ctrl_led_info* leds; + struct hidpp20_color_led_zone_info* color_leds_8070; + struct hidpp20_rgb_cluster_info* color_leds_8071; +}; + struct hidpp20_color { uint8_t red; uint8_t green;