Commit 2755a505a344e8356ae05ad735847b1c609659e7

Ryan C. Gordon 2019-01-14T19:36:54

evdev: Add touchscreen mouse emulation and pressure support (thanks, Zach!). This also solves reports of this log message: "INFO: The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list <https://discourse.libsdl.org/> EVDEV KeyCode 330" (EVDEV KeyCode 330 is BTN_TOUCH.) Fixes Bugzilla #4147.

diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c
index f8528e9..e557771 100644
--- a/src/core/linux/SDL_evdev.c
+++ b/src/core/linux/SDL_evdev.c
@@ -55,6 +55,7 @@
 #define ABS_MT_POSITION_X   0x35
 #define ABS_MT_POSITION_Y   0x36
 #define ABS_MT_TRACKING_ID  0x39
+#define ABS_MT_PRESSURE     0x3a
 #endif
 
 typedef struct SDL_evdevlist_item
@@ -74,6 +75,7 @@ typedef struct SDL_evdevlist_item
 
         int min_x, max_x, range_x;
         int min_y, max_y, range_y;
+        int min_pressure, max_pressure, range_pressure;
 
         int max_slots;
         int current_slot;
@@ -85,8 +87,10 @@ typedef struct SDL_evdevlist_item
                 EVDEV_TOUCH_SLOTDELTA_MOVE
             } delta;
             int tracking_id;
-            int x, y;
+            int x, y, pressure;
         } * slots;
+
+        int pointerFingerID;
     } * touchscreen_data;
 
     struct SDL_evdevlist_item *next;
@@ -229,7 +233,8 @@ SDL_EVDEV_Poll(void)
     SDL_Scancode scan_code;
     int mouse_button;
     SDL_Mouse *mouse;
-    float norm_x, norm_y;
+    float norm_x, norm_y, norm_pressure;
+    int abs_x, abs_y;
 
     if (!_this) {
         return;
@@ -264,6 +269,16 @@ SDL_EVDEV_Poll(void)
                         break;
                     }
 
+                    /* BTH_TOUCH event value 1 indicates there is contact with
+                       a touchscreen or trackpad (earlist finger's current
+                       position is sent in EV_ABS ABS_X/ABS_Y, switching to
+                       next finger after earlist is released) however using it
+                       for virtual mouse SDL_TOUCH_MOUSEID would differ from
+                       other SDL backends which require a new finger touch. */
+                    if (item->is_touchscreen && events[i].code == BTN_TOUCH) {
+                        break;
+                    }
+
                     /* Probably keyboard */
                     scan_code = SDL_EVDEV_translate_keycode(events[i].code);
                     if (scan_code != SDL_SCANCODE_UNKNOWN) {
@@ -308,6 +323,14 @@ SDL_EVDEV_Poll(void)
                             item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;
                         }
                         break;
+                    case ABS_MT_PRESSURE:
+                        if (!item->is_touchscreen) /* FIXME: temp hack */
+                            break;
+                        item->touchscreen_data->slots[item->touchscreen_data->current_slot].pressure = events[i].value;
+                        if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {
+                            item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;
+                        }
+                        break;
                     case ABS_X:
                         if (item->is_touchscreen) /* FIXME: temp hack */
                             break;
@@ -352,18 +375,45 @@ SDL_EVDEV_Poll(void)
                             norm_y = (float)(item->touchscreen_data->slots[j].y - item->touchscreen_data->min_y) /
                                 (float)item->touchscreen_data->range_y;
 
