From aa4c7687eeaf79552b3563cd61172b0d6a7a99e1 Mon Sep 17 00:00:00 2001
From: german <german@thesoftwareartisans.com>
Date: Wed, 30 Dec 2020 22:29:20 -0600
Subject: [PATCH] Port citra-emu/citra#5509

---
 src/input_common/sdl/sdl_impl.cpp | 47 +++++++++++++++++++++++++++++--
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index 7827e324c..d56b7587b 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -1014,11 +1014,44 @@ public:
         }
         return {};
     }
-    [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+    [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) {
         switch (event.type) {
         case SDL_JOYAXISMOTION:
-            if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+            if (!axis_memory.count(event.jaxis.which) ||
+                !axis_memory[event.jaxis.which].count(event.jaxis.axis)) {
+                axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value;
+                axis_event_count[event.jaxis.which][event.jaxis.axis] = 1;
                 break;
+            } else {
+                axis_event_count[event.jaxis.which][event.jaxis.axis]++;
+                // The joystick and axis exist in our map if we take this branch, so no checks
+                // needed
+                if (std::abs(
+                        (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) /
+                        32767.0) < 0.5) {
+                    break;
+                } else {
+                    if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 &&
+                        IsAxisAtPole(event.jaxis.value) &&
+                        IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) {
+                        // If we have exactly two events and both are near a pole, this is
+                        // likely a digital input masquerading as an analog axis; Instead of
+                        // trying to look at the direction the axis travelled, assume the first
+                        // event was press and the second was release; This should handle most
+                        // digital axes while deferring to the direction of travel for analog
+                        // axes
+                        event.jaxis.value = static_cast<Sint16>(
+                            std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis]));
+                    } else {
+                        // There are more than two events, so this is likely a true analog axis,
+                        // check the direction it travelled
+                        event.jaxis.value = static_cast<Sint16>(std::copysign(
+                            32767,
+                            event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]));
+                    }
+                    axis_memory.clear();
+                    axis_event_count.clear();
+                }
             }
             [[fallthrough]];
         case SDL_JOYBUTTONUP:
@@ -1027,6 +1060,16 @@ public:
         }
         return std::nullopt;
     }
+
+private:
+    // Determine whether an axis value is close to an extreme or center
+    // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per
+    // axis, which is why the center must be considered a pole
+    bool IsAxisAtPole(int16_t value) const {
+        return std::abs(value) >= 32767 || std::abs(value) < 327;
+    }
+    std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
+    std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count;
 };
 
 class SDLMotionPoller final : public SDLPoller {