From ba911631ce1013f3f7a0893cda2b859a515f78d4 Mon Sep 17 00:00:00 2001
From: Dennis Lambe Jr <malsyned@malsyned.net>
Date: Tue, 5 May 2020 17:09:36 -0400
Subject: [PATCH] Add support for Razer Basilisk V2

Translate tilt wheel clicks to REL_HWHEEL events in the kernel driver
Translate Profile and Clutch buttons to F18 and F19

There seems to be parallel construction between the remapping in this
driver and the M1-M5 key handling in the razerkbd driver. Since razerkbd
assigns F17 to input code 0x24, I didn't want to use F17 to mean some
other code in the mouse driver. Hence, F17 is skipped and the Basilisk's
new buttons are mapped starting at F18.

Repeat HWHEEL events while the wheel is held tilted with 250 ms delay &
33 ms repeat

Adjustable repeat rate and repeat delay on tilt wheel

Device attr to disable tilt wheel HWHEEL translation

Convert DPI, Profile buttons to BTN_ events on mouse intf

There are a lot of Razer mice that this driver tries to support, and
adding the need to grok and then patch report descriptors seemed like
adding too much to the reverse engineering burden. Instead, I do all of
my report interpretation in the raw_event hook.
---
 README.md                                     |   1 +
 daemon/openrazer_daemon/hardware/mouse.py     |  53 ++
 driver/razermouse_driver.c                    | 452 +++++++++++++++++-
 driver/razermouse_driver.h                    |  11 +
 ...io.github.openrazer.openrazer.metainfo.xml |   1 +
 install_files/udev/99-razer.rules             |   2 +-
 .../_fake_driver/razerbasiliskv2.cfg          |  26 +
 scripts/generate_fake_driver.sh               |   3 +
 8 files changed, 522 insertions(+), 27 deletions(-)
 create mode 100644 pylib/openrazer/_fake_driver/razerbasiliskv2.cfg

diff --git a/README.md b/README.md
index 6147ee36..57ca21d2 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,7 @@ The devices below are fully feature supported by OpenRazer, which means all avai
 | Razer DeathAdder V2 Pro (Wireless)            |  1532:007D  |
 | Razer Basilisk X HyperSpeed                   |  1532:0083  |
 | Razer DeathAdder V2                           |  1532:0084  |
+| Razer Basilisk V2                             |  1532:0085  |
 | Razer Viper Mini                              |  1532:008A  |
 | Razer DeathAdder V2 Mini                      |  1532:008C  |
 | Razer Naga Left-Handed Edition                |  1532:008D  |
diff --git a/daemon/openrazer_daemon/hardware/mouse.py b/daemon/openrazer_daemon/hardware/mouse.py
index 1d44be23..52721ef0 100644
--- a/daemon/openrazer_daemon/hardware/mouse.py
+++ b/daemon/openrazer_daemon/hardware/mouse.py
@@ -2071,6 +2071,59 @@ class RazerBasilisk(__RazerDeviceSpecialBrightnessSuspend):
         self.disable_notify = False
 
 
