Commit 0e98040d43dcdcfe7789eac30df8969432032f8d

Ryan C. Gordon 2020-06-28T16:23:05

joystick: Linux joysticks now recover better from dropped events. Fixes Bugzilla #4830.

diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index 45b7960..58471d4 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -79,6 +79,10 @@
 
 #include "../../core/linux/SDL_udev.h"
 
+#if 0
+#define DEBUG_INPUT_EVENTS 1
+#endif
+
 static int MaybeAddDevice(const char *path);
 #if SDL_USE_LIBUDEV
 static int MaybeRemoveDevice(const char *path);
@@ -838,7 +842,7 @@ LINUX_JoystickOpen(SDL_Joystick * joystick, int device_index)
     item->hwdata = joystick->hwdata;
 
     /* mark joystick as fresh and ready */
-    joystick->hwdata->fresh = 1;
+    joystick->hwdata->fresh = SDL_TRUE;
 
     return (0);
 }
@@ -950,11 +954,12 @@ static SDL_INLINE void
 PollAllValues(SDL_Joystick * joystick)
 {
     struct input_absinfo absinfo;
+    unsigned long keyinfo[NBITS(KEY_MAX)];
     int i;
 
     /* Poll all axis */
     for (i = ABS_X; i < ABS_MAX; i++) {
-        if (i == ABS_HAT0X) {
+        if (i == ABS_HAT0X) {  /* we handle hats in the next loop, skip them for now. */
             i = ABS_HAT3Y;
             continue;
         }
@@ -972,6 +977,37 @@ PollAllValues(SDL_Joystick * joystick)
             }
         }
     }
+
+    /* Poll all hats */
+    for (i = ABS_HAT0X; i <= ABS_HAT3Y; i++) {
+        const int baseaxis = i - ABS_HAT0X;
+        const int hatidx = baseaxis / 2;
+        SDL_assert(hatidx < SDL_arraysize(joystick->hwdata->has_hat));
+        if (joystick->hwdata->has_hat[hatidx]) {
+            if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) {
+                const int hataxis = baseaxis % 2;
+                HandleHat(joystick, joystick->hwdata->hats_indices[hatidx], hataxis, absinfo.value);
+            }
+        }
+    }
+
+    /* Poll all buttons */
+    SDL_zeroa(keyinfo);
+    if (ioctl(joystick->hwdata->fd, EVIOCGKEY(sizeof (keyinfo)), keyinfo) >= 0) {
+        for (i = 0; i < KEY_MAX; i++) {
+            if (joystick->hwdata->has_key[i]) {
+                const Uint8 value = test_bit(i, keyinfo) ? SDL_PRESSED : SDL_RELEASED;
+#ifdef DEBUG_INPUT_EVENTS
+                printf("Joystick : Re-read Button %d (%d) val= %d\n",
+                    joystick->hwdata->key_map[i], i, value);
+#endif
+                SDL_PrivateJoystickButton(joystick,
+                        joystick->hwdata->key_map[i], value);
+            }
+        }
+    }
+
+    /* Joyballs are relative input, so there's no poll state. Events only! */
 }
 
 static SDL_INLINE void
@@ -983,13 +1019,21 @@ HandleInputEvents(SDL_Joystick * joystick)
 
     if (joystick->hwdata->fresh) {
         PollAllValues(joystick);
-        joystick->hwdata->fresh = 0;
+        joystick->hwdata->fresh = SDL_FALSE;
     }
 
     while ((len = read(joystick->hwdata->fd, events, (sizeof events))) > 0) {
         len /= sizeof(events[0]);
         for (i = 0; i < len; ++i) {
             code = events[i].code;
+
+            /* If the kernel sent a SYN_DROPPED, we are supposed to ignore the
+               rest of the packet (the end of it signified by a SYN_REPORT) */
+            if ( joystick->hwdata->recovering_from_dropped &&
+                 ((events[i].type != EV_SYN) || (code != SYN_REPORT)) ) {
+                continue;
+            }
+
             switch (events[i].type) {
             case EV_KEY:
                 SDL_PrivateJoystickButton(joystick,
@@ -1037,7 +1081,13 @@ HandleInputEvents(SDL_Joystick * joystick)
 #ifdef DEBUG_INPUT_EVENTS
                     printf("Event SYN_DROPPED detected\n");
 #endif
-                    PollAllValues(joystick);
+                    joystick->hwdata->recovering_from_dropped = SDL_TRUE;
+                    break;
+                case SYN_REPORT :
+                    if (joystick->hwdata->recovering_from_dropped) {
+                        joystick->hwdata->recovering_from_dropped = SDL_FALSE;
+                        PollAllValues(joystick);  /* try to sync up to current state now */
+                    }
                     break;
                 default:
                     break;
diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h
index 6f162ae..bcbc255 100644
--- a/src/joystick/linux/SDL_sysjoystick_c.h
+++ b/src/joystick/linux/SDL_sysjoystick_c.h
@@ -62,7 +62,8 @@ struct joystick_hwdata
         int coef[3];
     } abs_correct[ABS_MAX];
 
-    int fresh;
+    SDL_bool fresh;
+    SDL_bool recovering_from_dropped;
 
     /* Steam Controller support */
     SDL_bool m_bSteamController;
diff --git a/test/testjoystick.c b/test/testjoystick.c
index 2d8d2e1..f838af7 100644
--- a/test/testjoystick.c
+++ b/test/testjoystick.c
@@ -167,6 +167,14 @@ loop(void *arg)
                    event.jbutton.which, event.jbutton.button);
             break;
         case SDL_KEYDOWN:
+            /* Press the L key to lag for 3 seconds, to see what happens
+                when SDL doesn't service the event loop quickly. */
+            if (event.key.keysym.sym == SDLK_l) {
+                SDL_Log("Lagging for 3 seconds...\n");
+                SDL_Delay(3000);
+                break;
+            }
+
             if ((event.key.keysym.sym != SDLK_ESCAPE) &&
                 (event.key.keysym.sym != SDLK_AC_BACK)) {
                 break;