diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c index 3e32a7d2..447689c1 100644 --- a/src/driver-hidpp20.c +++ b/src/driver-hidpp20.c @@ -51,6 +51,7 @@ #define HIDPP_CAP_BUTTON_KEY_1b04 (1 << 2) #define HIDPP_CAP_BATTERY_LEVEL_1000 (1 << 3) #define HIDPP_CAP_KBD_REPROGRAMMABLE_KEYS_1b00 (1 << 4) +#define HIDPP_CAP_ONBOARD_PROFILES_8100 (1 << 5) struct hidpp20drv_data { struct hidpp20_device *dev; @@ -59,6 +60,7 @@ struct hidpp20drv_data { struct hidpp20_sensor *sensors; unsigned num_controls; struct hidpp20_control_id *controls; + struct hidpp20_profiles *profiles; }; static void @@ -157,7 +159,18 @@ hidpp20drv_has_capability(const struct ratbag_device *device, static int hidpp20drv_current_profile(struct ratbag_device *device) { - return 0; + struct hidpp20drv_data *drv_data = ratbag_get_drv_data((struct ratbag_device *)device); + int rc; + + if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) + return 0; + + rc = hidpp20_onboard_profiles_get_current_profile(drv_data->dev, + drv_data->profiles); + if (rc < 0) + return rc; + + return rc - 1; } static int @@ -324,17 +337,64 @@ hidpp20drv_read_kbd_reprogrammable_key(struct ratbag_device *device) return rc; } +static int +hidpp20drv_read_onboard_profile(struct ratbag_device *device, unsigned index) +{ + struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); + int rc; + + if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) + return 0; + + if (!drv_data->profiles) { + rc = hidpp20_onboard_profiles_allocate(drv_data->dev, &drv_data->profiles); + if (rc < 0) + return rc; + } + + rc = hidpp20_onboard_profiles_read(drv_data->dev, index, drv_data->profiles); + if (rc < 0) + return rc; + + return 0; +} + static void hidpp20drv_read_profile(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; + struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); + struct ratbag_resolution *res; + unsigned i, dpi; hidpp20drv_read_resolution_dpi(profile); hidpp20drv_read_special_key_mouse(device); + hidpp20drv_read_onboard_profile(device, profile->index); profile->is_active = false; if ((int)index == hidpp20drv_current_profile(device)) profile->is_active = true; + + if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) + return; + + dpi = ratbag_resolution_get_dpi(profile->resolution.modes); + + profile->resolution.num_modes = drv_data->profiles->num_modes; + for (i = 0; i < drv_data->profiles->num_modes; i++) { + struct hidpp20_profile *p = &drv_data->profiles->profiles[index]; + + res = ratbag_resolution_init(profile, i, + p->dpi[i], + p->dpi[i], + p->report_rate); + + if (profile->is_active && + res->dpi_x == dpi) + res->is_active = true; + if (i == p->default_dpi) + res->is_default = true; + } } static int @@ -349,6 +409,13 @@ hidpp20drv_init_feature(struct ratbag_device *device, uint16_t feature) struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag *ratbag = device->ratbag; int rc; + uint8_t feature_index, feature_type, feature_version; + + rc = hidpp_root_get_feature(drv_data->dev, + feature, + &feature_index, + &feature_type, + &feature_version); switch (feature) { case HIDPP_PAGE_ROOT: @@ -408,6 +475,10 @@ hidpp20drv_init_feature(struct ratbag_device *device, uint16_t feature) } case HIDPP_PAGE_ONBOARD_PROFILES: { log_debug(ratbag, "device has onboard profiles\n"); + drv_data->capabilities |= HIDPP_CAP_ONBOARD_PROFILES_8100; + /* we read the profiles once with an incorect profile index + * to get the correct number of supported profiles. */ + hidpp20drv_read_onboard_profile(device, 0xffffff); break; } case HIDPP_PAGE_MOUSE_BUTTON_SPY: { @@ -500,7 +571,7 @@ hidpp20drv_probe(struct ratbag_device *device) goto err; } - ratbag_device_init_profiles(device, 1, + ratbag_device_init_profiles(device, drv_data->profiles ? drv_data->profiles->num_profiles : 1, device->num_buttons ? device->num_buttons : 8); return rc; @@ -517,6 +588,8 @@ hidpp20drv_remove(struct ratbag_device *device) ratbag_close_hidraw(device); + if (drv_data->profiles) + free(drv_data->profiles); free(drv_data->controls); free(drv_data->sensors); hidpp20_device_destroy(drv_data->dev); diff --git a/src/hidpp20.c b/src/hidpp20.c index 1240380f..74340299 100644 --- a/src/hidpp20.c +++ b/src/hidpp20.c @@ -171,6 +171,12 @@ hidpp20_get_unaligned_u16(uint8_t *buf) return (buf[0] << 8) | buf[1]; } +static inline uint16_t +hidpp20_get_unaligned_be_u16(uint8_t *buf) +{ + return (buf[1] << 8) | buf[0]; +} + /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ @@ -910,6 +916,149 @@ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, return 0; } +/* -------------------------------------------------------------------------- */ +/* 0x8100 - Onboard Profiles */ +/* -------------------------------------------------------------------------- */ + +#define CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR 0x00 +#define CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE 0x30 +#define CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE 0x40 +#define CMD_ONBOARD_PROFILES_MEMORY_READ 0x50 +#define CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE 0x60 +#define CMD_ONBOARD_PROFILES_MEMORY_WRITE 0x70 +#define CMD_ONBOARD_PROFILES_MEMORY_WRITE_END 0x80 +#define CMD_ONBOARD_PROFILES_GET_CURRENT_DPI_INDEX 0xb0 +#define CMD_ONBOARD_PROFILES_SET_CURRENT_DPI_INDEX 0xc0 + +static int +hidpp20_onboard_profiles_read_memory(struct hidpp20_device *device, + uint16_t page, + uint16_t section, + uint8_t *result) +{ + uint8_t feature_index; + int rc; + union hidpp20_message msg = { + .msg.report_id = REPORT_ID_LONG, + .msg.device_idx = device->index, + .msg.address = CMD_ONBOARD_PROFILES_MEMORY_READ, + .msg.parameters[0] = page >> 8, + .msg.parameters[1] = page & 0xFF, + .msg.parameters[2] = section >> 8, + .msg.parameters[3] = section & 0xFF, + }; + + feature_index = hidpp_root_get_feature_idx(device, + HIDPP_PAGE_ONBOARD_PROFILES); + if (feature_index == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_index; + + rc = hidpp20_request_command(device, &msg); + if (rc) + return rc; + + /* msg.msg.parameters is guaranteed to have a size >= 16 */ + memcpy(result, msg.msg.parameters, 16); + + return 0; +} + +int +hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device, + struct hidpp20_profiles *profiles_list) +{ + uint8_t feature_index; + int rc; + union hidpp20_message msg = { + .msg.report_id = REPORT_ID_SHORT, + .msg.device_idx = device->index, + .msg.address = CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE, + }; + + feature_index = hidpp_root_get_feature_idx(device, + HIDPP_PAGE_ONBOARD_PROFILES); + if (feature_index == 0) + return -ENOTSUP; + + msg.msg.sub_id = feature_index; + + rc = hidpp20_request_command(device, &msg); + if (rc) + return rc; + + return msg.msg.parameters[1]; +} + +int +hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, + struct hidpp20_profiles **profiles_list) +{ + unsigned i; + int rc; + uint8_t data[16] = {0}; + struct hidpp20_profiles *profiles; + unsigned profile_count = 0; + + rc = hidpp20_onboard_profiles_read_memory(device, + 0x0000, + 0x0000, + data); + if (rc < 0) + return rc; + + profiles = zalloc(sizeof(struct hidpp20_profiles)); + + for (i = 0; i < 3; i++) { + uint8_t *d = data + 4 * i; + + if (d[0] == 0xFF && d[1] == 0xFF) + break; + + profile_count++; + profiles->profiles[i].index = d[1]; + profiles->profiles[i].enabled = d[2]; + } + + profiles->num_profiles = profile_count; + profiles->num_modes = 5; + + *profiles_list = profiles; + + return profile_count; +} + +int hidpp20_onboard_profiles_read(struct hidpp20_device *device, + unsigned int index, + struct hidpp20_profiles *profiles_list) +{ + uint8_t data[16] = {0}; + struct hidpp20_profile *profile = &profiles_list->profiles[index]; + unsigned i; + int rc; + + if (index >= profiles_list->num_profiles) + return -EINVAL; + + rc = hidpp20_onboard_profiles_read_memory(device, + index + 1, + 0x0000, + data); + if (rc < 0) + return rc; + + profile->report_rate = 1000 / max(1, data[0]); + profile->default_dpi = data[1]; + profile->switched_dpi = data[2]; + + for (i = 0; i < 5; i++) { + profile->dpi[i] = hidpp20_get_unaligned_be_u16(&data[2 * i + 3]); + } + + return 0; +} + /* -------------------------------------------------------------------------- */ /* generic hidpp20 device operations */ /* -------------------------------------------------------------------------- */ diff --git a/src/hidpp20.h b/src/hidpp20.h index 4645cc04..c930c032 100644 --- a/src/hidpp20.h +++ b/src/hidpp20.h @@ -264,10 +264,51 @@ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, #define HIDPP_PAGE_ONBOARD_PROFILES 0x8100 +struct hidpp20_profile { + uint8_t index; + uint8_t enabled; + unsigned report_rate; + unsigned default_dpi; + unsigned switched_dpi; + uint16_t dpi[5]; +}; + +struct hidpp20_profiles { + uint8_t num_profiles; + uint8_t num_modes; + struct hidpp20_profile profiles[5]; +}; + +/** + * allocates a list of profiles that has to be freed by the caller. + * + * returns the number of profiles in the list or a negative error + */ +int hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, + struct hidpp20_profiles **profiles_list); + +/** + * return the current profile index or a negative error. + */ +int hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device, + struct hidpp20_profiles *profiles_list); + +/** + * parse a given profile from the mouse and fill in the right profile in + * profiles_list. + * + * return 0 or a negative error. + */ +int hidpp20_onboard_profiles_read(struct hidpp20_device *device, + unsigned int index, + struct hidpp20_profiles *profiles_list); + /* -------------------------------------------------------------------------- */ /* 0x8110 - Mouse Button Spy */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_MOUSE_BUTTON_SPY 0x8110 + + #endif /* HIDPP_20_H */