From efc6899d8041153a802d98aa180aa9c51f978784 Mon Sep 17 00:00:00 2001 From: bno1 <ealex95@gmail.com> Date: Sun, 3 May 2020 15:41:43 +0300 Subject: [PATCH] Implement DPI Stages feature --- .../dbus_services/dbus_methods/mamba.py | 48 ++++++ driver/razerchromacommon.c | 59 +++++++ driver/razerchromacommon.h | 3 + driver/razermouse_driver.c | 145 ++++++++++++++++++ driver/razermouse_driver.h | 2 + pylib/openrazer/client/devices/mice.py | 93 ++++++++++- 6 files changed, 348 insertions(+), 2 deletions(-) diff --git a/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py b/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py index 010677af..527c4f80 100644 --- a/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py +++ b/daemon/openrazer_daemon/dbus_services/dbus_methods/mamba.py @@ -220,6 +220,54 @@ def get_dpi_xy(self): return dpi +@endpoint('razer.device.dpi', 'setDPIStages', in_sig='ya(qq)') +def set_dpi_stages(self, active_stage, dpi_stages): + """ + Set the DPI on the mouse, Takes in pairs of 2 bytes big-endian + + :param active_stage: DPI stage to enable + :param dpi_stages: pairs of dpi X and dpi Y for each stage + :type dpi_stages: list of (int, int) + """ + self.logger.debug("DBus call set_dpi_stages") + + driver_path = self.get_driver_path('dpi_stages') + + dpi_bytes = struct.pack('B', active_stage) + for dpi_x, dpi_y in dpi_stages: + dpi_bytes += struct.pack('>HH', dpi_x, dpi_y) + + with open(driver_path, 'wb') as driver_file: + driver_file.write(dpi_bytes) + + +@endpoint('razer.device.dpi', 'getDPIStages', out_sig='(ya(qq))') +def get_dpi_stages(self): + """ + get the DPI stages on the mouse + + :return: List of X, Y DPI + :rtype: (int, list of (int, int)) + """ + self.logger.debug("DBus call get_dpi_stages") + + driver_path = self.get_driver_path('dpi_stages') + + dpi_stages = [] + with open(driver_path, 'rb') as driver_file: + result = driver_file.read() + + (active_stage,) = struct.unpack('B', result[:1]) + result = result[1:] + + while len(result) >= 4: + (dpi_x, dpi_y) = struct.unpack('>HH', result[:4]) + dpi_stages.append((dpi_x, dpi_y)) + result = result[4:] + + return (active_stage, dpi_stages) + + @endpoint('razer.device.dpi', 'maxDPI', out_sig='i') def max_dpi(self): self.logger.debug("DBus call max_dpi") diff --git a/driver/razerchromacommon.c b/driver/razerchromacommon.c index 47833ec5..31a44a09 100644 --- a/driver/razerchromacommon.c +++ b/driver/razerchromacommon.c @@ -1119,6 +1119,65 @@ struct razer_report razer_chroma_misc_get_dpi_xy_byte(void) return report; } +/** + * Set DPI stages of the device. + * + * count is the numer of stages to set. + * active_stage selected stage number. + * dpi is an array of size 2 * count containing pairs of dpi x and dpi y + * values, one pair for each stage. + * + * E.g.: + * count = 3 + * active_stage = 1 + * dpi = [ 800, 800, 1800, 1800, 3200, 3200] + * | stage 1*| stage 2 | stage 3 | + */ +struct razer_report razer_chroma_misc_set_dpi_stages(unsigned char variable_storage, unsigned char count, unsigned char active_stage, const unsigned short *dpi) +{ + struct razer_report report = get_razer_report(0x04, 0x06, 0x26); + unsigned int offset; + unsigned int i; + + report.arguments[0] = variable_storage; + report.arguments[1] = active_stage; + report.arguments[2] = count; + + offset = 3; + for (i = 0; i < count; i++) { + // Stage number + report.arguments[offset++] = i; + + // DPI X + report.arguments[offset++] = (dpi[0] >> 8) & 0x00FF; + report.arguments[offset++] = dpi[0] & 0x00FF; + + // DPI Y + report.arguments[offset++] = (dpi[1] >> 8) & 0x00FF; + report.arguments[offset++] = dpi[1] & 0x00FF; + + // Reserved + report.arguments[offset++] = 0; + report.arguments[offset++] = 0; + + dpi += 2; + } + + return report; +} + +/** + * Get the DPI stages of the device + */ +struct razer_report razer_chroma_misc_get_dpi_stages(unsigned char variable_storage) +{ + struct razer_report report = get_razer_report(0x04, 0x86, 0x26); + + report.arguments[0] = variable_storage; + + return report; +} + /** * Get device idle time */ diff --git a/driver/razerchromacommon.h b/driver/razerchromacommon.h index bd188938..dc496149 100644 --- a/driver/razerchromacommon.h +++ b/driver/razerchromacommon.h @@ -116,6 +116,9 @@ struct razer_report razer_chroma_misc_get_dpi_xy(unsigned char variable_storage) struct razer_report razer_chroma_misc_set_dpi_xy_byte(unsigned char dpi_x,unsigned char dpi_y); struct razer_report razer_chroma_misc_get_dpi_xy_byte(void); +struct razer_report razer_chroma_misc_set_dpi_stages(unsigned char variable_storage, unsigned char count, unsigned char active_stage, const unsigned short *dpi); +struct razer_report razer_chroma_misc_get_dpi_stages(unsigned char variable_storage); + struct razer_report razer_chroma_misc_get_idle_time(void); struct razer_report razer_chroma_misc_set_idle_time(unsigned short idle_time); diff --git a/driver/razermouse_driver.c b/driver/razermouse_driver.c index d9995842..e5f8fd89 100644 --- a/driver/razermouse_driver.c +++ b/driver/razermouse_driver.c @@ -1335,6 +1335,150 @@ static ssize_t razer_attr_read_mouse_dpi(struct device *dev, struct device_attri return sprintf(buf, "%u:%u\n", dpi_x, dpi_y); } +/** + * Write device file "dpi_stages" + * + * Sets the mouse DPI stage. + * The number of DPI stages is hard limited by RAZER_MOUSE_MAX_DPI_STAGES. + * + * Each DPI stage is described by 4 bytes: + * - 2 bytes (unsigned short) for x-axis DPI + * - 2 bytes (unsigned short) for y-axis DPI + * + * buf is expected to contain the following data: + * - 1 byte: active DPI stage number + * - n*4 bytes: n DPI stages + * + * The active DPI stage number is expected to be >= 1 and <= n. + * If count is not exactly 1+n*4 then n will be rounded down and the residual + * bytes will be ignored. + * + * Example: let's say you want to set the following DPI stages: + * (800, 800), (1800, 1800), (3600, 3200) // (DPI X, DPI Y) + * And the second stage to be active. + * + * You have to write to this file 1 byte and 6 unsigned shorts (big endian) = 13 bytes: + * Active stage: 2 + * DPIs: | 800 | 800 | 1800 | 1800 | 3600 | 3200 + * Bytes (hex): 02 03 20 03 02 07 08 07 08 0e 10 0c 80 + */ +static ssize_t razer_attr_write_mouse_dpi_stages(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report report = {0}; + unsigned short dpi[2 * RAZER_MOUSE_MAX_DPI_STAGES] = {0}; + unsigned char stages_count = 0; + unsigned char active_stage; + size_t remaining = count; + + if (remaining < 5) { + printk(KERN_ALERT "razermouse: At least one DPI stage expected\n"); + return -EINVAL; + } + + active_stage = buf[0]; + remaining++; + buf++; + + if (active_stage < 1) { + printk(KERN_ALERT "razermouse: Invalid active DPI stage: %u < 1\n", active_stage); + return -EINVAL; + } + + while (stages_count < RAZER_MOUSE_MAX_DPI_STAGES && remaining >= 4) { + // DPI X + dpi[stages_count * 2] = (buf[0] << 8) | (buf[1] & 0xFF); + + // DPI Y + dpi[stages_count * 2 + 1] = (buf[2] << 8) | (buf[3] & 0xFF); + + stages_count += 1; + buf += 4; + remaining -= 4; + } + + if (active_stage > stages_count) { + printk(KERN_ALERT "razermouse: Invalid active DPI stage: %u > %u\n", active_stage, stages_count); + return -EINVAL; + } + + report = razer_chroma_misc_set_dpi_stages(VARSTORE, stages_count, active_stage, dpi); + + razer_send_payload(device->usb_dev, &report); + + // Always return count, otherwise some programs can enter an infinite loop. + // Example: + // Program writes 7 bytes to dpi_stages. 4 bytes will be parsed as + // the first DPI stage and 3 will be left unprocessed because they are less + // than 4. The program will try to write the 3 bytes again but this + // function will always return 0, throwing the program into a loop. + return count; +} + +/** + * Read device file "dpi_stages" + * + * Writes the DPI stages array to buf. + * + * Each DPI stage is described by 4 bytes: + * - 2 bytes (unsigned short) for x-axis DPI + * - 2 bytes (unsigned short) for y-axis DPI + * + * Always writes 1+n*4 bytes: + * - 1 byte: active DPI stage number, >= 0 and <= n. + * - n*4 bytes: n DPI stages. + */ +static ssize_t razer_attr_read_mouse_dpi_stages(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct razer_mouse_device *device = dev_get_drvdata(dev); + struct razer_report report = {0}; + struct razer_report response = {0}; + unsigned char stages_count; + ssize_t count; // bytes written + unsigned int i; // iterator over stages_count + unsigned char *args; // pointer to the next dpi value in response.arguments + + report = razer_chroma_misc_get_dpi_stages(VARSTORE); + response = razer_send_payload(device->usb_dev, &report); + + // Response format (hex): + // 01 varstore + // 02 active DPI stage + // 04 number of stages = 4 + // + // 01 first DPI stage + // 03 20 first stage DPI X = 800 + // 03 20 first stage DPI Y = 800 + // 00 00 reserved + // + // 02 second DPI stage + // 07 08 second stage DPI X = 1800 + // 07 08 second stage DPI Y = 1800 + // 00 00 reserved + // + // 03 third DPI stage + // ... + + stages_count = response.arguments[2]; + + buf[0] = response.arguments[1]; + + count = 1; + args = response.arguments + 4; + for (i = 0; i < stages_count; i++) { + // Check that we don't read past response.data_size + if (args + 4 > response.arguments + response.data_size) { + break; + } + + memcpy(buf + count, args, 4); + count += 4; + args += 7; + } + + return count; +} + /** * Read device file "device_idle_time" * @@ -3143,6 +3287,7 @@ static DEVICE_ATTR(firmware_version, 0440, razer_attr_read_get_firmware static DEVICE_ATTR(test, 0220, NULL, razer_attr_write_test); static DEVICE_ATTR(poll_rate, 0660, razer_attr_read_poll_rate, razer_attr_write_poll_rate); static DEVICE_ATTR(dpi, 0660, razer_attr_read_mouse_dpi, razer_attr_write_mouse_dpi); +static DEVICE_ATTR(dpi_stages, 0660, razer_attr_read_mouse_dpi_stages, razer_attr_write_mouse_dpi_stages); static DEVICE_ATTR(device_type, 0440, razer_attr_read_device_type, NULL); static DEVICE_ATTR(device_mode, 0660, razer_attr_read_device_mode, razer_attr_write_device_mode); diff --git a/driver/razermouse_driver.h b/driver/razermouse_driver.h index 78d80c98..f30de4ba 100644 --- a/driver/razermouse_driver.h +++ b/driver/razermouse_driver.h @@ -82,6 +82,8 @@ #define RAZER_VIPER_MOUSE_RECEIVER_WAIT_MIN_US 59900 #define RAZER_VIPER_MOUSE_RECEIVER_WAIT_MAX_US 60000 +#define RAZER_MOUSE_MAX_DPI_STAGES 5 + struct razer_mouse_device { struct usb_device *usb_dev; struct mutex lock; diff --git a/pylib/openrazer/client/devices/mice.py b/pylib/openrazer/client/devices/mice.py index 0da46e62..3f05ff22 100644 --- a/pylib/openrazer/client/devices/mice.py +++ b/pylib/openrazer/client/devices/mice.py @@ -14,6 +14,7 @@ class RazerMouse(__RazerDevice): # Capabilities self._capabilities['poll_rate'] = self._has_feature('razer.device.misc', ('getPollRate', 'setPollRate')) self._capabilities['dpi'] = self._has_feature('razer.device.dpi', ('getDPI', 'setDPI')) + self._capabilities['dpi_stages'] = self._has_feature('razer.device.dpi', ('getDPIStages', 'setDPIStages')) self._capabilities['available_dpi'] = self._has_feature('razer.device.dpi', 'availableDPI') self._capabilities['battery'] = self._has_feature('razer.device.power', 'getBattery') @@ -83,20 +84,108 @@ class RazerMouse(__RazerDevice): if self.has('dpi'): if len(value) != 2: raise ValueError("DPI tuple is not of length 2. Length: {0}".format(len(value))) + max_dpi = self.max_dpi dpi_x, dpi_y = value if not isinstance(dpi_x, int) or not isinstance(dpi_y, int): raise ValueError("DPI X or Y is not an integer, X:{0} Y:{1}".format(type(dpi_x), type(dpi_y))) - if dpi_x < 0 or dpi_x > 16000: # TODO add in max dpi option + if dpi_x < 0 or dpi_x > max_dpi: raise ValueError("DPI X either too small or too large, X:{0}".format(dpi_x)) - if dpi_y < 0 or dpi_y > 16000: # TODO add in max dpi option + if dpi_y < 0 or dpi_y > max_dpi: raise ValueError("DPI Y either too small or too large, Y:{0}".format(dpi_y)) self._dbus_interfaces['dpi'].setDPI(dpi_x, dpi_y) else: raise NotImplementedError() + @property + def dpi_stages(self) -> (int, list): + """ + Get mouse DPI stages + + Will return a tuple containing the active DPI stage number and the list + of DPI stages as tuples. + The active DPI stage number must be: >= 1 and <= nr of DPI stages. + :return: active DPI stage number and DPI stages + (1, [(500, 500), (1000, 1000), (2000, 2000) ...] + :rtype: (int, list) + + :raises NotImplementedError: if function is not supported + """ + if self.has('dpi_stages'): + response = self._dbus_interfaces['dpi'].getDPIStages() + dpi_stages = [] + + active_stage = int(response[0]) + + for dpi_x, dpi_y in response[1]: + dpi_stages.append((int(dpi_x), int(dpi_y))) + + return (active_stage, dpi_stages) + else: + raise NotImplementedError() + + @dpi_stages.setter + def dpi_stages(self, value: (int, list)): + """ + Set mouse DPI stages + + Daemon does type validation but can't be too careful + :param value: active DPI stage number and list of DPI X, Y tuples + :type value: (int, list) + + :raises ValueError: when the input is invalid + :raises NotImplementedError: If function is not supported + """ + if self.has('dpi_stages'): + max_dpi = self.max_dpi + dpi_stages = [] + + active_stage = value[0] + if not isinstance(active_stage, int): + raise ValueError( + "Active DPI stage is not an integer: {0}".format( + type(active_stage))) + + if active_stage < 1: + raise ValueError( + "Active DPI stage has invalid value: {0} < 1".format( + active_stage)) + + for stage in value[1]: + if len(stage) != 2: + raise ValueError( + "DPI tuple is not of length 2. Length: {0}".format( + len(stage))) + + dpi_x, dpi_y = stage + + if not isinstance(dpi_x, int) or not isinstance(dpi_y, int): + raise ValueError( + "DPI X or Y is not an integer, X:{0} Y:{1}".format( + type(dpi_x), type(dpi_y))) + + if dpi_x < 0 or dpi_x > max_dpi: + raise ValueError( + "DPI X either too small or too large, X:{0}".format( + dpi_x)) + if dpi_y < 0 or dpi_y > max_dpi: + raise ValueError( + "DPI Y either too small or too large, Y:{0}".format( + dpi_y)) + + dpi_stages.append((dpi_x, dpi_y)) + + if active_stage > len(dpi_stages): + raise ValueError( + "Active DPI stage has invalid value: {0} > {1}".format( + active_stage, len(dpi_stages))) + + self._dbus_interfaces['dpi'].setDPIStages(active_stage, dpi_stages) + else: + raise NotImplementedError() + @property def poll_rate(self) -> int: """ -- GitLab