+                            if (item->touchscreen_data->range_pressure > 0) {
+                                norm_pressure = (float)(item->touchscreen_data->slots[j].pressure - item->touchscreen_data->min_pressure) /
+                                    (float)item->touchscreen_data->range_pressure;
+                            } else {
+                                /* This touchscreen does not support pressure */
+                                norm_pressure = 1.0f;
+                            }
+
+                            abs_x = item->touchscreen_data->slots[j].x;
+                            abs_y = item->touchscreen_data->slots[j].y;
+
                             switch(item->touchscreen_data->slots[j].delta) {
                             case EVDEV_TOUCH_SLOTDELTA_DOWN:
-                                SDL_SendTouch(item->fd, item->touchscreen_data->slots[j].tracking_id, SDL_TRUE, norm_x, norm_y, 1.0f);
+                                if (item->touchscreen_data->pointerFingerID == -1) {
+                                    SDL_SendMouseMotion(mouse->focus, SDL_TOUCH_MOUSEID, 0, abs_x, abs_y);
+
+                                    SDL_SendMouseButton(mouse->focus, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT);
+                                    item->touchscreen_data->pointerFingerID = item->touchscreen_data->slots[j].tracking_id;
+                                }
+
+                                SDL_SendTouch(item->fd, item->touchscreen_data->slots[j].tracking_id, SDL_TRUE, norm_x, norm_y, norm_pressure);
                                 item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;
                                 break;
                             case EVDEV_TOUCH_SLOTDELTA_UP:
-                                SDL_SendTouch(item->fd, item->touchscreen_data->slots[j].tracking_id, SDL_FALSE, norm_x, norm_y, 1.0f);
+                                if (item->touchscreen_data->pointerFingerID == item->touchscreen_data->slots[j].tracking_id) {
+                                    SDL_SendMouseButton(mouse->focus, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT);
+                                    item->touchscreen_data->pointerFingerID = -1;
+                                }
+
+                                SDL_SendTouch(item->fd, item->touchscreen_data->slots[j].tracking_id, SDL_FALSE, norm_x, norm_y, norm_pressure);
                                 item->touchscreen_data->slots[j].tracking_id = -1;
                                 item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;
                                 break;
                             case EVDEV_TOUCH_SLOTDELTA_MOVE:
-                                SDL_SendTouchMotion(item->fd, item->touchscreen_data->slots[j].tracking_id, norm_x, norm_y, 1.0f);
+                                if (item->touchscreen_data->pointerFingerID == item->touchscreen_data->slots[j].tracking_id) {
+                                    SDL_SendMouseMotion(mouse->focus, SDL_TOUCH_MOUSEID, 0, abs_x, abs_y);
+                                }
+
+                                SDL_SendTouchMotion(item->fd, item->touchscreen_data->slots[j].tracking_id, norm_x, norm_y, norm_pressure);
                                 item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;
                                 break;
                             default:
@@ -453,6 +503,16 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
     item->touchscreen_data->max_y = abs_info.maximum;
     item->touchscreen_data->range_y = abs_info.maximum - abs_info.minimum;
 
+    ret = ioctl(item->fd, EVIOCGABS(ABS_MT_PRESSURE), &abs_info);
+    if (ret < 0) {
+        SDL_free(item->touchscreen_data->name);
+        SDL_free(item->touchscreen_data);
+        return SDL_SetError("Failed to get evdev touchscreen limits");
+    }
+    item->touchscreen_data->min_pressure = abs_info.minimum;
+    item->touchscreen_data->max_pressure = abs_info.maximum;
+    item->touchscreen_data->range_pressure = abs_info.maximum - abs_info.minimum;
+
     ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);
     if (ret < 0) {
         SDL_free(item->touchscreen_data->name);
@@ -474,6 +534,8 @@ SDL_EVDEV_init_touchscreen(SDL_evdevlist_item* item)
         item->touchscreen_data->slots[i].tracking_id = -1;
     }
 
+    item->touchscreen_data->pointerFingerID = -1;
+
     ret = SDL_AddTouch(item->fd, /* I guess our fd is unique enough */
         SDL_TOUCH_DEVICE_DIRECT,
         item->touchscreen_data->name);
@@ -594,6 +656,24 @@ SDL_EVDEV_sync_device(SDL_evdevlist_item *item)
         }
     }
 
+    *mt_req_code = ABS_MT_PRESSURE;
+    ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
+    if (ret < 0) {
+        SDL_free(mt_req_code);
+        return;
+    }
+    for(i = 0; i < item->touchscreen_data->max_slots; i++) {
+        if (item->touchscreen_data->slots[i].tracking_id >= 0 &&
+            item->touchscreen_data->slots[i].pressure != mt_req_values[i]) {
+            item->touchscreen_data->slots[i].pressure = mt_req_values[i];
+            if (item->touchscreen_data->slots[i].delta ==
+                EVDEV_TOUCH_SLOTDELTA_NONE) {
+                item->touchscreen_data->slots[i].delta =
+                    EVDEV_TOUCH_SLOTDELTA_MOVE;
+            }
+        }
+    }
+
     ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);
     if (ret < 0) {
         SDL_free(mt_req_code);