Bug 1465643 - Part 2: Remapping DualShock 4 buttons and axes on Windows. r=qdot
authorDaosheng Mu <daoshengmu@gmail.com>
Mon, 08 Apr 2019 20:21:28 +0000
changeset 468417 3c267ad86e3c01403df560077f0aa6ef9dd19933
parent 468416 9133f02b68c460407237f9935511c7ca0ee90de5
child 468418 f7514ce0ff13dcad13fe04fce8c18297302f0711
push id35837
push userrmaries@mozilla.com
push dateTue, 09 Apr 2019 03:43:40 +0000
treeherdermozilla-central@9eb55c9bf557 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersqdot
bugs1465643
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1465643 - Part 2: Remapping DualShock 4 buttons and axes on Windows. r=qdot Differential Revision: https://phabricator.services.mozilla.com/D24217
dom/gamepad/GamepadRemapping.cpp
dom/gamepad/GamepadRemapping.h
dom/gamepad/moz.build
dom/gamepad/windows/WindowsGamepad.cpp
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadRemapping.cpp
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Based on
+// https://cs.chromium.org/chromium/src/device/gamepad/gamepad_standard_mappings.h
+
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mozilla/dom/GamepadRemapping.h"
+
+namespace mozilla {
+namespace dom {
+
+// Follow the canonical ordering recommendation for the "Standard Gamepad"
+// from https://www.w3.org/TR/gamepad/#remapping.
+enum CanonicalButtonIndex {
+  BUTTON_INDEX_PRIMARY,
+  BUTTON_INDEX_SECONDARY,
+  BUTTON_INDEX_TERTIARY,
+  BUTTON_INDEX_QUATERNARY,
+  BUTTON_INDEX_LEFT_SHOULDER,
+  BUTTON_INDEX_RIGHT_SHOULDER,
+  BUTTON_INDEX_LEFT_TRIGGER,
+  BUTTON_INDEX_RIGHT_TRIGGER,
+  BUTTON_INDEX_BACK_SELECT,
+  BUTTON_INDEX_START,
+  BUTTON_INDEX_LEFT_THUMBSTICK,
+  BUTTON_INDEX_RIGHT_THUMBSTICK,
+  BUTTON_INDEX_DPAD_UP,
+  BUTTON_INDEX_DPAD_DOWN,
+  BUTTON_INDEX_DPAD_LEFT,
+  BUTTON_INDEX_DPAD_RIGHT,
+  BUTTON_INDEX_META,
+  BUTTON_INDEX_COUNT
+};
+
+enum CanonicalAxisIndex {
+  AXIS_INDEX_LEFT_STICK_X,
+  AXIS_INDEX_LEFT_STICK_Y,
+  AXIS_INDEX_RIGHT_STICK_X,
+  AXIS_INDEX_RIGHT_STICK_Y,
+  AXIS_INDEX_COUNT
+};
+
+void FetchDpadFromAxis(uint32_t aIndex, double dir) {
+  bool up = false;
+  bool right = false;
+  bool down = false;
+  bool left = false;
+
+  // Dpad is mapped as a direction on one axis, where -1 is up and it
+  // increases clockwise to 1, which is up + left. It's set to a large (> 1.f)
+  // number when nothing is depressed, except on start up, sometimes it's 0.0
+  // for no data, rather than the large number.
+  if (dir != 0.0f) {
+    up = (dir >= -1.f && dir < -0.7f) || (dir >= .95f && dir <= 1.f);
+    right = dir >= -.75f && dir < -.1f;
+    down = dir >= -.2f && dir < .45f;
+    left = dir >= .4f && dir <= 1.f;
+  }
+
+  RefPtr<GamepadPlatformService> service =
+      GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
+
+  service->NewButtonEvent(aIndex, BUTTON_INDEX_DPAD_UP, up);
+  service->NewButtonEvent(aIndex, BUTTON_INDEX_DPAD_RIGHT, right);
+  service->NewButtonEvent(aIndex, BUTTON_INDEX_DPAD_DOWN, down);
+  service->NewButtonEvent(aIndex, BUTTON_INDEX_DPAD_LEFT, left);
+}
+
+class DefaultRemapper final : public GamepadRemapper {
+  public:
+    virtual uint32_t GetAxisCount() const override {
+      return numAxes;
+    }
+
+    virtual uint32_t GetButtonCount() const override {
+      return numButtons;
+    }
+
+    virtual void SetAxisCount(uint32_t aAxisCount) override {
+      numAxes = aAxisCount;
+    }
+
+    virtual void SetButtonCount(uint32_t aButtonCount) override {
+      numButtons = aButtonCount;
+    }
+
+    virtual void RemapAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
+                                  double aValue) const override {
+      RefPtr<GamepadPlatformService> service =
+          GamepadPlatformService::GetParentService();
+      if (!service) {
+        return;
+      }                             
+      service->NewAxisMoveEvent(aIndex, aAxis, aValue);
+    }
+
+    virtual void RemapButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                  bool aPressed) const override {
+      RefPtr<GamepadPlatformService> service =
+          GamepadPlatformService::GetParentService();
+      if (!service) {
+        return;
+      }
+      service->NewButtonEvent(aIndex, aButton, aPressed);
+    }
+
+  private:
+    uint32_t numAxes;
+    uint32_t numButtons;
+};
+
+class Dualshock4Remapper final : public GamepadRemapper {
+ public:
+  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+  virtual uint32_t GetButtonCount() const override {
+    return DUALSHOCK_BUTTON_COUNT;
+  }
+
+  virtual GamepadMappingType GetMappingType() const override {
+    return GamepadMappingType::_empty;
+  }
+
+  virtual void RemapAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
+                                  double aValue) const override {
+    RefPtr<GamepadPlatformService> service =
+        GamepadPlatformService::GetParentService();
+    if (!service) {
+      return;
+    }
+
+    switch (aAxis) {
+      case 0:
+        service->NewAxisMoveEvent(aIndex, AXIS_INDEX_LEFT_STICK_X, aValue);
+        break;
+      case 1:
+        service->NewAxisMoveEvent(aIndex, AXIS_INDEX_LEFT_STICK_Y, aValue);
+        break;
+      case 2:
+        service->NewAxisMoveEvent(aIndex, AXIS_INDEX_RIGHT_STICK_X, aValue);
+        break;
+      case 3:
+        service->NewButtonEvent(aIndex, BUTTON_INDEX_LEFT_TRIGGER,
+                                aValue > 0.1f);
+        break;
+      case 4:
+        service->NewButtonEvent(aIndex, BUTTON_INDEX_RIGHT_TRIGGER,
+                                aValue > 0.1f);
+        break;
+      case 5:
+        service->NewAxisMoveEvent(aIndex, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+        break;
+      case 9:
+        FetchDpadFromAxis(aIndex, aValue);
+        break;
+      default:
+        NS_WARNING(
+            nsPrintfCString(
+                "Axis idx '%d' doesn't support in Dualshock4Remapper().", aAxis)
+                .get());
+        break;
+    }
+  }
+
+  virtual void RemapButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                bool aPressed) const override {
+    RefPtr<GamepadPlatformService> service =
+        GamepadPlatformService::GetParentService();
+    if (!service) {
+      return;
+    }
+
+    const std::vector<uint32_t> buttonMapping = {
+      BUTTON_INDEX_TERTIARY,
+      BUTTON_INDEX_PRIMARY,
+      BUTTON_INDEX_SECONDARY,
+      BUTTON_INDEX_QUATERNARY,
+      BUTTON_INDEX_LEFT_SHOULDER,
+      BUTTON_INDEX_RIGHT_SHOULDER,
+      BUTTON_INDEX_LEFT_TRIGGER,
+      BUTTON_INDEX_RIGHT_TRIGGER,
+      BUTTON_INDEX_BACK_SELECT,
+      BUTTON_INDEX_START,
+      BUTTON_INDEX_LEFT_THUMBSTICK,
+      BUTTON_INDEX_RIGHT_THUMBSTICK,
+      BUTTON_INDEX_META,
+      DUALSHOCK_BUTTON_TOUCHPAD
+    };
+
+    if (buttonMapping.size() <= aIndex) {
+      NS_WARNING(
+            nsPrintfCString(
+                "Button idx '%d' doesn't support in Dualshock4Remapper().",
+                aButton)
+                .get());
+      return;
+    }
+
+    service->NewButtonEvent(aIndex, buttonMapping[aButton], aPressed);
+  }
+
+ private:
+  enum Dualshock4Buttons {
+    DUALSHOCK_BUTTON_TOUCHPAD = BUTTON_INDEX_COUNT,
+    DUALSHOCK_BUTTON_COUNT
+  };
+};
+
+already_AddRefed<GamepadRemapper> GetGamepadRemapper(const uint16_t aVendorId,
+                                                     const uint16_t aProductId) {
+  const std::vector<GamepadRemappingData> remappingRules = {
+      {GamepadId::kSonyDualshock4, new Dualshock4Remapper()},
+      {GamepadId::kSonyDualshock4Slim, new Dualshock4Remapper()},
+      {GamepadId::kSonyDualshock4USBReceiver, new Dualshock4Remapper()}
+  };
+  const GamepadId id = static_cast<GamepadId>((aVendorId << 16) | aProductId);
+
+  for (uint32_t i = 0; i < remappingRules.size(); ++i) {
+    if (id == remappingRules[i].id) {
+      return do_AddRef(remappingRules[i].remapping.get());
+    }
+  }
+
+  static RefPtr<GamepadRemapper> defaultRemapper = new DefaultRemapper();
+  return do_AddRef(defaultRemapper.get());
+}
+
+}  // namespace dom
+}  // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadRemapping.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_GamepadRemapping_h_
+#define mozilla_dom_GamepadRemapping_h_
+
+namespace mozilla {
+namespace dom {
+
+// GamepadId is (vendorId << 16) | productId)
+enum class GamepadId : uint32_t {
+  kSonyDualshock4 = 0x054c05c4,
+  kSonyDualshock4Slim = 0x054c09cc,
+  kSonyDualshock4USBReceiver = 0x054c0ba0,
+};
+
+class GamepadRemapper {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadRemapper)
+
+ public:
+  virtual uint32_t GetAxisCount() const = 0;
+  virtual uint32_t GetButtonCount() const = 0;
+  virtual void SetAxisCount(uint32_t aButtonCount) {}
+  virtual void SetButtonCount(uint32_t aButtonCount) {}
+  virtual GamepadMappingType GetMappingType() const {
+    return GamepadMappingType::Standard;
+  }
+  virtual void RemapAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
+                                  double aValue) const = 0;
+  virtual void RemapButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                bool aPressed) const = 0;
+  
+ protected:
+  GamepadRemapper() = default;
+  virtual ~GamepadRemapper() = default;
+};
+
+struct GamepadRemappingData {
+  GamepadId id;
+  RefPtr<GamepadRemapper> remapping;
+};
+
+already_AddRefed<GamepadRemapper> GetGamepadRemapper(const uint16_t aVendorId,
+                                                     const uint16_t aProductId);
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -17,16 +17,17 @@ EXPORTS.mozilla.dom += [
     'Gamepad.h',
     'GamepadButton.h',
     'GamepadHapticActuator.h',
     'GamepadManager.h',
     'GamepadMonitoring.h',
     'GamepadPlatformService.h',
     'GamepadPose.h',
     'GamepadPoseState.h',
+    'GamepadRemapping.h',
     'GamepadServiceTest.h',
     'ipc/GamepadEventChannelChild.h',
     'ipc/GamepadEventChannelParent.h',
     'ipc/GamepadMessageUtils.h',
     'ipc/GamepadServiceType.h',
     'ipc/GamepadTestChannelChild.h',
     'ipc/GamepadTestChannelParent.h'
 ]
