diff --git a/hwdb/70-libratbag-mouse.hwdb b/hwdb/70-libratbag-mouse.hwdb index 5f3882d9..6111c58f 100644 --- a/hwdb/70-libratbag-mouse.hwdb +++ b/hwdb/70-libratbag-mouse.hwdb @@ -66,10 +66,12 @@ mouse:usb:v046dp1028:name:*: # G5 mouse:usb:v046dpc041:name:*: RATBAG_DRIVER=hidpp10 + RATBAG_HIDPP10_DPI_LIST=400;800;1600;2000 # G5 2007 mouse:usb:v046dpc049:name:*: RATBAG_DRIVER=hidpp10 + RATBAG_HIDPP10_DPI_LIST=400;800;1600;2000 # G7 mouse:usb:v046dpc51a:name:*: @@ -78,23 +80,28 @@ mouse:usb:v046dpc51a:name:*: # G9 mouse:usb:v046dpc048:name:*: RATBAG_DRIVER=hidpp10 + RATBAG_HIDPP10_DPI_LIST=0;200;400;600;800;1000;1200;1400;1600;1800;2000;2200;2400;2600;2800;3000;3200 # G9x [Original] mouse:usb:v046dpc066:name:*: RATBAG_DRIVER=hidpp10 + RATBAG_HIDPP10_DPI=0:5700@23.53 # G9x [Call of Duty MW3 Edition] mouse:usb:v046dpc249:name:*: RATBAG_DRIVER=hidpp10 + RATBAG_HIDPP10_DPI=0:5700@23.53 # G500 mouse:usb:v046dpc068:name:*: RATBAG_DRIVER=hidpp10 + RATBAG_HIDPP10_DPI=0:5700@23.53 # G500s mouse:usb:v046dpc24e:name:*: RATBAG_DRIVER=hidpp10 RATBAG_SVG=logitech-g500s.svg + RATBAG_HIDPP10_DPI=0:8200@50 # G502 over USB mouse:usb:v046dpc07d:name:*: diff --git a/src/Makefile.am b/src/Makefile.am index a380c10e..dbbb75fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -50,6 +50,7 @@ libhidpp_la_SOURCES = \ hidpp10.c \ hidpp20.h \ hidpp20.c +libhidpp_la_LDFLAGS = -lm libutil_la_SOURCES = \ libratbag-util.c \ libratbag-util.h diff --git a/src/driver-hidpp10.c b/src/driver-hidpp10.c index d40470ac..10d28917 100644 --- a/src/driver-hidpp10.c +++ b/src/driver-hidpp10.c @@ -212,6 +212,7 @@ hidpp10drv_probe(struct ratbag_device *device) struct hidpp10drv_data *drv_data = NULL; struct hidpp10_device *dev = NULL; struct hidpp_device base; + const char *prop; rc = ratbag_find_hidraw(device, hidpp10drv_test_hidraw); if (rc == -ENODEV) { @@ -243,6 +244,33 @@ hidpp10drv_probe(struct ratbag_device *device) goto err; } + prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP10_DPI"); + if (prop) { + rc = hidpp10_build_dpi_table_from_dpi_info(dev, prop); + if (rc) + log_error(device->ratbag, + "Error parsing RATBAG_HIDPP10_DPI: '%s' for %s\n", + prop, + device->name); + } + + prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP10_DPI_LIST"); + if (prop) { + rc = hidpp10_build_dpi_table_from_list(dev, prop); + if (rc) + log_error(device->ratbag, + "Error parsing RATBAG_HIDPP10_DPI_LIST: '%s' for %s\n", + prop, + device->name); + } + + if (!dev->dpi_count) + log_info(device->ratbag, + "Device %s might have wrong dpi settings. " + "Please add RATBAG_HIDPP10_DPI or RATBAG_HIDPP10_DPI_LIST " + "to the udev properties.\n", + device->name); + drv_data->dev = dev; ratbag_set_drv_data(device, drv_data); diff --git a/src/hidpp10.c b/src/hidpp10.c index 1ed3ad49..538d87f3 100644 --- a/src/hidpp10.c +++ b/src/hidpp10.c @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include "hidpp10.h" @@ -83,6 +85,98 @@ const char *device_types[0xFF] = { [0x0A ... 0xFE] = NULL, }; +int +hidpp10_build_dpi_table_from_list(struct hidpp10_device *dev, + const char *str_list) +{ + unsigned int i, count, index; + int nread, dpi = 0; + char c; + /* + * str_list is in the form: + * "0;200;400;600;800;1000;1200" + */ + + /* first, count how many elements do we have in the table */ + count = 1; + i = 0; + while (str_list[i] != 0) { + c = str_list[i++]; + if (c == ';') + count++; + } + + index = 0; + + /* check that the max raw value fits in a uint8_t */ + if (count + 0x80 - 1> 0xff) + return -EINVAL; + + dev->dpi_count = count; + dev->dpi_table = zalloc(count * sizeof(*dev->dpi_table)); + + while (*str_list != 0 && index < count) { + if (*str_list == ';') { + str_list++; + continue; + } + + nread = 0; + sscanf(str_list, "%d%n", &dpi, &nread); + if (!nread || dpi < 0) + goto err; + + dev->dpi_table[index].raw_value = index + 0x80; + dev->dpi_table[index].dpi = dpi; + + str_list += nread; + index++; + } + + if (index != count) + goto err; + + return 0; + +err: + dev->dpi_count = 0; + free(dev->dpi_table); + dev->dpi_table = NULL; + return -EINVAL; +} + +int +hidpp10_build_dpi_table_from_dpi_info(struct hidpp10_device *dev, + const char *str_dpi) +{ + float min, max, step; + unsigned raw_max, i; + int rc; + /* + * str_list is in the form: + * "MIN:MAX@STEP" + */ + + rc = sscanf(str_dpi, "%f:%f@%f", &min, &max, &step); + if (rc != 3) + return -EINVAL; + + raw_max = (max - min) / step; + if (raw_max > 0xff) + return -EINVAL; + + dev->dpi_count = raw_max + 1; + dev->dpi_table = zalloc((raw_max + 1) * sizeof(*dev->dpi_table)); + + for (i = 1; i <= raw_max; i++) { + dev->dpi_table[i].raw_value = i; + dev->dpi_table[i].dpi = round((min + step * i) / 25.0f) * 25; + } + + return 0; +} + + static int hidpp10_request_command(struct hidpp10_device *dev, union hidpp10_message *msg) { @@ -479,6 +573,50 @@ union _hidpp10_profile_data { }; _Static_assert((sizeof(union _hidpp10_profile_data) % 16) == 0, "Invalid size"); +static uint8_t +hidpp10_get_dpi_mapping(struct hidpp10_device *dev, unsigned int value) +{ + struct hidpp10_dpi_mapping *m; + unsigned int i, delta, min_delta; + uint8_t result = 0; + + if (!dev->dpi_table) + return value / 50; + + m = dev->dpi_table; + + min_delta = INT_MAX; + + for (i = 0; i < dev->dpi_count; i++) { + delta = abs(value - m[i].dpi); + if (delta < min_delta) { + result = m[i].raw_value; + min_delta = delta; + } + } + + return result; +} + +static unsigned int +hidpp10_get_dpi_value(struct hidpp10_device *dev, uint8_t raw_value) +{ + struct hidpp10_dpi_mapping *m; + unsigned int i; + + if (!dev->dpi_table) + return raw_value * 50; + + m = dev->dpi_table; + + for (i = 0; i < dev->dpi_count; i++) { + if (raw_value == m[i].raw_value) + return m[i].dpi; + } + + return 0; +} + int hidpp10_get_current_profile(struct hidpp10_device *dev, int8_t *current_profile) { @@ -540,9 +678,9 @@ hidpp10_get_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_pr struct _hidpp10_dpi_mode *dpi = &p->dpi_modes[i]; be = (uint8_t*)&dpi->xres; - profile.dpi_modes[i].xres = hidpp_get_unaligned_be_u16(be) * 50; + profile.dpi_modes[i].xres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_be_u16(be)); be = (uint8_t*)&dpi->yres; - profile.dpi_modes[i].yres = hidpp_get_unaligned_be_u16(be) * 50; + profile.dpi_modes[i].yres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_be_u16(be)); profile.dpi_modes[i].led[0] = dpi->led1 == 0x2; profile.dpi_modes[i].led[1] = dpi->led2 == 0x2; @@ -908,8 +1046,8 @@ hidpp10_get_current_resolution(struct hidpp10_device *dev, return res; /* resolution is in 50dpi multiples */ - *xres = hidpp_get_unaligned_le_u16(&resolution.data[4]) * 50; - *yres = hidpp_get_unaligned_le_u16(&resolution.data[6]) * 50; + *xres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_le_u16(&resolution.data[4])); + *yres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_le_u16(&resolution.data[6])); return 0; } @@ -922,8 +1060,8 @@ hidpp10_set_current_resolution(struct hidpp10_device *dev, unsigned idx = dev->index; union hidpp10_message resolution = CMD_CURRENT_RESOLUTION(REPORT_ID_LONG, idx, SET_LONG_REGISTER_REQ); - hidpp_set_unaligned_le_u16(&resolution.data[4], xres / 50); - hidpp_set_unaligned_le_u16(&resolution.data[6], yres / 50); + hidpp_set_unaligned_le_u16(&resolution.data[4], hidpp10_get_dpi_mapping(dev, xres)); + hidpp_set_unaligned_le_u16(&resolution.data[6], hidpp10_get_dpi_mapping(dev, yres)); return hidpp10_request_command(dev, &resolution); } @@ -1255,5 +1393,7 @@ hidpp10_device_new(const struct hidpp_device *base, int idx) void hidpp10_device_destroy(struct hidpp10_device *dev) { + if (dev->dpi_table) + free(dev->dpi_table); free(dev); } diff --git a/src/hidpp10.h b/src/hidpp10.h index 5b1cf1fe..36755303 100644 --- a/src/hidpp10.h +++ b/src/hidpp10.h @@ -40,9 +40,16 @@ #define HIDPP10_MAX_PAGE_NUMBER 31 +struct hidpp10_dpi_mapping { + uint8_t raw_value; + unsigned dpi; +}; + struct hidpp10_device { struct hidpp_device base; unsigned index; + uint8_t dpi_count; + struct hidpp10_dpi_mapping *dpi_table; /* must be null terminated */ }; struct hidpp10_device* @@ -51,6 +58,35 @@ hidpp10_device_new(const struct hidpp_device *base, int idx); void hidpp10_device_destroy(struct hidpp10_device *dev); +/** + * Builds the table of dpi for the device from the given string. + * + * @param dev a struct hidpp10_device previously allocated + * @param str_list a string representing the dpi table + * + * The given string contains only positive integer values, separated by + * semicolons (';'). The n-th element in the list corresponds to the + * raw value (0x80 + n - 1) + */ +int +hidpp10_build_dpi_table_from_list(struct hidpp10_device *dev, + const char *str_list); + +/** + * Builds the table of dpi for the device from the given dpi description. + * + * @param dev a struct hidpp10_device previously allocated + * @param str_dpi a string representing the dpi parameters + * + * The given string contains 3 float values, separated by colons (':'). + * The format is MIN:MAX@STEP + * MIN corresponds to the raw_value '0' + * MAX corresponds to the raw_value floor((MAX - MIN) / STEP) + */ +int +hidpp10_build_dpi_table_from_dpi_info(struct hidpp10_device *dev, + const char *str_dpi); + /* -------------------------------------------------------------------------- */ /* 0x00: Enable HID++ Notifications */ /* -------------------------------------------------------------------------- */