SDL: evdev: Add support for REL_WHEEL_HI_RES and REL_HWHEEL_HI_RES

From 2d673e5b560ba71f2a56d26b3f535f88082a821b Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Mon, 6 Dec 2021 22:07:33 -0600
Subject: [PATCH] evdev: Add support for REL_WHEEL_HI_RES and REL_HWHEEL_HI_RES

If supported, these come alongside the regular REL_WHEEL and REL_HWHEEL
events so it's important that we only process one or the other.
---
 src/core/linux/SDL_evdev.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c
index 9d7c39a87ac..33128e6e70c 100644
--- a/src/core/linux/SDL_evdev.c
+++ b/src/core/linux/SDL_evdev.c
@@ -57,6 +57,10 @@
 #define ABS_MT_TRACKING_ID  0x39
 #define ABS_MT_PRESSURE     0x3a
 #endif
+#ifndef REL_WHEEL_HI_RES
+#define REL_WHEEL_HI_RES    0x0b
+#define REL_HWHEEL_HI_RES   0x0c
+#endif
 
 typedef struct SDL_evdevlist_item
 {
@@ -92,6 +96,9 @@ typedef struct SDL_evdevlist_item
 
     } * touchscreen_data;
 
+    SDL_bool high_res_wheel;
+    SDL_bool high_res_hwheel;
+
     struct SDL_evdevlist_item *next;
 } SDL_evdevlist_item;
 
@@ -378,10 +385,20 @@ SDL_EVDEV_Poll(void)
                         SDL_SendMouseMotion(mouse->focus, mouse->mouseID, SDL_TRUE, 0, events[i].value);
                         break;
                     case REL_WHEEL:
-                        SDL_SendMouseWheel(mouse->focus, mouse->mouseID, 0, events[i].value, SDL_MOUSEWHEEL_NORMAL);
+                        if (!item->high_res_wheel)
+                            SDL_SendMouseWheel(mouse->focus, mouse->mouseID, 0, events[i].value, SDL_MOUSEWHEEL_NORMAL);
+                        break;
+                    case REL_WHEEL_HI_RES:
+                        SDL_assert(item->high_res_wheel);
+                        SDL_SendMouseWheel(mouse->focus, mouse->mouseID, 0, events[i].value / 120.0f, SDL_MOUSEWHEEL_NORMAL);
                         break;
                     case REL_HWHEEL:
-                        SDL_SendMouseWheel(mouse->focus, mouse->mouseID, events[i].value, 0, SDL_MOUSEWHEEL_NORMAL);
+                        if (!item->high_res_hwheel)
+                            SDL_SendMouseWheel(mouse->focus, mouse->mouseID, events[i].value, 0, SDL_MOUSEWHEEL_NORMAL);
+                        break;
+                    case REL_HWHEEL_HI_RES:
+                        SDL_assert(item->high_res_hwheel);
+                        SDL_SendMouseWheel(mouse->focus, mouse->mouseID, events[i].value / 120.0f, 0, SDL_MOUSEWHEEL_NORMAL);
                         break;
                     default:
                         break;
@@ -715,6 +732,7 @@ SDL_EVDEV_device_added(const char *dev_path, int udev_class)
 {
     int ret;
     SDL_evdevlist_item *item;
+    unsigned long relbit[NBITS(REL_MAX)] = { 0 };
 
     /* Check to make sure it's not already in list. */
     for (item = _this->first; item != NULL; item = item->next) {
@@ -741,11 +759,17 @@ SDL_EVDEV_device_added(const char *dev_path, int udev_class)
         return SDL_OutOfMemory();
     }
 
+    if (ioctl(item->fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0) {
+        item->high_res_wheel = test_bit(REL_WHEEL_HI_RES, relbit);
+        item->high_res_hwheel = test_bit(REL_HWHEEL_HI_RES, relbit);
+    }
+
     if (udev_class & SDL_UDEV_DEVICE_TOUCHSCREEN) {
         item->is_touchscreen = 1;
 
         if ((ret = SDL_EVDEV_init_touchscreen(item)) < 0) {
             close(item->fd);
+            SDL_free(item->path);
             SDL_free(item);
             return ret;
         }