+class RazerBasiliskV2(__RazerDeviceSpecialBrightnessSuspend):
+    """
+    Class for the Razer Basilisk V2
+    """
+    EVENT_FILE_REGEX = re.compile(r'.*Razer_Basilisk_V2-if0(1|2)-event-kbd')
+
+    USB_VID = 0x1532
+    USB_PID = 0x0085
+    HAS_MATRIX = True
+    MATRIX_DIMS = [1, 1]
+    METHODS = ['get_device_type_mouse', 'max_dpi', 'get_dpi_xy', 'set_dpi_xy', 'get_poll_rate', 'set_poll_rate',
+               'get_logo_brightness', 'set_logo_brightness', 'get_scroll_brightness', 'set_scroll_brightness',
+               # Logo
+               'set_logo_static_naga_hex_v2', 'set_logo_spectrum_naga_hex_v2', 'set_logo_none_naga_hex_v2', 'set_logo_reactive_naga_hex_v2', 'set_logo_breath_random_naga_hex_v2', 'set_logo_breath_single_naga_hex_v2', 'set_logo_breath_dual_naga_hex_v2',
+               # Scroll wheel
+               'set_scroll_static_naga_hex_v2', 'set_scroll_spectrum_naga_hex_v2', 'set_scroll_none_naga_hex_v2', 'set_scroll_reactive_naga_hex_v2', 'set_scroll_breath_random_naga_hex_v2', 'set_scroll_breath_single_naga_hex_v2', 'set_scroll_breath_dual_naga_hex_v2',
+               # Can set LOGO and Scroll with custom
+               'set_custom_effect', 'set_key_row']
+
+    DEVICE_IMAGE = "https://assets.razerzone.com/eeimages/support/products/1617/1617_basilisk-v2.png"
+
+    DPI_MAX = 20000
+
+    def _suspend_device(self):
+        """
+        Suspend the device
+
+        Get the current brightness level, store it for later and then set the brightness to 0
+        """
+        self.suspend_args.clear()
+        self.suspend_args['brightness'] = (_da_get_logo_brightness(self), _da_get_scroll_brightness(self))
+
+        # Todo make it context?
+        self.disable_notify = True
+        _da_set_logo_brightness(self, 0)
+        _da_set_scroll_brightness(self, 0)
+        self.disable_notify = False
+
+    def _resume_device(self):
+        """
+        Resume the device
+
+        Get the last known brightness and then set the brightness
+        """
+        logo_brightness = self.suspend_args.get('brightness', (100, 100))[0]
+        scroll_brightness = self.suspend_args.get('brightness', (100, 100))[1]
+
+        self.disable_notify = True
+        _da_set_logo_brightness(self, logo_brightness)
+        _da_set_scroll_brightness(self, scroll_brightness)
+        self.disable_notify = False
+
+
 class RazerDeathAdderV2(__RazerDeviceSpecialBrightnessSuspend):
     """
     Class for the Razer DeathAdder V2
diff --git a/driver/razermouse_driver.c b/driver/razermouse_driver.c
index 0a7a1697..2dab2f45 100644
--- a/driver/razermouse_driver.c
+++ b/driver/razermouse_driver.c
@@ -24,6 +24,7 @@
 #include <linux/init.h>
 #include <linux/usb/input.h>
 #include <linux/hid.h>
+#include <linux/hrtimer.h>
 #include <linux/random.h>
 
 #include "razermouse_driver.h"
@@ -394,6 +395,10 @@ static ssize_t razer_attr_read_device_type(struct device *dev, struct device_att
         device_type = "Razer Basilisk\n";
         break;
 
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
+        device_type = "Razer Basilisk V2\n";
+        break;
+
     case USB_DEVICE_ID_RAZER_DEATHADDER_V2:
         device_type = "Razer DeathAdder V2\n";
         break;
@@ -456,6 +461,7 @@ static ssize_t razer_attr_read_get_firmware_version(struct device *dev, struct d
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
     case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
 
@@ -567,6 +573,7 @@ static ssize_t razer_attr_write_mode_custom(struct device *dev, struct device_at
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_effect_custom_frame();
         report.transaction_id.id = 0x1f;
         break;
@@ -801,6 +808,7 @@ static ssize_t razer_attr_read_get_serial(struct device *dev, struct device_attr
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
     }
@@ -978,6 +986,7 @@ static ssize_t razer_attr_read_poll_rate(struct device *dev, struct device_attri
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
     }
@@ -1047,6 +1056,7 @@ static ssize_t razer_attr_write_poll_rate(struct device *dev, struct device_attr
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
     }
@@ -1279,6 +1289,7 @@ static ssize_t razer_attr_write_mouse_dpi(struct device *dev, struct device_attr
         case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
         case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
         case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
             report.transaction_id.id = 0x1f;
             break;
         }
@@ -1339,6 +1350,7 @@ static ssize_t razer_attr_read_mouse_dpi(struct device *dev, struct device_attri
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
     case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_misc_get_dpi_xy(NOSTORE);
         report.transaction_id.id = 0x1f;
         break;
@@ -1389,6 +1401,54 @@ static ssize_t razer_attr_read_mouse_dpi(struct device *dev, struct device_attri
     return sprintf(buf, "%u:%u\n", dpi_x, dpi_y);
 }
 
+static ssize_t razer_attr_write_tilt_hwheel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct razer_mouse_device *device = dev_get_drvdata(dev);
+    unsigned int tilt_hwheel;
+    if (kstrtouint(buf, 0, &tilt_hwheel) < 0)
+        return -EINVAL;
+    device->tilt_hwheel = !!tilt_hwheel;
+    return count;
+}
+
+static ssize_t razer_attr_read_tilt_hwheel(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct razer_mouse_device *device = dev_get_drvdata(dev);
+    return sprintf(buf, "%u\n", device->tilt_hwheel);
+}
+
+static ssize_t razer_attr_write_tilt_repeat(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct razer_mouse_device *device = dev_get_drvdata(dev);
+    unsigned int tilt_repeat;
+    if (kstrtouint(buf, 0, &tilt_repeat) < 0)
+        return -EINVAL;
+    device->tilt_repeat = tilt_repeat;
+    return count;
+}
+
+static ssize_t razer_attr_read_tilt_repeat(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct razer_mouse_device *device = dev_get_drvdata(dev);
+    return sprintf(buf, "%u\n", device->tilt_repeat);
+}
+
+static ssize_t razer_attr_write_tilt_repeat_delay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct razer_mouse_device *device = dev_get_drvdata(dev);
+    unsigned int tilt_repeat_delay;
+    if (kstrtouint(buf, 0, &tilt_repeat_delay) < 0)
+        return -EINVAL;
+    device->tilt_repeat_delay = tilt_repeat_delay;
+    return count;
+}
+
+static ssize_t razer_attr_read_tilt_repeat_delay(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    struct razer_mouse_device *device = dev_get_drvdata(dev);
+    return sprintf(buf, "%u\n", device->tilt_repeat_delay);
+}
+
 /**
  * Write device file "dpi_stages"
  *
@@ -1673,6 +1733,11 @@ static ssize_t razer_attr_write_set_key_row(struct device *dev, struct device_at
             report = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]);
             break;
 
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
+            report = razer_chroma_extended_matrix_set_custom_frame(row_id, start_col, stop_col, (unsigned char*)&buf[offset]);
+            report.transaction_id.id = 0x1f;
+            break;
+
         case USB_DEVICE_ID_RAZER_MAMBA_WIRED:
         case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS:
             report = razer_chroma_misc_one_row_set_custom_frame(start_col, stop_col, (unsigned char*)&buf[offset]);
@@ -1720,6 +1785,7 @@ static ssize_t razer_attr_write_device_mode(struct device *dev, struct device_at
         case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
         case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
         case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
             report.transaction_id.id = 0x1f;
             break;
 
@@ -1788,6 +1854,7 @@ static ssize_t razer_attr_read_device_mode(struct device *dev, struct device_att
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
     case USB_DEVICE_ID_RAZER_ATHERIS_RECEIVER:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
     }
@@ -1817,6 +1884,7 @@ static ssize_t razer_attr_read_scroll_led_brightness(struct device *dev, struct
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_get_brightness(VARSTORE, SCROLL_WHEEL_LED);
         report.transaction_id.id = 0x1f;
         break;
@@ -1864,6 +1932,7 @@ static ssize_t razer_attr_write_scroll_led_brightness(struct device *dev, struct
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_brightness(VARSTORE, SCROLL_WHEEL_LED, brightness);
         report.transaction_id.id = 0x1f;
         break;
@@ -1911,6 +1980,7 @@ static ssize_t razer_attr_read_logo_led_brightness(struct device *dev, struct de
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_get_brightness(VARSTORE, LOGO_LED);
         report.transaction_id.id = 0x1f;
         break;
@@ -1967,6 +2037,7 @@ static ssize_t razer_attr_write_logo_led_brightness(struct device *dev, struct d
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_brightness(VARSTORE, LOGO_LED, brightness);
         report.transaction_id.id = 0x1f;
         break;
@@ -2436,6 +2507,7 @@ static ssize_t razer_attr_write_scroll_mode_spectrum(struct device *dev, struct
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, SCROLL_WHEEL_LED);
         report.transaction_id.id = 0x1f;
         break;
@@ -2485,6 +2557,7 @@ static ssize_t razer_attr_write_scroll_mode_reactive(struct device *dev, struct
 
         case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
         case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
             report = razer_chroma_extended_matrix_effect_reactive(VARSTORE, SCROLL_WHEEL_LED, speed, (struct razer_rgb*)&buf[1]);
             report.transaction_id.id = 0x1f;
             break;
@@ -2545,6 +2618,7 @@ static ssize_t razer_attr_write_scroll_mode_breath(struct device *dev, struct de
     case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_RECEIVER:
     case USB_DEVICE_ID_RAZER_MAMBA_WIRELESS_WIRED:
     case USB_DEVICE_ID_RAZER_BASILISK:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
     case USB_DEVICE_ID_RAZER_DEATHADDER_V2:
         switch(count) {
         case 3: // Single colour mode
@@ -2565,6 +2639,7 @@ static ssize_t razer_attr_write_scroll_mode_breath(struct device *dev, struct de
     switch(usb_dev->descriptor.idProduct) {
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
 
@@ -2613,6 +2688,7 @@ static ssize_t razer_attr_write_scroll_mode_static(struct device *dev, struct de
 
         case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
         case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
             report = razer_chroma_extended_matrix_effect_static(VARSTORE, SCROLL_WHEEL_LED, (struct razer_rgb*)&buf[0]);
             report.transaction_id.id = 0x1f;
             break;
@@ -2666,6 +2742,7 @@ static ssize_t razer_attr_write_scroll_mode_none(struct device *dev, struct devi
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_effect_none(VARSTORE, SCROLL_WHEEL_LED);
         report.transaction_id.id = 0x1f;
         break;
@@ -2756,6 +2833,7 @@ static ssize_t razer_attr_write_logo_mode_spectrum(struct device *dev, struct de
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_effect_spectrum(VARSTORE, LOGO_LED);
         report.transaction_id.id = 0x1f;
         break;
@@ -2813,6 +2891,7 @@ static ssize_t razer_attr_write_logo_mode_reactive(struct device *dev, struct de
 
         case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
         case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
             report = razer_chroma_extended_matrix_effect_reactive(VARSTORE, LOGO_LED, speed, (struct razer_rgb*)&buf[1]);
             report.transaction_id.id = 0x1f;
             break;
@@ -2879,6 +2958,7 @@ static ssize_t razer_attr_write_logo_mode_breath(struct device *dev, struct devi
     case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED:
     case USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS:
     case USB_DEVICE_ID_RAZER_BASILISK:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
     case USB_DEVICE_ID_RAZER_DEATHADDER_V2:
     case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED:
     case USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS:
@@ -2902,6 +2982,7 @@ static ssize_t razer_attr_write_logo_mode_breath(struct device *dev, struct devi
     switch(usb_dev->descriptor.idProduct) {
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report.transaction_id.id = 0x1f;
         break;
 
@@ -2958,6 +3039,7 @@ static ssize_t razer_attr_write_logo_mode_static(struct device *dev, struct devi
 
         case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
         case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
             report = razer_chroma_extended_matrix_effect_static(VARSTORE, LOGO_LED, (struct razer_rgb*)&buf[0]);
             report.transaction_id.id = 0x1f;
             break;
@@ -3018,6 +3100,7 @@ static ssize_t razer_attr_write_logo_mode_none(struct device *dev, struct device
 
     case USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020:
     case USB_DEVICE_ID_RAZER_MAMBA_ELITE:
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
         report = razer_chroma_extended_matrix_effect_none(VARSTORE, LOGO_LED);
         report.transaction_id.id = 0x1f;
         break;
@@ -3411,6 +3494,10 @@ static DEVICE_ATTR(device_mode,               0660, razer_attr_read_device_mode,
 static DEVICE_ATTR(device_serial,             0440, razer_attr_read_get_serial,            NULL);
 static DEVICE_ATTR(device_idle_time,          0660, razer_attr_read_get_idle_time,         razer_attr_write_set_idle_time);
 
+static DEVICE_ATTR(tilt_hwheel,               0660, razer_attr_read_tilt_hwheel,           razer_attr_write_tilt_hwheel);
+static DEVICE_ATTR(tilt_repeat,               0660, razer_attr_read_tilt_repeat,           razer_attr_write_tilt_repeat);
+static DEVICE_ATTR(tilt_repeat_delay,         0660, razer_attr_read_tilt_repeat_delay,     razer_attr_write_tilt_repeat_delay);
+
 static DEVICE_ATTR(charge_level,              0440, razer_attr_read_get_battery,           NULL);
 static DEVICE_ATTR(charge_status,             0440, razer_attr_read_is_charging,           NULL);
 static DEVICE_ATTR(charge_effect,             0220, NULL,                                  razer_attr_write_set_charging_effect);
@@ -3476,6 +3563,200 @@ static DEVICE_ATTR(right_matrix_effect_none,        0220, NULL,
 static DEVICE_ATTR(backlight_led_state,            0660, razer_attr_read_backlight_led_state, razer_attr_write_backlight_led_state);
 
 
+#define REP4_DPI_UP  0x20
+#define REP4_DPI_DN  0x21
+#define REP4_TILT_L  0x22
+#define REP4_TILT_R  0x23
+#define REP4_PROFILE 0x50
+#define REP4_SNIPER  0x51
+
+#define BIT_TILT_L 5
+#define BIT_TILT_R 6
+
+/**
+ * Map "Report 4" codes to evdev key codes
+ */
+static const __u16 rep4_key_codes[] = {
+    [REP4_TILT_L]  = BTN_BACK,          /* BTN_MOUSE + 6 */
+    [REP4_TILT_R]  = BTN_FORWARD,       /* BTN_MOUSE + 5 */
+    [REP4_SNIPER]  = BTN_TASK,          /* BTN_MOUSE + 7 */
+    [REP4_DPI_UP]  = BTN_MOUSE + 8,
+    [REP4_DPI_DN]  = BTN_MOUSE + 9,
+    [REP4_PROFILE] = BTN_MOUSE + 10,
+    /* NOTE: Highest legal mouse button is BTN_MOUSE + 15 */
+};
+
+struct button_mapping {
+    u8 bit;
+    __u16 code;          /* when tilt_hwheel == 0 */
+    __s32 hwheel_value;         /* when tilt_hwheel == 1 */
+};
+
+/**
+ * Map bits in the first byte of the mouse report to evdev keycodes
+ * and REL_HWHEEL values
+ */
+static const struct button_mapping button_mappings[] = {
+    {BIT_TILT_L, BTN_BACK, -1},
+    {BIT_TILT_R, BTN_FORWARD, 1},
+};
+
+/**
+ * Convert an evdev mouse button code to the corresponding HID usage
+ */
+u32 mouse_button_to_usage(__u16 code)
+{
+    return HID_UP_BUTTON + (code - BTN_MOUSE) + 1;
+}
+
+/**
+ * Send the MSC_SCAN event for the usage code associated with an evdev
+ * mouse button code
+ */
+void input_button_msc_scan(struct input_dev *input, __u16 button)
+{
+    input_event(input, EV_MSC, MSC_SCAN, mouse_button_to_usage(button));
+}
+
+/**
+ * Look up and send the evdev key associated with the Razer "report 4"
+ * code
+ */
+void input_rep4_code(struct input_dev *input, u8 code, __s32 value)
+{
+    if (code < ARRAY_SIZE(rep4_key_codes) && rep4_key_codes[code]) {
+        unsigned int button = rep4_key_codes[code];
+        input_button_msc_scan(input, button);
+        input_report_key(input, button, value);
+        input_sync(input);
+    }
+}
+
+/**
+ * Timer callback for wheel tilt repeating
+ */
+static enum hrtimer_restart wheel_tilt_repeat(struct hrtimer *timer)
+{
+    struct razer_mouse_device *dev =
+        container_of(timer, struct razer_mouse_device, repeat_timer);
+    input_report_rel(dev->input, REL_HWHEEL, dev->hwheel_value);
+    input_sync(dev->input);
+    if (dev->tilt_repeat)
+        hrtimer_forward_now(timer, ms_to_ktime(dev->tilt_repeat));
+    return HRTIMER_RESTART;
+}
+
+/**
+ * Send a tilt-wheel event and, if configured, start the key-repeat timer
+ */
+static void tilt_hwheel_start(struct razer_mouse_device *rdev,
+                              __s32 rel_value)
+{
+    input_report_rel(rdev->input, REL_HWHEEL, rel_value);
+    input_sync(rdev->input);
+
+    if (rdev->tilt_repeat && rdev->tilt_repeat_delay) {
+        rdev->hwheel_value = rel_value;
+        hrtimer_start_range_ns(
+            &rdev->repeat_timer, ms_to_ktime(rdev->tilt_repeat_delay),
+            1000, HRTIMER_MODE_REL);
+    }
+}
+
+/**
+ * Stop the tilt wheel key-repeat timer
+ */
+static void tilt_hwheel_stop(struct razer_mouse_device *rdev)
+{
+    hrtimer_cancel(&rdev->repeat_timer);
+}
+
+/**
+ * Test if a device is a HID device
+ */
+static int dev_is_on_bus(struct device *dev, void *data)
+{
+    return (dev->bus == data);
+}
+
+/**
+ * Find an interface on a usb_device with the specified protocol
+ */
+struct usb_interface *find_intf_with_proto(struct usb_device *usbdev, u8 proto)
+{
+    struct usb_interface *intf;
+    int i;
+
+    for (i = 0; i < usbdev->actconfig->desc.bNumInterfaces; i++) {
+        intf = usb_ifnum_to_if(usbdev, i);
+        if (intf && intf->cur_altsetting->desc.bInterfaceProtocol == proto)
+            return intf;
+    }
+
+    return NULL;
+}
+
+/**
+ * Walk up the device tree from an interface to the device it is a
+ * part of, then back down through the interface with protocol == MOUSE
+ * to the razer_mouse_device associated with it
+ */
+static struct razer_mouse_device *find_mouse(struct hid_device *hdev)
+{
+    struct bus_type *hid_bus_type = hdev->dev.bus;
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct usb_device *usbdev = interface_to_usbdev(intf);
+    struct usb_interface *m_intf = find_intf_with_proto(usbdev, USB_INTERFACE_PROTOCOL_MOUSE);
+    struct device *dev;
+    struct razer_mouse_device *rdev;
+
+    if (!m_intf)
+        return NULL;
+
+    dev = device_find_child(&m_intf->dev, hid_bus_type, dev_is_on_bus);
+    if (!dev)
+        return NULL;
+
+    rdev = dev_get_drvdata(dev);
+    put_device(dev);
+    return rdev;
+}
+
+/**
+ * Test if a bit is cleared in 'prev' and set in 'cur'
+ */
+static int rising_bit(u8 prev, u8 cur, u8 mask)
+{
+    return !(prev & mask) && cur & mask;
+}
+
+/**
+ * Test if a bit is set in 'prev' and cleared in 'cur'
+ */
+static int falling_bit(u8 prev, u8 cur, u8 mask)
+{
+    return prev & mask && !(cur & mask);
+}
+
+/**
+ * Test if a bit is different between 'prev' and 'cur'
+ */
+static int edge_bit(u8 prev, u8 cur, u8 mask)
+{
+    return (prev & mask) != (cur & mask);
+}
+
+/**
+ * Search a byte array for a value
+ */
+static int search(u8 *array, u8 value, unsigned n)
+{
+    while (n--) {
+        if (*array++ == value)
+            return 1;
+    }
+    return 0;
+}
 
 /**
  * Raw event function
@@ -3483,41 +3764,138 @@ static DEVICE_ATTR(backlight_led_state,            0660, razer_attr_read_backlig
 static int razer_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
 {
     struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct razer_mouse_device *rdev = hid_get_drvdata(hdev);
+
+    switch (hdev->product) {
+    case USB_DEVICE_ID_RAZER_BASILISK_V2:
+        /* Detect wheel tilt edges */
+        if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) {
+            int i;
+            for (i = 0; i < ARRAY_SIZE(button_mappings); i++) {
+                const struct button_mapping *mapping = &button_mappings[i];
+                u8 mask = 1 << mapping->bit;
+                if (mapping->hwheel_value && rdev->tilt_hwheel) {
+                    __s32 rel_value = mapping->hwheel_value;
+                    if (rising_bit(rdev->button_byte, data[0], mask))
+                        tilt_hwheel_start(rdev, rel_value);
+                    if (falling_bit(rdev->button_byte, data[0], mask))
+                        tilt_hwheel_stop(rdev);
+                } else if (edge_bit(rdev->button_byte, data[0], mask)) {
+                    unsigned int code = mapping->code;
+                    input_button_msc_scan(rdev->input, code);
+                    input_report_key(rdev->input, code, !!(data[0] & mask));
+                    input_sync(rdev->input);
+                }
+            }
+            rdev->button_byte = data[0];
+        }
 
-    // The event were looking for is 16 bytes long and starts with 0x04
-    if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && size == 16 && data[0] == 0x04) {
-        // Convert 04... to 0100...
-        int index = size-1; // This way we start at 2nd last value, does subtract 1 from the 15key rollover though (not an issue cmon)
-        u8 cur_value = 0x00;
+        /* Detect buttons reported on the keyboard interface */
+        if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && size == 16 && data[0] == 0x04) {
+            struct razer_mouse_device *m_rdev = find_mouse(hdev);
+            int i;
 
-        while(--index > 0) {
-            cur_value = data[index];
-            if(cur_value == 0x00) { // Skip 0x00
-                continue;
+            if (!m_rdev) {
+                printk(KERN_WARNING "razermouse: Couldn't find mouse intf from kbd intf");
+                return 1;
             }
 
-            switch(cur_value) {
-            case 0x20: // DPI Up
-                cur_value = 0x68; // F13
-                break;
-            case 0x21: // DPI Down
-                cur_value = 0x69; // F14
-                break;
-            case 0x22: // Wheel Left
-                cur_value = 0x6A; // F15
-                break;
-            case 0x23: // Wheel Right
-                cur_value = 0x6B; // F16
-                break;
+            for (i = 1; i < size; i++) {
+                if (!search(rdev->rep4 + 1, data[i], size - 1))
+                    input_rep4_code(m_rdev->input, data[i], 1);
+                if (!search(data + 1, rdev->rep4[i], size - 1))
+                    input_rep4_code(m_rdev->input, rdev->rep4[i], 0);
+            }
+            memcpy(rdev->rep4, data, 16);
+            return 1;
+        }
+        break;
+    default:
+        // The event were looking for is 16 bytes long and starts with 0x04
+        if(intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD && size == 16 && data[0] == 0x04) {
+            // Convert 04... to 0100...
+            int index = size-1; // This way we start at 2nd last value, does subtract 1 from the 15key rollover though (not an issue cmon)
+            u8 cur_value = 0x00;
+
+            while(--index > 0) {
+                cur_value = data[index];
+                if(cur_value == 0x00) { // Skip 0x00
+                    continue;
+                }
+
+                switch(cur_value) {
+                case 0x20: // DPI Up
+                    cur_value = 0x68; // F13
+                    break;
+                case 0x21: // DPI Down
+                    cur_value = 0x69; // F14
+                    break;
+                case 0x22: // Wheel Left
+                    cur_value = 0x6A; // F15
+                    break;
+                case 0x23: // Wheel Right
+                    cur_value = 0x6B; // F16
+                    break;
+                }
+
+                data[index+1] = cur_value;
             }
 
-            data[index+1] = cur_value;
+
+            data[0] = 0x01;
+            data[1] = 0x00;
+            return 1;
         }
+        break;
+    }
 
+    return 0;
+}
+
+/**
+ * Input mapping function
+ */
+static int
+razer_input_mapping(struct hid_device *hdev, struct hid_input *hidinput,
+                    struct hid_field *field, struct hid_usage *usage,
+                    unsigned long **bit, int *max)
+{
+    /* Some higher nonstandard mouse buttons are reported in
+     * 15-element arrays on reports 4 and 5 with usage 0x10003. If
+     * hid-core tries to interpret this misshapen descriptor it will
+     * botch it and add spurious event codes to input->evkey. */
+    if (field->application == HID_UP_GENDESK
+        && usage->hid == (HID_UP_GENDESK | 0x0003)) {
+        return -1;
+    }
+    return 0;
+}
 
-        data[0] = 0x01;
-        data[1] = 0x00;
-        return 1;
+/**
+ * Input configured function
+ */
+static int razer_input_configured(struct hid_device *hdev,
+                                  struct hid_input *hidinput)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct razer_mouse_device *dev = hid_get_drvdata(hdev);
+
+    dev->input = hidinput->input;
+
+    if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) {
+        switch (hdev->product) {
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
+            /* Linux HID doesn't detect the Basilisk V2's tilt wheel
+             * or buttons beyond the first 5 */
+            input_set_capability(hidinput->input, EV_REL, REL_HWHEEL);
+            input_set_capability(hidinput->input, EV_KEY, BTN_FORWARD);
+            input_set_capability(hidinput->input, EV_KEY, BTN_BACK);
+            input_set_capability(hidinput->input, EV_KEY, BTN_TASK);
+            input_set_capability(hidinput->input, EV_KEY, BTN_MOUSE + 8);
+            input_set_capability(hidinput->input, EV_KEY, BTN_MOUSE + 9);
+            input_set_capability(hidinput->input, EV_KEY, BTN_MOUSE + 10);
+            break;
+        }
     }
 
     return 0;
@@ -3553,6 +3931,13 @@ static void razer_mouse_init(struct razer_mouse_device *dev, struct usb_interfac
     dev->da3_5g.dpi = 1; // 3500 DPI
     dev->da3_5g.profile = 1; // Profile 1
     dev->da3_5g.poll = 1; // Poll rate 1000
+
+    // Setup tilt wheel HWHEEL emulation
+    hrtimer_init(&dev->repeat_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+    dev->repeat_timer.function = wheel_tilt_repeat;
+    dev->tilt_hwheel = 1;
+    dev->tilt_repeat_delay = 250;
+    dev->tilt_repeat = 33;
 }
 
 /**
@@ -3654,6 +4039,11 @@ static int razer_mouse_probe(struct hid_device *hdev, const struct hid_device_id
             CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_matrix_custom_frame);
             break;
 
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
+            CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_hwheel);
+            CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat_delay);
+            CREATE_DEVICE_FILE(&hdev->dev, &dev_attr_tilt_repeat);
+        /* Fall through */
         case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE:
         case USB_DEVICE_ID_RAZER_BASILISK:
         case USB_DEVICE_ID_RAZER_DEATHADDER_V2:
@@ -4169,6 +4559,11 @@ static void razer_mouse_disconnect(struct hid_device *hdev)
             device_remove_file(&hdev->dev, &dev_attr_matrix_custom_frame);
             break;
 
+        case USB_DEVICE_ID_RAZER_BASILISK_V2:
+            device_remove_file(&hdev->dev, &dev_attr_tilt_hwheel);
+            device_remove_file(&hdev->dev, &dev_attr_tilt_repeat_delay);
+            device_remove_file(&hdev->dev, &dev_attr_tilt_repeat);
+        /* Fall through */
         case USB_DEVICE_ID_RAZER_DEATHADDER_ELITE:
         case USB_DEVICE_ID_RAZER_BASILISK:
         case USB_DEVICE_ID_RAZER_DEATHADDER_V2:
@@ -4559,6 +4954,8 @@ static void razer_mouse_disconnect(struct hid_device *hdev)
 
 
     hid_hw_stop(hdev);
+    hrtimer_cancel(&dev->repeat_timer);
+
     kfree(dev);
     dev_info(&intf->dev, "Razer Device disconnected\n");
 }
@@ -4614,6 +5011,7 @@ static const struct hid_device_id razer_devices[] = {
     { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRED) },
     { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_VIPER_ULTIMATE_WIRELESS) },
     { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK) },
+    { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_BASILISK_V2) },
     { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2) },
     { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRED) },
     { HID_USB_DEVICE(USB_VENDOR_ID_RAZER,USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS) },
@@ -4638,6 +5036,8 @@ static struct hid_driver razer_mouse_driver = {
     .remove    = razer_mouse_disconnect,
 
     .raw_event = razer_raw_event,
+    .input_mapping = razer_input_mapping,
+    .input_configured = razer_input_configured,
 };
 
 module_hid_driver(razer_mouse_driver);
diff --git a/driver/razermouse_driver.h b/driver/razermouse_driver.h
index aab8656e..adf7a692 100644
--- a/driver/razermouse_driver.h
+++ b/driver/razermouse_driver.h
@@ -63,6 +63,7 @@
 #define USB_DEVICE_ID_RAZER_DEATHADDER_V2_PRO_WIRELESS 0x007D
 #define USB_DEVICE_ID_RAZER_BASILISK_X_HYPERSPEED 0x0083
 #define USB_DEVICE_ID_RAZER_DEATHADDER_V2 0x0084
+#define USB_DEVICE_ID_RAZER_BASILISK_V2 0x0085
 #define USB_DEVICE_ID_RAZER_VIPER_MINI 0x008A
 #define USB_DEVICE_ID_RAZER_DEATHADDER_V2_MINI 0x008C
 #define USB_DEVICE_ID_RAZER_NAGA_LEFT_HANDED_2020 0x008D
@@ -91,6 +92,16 @@
 struct razer_mouse_device {
     struct usb_device *usb_dev;
     struct mutex lock;
+
+    struct input_dev *input;
+    struct hrtimer repeat_timer;
+    unsigned int tilt_hwheel;
+    unsigned int tilt_repeat_delay;
+    unsigned int tilt_repeat;
+    __s32 hwheel_value;
+    u8 button_byte; // Previous value of mouse button byte in HID record
+    u8 rep4[16]; // Previous value of report 4 on the keyboard intf
+
     unsigned char usb_interface_protocol;
     unsigned char usb_interface_subclass;
 
diff --git a/install_files/appstream/io.github.openrazer.openrazer.metainfo.xml b/install_files/appstream/io.github.openrazer.openrazer.metainfo.xml
index 46af1acb..a9b5f585 100644
--- a/install_files/appstream/io.github.openrazer.openrazer.metainfo.xml
+++ b/install_files/appstream/io.github.openrazer.openrazer.metainfo.xml
@@ -67,6 +67,7 @@
     <modalias>usb:v1532p007Dd*</modalias>
     <modalias>usb:v1532p0083d*</modalias>
     <modalias>usb:v1532p0084d*</modalias>
+    <modalias>usb:v1532p0085d*</modalias>
     <modalias>usb:v1532p008Ad*</modalias>
     <modalias>usb:v1532p008Cd*</modalias>
     <modalias>usb:v1532p008Dd*</modalias>
diff --git a/install_files/udev/99-razer.rules b/install_files/udev/99-razer.rules
index d5b9f646..4155ce42 100644
--- a/install_files/udev/99-razer.rules
+++ b/install_files/udev/99-razer.rules
@@ -5,7 +5,7 @@ GOTO="razer_end"
 LABEL="razer_vendor"
 
 # Mice
-ATTRS{idProduct}=="0013|0016|0020|0024|0025|002e|002f|0032|0034|0036|0037|0038|0039|0040|0041|0042|0043|0044|0045|0046|0048|004c|004f|0050|0053|0054|0059|005a|005b|005c|005e|0060|0062|0064|0067|006a|006b|006c|006e|006f|0070|0071|0072|0073|0078|007a|007b|007c|007d|0083|0084|008a|008c|008d", \
+ATTRS{idProduct}=="0013|0016|0020|0024|0025|002e|002f|0032|0034|0036|0037|0038|0039|0040|0041|0042|0043|0044|0045|0046|0048|004c|004f|0050|0053|0054|0059|005a|005b|005c|005e|0060|0062|0064|0067|006a|006b|006c|006e|006f|0070|0071|0072|0073|0078|007a|007b|007c|007d|0083|0084|0085|008a|008c|008d", \
     ATTRS{idVendor}=="1532", \
     ENV{ID_RAZER_CHROMA}="1", ENV{RAZER_DRIVER}="razermouse"
 
diff --git a/pylib/openrazer/_fake_driver/razerbasiliskv2.cfg b/pylib/openrazer/_fake_driver/razerbasiliskv2.cfg
new file mode 100644
index 00000000..925f0224
--- /dev/null
+++ b/pylib/openrazer/_fake_driver/razerbasiliskv2.cfg
@@ -0,0 +1,26 @@
+[device]
+dir_name = 0003:1532:0085.0001
+name = Razer Basilisk V2
+files = r,device_serial,XX0000000085
+        r,device_type,%(name)s
+        rw,dpi,800:800
+        r,firmware_version,v1.0
+        rw,logo_led_brightness,0
+        w,logo_matrix_effect_breath
+        w,logo_matrix_effect_none
+        w,logo_matrix_effect_reactive
+        w,logo_matrix_effect_spectrum
+        w,logo_matrix_effect_static
+        w,matrix_custom_frame
+        w,matrix_effect_custom
+        rw,poll_rate,500
+        rw,scroll_led_brightness,0
+        w,scroll_matrix_effect_breath
+        w,scroll_matrix_effect_none
+        w,scroll_matrix_effect_reactive
+        w,scroll_matrix_effect_spectrum
+        w,scroll_matrix_effect_static
+        rw,tilt_hwheel,0
+        rw,tilt_repeat,0
+        rw,tilt_repeat_delay,0
+        r,version,1.0.0
diff --git a/scripts/generate_fake_driver.sh b/scripts/generate_fake_driver.sh
index dd5e8b31..e50a5417 100755
--- a/scripts/generate_fake_driver.sh
+++ b/scripts/generate_fake_driver.sh
@@ -71,6 +71,9 @@ declare -A files_metadata=(
     ["scroll_matrix_effect_spectrum"]="w;"
     ["scroll_matrix_effect_static"]="w;"
     ["scroll_matrix_effect_wave"]="w;"
+    ["tilt_hwheel"]="rw;0"
+    ["tilt_repeat"]="rw;0"
+    ["tilt_repeat_delay"]="rw;0"
     ["version"]="r;1.0.0"
 )
 
-- 
GitLab