@@ -34,16 +35,17 @@ EXPORTS.mozilla.dom += [
 UNIFIED_SOURCES = [
     'Gamepad.cpp',
     'GamepadButton.cpp',
     'GamepadHapticActuator.cpp',
     'GamepadManager.cpp',
     'GamepadMonitoring.cpp',
     'GamepadPlatformService.cpp',
     'GamepadPose.cpp',
+    'GamepadRemapping.cpp',
     'GamepadServiceTest.cpp',
     'ipc/GamepadEventChannelChild.cpp',
     'ipc/GamepadEventChannelParent.cpp',
     'ipc/GamepadTestChannelChild.cpp',
     'ipc/GamepadTestChannelParent.cpp'
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -26,24 +26,26 @@
 #include "mozilla/dom/GamepadPlatformService.h"
 
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::ArrayLength;
 
+// USB HID usage tables, page 1, 0x30 = X
+const uint32_t kAxisMinimumUsageNumber = 0x30;
 // USB HID usage tables, page 1 (Hat switch)
-const unsigned kUsageDpad = 0x39;
-// USB HID usage tables, page 1, 0x30 = X
-const unsigned kFirstAxis = 0x30;
+const uint32_t kDpadMinimumUsageNumber = 0x39;
+const uint32_t kAxesLengthCap = 16;
 
 // USB HID usage tables
-const unsigned kDesktopUsagePage = 0x1;
-const unsigned kButtonUsagePage = 0x9;
+const uint32_t kDesktopUsagePage = 0x1;
+const uint32_t kGameControlsUsagePage = 0x5;
+const uint32_t kButtonUsagePage = 0x9;
 
 // Multiple devices-changed notifications can be sent when a device
 // is connected, because USB devices consist of multiple logical devices.
 // Therefore, we wait a bit after receiving one before looking for
 // device changes.
 const uint32_t kDevicesChangedStableDelay = 200;
 // Both DirectInput and XInput are polling-driven here,
 // so we need to poll it periodically.
@@ -114,19 +116,26 @@ class Gamepad {
   // Information about the physical device.
   unsigned numAxes;
   unsigned numButtons;
 
   nsTArray<bool> buttons;
   struct axisValue {
     HIDP_VALUE_CAPS caps;
     double value;
+    bool active;
+
+    axisValue() = default;
+    explicit axisValue(const HIDP_VALUE_CAPS& aCaps)
+        : caps(aCaps), value(0.0f), active(true) {}
   };
   nsTArray<axisValue> axes;
 
+  RefPtr<GamepadRemapper> remapper;
+
   // Used during rescan to find devices that were disconnected.
   bool present;
 
   Gamepad(uint32_t aNumAxes, uint32_t aNumButtons, GamepadType aType)
       : type(aType),
         numAxes(aNumAxes),
         numButtons(aNumButtons),
         present(true) {
@@ -538,28 +547,33 @@ void WindowsGamepadService::CheckXInputC
                               -1.0 * state.Gamepad.sThumbRY / 32767.0);
   }
   gamepad.state = state;
 }
 
 // Used to sort a list of axes by HID usage.
 class HidValueComparator {
  public:
-  bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const {
-    return c1.UsagePage == c2.UsagePage &&
-           c1.Range.UsageMin == c2.Range.UsageMin;
+  bool Equals(const Gamepad::axisValue& c1,
+              const Gamepad::axisValue& c2) const {
+    return c1.caps.UsagePage == c2.caps.UsagePage &&
+           c1.caps.Range.UsageMin == c2.caps.Range.UsageMin;
   }
-  bool LessThan(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const {
-    if (c1.UsagePage == c2.UsagePage) {
-      return c1.Range.UsageMin < c2.Range.UsageMin;
+  bool LessThan(const Gamepad::axisValue& c1,
+                const Gamepad::axisValue& c2) const {
+    if (c1.caps.UsagePage == c2.caps.UsagePage) {
+      return c1.caps.Range.UsageMin < c2.caps.Range.UsageMin;
     }
-    return c1.UsagePage < c2.UsagePage;
+    return c1.caps.UsagePage < c2.caps.UsagePage;
   }
 };
 
+// GetRawGamepad() processes its raw data from HID and
+// then trying to remapping buttons and axes based on
+// the mapping rules that are defined for different gamepad products.
 bool WindowsGamepadService::GetRawGamepad(HANDLE handle) {
   RefPtr<GamepadPlatformService> service =
       GamepadPlatformService::GetParentService();
   if (!service) {
     return true;
   }
 
   if (!mHID) {
@@ -608,17 +622,17 @@ bool WindowsGamepadService::GetRawGamepa
   // Product string is a human-readable name.
   // Per
   // http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx
   // "For USB devices, the maximum string length is 126 wide characters (not
   // including the terminating NULL character)."
   wchar_t name[128] = {0};
   size = sizeof(name);
   nsTArray<char> gamepad_name;
-  HANDLE hid_handle = CreateFile(
+  const HANDLE hid_handle = CreateFile(
       devname.Elements(), GENERIC_READ | GENERIC_WRITE,
       FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
   if (hid_handle) {
     if (mHID.mHidD_GetProductString(hid_handle, &name, size)) {
       int bytes = WideCharToMultiByte(CP_UTF8, 0, name, -1, nullptr, 0, nullptr,
                                       nullptr);
       gamepad_name.SetLength(bytes);
       WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad_name.Elements(), bytes,
@@ -660,59 +674,58 @@ bool WindowsGamepadService::GetRawGamepa
   for (unsigned i = 0; i < count; i++) {
     // Each buttonCaps is typically a range of buttons.
     numButtons +=
         buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
   }
 
   // Enumerate value caps, which represent axes and d-pads.
   count = caps.NumberInputValueCaps;
-  nsTArray<HIDP_VALUE_CAPS> valueCaps(count);
-  valueCaps.SetLength(count);
-  if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count,
+  nsTArray<HIDP_VALUE_CAPS> axisCaps(count);
+  axisCaps.SetLength(count);
+  if (mHID.mHidP_GetValueCaps(HidP_Input, axisCaps.Elements(), &count,
                               parsed) != HIDP_STATUS_SUCCESS) {
     return false;
   }
-  nsTArray<HIDP_VALUE_CAPS> axes;
-  // Sort the axes by usagePage and usage to expose a consistent ordering.
-  bool hasDpad;
-  HIDP_VALUE_CAPS dpadCaps;
+
+  size_t numAxes = 0;
+  nsTArray<Gamepad::axisValue> axes(kAxesLengthCap);
+  // We store these value caps and handle the dpad info in GamepadRemapper later.
+  axes.SetLength(kAxesLengthCap);
 
-  HidValueComparator comparator;
-  for (unsigned i = 0; i < count; i++) {
-    if (valueCaps[i].UsagePage == kDesktopUsagePage &&
-        valueCaps[i].Range.UsageMin == kUsageDpad
-        // Don't know how to handle d-pads that return weird values.
-        && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7) {
-      // d-pad gets special handling.
-      // Ostensibly HID devices can expose multiple d-pads, but this
-      // doesn't happen in practice.
-      hasDpad = true;
-      dpadCaps = valueCaps[i];
-      // Expose d-pad as 4 additional buttons.
-      numButtons += 4;
-    } else {
-      axes.InsertElementSorted(valueCaps[i], comparator);
+  // Looking for the exisiting ramapping rule.
+  RefPtr<GamepadRemapper> remapper =
+      GetGamepadRemapper(rdi.hid.dwVendorId, rdi.hid.dwProductId);
+  MOZ_ASSERT(remapper);
+
+  for (size_t i = 0; i < count; i++) {
+    const size_t axisIndex = axisCaps[i].Range.UsageMin - kAxisMinimumUsageNumber;
+    if (axisIndex < kAxesLengthCap && !axes[axisIndex].active) {
+      axes[axisIndex].caps = axisCaps[i];
+      axes[axisIndex].active = true;
+      numAxes = std::max(numAxes, axisIndex + 1);
     }
   }
 
-  uint32_t numAxes = axes.Length();
+  // Not already present, add it.
 
-  // Not already present, add it.
-  Gamepad gamepad(numAxes, numButtons, true, kRawInputGamepad);
-
+  remapper->SetAxisCount(numAxes);
+  remapper->SetButtonCount(numButtons);
+  Gamepad gamepad(numAxes, numButtons, kRawInputGamepad);
   gamepad.handle = handle;
 
   for (unsigned i = 0; i < gamepad.numAxes; i++) {
-    gamepad.axes[i].caps = axes[i];
+    gamepad.axes[i] = axes[i];
   }
 
-  gamepad.id = service->AddGamepad(gamepad_id, GamepadMappingType::_empty,
-                                   GamepadHand::_empty, gamepad.numButtons,
-                                   gamepad.numAxes, 0);
+  gamepad.remapper = remapper.forget();
+  gamepad.id =
+      service->AddGamepad(gamepad_id, gamepad.remapper->GetMappingType(),
+                          GamepadHand::_empty, gamepad.remapper->GetButtonCount(),
+                          gamepad.remapper->GetAxisCount(), 0);
   mGamepads.AppendElement(gamepad);
   return true;
 }
 
 bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle) {
   if (!mHID) {
     return false;
   }
@@ -778,17 +791,17 @@ bool WindowsGamepadService::HandleRawInp
     if (NS_WARN_IF((usages[i] - 1u) >= buttons.Length())) {
       continue;
     }
     buttons[usages[i] - 1u] = true;
   }
 
   for (unsigned i = 0; i < gamepad->numButtons; i++) {
     if (gamepad->buttons[i] != buttons[i]) {
-      service->NewButtonEvent(gamepad->id, i, buttons[i]);
+      gamepad->remapper->RemapButtonEvent(gamepad->id, i, buttons[i]);
       gamepad->buttons[i] = buttons[i];
     }
   }
 
   // Get all axis values.
   for (unsigned i = 0; i < gamepad->numAxes; i++) {
     double new_value;
     if (gamepad->axes[i].caps.LogicalMin < 0) {
@@ -811,17 +824,17 @@ bool WindowsGamepadService::HandleRawInp
               raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
         continue;
       }
 
       new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                             gamepad->axes[i].caps.LogicalMax);
     }
     if (gamepad->axes[i].value != new_value) {
-      service->NewAxisMoveEvent(gamepad->id, i, new_value);
+      gamepad->remapper->RemapAxisMoveEvent(gamepad->id, i, new_value);
       gamepad->axes[i].value = new_value;
     }
   }
 
   return true;
 }
 
 void WindowsGamepadService::Startup() { ScanForDevices(); }