summaryrefslogtreecommitdiffstats
path: root/dom/gamepad
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/gamepad/Gamepad.cpp188
-rw-r--r--dom/gamepad/Gamepad.h145
-rw-r--r--dom/gamepad/GamepadButton.cpp28
-rw-r--r--dom/gamepad/GamepadButton.h53
-rw-r--r--dom/gamepad/GamepadHandle.cpp40
-rw-r--r--dom/gamepad/GamepadHandle.h89
-rw-r--r--dom/gamepad/GamepadHapticActuator.cpp80
-rw-r--r--dom/gamepad/GamepadHapticActuator.h51
-rw-r--r--dom/gamepad/GamepadLightIndicator.cpp69
-rw-r--r--dom/gamepad/GamepadLightIndicator.h53
-rw-r--r--dom/gamepad/GamepadManager.cpp660
-rw-r--r--dom/gamepad/GamepadManager.h156
-rw-r--r--dom/gamepad/GamepadMonitoring.h24
-rw-r--r--dom/gamepad/GamepadPlatformService.cpp336
-rw-r--r--dom/gamepad/GamepadPlatformService.h150
-rw-r--r--dom/gamepad/GamepadPose.cpp110
-rw-r--r--dom/gamepad/GamepadPose.h57
-rw-r--r--dom/gamepad/GamepadPoseState.h110
-rw-r--r--dom/gamepad/GamepadRemapping.cpp2123
-rw-r--r--dom/gamepad/GamepadRemapping.h140
-rw-r--r--dom/gamepad/GamepadServiceTest.cpp392
-rw-r--r--dom/gamepad/GamepadServiceTest.h115
-rw-r--r--dom/gamepad/GamepadTouch.cpp70
-rw-r--r--dom/gamepad/GamepadTouch.h47
-rw-r--r--dom/gamepad/GamepadTouchState.h42
-rw-r--r--dom/gamepad/android/AndroidGamepad.cpp118
-rw-r--r--dom/gamepad/cocoa/CocoaGamepad.cpp650
-rw-r--r--dom/gamepad/fallback/FallbackGamepad.cpp22
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelChild.cpp63
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelChild.h39
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelParent.cpp109
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelParent.h48
-rw-r--r--dom/gamepad/ipc/GamepadEventTypes.ipdlh78
-rw-r--r--dom/gamepad/ipc/GamepadMessageUtils.h154
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelChild.cpp34
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelChild.h45
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelParent.cpp129
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelParent.h49
-rw-r--r--dom/gamepad/ipc/PGamepadEventChannel.ipdl31
-rw-r--r--dom/gamepad/ipc/PGamepadTestChannel.ipdl23
-rw-r--r--dom/gamepad/linux/LinuxGamepad.cpp362
-rw-r--r--dom/gamepad/linux/udev.h150
-rw-r--r--dom/gamepad/moz.build82
-rw-r--r--dom/gamepad/windows/WindowsGamepad.cpp1136
44 files changed, 8650 insertions, 0 deletions
diff --git a/dom/gamepad/Gamepad.cpp b/dom/gamepad/Gamepad.cpp
new file mode 100644
index 0000000000..f64bb7f4eb
--- /dev/null
+++ b/dom/gamepad/Gamepad.cpp
@@ -0,0 +1,188 @@
+/* -*- 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/. */
+
+#include "Gamepad.h"
+#include "nsPIDOMWindow.h"
+#include "nsTArray.h"
+#include "nsVariant.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/GamepadBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Gamepad)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Gamepad)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Gamepad)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Gamepad, mParent, mButtons, mPose,
+ mHapticActuators, mLightIndicators,
+ mTouchEvents)
+
+void Gamepad::UpdateTimestamp() {
+ nsCOMPtr<nsPIDOMWindowInner> newWindow(do_QueryInterface(mParent));
+ if (newWindow) {
+ Performance* perf = newWindow->GetPerformance();
+ if (perf) {
+ mTimestamp = perf->Now();
+ }
+ }
+}
+
+Gamepad::Gamepad(nsISupports* aParent, const nsAString& aID, int32_t aIndex,
+ GamepadHandle aHandle, GamepadMappingType aMapping,
+ GamepadHand aHand, uint32_t aDisplayID, uint32_t aNumButtons,
+ uint32_t aNumAxes, uint32_t aNumHaptics,
+ uint32_t aNumLightIndicator, uint32_t aNumTouchEvents)
+ : mParent(aParent),
+ mID(aID),
+ mIndex(aIndex),
+ mHandle(aHandle),
+ mDisplayId(aDisplayID),
+ mTouchIdHashValue(0),
+ mMapping(aMapping),
+ mHand(aHand),
+ mConnected(true),
+ mButtons(aNumButtons),
+ mAxes(aNumAxes),
+ mTimestamp(0) {
+ for (unsigned i = 0; i < aNumButtons; i++) {
+ mButtons.InsertElementAt(i, new GamepadButton(mParent));
+ }
+ mAxes.InsertElementsAt(0, aNumAxes, 0.0f);
+ mPose = new GamepadPose(aParent);
+ for (uint32_t i = 0; i < aNumHaptics; ++i) {
+ mHapticActuators.AppendElement(
+ new GamepadHapticActuator(mParent, mHandle, i));
+ }
+ for (uint32_t i = 0; i < aNumLightIndicator; ++i) {
+ mLightIndicators.AppendElement(
+ new GamepadLightIndicator(mParent, mHandle, i));
+ }
+ for (uint32_t i = 0; i < aNumTouchEvents; ++i) {
+ mTouchEvents.AppendElement(new GamepadTouch(mParent));
+ }
+
+ // Mapping touchId(0) to touchIdHash(0) by default.
+ mTouchIdHash.InsertOrUpdate(0, mTouchIdHashValue);
+ ++mTouchIdHashValue;
+ UpdateTimestamp();
+}
+
+void Gamepad::SetIndex(int32_t aIndex) { mIndex = aIndex; }
+
+void Gamepad::SetConnected(bool aConnected) { mConnected = aConnected; }
+
+void Gamepad::SetButton(uint32_t aButton, bool aPressed, bool aTouched,
+ double aValue) {
+ MOZ_ASSERT(aButton < mButtons.Length());
+ mButtons[aButton]->SetPressed(aPressed);
+ mButtons[aButton]->SetTouched(aTouched);
+ mButtons[aButton]->SetValue(aValue);
+ UpdateTimestamp();
+}
+
+void Gamepad::SetAxis(uint32_t aAxis, double aValue) {
+ MOZ_ASSERT(aAxis < mAxes.Length());
+ if (mAxes[aAxis] != aValue) {
+ mAxes[aAxis] = aValue;
+ Gamepad_Binding::ClearCachedAxesValue(this);
+ }
+ UpdateTimestamp();
+}
+
+void Gamepad::SetPose(const GamepadPoseState& aPose) {
+ mPose->SetPoseState(aPose);
+ UpdateTimestamp();
+}
+
+void Gamepad::SetLightIndicatorType(uint32_t aLightIndex,
+ GamepadLightIndicatorType aType) {
+ mLightIndicators[aLightIndex]->SetType(aType);
+ UpdateTimestamp();
+}
+
+void Gamepad::SetTouchEvent(uint32_t aTouchIndex,
+ const GamepadTouchState& aTouch) {
+ if (aTouchIndex >= mTouchEvents.Length()) {
+ MOZ_CRASH("Touch index exceeds the event array.");
+ return;
+ }
+
+ // Handling cross-origin tracking.
+ GamepadTouchState touchState(aTouch);
+ touchState.touchId = mTouchIdHash.LookupOrInsertWith(
+ touchState.touchId, [&] { return mTouchIdHashValue++; });
+ mTouchEvents[aTouchIndex]->SetTouchState(touchState);
+ UpdateTimestamp();
+}
+
+void Gamepad::SetHand(GamepadHand aHand) { mHand = aHand; }
+
+void Gamepad::SyncState(Gamepad* aOther) {
+ if (mButtons.Length() != aOther->mButtons.Length() ||
+ mAxes.Length() != aOther->mAxes.Length()) {
+ return;
+ }
+
+ mConnected = aOther->mConnected;
+ for (uint32_t i = 0; i < mButtons.Length(); ++i) {
+ mButtons[i]->SetPressed(aOther->mButtons[i]->Pressed());
+ mButtons[i]->SetTouched(aOther->mButtons[i]->Touched());
+ mButtons[i]->SetValue(aOther->mButtons[i]->Value());
+ }
+
+ bool changed = false;
+ for (uint32_t i = 0; i < mAxes.Length(); ++i) {
+ changed = changed || (mAxes[i] != aOther->mAxes[i]);
+ mAxes[i] = aOther->mAxes[i];
+ }
+ if (changed) {
+ Gamepad_Binding::ClearCachedAxesValue(this);
+ }
+
+ if (StaticPrefs::dom_gamepad_extensions_enabled()) {
+ MOZ_ASSERT(aOther->GetPose());
+ mPose->SetPoseState(aOther->GetPose()->GetPoseState());
+ mHand = aOther->Hand();
+ for (uint32_t i = 0; i < mHapticActuators.Length(); ++i) {
+ mHapticActuators[i]->Set(aOther->mHapticActuators[i]);
+ }
+
+ if (StaticPrefs::dom_gamepad_extensions_lightindicator()) {
+ for (uint32_t i = 0; i < mLightIndicators.Length(); ++i) {
+ mLightIndicators[i]->Set(aOther->mLightIndicators[i]);
+ }
+ }
+ if (StaticPrefs::dom_gamepad_extensions_multitouch()) {
+ for (uint32_t i = 0; i < mTouchEvents.Length(); ++i) {
+ mTouchEvents[i]->Set(aOther->mTouchEvents[i]);
+ }
+ }
+ }
+
+ UpdateTimestamp();
+}
+
+already_AddRefed<Gamepad> Gamepad::Clone(nsISupports* aParent) {
+ RefPtr<Gamepad> out =
+ new Gamepad(aParent, mID, mIndex, mHandle, mMapping, mHand, mDisplayId,
+ mButtons.Length(), mAxes.Length(), mHapticActuators.Length(),
+ mLightIndicators.Length(), mTouchEvents.Length());
+ out->SyncState(this);
+ return out.forget();
+}
+
+/* virtual */
+JSObject* Gamepad::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Gamepad_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/Gamepad.h b/dom/gamepad/Gamepad.h
new file mode 100644
index 0000000000..689ea7f6d2
--- /dev/null
+++ b/dom/gamepad/Gamepad.h
@@ -0,0 +1,145 @@
+/* -*- 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_gamepad_Gamepad_h
+#define mozilla_dom_gamepad_Gamepad_h
+
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadButton.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadPose.h"
+#include "mozilla/dom/GamepadHapticActuator.h"
+#include "mozilla/dom/GamepadLightIndicator.h"
+#include "mozilla/dom/GamepadTouch.h"
+#include "mozilla/dom/Performance.h"
+#include <stdint.h>
+#include "nsCOMPtr.h"
+#include "nsTHashMap.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class GamepadHapticActuator;
+
+// Per spec:
+// https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#remapping
+const int kStandardGamepadButtons = 17;
+const int kStandardGamepadAxes = 4;
+
+const int kButtonLeftTrigger = 6;
+const int kButtonRightTrigger = 7;
+
+const int kLeftStickXAxis = 0;
+const int kLeftStickYAxis = 1;
+const int kRightStickXAxis = 2;
+const int kRightStickYAxis = 3;
+
+class Gamepad final : public nsISupports, public nsWrapperCache {
+ public:
+ Gamepad(nsISupports* aParent, const nsAString& aID, int32_t aIndex,
+ GamepadHandle aHandle, GamepadMappingType aMapping, GamepadHand aHand,
+ uint32_t aDisplayID, uint32_t aNumButtons, uint32_t aNumAxes,
+ uint32_t aNumHaptics, uint32_t aNumLightIndicator,
+ uint32_t aNumTouchEvents);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Gamepad)
+
+ void SetConnected(bool aConnected);
+ void SetButton(uint32_t aButton, bool aPressed, bool aTouched, double aValue);
+ void SetAxis(uint32_t aAxis, double aValue);
+ void SetIndex(int32_t aIndex);
+ void SetPose(const GamepadPoseState& aPose);
+ void SetLightIndicatorType(uint32_t aLightIndex,
+ GamepadLightIndicatorType aType);
+ void SetTouchEvent(uint32_t aTouchIndex, const GamepadTouchState& aTouch);
+ void SetHand(GamepadHand aHand);
+
+ // Make the state of this gamepad equivalent to other.
+ void SyncState(Gamepad* aOther);
+
+ // Return a new Gamepad containing the same data as this object,
+ // parented to aParent.
+ already_AddRefed<Gamepad> Clone(nsISupports* aParent);
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetId(nsAString& aID) const { aID = mID; }
+
+ DOMHighResTimeStamp Timestamp() const { return mTimestamp; }
+
+ GamepadMappingType Mapping() { return mMapping; }
+
+ uint32_t DisplayId() const { return mDisplayId; }
+
+ GamepadHand Hand() { return mHand; }
+
+ bool Connected() const { return mConnected; }
+
+ int32_t Index() const { return mIndex; }
+
+ void GetButtons(nsTArray<RefPtr<GamepadButton>>& aButtons) const {
+ aButtons = mButtons.Clone();
+ }
+
+ void GetAxes(nsTArray<double>& aAxes) const { aAxes = mAxes.Clone(); }
+
+ GamepadPose* GetPose() const { return mPose; }
+
+ void GetHapticActuators(
+ nsTArray<RefPtr<GamepadHapticActuator>>& aHapticActuators) const {
+ aHapticActuators = mHapticActuators.Clone();
+ }
+
+ void GetLightIndicators(
+ nsTArray<RefPtr<GamepadLightIndicator>>& aLightIndicators) const {
+ aLightIndicators = mLightIndicators.Clone();
+ }
+
+ void GetTouchEvents(nsTArray<RefPtr<GamepadTouch>>& aTouchEvents) const {
+ aTouchEvents = mTouchEvents.Clone();
+ }
+
+ GamepadHandle GetHandle() const { return mHandle; }
+
+ private:
+ virtual ~Gamepad() = default;
+ void UpdateTimestamp();
+
+ protected:
+ nsCOMPtr<nsISupports> mParent;
+ nsString mID;
+ int32_t mIndex;
+ // the gamepad hash key in GamepadManager
+ GamepadHandle mHandle;
+ uint32_t mDisplayId;
+ uint32_t mTouchIdHashValue;
+ // The mapping in use.
+ GamepadMappingType mMapping;
+ GamepadHand mHand;
+
+ // true if this gamepad is currently connected.
+ bool mConnected;
+
+ // Current state of buttons, axes.
+ nsTArray<RefPtr<GamepadButton>> mButtons;
+ nsTArray<double> mAxes;
+ DOMHighResTimeStamp mTimestamp;
+ RefPtr<GamepadPose> mPose;
+ nsTArray<RefPtr<GamepadHapticActuator>> mHapticActuators;
+ nsTArray<RefPtr<GamepadLightIndicator>> mLightIndicators;
+ nsTArray<RefPtr<GamepadTouch>> mTouchEvents;
+ nsTHashMap<nsUint32HashKey, uint32_t> mTouchIdHash;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_Gamepad_h
diff --git a/dom/gamepad/GamepadButton.cpp b/dom/gamepad/GamepadButton.cpp
new file mode 100644
index 0000000000..3ee985af3c
--- /dev/null
+++ b/dom/gamepad/GamepadButton.cpp
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/GamepadButton.h"
+#include "mozilla/dom/GamepadBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GamepadButton)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GamepadButton)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GamepadButton)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GamepadButton, mParent)
+
+/* virtual */
+JSObject* GamepadButton::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return GamepadButton_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadButton.h b/dom/gamepad/GamepadButton.h
new file mode 100644
index 0000000000..74bde07fcf
--- /dev/null
+++ b/dom/gamepad/GamepadButton.h
@@ -0,0 +1,53 @@
+/* -*- 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_gamepad_GamepadButton_h
+#define mozilla_dom_gamepad_GamepadButton_h
+
+#include <stdint.h>
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class GamepadButton : public nsISupports, public nsWrapperCache {
+ public:
+ explicit GamepadButton(nsISupports* aParent)
+ : mParent(aParent), mValue(0), mPressed(false), mTouched(false) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GamepadButton)
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetPressed(bool aPressed) { mPressed = aPressed; }
+
+ void SetTouched(bool aTouched) { mTouched = aTouched; }
+
+ void SetValue(double aValue) { mValue = aValue; }
+
+ bool Pressed() const { return mPressed; }
+
+ bool Touched() const { return mTouched; }
+
+ double Value() const { return mValue; }
+
+ private:
+ virtual ~GamepadButton() = default;
+
+ protected:
+ nsCOMPtr<nsISupports> mParent;
+ double mValue;
+ bool mPressed;
+ bool mTouched;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadButton_h
diff --git a/dom/gamepad/GamepadHandle.cpp b/dom/gamepad/GamepadHandle.cpp
new file mode 100644
index 0000000000..0357975a71
--- /dev/null
+++ b/dom/gamepad/GamepadHandle.cpp
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#include "GamepadHandle.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/HashFunctions.h"
+
+namespace mozilla::dom {
+
+GamepadHandle::GamepadHandle(uint32_t aValue, GamepadHandleKind aKind)
+ : mValue(aValue), mKind(aKind) {
+ MOZ_RELEASE_ASSERT(mValue);
+}
+
+GamepadHandleKind GamepadHandle::GetKind() const { return mKind; }
+
+PLDHashNumber GamepadHandle::Hash() const {
+ return HashGeneric(mValue, uint8_t(mKind));
+}
+
+bool operator==(const GamepadHandle& a, const GamepadHandle& b) {
+ return (a.mValue == b.mValue) && (a.mKind == b.mKind);
+}
+
+bool operator!=(const GamepadHandle& a, const GamepadHandle& b) {
+ return !(a == b);
+}
+
+bool operator<(const GamepadHandle& a, const GamepadHandle& b) {
+ if (a.mKind == b.mKind) {
+ return a.mValue < b.mValue;
+ }
+ // Arbitrarily order them by kind
+ return uint8_t(a.mKind) < uint8_t(b.mKind);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadHandle.h b/dom/gamepad/GamepadHandle.h
new file mode 100644
index 0000000000..bd3e67f857
--- /dev/null
+++ b/dom/gamepad/GamepadHandle.h
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+// This file defines a strongly-typed opaque gamepad handle
+//
+// The handle is designed to be copied around and passed over IPC. It keeps
+// track of which "provider" created the handle so it can ensure that
+// providers are never mixed. It also allows each provider to have its own
+// algorithm for generating gamepad IDs, since the VR and Platform services
+// do it differently.
+
+#ifndef mozilla_dom_gamepad_GamepadHandle_h
+#define mozilla_dom_gamepad_GamepadHandle_h
+#include "mozilla/Tuple.h"
+#include "PLDHashTable.h"
+#include <type_traits>
+#include <cinttypes>
+
+namespace IPC {
+
+template <class>
+struct ParamTraits;
+
+} // namespace IPC
+
+namespace mozilla::gfx {
+
+class VRDisplayClient;
+class VRManager;
+
+} // namespace mozilla::gfx
+
+namespace mozilla::dom {
+
+class GamepadPlatformService;
+class GamepadServiceTest;
+class XRInputSource;
+
+// The "kind" of a gamepad handle is based on which provider created it
+enum class GamepadHandleKind : uint8_t {
+ GamepadPlatformManager,
+ VR,
+};
+
+class GamepadHandle {
+ public:
+ // Allow handle to be passed around as a simple object
+ GamepadHandle() = default;
+ GamepadHandle(const GamepadHandle&) = default;
+ GamepadHandle& operator=(const GamepadHandle&) = default;
+
+ // Helps code know which manager to send requests to
+ GamepadHandleKind GetKind() const;
+
+ // Define operators so the handle can compared and stored in maps
+ friend bool operator==(const GamepadHandle& a, const GamepadHandle& b);
+ friend bool operator!=(const GamepadHandle& a, const GamepadHandle& b);
+ friend bool operator<(const GamepadHandle& a, const GamepadHandle& b);
+
+ PLDHashNumber Hash() const;
+
+ private:
+ explicit GamepadHandle(uint32_t aValue, GamepadHandleKind aKind);
+ uint32_t GetValue() const { return mValue; }
+
+ uint32_t mValue{0};
+ GamepadHandleKind mKind{GamepadHandleKind::GamepadPlatformManager};
+
+ // These are the classes that are "gamepad managers". They are allowed to
+ // create new handles and inspect their actual value
+ friend class mozilla::dom::GamepadPlatformService;
+ friend class mozilla::dom::GamepadServiceTest;
+ friend class mozilla::dom::XRInputSource;
+ friend class mozilla::gfx::VRDisplayClient;
+ friend class mozilla::gfx::VRManager;
+
+ // Allow IPDL to serialize us
+ friend struct IPC::ParamTraits<mozilla::dom::GamepadHandle>;
+};
+
+static_assert(std::is_trivially_copyable<GamepadHandle>::value,
+ "GamepadHandle must be trivially copyable");
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadHandle_h
diff --git a/dom/gamepad/GamepadHapticActuator.cpp b/dom/gamepad/GamepadHapticActuator.cpp
new file mode 100644
index 0000000000..462e386c8e
--- /dev/null
+++ b/dom/gamepad/GamepadHapticActuator.cpp
@@ -0,0 +1,80 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/GamepadHapticActuator.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GamepadHapticActuator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GamepadHapticActuator)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GamepadHapticActuator)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GamepadHapticActuator, mParent)
+
+GamepadHapticActuator::GamepadHapticActuator(nsISupports* aParent,
+ GamepadHandle aGamepadHandle,
+ uint32_t aIndex)
+ : mParent(aParent),
+ mGamepadHandle(aGamepadHandle),
+ mType(GamepadHapticActuatorType::Vibration),
+ mIndex(aIndex) {}
+
+/* virtual */
+JSObject* GamepadHapticActuator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return GamepadHapticActuator_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports* GamepadHapticActuator::GetParentObject() const { return mParent; }
+
+#define CLAMP(f, min, max) (((f) < min) ? min : (((f) > max) ? max : (f)))
+
+already_AddRefed<Promise> GamepadHapticActuator::Pulse(double aValue,
+ double aDuration,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ MOZ_ASSERT(global);
+
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ MOZ_ASSERT(gamepadManager);
+
+ // Clamp intensity aValue to be 0~1.
+ double value = CLAMP(aValue, 0, 1);
+ // aDuration should be always positive.
+ double duration = CLAMP(aDuration, 0, aDuration);
+
+ switch (mType) {
+ case GamepadHapticActuatorType::Vibration: {
+ RefPtr<Promise> promise = gamepadManager->VibrateHaptic(
+ mGamepadHandle, mIndex, value, duration, global, aRv);
+ if (!promise) {
+ return nullptr;
+ }
+ return promise.forget();
+ }
+ default: {
+ // We need to implement other types of haptic
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+ }
+}
+
+GamepadHapticActuatorType GamepadHapticActuator::Type() const { return mType; }
+
+void GamepadHapticActuator::Set(const GamepadHapticActuator* aOther) {
+ mGamepadHandle = aOther->mGamepadHandle;
+ mType = aOther->mType;
+ mIndex = aOther->mIndex;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadHapticActuator.h b/dom/gamepad/GamepadHapticActuator.h
new file mode 100644
index 0000000000..18b659137b
--- /dev/null
+++ b/dom/gamepad/GamepadHapticActuator.h
@@ -0,0 +1,51 @@
+/* -*- 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_gamepad_GamepadHapticActuator_h
+#define mozilla_dom_gamepad_GamepadHapticActuator_h
+
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/GamepadHapticActuatorBinding.h"
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadHandle.h"
+
+namespace mozilla::dom {
+class Promise;
+
+class GamepadHapticActuator : public nsISupports, public nsWrapperCache {
+ public:
+ GamepadHapticActuator(nsISupports* aParent, GamepadHandle aGamepadHandle,
+ uint32_t aIndex);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GamepadHapticActuator)
+
+ nsISupports* GetParentObject() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise> Pulse(double aValue, double aDuration,
+ ErrorResult& aRv);
+
+ GamepadHapticActuatorType Type() const;
+
+ void Set(const GamepadHapticActuator* aOther);
+
+ private:
+ virtual ~GamepadHapticActuator() = default;
+
+ protected:
+ nsCOMPtr<nsISupports> mParent;
+ GamepadHandle mGamepadHandle;
+ GamepadHapticActuatorType mType;
+ uint32_t mIndex;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadHapticActuator_h
diff --git a/dom/gamepad/GamepadLightIndicator.cpp b/dom/gamepad/GamepadLightIndicator.cpp
new file mode 100644
index 0000000000..0ddb1bffe6
--- /dev/null
+++ b/dom/gamepad/GamepadLightIndicator.cpp
@@ -0,0 +1,69 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/GamepadLightIndicator.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GamepadLightIndicator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GamepadLightIndicator)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GamepadLightIndicator)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GamepadLightIndicator, mParent)
+
+GamepadLightIndicator::GamepadLightIndicator(nsISupports* aParent,
+ GamepadHandle aGamepadHandle,
+ uint32_t aIndex)
+ : mParent(aParent),
+ mType(DefaultType()),
+ mGamepadHandle(aGamepadHandle),
+ mIndex(aIndex) {}
+
+GamepadLightIndicator::~GamepadLightIndicator() {
+ mozilla::DropJSObjects(this);
+}
+
+/* virtual */ JSObject* GamepadLightIndicator::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return GamepadLightIndicator_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports* GamepadLightIndicator::GetParentObject() const { return mParent; }
+
+already_AddRefed<Promise> GamepadLightIndicator::SetColor(
+ const GamepadLightColor& color, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ MOZ_ASSERT(global);
+
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ MOZ_ASSERT(gamepadManager);
+
+ RefPtr<Promise> promise = gamepadManager->SetLightIndicatorColor(
+ mGamepadHandle, mIndex, color.mRed, color.mGreen, color.mBlue, global,
+ aRv);
+ if (!promise) {
+ return nullptr;
+ }
+ return promise.forget();
+}
+
+GamepadLightIndicatorType GamepadLightIndicator::Type() const { return mType; }
+
+void GamepadLightIndicator::Set(const GamepadLightIndicator* aOther) {
+ MOZ_ASSERT(aOther);
+ mGamepadHandle = aOther->mGamepadHandle;
+ mType = aOther->mType;
+ mIndex = aOther->mIndex;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadLightIndicator.h b/dom/gamepad/GamepadLightIndicator.h
new file mode 100644
index 0000000000..80c6f9c13b
--- /dev/null
+++ b/dom/gamepad/GamepadLightIndicator.h
@@ -0,0 +1,53 @@
+/* -*- 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_gamepad_GamepadLightIndicator_h
+#define mozilla_dom_gamepad_GamepadLightIndicator_h
+
+#include "mozilla/dom/GamepadLightIndicatorBinding.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class GamepadLightIndicator final : public nsISupports, public nsWrapperCache {
+ public:
+ GamepadLightIndicator(nsISupports* aParent, GamepadHandle aGamepadHandle,
+ uint32_t aIndex);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GamepadLightIndicator)
+
+ static GamepadLightIndicatorType DefaultType() {
+ return GamepadLightIndicatorType::Rgb;
+ }
+
+ nsISupports* GetParentObject() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise> SetColor(const GamepadLightColor& color,
+ ErrorResult& aRv);
+
+ void SetType(GamepadLightIndicatorType aType) { mType = aType; }
+
+ GamepadLightIndicatorType Type() const;
+
+ void Set(const GamepadLightIndicator* aOther);
+
+ private:
+ virtual ~GamepadLightIndicator();
+
+ nsCOMPtr<nsISupports> mParent;
+ GamepadLightIndicatorType mType;
+ GamepadHandle mGamepadHandle;
+ uint32_t mIndex;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadLightIndicator_h
diff --git a/dom/gamepad/GamepadManager.cpp b/dom/gamepad/GamepadManager.cpp
new file mode 100644
index 0000000000..02d7be2718
--- /dev/null
+++ b/dom/gamepad/GamepadManager.cpp
@@ -0,0 +1,660 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/GamepadManager.h"
+
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadAxisMoveEvent.h"
+#include "mozilla/dom/GamepadButtonEvent.h"
+#include "mozilla/dom/GamepadEvent.h"
+#include "mozilla/dom/GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+#include "mozilla/dom/Promise.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "VRManagerChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+
+#include <cstddef>
+
+using namespace mozilla::ipc;
+
+namespace mozilla::dom {
+
+namespace {
+
+const nsTArray<RefPtr<nsGlobalWindowInner>>::index_type NoIndex =
+ nsTArray<RefPtr<nsGlobalWindowInner>>::NoIndex;
+
+bool sShutdown = false;
+
+StaticRefPtr<GamepadManager> gGamepadManagerSingleton;
+
+// A threshold value of axis move to determine the first
+// intent.
+const float AXIS_FIRST_INTENT_THRESHOLD_VALUE = 0.1f;
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver)
+
+GamepadManager::GamepadManager()
+ : mEnabled(false),
+ mNonstandardEventsEnabled(false),
+ mShuttingDown(false),
+ mPromiseID(0) {}
+
+nsresult GamepadManager::Init() {
+ mEnabled = StaticPrefs::dom_gamepad_enabled();
+ mNonstandardEventsEnabled =
+ StaticPrefs::dom_gamepad_non_standard_events_enabled();
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ rv = observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+ false);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GamepadManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ }
+ BeginShutdown();
+ return NS_OK;
+}
+
+void GamepadManager::StopMonitoring() {
+ if (mChannelChild) {
+ PGamepadEventChannelChild::Send__delete__(mChannelChild);
+ mChannelChild = nullptr;
+ }
+ if (gfx::VRManagerChild::IsCreated()) {
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ vm->SendControllerListenerRemoved();
+ }
+ mGamepads.Clear();
+}
+
+void GamepadManager::BeginShutdown() {
+ mShuttingDown = true;
+ StopMonitoring();
+ // Don't let windows call back to unregister during shutdown
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ mListeners[i]->SetHasGamepadEventListener(false);
+ }
+ mListeners.Clear();
+ sShutdown = true;
+}
+
+void GamepadManager::AddListener(nsGlobalWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // IPDL child has not been created
+ if (!mChannelChild) {
+ PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actor)) {
+ // We are probably shutting down.
+ return;
+ }
+
+ RefPtr<GamepadEventChannelChild> child(GamepadEventChannelChild::Create());
+ if (!actor->SendPGamepadEventChannelConstructor(child.get())) {
+ // We are probably shutting down.
+ return;
+ }
+
+ mChannelChild = child;
+
+ if (gfx::VRManagerChild::IsCreated()) {
+ // Construct VRManagerChannel and ask adding the connected
+ // VR controllers to GamepadManager
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ vm->SendControllerListenerAdded();
+ }
+ }
+
+ if (!mEnabled || mShuttingDown || aWindow->ShouldResistFingerprinting()) {
+ return;
+ }
+
+ if (mListeners.IndexOf(aWindow) != NoIndex) {
+ return; // already exists
+ }
+
+ mListeners.AppendElement(aWindow);
+}
+
+void GamepadManager::RemoveListener(nsGlobalWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ if (mShuttingDown) {
+ // Doesn't matter at this point. It's possible we're being called
+ // as a result of our own destructor here, so just bail out.
+ return;
+ }
+
+ if (mListeners.IndexOf(aWindow) == NoIndex) {
+ return; // doesn't exist
+ }
+
+ for (const auto& key : mGamepads.Keys()) {
+ aWindow->RemoveGamepad(key);
+ }
+
+ mListeners.RemoveElement(aWindow);
+
+ if (mListeners.IsEmpty()) {
+ StopMonitoring();
+ }
+}
+
+already_AddRefed<Gamepad> GamepadManager::GetGamepad(
+ GamepadHandle aHandle) const {
+ RefPtr<Gamepad> gamepad;
+ if (mGamepads.Get(aHandle, getter_AddRefs(gamepad))) {
+ return gamepad.forget();
+ }
+
+ return nullptr;
+}
+
+void GamepadManager::AddGamepad(GamepadHandle aHandle, const nsAString& aId,
+ GamepadMappingType aMapping, GamepadHand aHand,
+ uint32_t aDisplayID, uint32_t aNumButtons,
+ uint32_t aNumAxes, uint32_t aNumHaptics,
+ uint32_t aNumLightIndicator,
+ uint32_t aNumTouchEvents) {
+ // TODO: bug 852258: get initial button/axis state
+ RefPtr<Gamepad> newGamepad =
+ new Gamepad(nullptr, aId,
+ 0, // index is set by global window
+ aHandle, aMapping, aHand, aDisplayID, aNumButtons, aNumAxes,
+ aNumHaptics, aNumLightIndicator, aNumTouchEvents);
+
+ // We store the gamepad related to its index given by the parent process,
+ // and no duplicate index is allowed.
+ MOZ_ASSERT(!mGamepads.Contains(aHandle));
+ mGamepads.InsertOrUpdate(aHandle, std::move(newGamepad));
+ NewConnectionEvent(aHandle, true);
+}
+
+void GamepadManager::RemoveGamepad(GamepadHandle aHandle) {
+ RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
+ if (!gamepad) {
+ NS_WARNING("Trying to delete gamepad with invalid index");
+ return;
+ }
+ gamepad->SetConnected(false);
+ NewConnectionEvent(aHandle, false);
+ mGamepads.Remove(aHandle);
+}
+
+void GamepadManager::FireButtonEvent(EventTarget* aTarget, Gamepad* aGamepad,
+ uint32_t aButton, double aValue) {
+ nsString name =
+ aValue == 1.0L ? u"gamepadbuttondown"_ns : u"gamepadbuttonup"_ns;
+ GamepadButtonEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mGamepad = aGamepad;
+ init.mButton = aButton;
+ RefPtr<GamepadButtonEvent> event =
+ GamepadButtonEvent::Constructor(aTarget, name, init);
+
+ event->SetTrusted(true);
+
+ aTarget->DispatchEvent(*event);
+}
+
+void GamepadManager::FireAxisMoveEvent(EventTarget* aTarget, Gamepad* aGamepad,
+ uint32_t aAxis, double aValue) {
+ GamepadAxisMoveEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mGamepad = aGamepad;
+ init.mAxis = aAxis;
+ init.mValue = aValue;
+ RefPtr<GamepadAxisMoveEvent> event =
+ GamepadAxisMoveEvent::Constructor(aTarget, u"gamepadaxismove"_ns, init);
+
+ event->SetTrusted(true);
+
+ aTarget->DispatchEvent(*event);
+}
+
+void GamepadManager::NewConnectionEvent(GamepadHandle aHandle,
+ bool aConnected) {
+ if (mShuttingDown) {
+ return;
+ }
+
+ RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
+ if (!gamepad) {
+ return;
+ }
+
+ // Hold on to listeners in a separate array because firing events
+ // can mutate the mListeners array.
+ nsTArray<RefPtr<nsGlobalWindowInner>> listeners(mListeners.Clone());
+
+ if (aConnected) {
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+#ifdef NIGHTLY_BUILD
+ // Don't fire a gamepadconnected event unless it's a secure context
+ if (!listeners[i]->IsSecureContext()) {
+ continue;
+ }
+#endif
+
+ // Do not fire gamepadconnected and gamepaddisconnected events when
+ // privacy.resistFingerprinting is true.
+ if (listeners[i]->ShouldResistFingerprinting()) {
+ continue;
+ }
+
+ // Only send events to non-background windows
+ if (!listeners[i]->IsCurrentInnerWindow() ||
+ listeners[i]->GetOuterWindow()->IsBackground()) {
+ continue;
+ }
+
+ // We don't fire a connected event here unless the window
+ // has seen input from at least one device.
+ if (!listeners[i]->HasSeenGamepadInput()) {
+ continue;
+ }
+
+ SetWindowHasSeenGamepad(listeners[i], aHandle);
+
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aHandle);
+ if (listenerGamepad) {
+ // Fire event
+ FireConnectionEvent(listeners[i], listenerGamepad, aConnected);
+ }
+ }
+ } else {
+ // For disconnection events, fire one at every window that has received
+ // data from this gamepad.
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+ // Even background windows get these events, so we don't have to
+ // deal with the hassle of syncing the state of removed gamepads.
+
+ // Do not fire gamepadconnected and gamepaddisconnected events when
+ // privacy.resistFingerprinting is true.
+ if (listeners[i]->ShouldResistFingerprinting()) {
+ continue;
+ }
+
+ if (WindowHasSeenGamepad(listeners[i], aHandle)) {
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aHandle);
+ if (listenerGamepad) {
+ listenerGamepad->SetConnected(false);
+ // Fire event
+ FireConnectionEvent(listeners[i], listenerGamepad, false);
+ listeners[i]->RemoveGamepad(aHandle);
+ }
+ }
+ }
+ }
+}
+
+void GamepadManager::FireConnectionEvent(EventTarget* aTarget,
+ Gamepad* aGamepad, bool aConnected) {
+ nsString name =
+ aConnected ? u"gamepadconnected"_ns : u"gamepaddisconnected"_ns;
+ GamepadEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mGamepad = aGamepad;
+ RefPtr<GamepadEvent> event = GamepadEvent::Constructor(aTarget, name, init);
+
+ event->SetTrusted(true);
+
+ aTarget->DispatchEvent(*event);
+}
+
+void GamepadManager::SyncGamepadState(GamepadHandle aHandle,
+ nsGlobalWindowInner* aWindow,
+ Gamepad* aGamepad) {
+ if (mShuttingDown || !mEnabled || aWindow->ShouldResistFingerprinting()) {
+ return;
+ }
+
+ RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
+ if (!gamepad) {
+ return;
+ }
+
+ aGamepad->SyncState(gamepad);
+}
+
+// static
+bool GamepadManager::IsServiceRunning() { return !!gGamepadManagerSingleton; }
+
+// static
+already_AddRefed<GamepadManager> GamepadManager::GetService() {
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ if (!gGamepadManagerSingleton) {
+ RefPtr<GamepadManager> manager = new GamepadManager();
+ nsresult rv = manager->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ gGamepadManagerSingleton = manager;
+ ClearOnShutdown(&gGamepadManagerSingleton);
+ }
+
+ RefPtr<GamepadManager> service(gGamepadManagerSingleton);
+ return service.forget();
+}
+
+bool GamepadManager::AxisMoveIsFirstIntent(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle,
+ const GamepadChangeEvent& aEvent) {
+ const GamepadChangeEventBody& body = aEvent.body();
+ if (!WindowHasSeenGamepad(aWindow, aHandle) &&
+ body.type() == GamepadChangeEventBody::TGamepadAxisInformation) {
+ // Some controllers would send small axis values even they are just idle.
+ // To avoid controllers be activated without its first intent.
+ const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
+ if (abs(a.value()) < AXIS_FIRST_INTENT_THRESHOLD_VALUE) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle) {
+ if (!WindowHasSeenGamepad(aWindow, aHandle)) {
+ // This window hasn't seen this gamepad before, so
+ // send a connection event first.
+ SetWindowHasSeenGamepad(aWindow, aHandle);
+ return false;
+ }
+ return true;
+}
+
+bool GamepadManager::WindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle) const {
+ RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aHandle);
+ return gamepad != nullptr;
+}
+
+void GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle,
+ bool aHasSeen) {
+ MOZ_ASSERT(aWindow);
+
+ if (mListeners.IndexOf(aWindow) == NoIndex) {
+ // This window isn't even listening for gamepad events.
+ return;
+ }
+
+ if (aHasSeen) {
+ aWindow->SetHasSeenGamepadInput(true);
+ nsCOMPtr<nsISupports> window = ToSupports(aWindow);
+ RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
+ if (!gamepad) {
+ return;
+ }
+ RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window);
+ aWindow->AddGamepad(aHandle, clonedGamepad);
+ } else {
+ aWindow->RemoveGamepad(aHandle);
+ }
+}
+
+void GamepadManager::Update(const GamepadChangeEvent& aEvent) {
+ if (!mEnabled || mShuttingDown ||
+ nsContentUtils::ShouldResistFingerprinting()) {
+ return;
+ }
+
+ const GamepadHandle handle = aEvent.handle();
+
+ GamepadChangeEventBody body = aEvent.body();
+
+ if (body.type() == GamepadChangeEventBody::TGamepadAdded) {
+ const GamepadAdded& a = body.get_GamepadAdded();
+ AddGamepad(handle, a.id(), static_cast<GamepadMappingType>(a.mapping()),
+ static_cast<GamepadHand>(a.hand()), a.display_id(),
+ a.num_buttons(), a.num_axes(), a.num_haptics(), a.num_lights(),
+ a.num_touches());
+ return;
+ }
+ if (body.type() == GamepadChangeEventBody::TGamepadRemoved) {
+ RemoveGamepad(handle);
+ return;
+ }
+
+ if (!SetGamepadByEvent(aEvent)) {
+ return;
+ }
+
+ // Hold on to listeners in a separate array because firing events
+ // can mutate the mListeners array.
+ nsTArray<RefPtr<nsGlobalWindowInner>> listeners(mListeners.Clone());
+
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+ // Only send events to non-background windows
+ if (!listeners[i]->IsCurrentInnerWindow() ||
+ listeners[i]->GetOuterWindow()->IsBackground()) {
+ continue;
+ }
+
+ SetGamepadByEvent(aEvent, listeners[i]);
+ MaybeConvertToNonstandardGamepadEvent(aEvent, listeners[i]);
+ }
+}
+
+void GamepadManager::MaybeConvertToNonstandardGamepadEvent(
+ const GamepadChangeEvent& aEvent, nsGlobalWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ if (!mNonstandardEventsEnabled) {
+ return;
+ }
+
+ GamepadHandle handle = aEvent.handle();
+
+ RefPtr<Gamepad> gamepad = aWindow->GetGamepad(handle);
+ const GamepadChangeEventBody& body = aEvent.body();
+
+ if (gamepad) {
+ switch (body.type()) {
+ case GamepadChangeEventBody::TGamepadButtonInformation: {
+ const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
+ FireButtonEvent(aWindow, gamepad, a.button(), a.value());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadAxisInformation: {
+ const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
+ FireAxisMoveEvent(aWindow, gamepad, a.axis(), a.value());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+bool GamepadManager::SetGamepadByEvent(const GamepadChangeEvent& aEvent,
+ nsGlobalWindowInner* aWindow) {
+ bool ret = false;
+ bool firstTime = false;
+
+ GamepadHandle handle = aEvent.handle();
+
+ if (aWindow) {
+ if (!AxisMoveIsFirstIntent(aWindow, handle, aEvent)) {
+ return false;
+ }
+ firstTime = !MaybeWindowHasSeenGamepad(aWindow, handle);
+ }
+
+ RefPtr<Gamepad> gamepad =
+ aWindow ? aWindow->GetGamepad(handle) : GetGamepad(handle);
+ const GamepadChangeEventBody& body = aEvent.body();
+
+ if (gamepad) {
+ switch (body.type()) {
+ case GamepadChangeEventBody::TGamepadButtonInformation: {
+ const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
+ gamepad->SetButton(a.button(), a.pressed(), a.touched(), a.value());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadAxisInformation: {
+ const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
+ gamepad->SetAxis(a.axis(), a.value());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadPoseInformation: {
+ const GamepadPoseInformation& a = body.get_GamepadPoseInformation();
+ gamepad->SetPose(a.pose_state());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadLightIndicatorTypeInformation: {
+ const GamepadLightIndicatorTypeInformation& a =
+ body.get_GamepadLightIndicatorTypeInformation();
+ gamepad->SetLightIndicatorType(a.light(), a.type());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadTouchInformation: {
+ // Avoid GamepadTouch's touchId be accessed in cross-origin tracking.
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ RefPtr<Gamepad> listenerGamepad = mListeners[i]->GetGamepad(handle);
+ if (listenerGamepad && mListeners[i]->IsCurrentInnerWindow() &&
+ !mListeners[i]->GetOuterWindow()->IsBackground()) {
+ const GamepadTouchInformation& a =
+ body.get_GamepadTouchInformation();
+ listenerGamepad->SetTouchEvent(a.index(), a.touch_state());
+ }
+ }
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadHandInformation: {
+ const GamepadHandInformation& a = body.get_GamepadHandInformation();
+ gamepad->SetHand(a.hand());
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+ ret = true;
+ }
+
+ if (aWindow && firstTime) {
+ FireConnectionEvent(aWindow, gamepad, true);
+ }
+
+ return ret;
+}
+
+already_AddRefed<Promise> GamepadManager::VibrateHaptic(
+ GamepadHandle aHandle, uint32_t aHapticIndex, double aIntensity,
+ double aDuration, nsIGlobalObject* aGlobal, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ if (StaticPrefs::dom_gamepad_haptic_feedback_enabled()) {
+ if (aHandle.GetKind() == GamepadHandleKind::VR) {
+ if (gfx::VRManagerChild::IsCreated()) {
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ vm->AddPromise(mPromiseID, promise);
+ vm->SendVibrateHaptic(aHandle, aHapticIndex, aIntensity, aDuration,
+ mPromiseID);
+ }
+ } else {
+ if (mChannelChild) {
+ mChannelChild->AddPromise(mPromiseID, promise);
+ mChannelChild->SendVibrateHaptic(aHandle, aHapticIndex, aIntensity,
+ aDuration, mPromiseID);
+ }
+ }
+ }
+
+ ++mPromiseID;
+ return promise.forget();
+}
+
+void GamepadManager::StopHaptics() {
+ if (!StaticPrefs::dom_gamepad_haptic_feedback_enabled()) {
+ return;
+ }
+
+ for (const auto& entry : mGamepads) {
+ const GamepadHandle handle = entry.GetWeak()->GetHandle();
+ if (handle.GetKind() == GamepadHandleKind::VR) {
+ if (gfx::VRManagerChild::IsCreated()) {
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ vm->SendStopVibrateHaptic(handle);
+ }
+ } else {
+ if (mChannelChild) {
+ mChannelChild->SendStopVibrateHaptic(handle);
+ }
+ }
+ }
+}
+
+already_AddRefed<Promise> GamepadManager::SetLightIndicatorColor(
+ GamepadHandle aHandle, uint32_t aLightColorIndex, uint8_t aRed,
+ uint8_t aGreen, uint8_t aBlue, nsIGlobalObject* aGlobal, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ if (StaticPrefs::dom_gamepad_extensions_lightindicator()) {
+ MOZ_RELEASE_ASSERT(aHandle.GetKind() != GamepadHandleKind::VR,
+ "We don't support light indicator in VR.");
+
+ if (mChannelChild) {
+ mChannelChild->AddPromise(mPromiseID, promise);
+ mChannelChild->SendLightIndicatorColor(aHandle, aLightColorIndex, aRed,
+ aGreen, aBlue, mPromiseID);
+ }
+ }
+
+ ++mPromiseID;
+ return promise.forget();
+}
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadManager.h b/dom/gamepad/GamepadManager.h
new file mode 100644
index 0000000000..794c0d1ca4
--- /dev/null
+++ b/dom/gamepad/GamepadManager.h
@@ -0,0 +1,156 @@
+/* -*- 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_GamepadManager_h_
+#define mozilla_dom_GamepadManager_h_
+
+#include "nsIObserver.h"
+#include "nsRefPtrHashtable.h"
+// Needed for GamepadMappingType
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include <utility>
+
+class nsGlobalWindowInner;
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace gfx {
+class VRManagerChild;
+} // namespace gfx
+namespace dom {
+
+class EventTarget;
+class Gamepad;
+class GamepadChangeEvent;
+class GamepadEventChannelChild;
+
+class GamepadManager final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // Returns true if we actually have a service up and running
+ static bool IsServiceRunning();
+ // Get the singleton service
+ static already_AddRefed<GamepadManager> GetService();
+
+ void BeginShutdown();
+ void StopMonitoring();
+
+ // Indicate that |aWindow| wants to receive gamepad events.
+ void AddListener(nsGlobalWindowInner* aWindow);
+ // Indicate that |aWindow| should no longer receive gamepad events.
+ void RemoveListener(nsGlobalWindowInner* aWindow);
+
+ // Add a gamepad to the list of known gamepads.
+ void AddGamepad(GamepadHandle aHandle, const nsAString& aID,
+ GamepadMappingType aMapping, GamepadHand aHand,
+ uint32_t aDisplayID, uint32_t aNumButtons, uint32_t aNumAxes,
+ uint32_t aNumHaptics, uint32_t aNumLightIndicator,
+ uint32_t aNumTouchEvents);
+
+ // Remove the gamepad at |aIndex| from the list of known gamepads.
+ void RemoveGamepad(GamepadHandle aHandle);
+
+ // Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
+ void SyncGamepadState(GamepadHandle aHandle, nsGlobalWindowInner* aWindow,
+ Gamepad* aGamepad);
+
+ // Returns gamepad object if index exists, null otherwise
+ already_AddRefed<Gamepad> GetGamepad(GamepadHandle aHandle) const;
+
+ // Receive GamepadChangeEvent messages from parent process to fire DOM events
+ void Update(const GamepadChangeEvent& aGamepadEvent);
+
+ // Trigger vibrate haptic event to gamepad channels.
+ already_AddRefed<Promise> VibrateHaptic(GamepadHandle aHandle,
+ uint32_t aHapticIndex,
+ double aIntensity, double aDuration,
+ nsIGlobalObject* aGlobal,
+ ErrorResult& aRv);
+ // Send stop haptic events to gamepad channels.
+ void StopHaptics();
+
+ // Set light indicator color event to gamepad channels.
+ already_AddRefed<Promise> SetLightIndicatorColor(GamepadHandle aHandle,
+ uint32_t aLightColorIndex,
+ uint8_t aRed, uint8_t aGreen,
+ uint8_t aBlue,
+ nsIGlobalObject* aGlobal,
+ ErrorResult& aRv);
+
+ protected:
+ GamepadManager();
+ ~GamepadManager() = default;
+
+ // Fire a gamepadconnected or gamepaddisconnected event for the gamepad
+ // at |aIndex| to all windows that are listening and have received
+ // gamepad input.
+ void NewConnectionEvent(GamepadHandle aHandle, bool aConnected);
+
+ // Fire a gamepadaxismove event to the window at |aTarget| for |aGamepad|.
+ void FireAxisMoveEvent(EventTarget* aTarget, Gamepad* aGamepad, uint32_t axis,
+ double value);
+
+ // Fire one of gamepadbutton{up,down} event at the window at |aTarget| for
+ // |aGamepad|.
+ void FireButtonEvent(EventTarget* aTarget, Gamepad* aGamepad,
+ uint32_t aButton, double aValue);
+
+ // Fire one of gamepad{connected,disconnected} event at the window at
+ // |aTarget| for |aGamepad|.
+ void FireConnectionEvent(EventTarget* aTarget, Gamepad* aGamepad,
+ bool aConnected);
+
+ // true if this feature is enabled in preferences
+ bool mEnabled;
+ // true if non-standard events are enabled in preferences
+ bool mNonstandardEventsEnabled;
+ // true when shutdown has begun
+ bool mShuttingDown;
+
+ RefPtr<GamepadEventChannelChild> mChannelChild;
+
+ private:
+ nsresult Init();
+
+ void MaybeConvertToNonstandardGamepadEvent(const GamepadChangeEvent& aEvent,
+ nsGlobalWindowInner* aWindow);
+
+ bool SetGamepadByEvent(const GamepadChangeEvent& aEvent,
+ nsGlobalWindowInner* aWindow = nullptr);
+ // To avoid unintentionally causing the gamepad be activated.
+ // Returns false if this gamepad hasn't been seen by this window
+ // and the axis move data is less than AXIS_FIRST_INTENT_THRESHOLD_VALUE.
+ bool AxisMoveIsFirstIntent(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle,
+ const GamepadChangeEvent& aEvent);
+ bool MaybeWindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle);
+ // Returns true if we have already sent data from this gamepad
+ // to this window. This should only return true if the user
+ // explicitly interacted with a gamepad while this window
+ // was focused, by pressing buttons or similar actions.
+ bool WindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle) const;
+ // Indicate that a window has received data from a gamepad.
+ void SetWindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
+ GamepadHandle aHandle, bool aHasSeen = true);
+
+ // Gamepads connected to the system. Copies of these are handed out
+ // to each window.
+ nsRefPtrHashtable<nsGenericHashKey<GamepadHandle>, Gamepad> mGamepads;
+ // Inner windows that are listening for gamepad events.
+ // has been sent to that window.
+ nsTArray<RefPtr<nsGlobalWindowInner>> mListeners;
+ uint32_t mPromiseID;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GamepadManager_h_
diff --git a/dom/gamepad/GamepadMonitoring.h b/dom/gamepad/GamepadMonitoring.h
new file mode 100644
index 0000000000..67b4737470
--- /dev/null
+++ b/dom/gamepad/GamepadMonitoring.h
@@ -0,0 +1,24 @@
+/* -*- 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_GamepadMonitoring_h_
+#define mozilla_dom_GamepadMonitoring_h_
+#include "mozilla/dom/GamepadHandle.h"
+
+namespace mozilla::dom {
+
+// These two functions are implemented in the platform specific service files
+// (linux/LinuxGamepad.cpp, cocoa/CocoaGamepad.cpp, etc)
+void StartGamepadMonitoring();
+void StopGamepadMonitoring();
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue);
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/GamepadPlatformService.cpp b/dom/gamepad/GamepadPlatformService.cpp
new file mode 100644
index 0000000000..7d730e3d0a
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.cpp
@@ -0,0 +1,336 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/GamepadPlatformService.h"
+
+#include "mozilla/dom/GamepadEventChannelParent.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+#include "mozilla/dom/GamepadTestChannelParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::dom {
+
+namespace {
+
+// This is the singleton instance of GamepadPlatformService, can be called
+// by both background and monitor thread.
+StaticRefPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton;
+
+} // namespace
+
+// static
+GamepadMonitoringState& GamepadMonitoringState::GetSingleton() {
+ static GamepadMonitoringState sInstance{};
+ return sInstance;
+}
+
+void GamepadMonitoringState::AddObserver(GamepadTestChannelParent* aParent) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ALWAYS_TRUE(mObservers.append(aParent));
+}
+
+void GamepadMonitoringState::RemoveObserver(GamepadTestChannelParent* aParent) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+
+ WeakPtr<GamepadTestChannelParent>* observer = nullptr;
+
+ for (auto& item : mObservers) {
+ if (item == aParent) {
+ observer = &item;
+ }
+ }
+
+ MOZ_ASSERT(
+ observer,
+ "Attempted to remove a GamepadTestChannelParent that was never added");
+
+ std::swap(*observer, mObservers.back());
+ mObservers.popBack();
+}
+
+bool GamepadMonitoringState::IsMonitoring() const {
+ AssertIsOnBackgroundThread();
+ return mIsMonitoring;
+}
+
+void GamepadMonitoringState::Set(bool aIsMonitoring) {
+ AssertIsOnBackgroundThread();
+
+ if (mIsMonitoring != aIsMonitoring) {
+ mIsMonitoring = aIsMonitoring;
+ for (auto& observer : mObservers) {
+ // Since each GamepadTestChannelParent removes itself in its dtor, this
+ // should never be nullptr
+ MOZ_RELEASE_ASSERT(observer);
+ observer->OnMonitoringStateChanged(aIsMonitoring);
+ }
+ }
+}
+
+GamepadPlatformService::GamepadPlatformService()
+ : mNextGamepadHandleValue(1),
+ mMutex("mozilla::dom::GamepadPlatformService") {}
+
+GamepadPlatformService::~GamepadPlatformService() { Cleanup(); }
+
+// static
+already_AddRefed<GamepadPlatformService>
+GamepadPlatformService::GetParentService() {
+ // GamepadPlatformService can only be accessed in parent process
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!gGamepadPlatformServiceSingleton) {
+ // Only Background Thread can create new GamepadPlatformService instance.
+ if (IsOnBackgroundThread()) {
+ gGamepadPlatformServiceSingleton = new GamepadPlatformService();
+ } else {
+ return nullptr;
+ }
+ }
+ RefPtr<GamepadPlatformService> service(gGamepadPlatformServiceSingleton);
+ return service.forget();
+}
+
+template <class T>
+void GamepadPlatformService::NotifyGamepadChange(GamepadHandle aHandle,
+ const T& aInfo) {
+ // This method is called by monitor populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ GamepadChangeEventBody body(aInfo);
+ GamepadChangeEvent e(aHandle, body);
+
+ // mChannelParents may be accessed by background thread in the
+ // same time, we use mutex to prevent possible race condtion
+ MutexAutoLock autoLock(mMutex);
+
+ for (uint32_t i = 0; i < mChannelParents.Length(); ++i) {
+ mChannelParents[i]->DispatchUpdateEvent(e);
+ }
+}
+
+GamepadHandle GamepadPlatformService::AddGamepad(
+ const char* aID, GamepadMappingType aMapping, GamepadHand aHand,
+ uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aHaptics,
+ uint32_t aNumLightIndicator, uint32_t aNumTouchEvents) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ GamepadHandle gamepadHandle{mNextGamepadHandleValue++,
+ GamepadHandleKind::GamepadPlatformManager};
+
+ // Only VR controllers has displayID, we give 0 to the general gamepads.
+ GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), aMapping,
+ aHand, 0, aNumButtons, aNumAxes, aHaptics, aNumLightIndicator,
+ aNumTouchEvents);
+
+ mGamepadAdded.emplace(gamepadHandle, a);
+ NotifyGamepadChange<GamepadAdded>(gamepadHandle, a);
+ return gamepadHandle;
+}
+
+void GamepadPlatformService::RemoveGamepad(GamepadHandle aHandle) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ GamepadRemoved a;
+ NotifyGamepadChange<GamepadRemoved>(aHandle, a);
+ mGamepadAdded.erase(aHandle);
+}
+
+void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
+ uint32_t aButton, bool aPressed,
+ bool aTouched, double aValue) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
+ NotifyGamepadChange<GamepadButtonInformation>(aHandle, a);
+}
+
+void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
+ uint32_t aButton, bool aPressed,
+ double aValue) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ // When only a digital button is available the value will be synthesized.
+ NewButtonEvent(aHandle, aButton, aPressed, aPressed, aValue);
+}
+
+void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
+ uint32_t aButton, bool aPressed,
+ bool aTouched) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ // When only a digital button is available the value will be synthesized.
+ NewButtonEvent(aHandle, aButton, aPressed, aTouched, aPressed ? 1.0L : 0.0L);
+}
+
+void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
+ uint32_t aButton, bool aPressed) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ // When only a digital button is available the value will be synthesized.
+ NewButtonEvent(aHandle, aButton, aPressed, aPressed, aPressed ? 1.0L : 0.0L);
+}
+
+void GamepadPlatformService::NewAxisMoveEvent(GamepadHandle aHandle,
+ uint32_t aAxis, double aValue) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ GamepadAxisInformation a(aAxis, aValue);
+ NotifyGamepadChange<GamepadAxisInformation>(aHandle, a);
+}
+
+void GamepadPlatformService::NewLightIndicatorTypeEvent(
+ GamepadHandle aHandle, uint32_t aLight, GamepadLightIndicatorType aType) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ GamepadLightIndicatorTypeInformation a(aLight, aType);
+ NotifyGamepadChange<GamepadLightIndicatorTypeInformation>(aHandle, a);
+}
+
+void GamepadPlatformService::NewPoseEvent(GamepadHandle aHandle,
+ const GamepadPoseState& aState) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ GamepadPoseInformation a(aState);
+ NotifyGamepadChange<GamepadPoseInformation>(aHandle, a);
+}
+
+void GamepadPlatformService::NewMultiTouchEvent(
+ GamepadHandle aHandle, uint32_t aTouchArrayIndex,
+ const GamepadTouchState& aState) {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ GamepadTouchInformation a(aTouchArrayIndex, aState);
+ NotifyGamepadChange<GamepadTouchInformation>(aHandle, a);
+}
+
+void GamepadPlatformService::ResetGamepadIndexes() {
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ mNextGamepadHandleValue = 1;
+}
+
+void GamepadPlatformService::AddChannelParent(
+ GamepadEventChannelParent* aParent) {
+ // mChannelParents can only be modified once GamepadEventChannelParent
+ // is created or removed in Background thread
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(!mChannelParents.Contains(aParent));
+
+ // We use mutex here to prevent race condition with monitor thread
+ {
+ MutexAutoLock autoLock(mMutex);
+ mChannelParents.AppendElement(aParent);
+
+ // For a new GamepadEventChannel, we have to send the exising GamepadAdded
+ // to it to make it can have the same amount of gamepads with others.
+ if (mChannelParents.Length() > 1) {
+ for (const auto& evt : mGamepadAdded) {
+ GamepadChangeEventBody body(evt.second);
+ GamepadChangeEvent e(evt.first, body);
+ aParent->DispatchUpdateEvent(e);
+ }
+ }
+ }
+
+ StartGamepadMonitoring();
+
+ GamepadMonitoringState::GetSingleton().Set(true);
+}
+
+void GamepadPlatformService::RemoveChannelParent(
+ GamepadEventChannelParent* aParent) {
+ // mChannelParents can only be modified once GamepadEventChannelParent
+ // is created or removed in Background thread
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(mChannelParents.Contains(aParent));
+
+ // We use mutex here to prevent race condition with monitor thread
+ {
+ MutexAutoLock autoLock(mMutex);
+ mChannelParents.RemoveElement(aParent);
+ if (!mChannelParents.IsEmpty()) {
+ return;
+ }
+ }
+
+ GamepadMonitoringState::GetSingleton().Set(false);
+
+ StopGamepadMonitoring();
+ ResetGamepadIndexes();
+ MaybeShutdown();
+}
+
+void GamepadPlatformService::MaybeShutdown() {
+ // This method is invoked in MaybeStopGamepadMonitoring when
+ // an IPDL channel is going to be destroyed
+ AssertIsOnBackgroundThread();
+
+ // We have to release gGamepadPlatformServiceSingleton ouside
+ // the mutex as well as making upcoming GetParentService() call
+ // recreate new singleton, so we use this RefPtr to temporarily
+ // hold the reference, postponing the release process until this
+ // method ends.
+ RefPtr<GamepadPlatformService> kungFuDeathGrip;
+
+ bool isChannelParentEmpty;
+ {
+ MutexAutoLock autoLock(mMutex);
+ isChannelParentEmpty = mChannelParents.IsEmpty();
+ if (isChannelParentEmpty) {
+ kungFuDeathGrip = gGamepadPlatformServiceSingleton;
+ gGamepadPlatformServiceSingleton = nullptr;
+ mGamepadAdded.clear();
+ }
+ }
+}
+
+void GamepadPlatformService::Cleanup() {
+ // This method is called when GamepadPlatformService is
+ // successfully distructed in background thread
+ AssertIsOnBackgroundThread();
+
+ MutexAutoLock autoLock(mMutex);
+ mChannelParents.Clear();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadPlatformService.h b/dom/gamepad/GamepadPlatformService.h
new file mode 100644
index 0000000000..7353f1f449
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.h
@@ -0,0 +1,150 @@
+/* -*- 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_GamepadPlatformService_h_
+#define mozilla_dom_GamepadPlatformService_h_
+
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadHandle.h"
+
+#include <map>
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla::dom {
+
+class GamepadAdded;
+class GamepadEventChannelParent;
+enum class GamepadLightIndicatorType : uint8_t;
+struct GamepadPoseState;
+class GamepadTestChannelParent;
+struct GamepadTouchState;
+class GamepadPlatformService;
+
+class GamepadMonitoringState {
+ public:
+ static GamepadMonitoringState& GetSingleton();
+
+ void AddObserver(GamepadTestChannelParent* aParent);
+ void RemoveObserver(GamepadTestChannelParent* aParent);
+
+ bool IsMonitoring() const;
+
+ GamepadMonitoringState(const GamepadMonitoringState&) = delete;
+ GamepadMonitoringState(GamepadMonitoringState&&) = delete;
+ GamepadMonitoringState& operator=(const GamepadMonitoringState) = delete;
+ GamepadMonitoringState& operator=(GamepadMonitoringState&&) = delete;
+
+ private:
+ GamepadMonitoringState() = default;
+ ~GamepadMonitoringState() = default;
+
+ void Set(bool aIsMonitoring);
+
+ bool mIsMonitoring{false};
+ Vector<WeakPtr<GamepadTestChannelParent>> mObservers;
+
+ friend class mozilla::dom::GamepadPlatformService;
+};
+
+// Platform Service for building and transmitting IPDL messages
+// through the HAL sandbox. Used by platform specific
+// Gamepad implementations
+//
+// This class can be accessed by the following 2 threads :
+// 1. Background thread:
+// This thread takes charge of IPDL communications
+// between here and DOM side
+//
+// 2. Monitor Thread:
+// This thread is populated in platform-dependent backends, which
+// is in charge of processing gamepad hardware events from OS
+class GamepadPlatformService final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadPlatformService)
+ public:
+ // Get the singleton service
+ static already_AddRefed<GamepadPlatformService> GetParentService();
+
+ // Add a gamepad to the list of known gamepads, and return its handle.
+ GamepadHandle AddGamepad(const char* aID, GamepadMappingType aMapping,
+ GamepadHand aHand, uint32_t aNumButtons,
+ uint32_t aNumAxes, uint32_t aNumHaptics,
+ uint32_t aNumLightIndicator,
+ uint32_t aNumTouchEvents);
+ // Remove the gamepad at |aHandle| from the list of known gamepads.
+ void RemoveGamepad(GamepadHandle aHandle);
+
+ // Update the state of |aButton| for the gamepad at |aHandle| for all
+ // windows that are listening and visible, and fire one of
+ // a gamepadbutton{up,down} event at them as well.
+ // aPressed is used for digital buttons, aTouched is for detecting touched
+ // events, aValue is for analog buttons.
+ void NewButtonEvent(GamepadHandle aHandle, uint32_t aButton, bool aPressed,
+ bool aTouched, double aValue);
+ // When only a digital button is available the value will be synthesized.
+ void NewButtonEvent(GamepadHandle aHandle, uint32_t aButton, bool aPressed);
+ // When only a digital button are available the value will be synthesized.
+ void NewButtonEvent(GamepadHandle aHandle, uint32_t aButton, bool aPressed,
+ bool aTouched);
+ // When only a digital button are available the value will be synthesized.
+ void NewButtonEvent(GamepadHandle aHandle, uint32_t aButton, bool aPressed,
+ double aValue);
+ // Update the state of |aAxis| for the gamepad at |aHandle| for all
+ // windows that are listening and visible, and fire a gamepadaxismove
+ // event at them as well.
+ void NewAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis, double aValue);
+ // Update the state of |aState| for the gamepad at |aHandle| for all
+ // windows that are listening and visible.
+ void NewPoseEvent(GamepadHandle aHandle, const GamepadPoseState& aState);
+ // Update the type of |aType| for the gamepad at |aHandle| for all
+ // windows that are listening and visible.
+ void NewLightIndicatorTypeEvent(GamepadHandle aHandle, uint32_t aLight,
+ GamepadLightIndicatorType aType);
+ // Update the state of |aState| for the gamepad at |aHandle| with
+ // |aTouchArrayIndex| for all windows that are listening and visible.
+ void NewMultiTouchEvent(GamepadHandle aHandle, uint32_t aTouchArrayIndex,
+ const GamepadTouchState& aState);
+
+ // When shutting down the platform communications for gamepad, also reset the
+ // indexes.
+ void ResetGamepadIndexes();
+
+ // Add IPDL parent instance
+ void AddChannelParent(GamepadEventChannelParent* aParent);
+
+ // Remove IPDL parent instance
+ void RemoveChannelParent(GamepadEventChannelParent* aParent);
+
+ void MaybeShutdown();
+
+ private:
+ GamepadPlatformService();
+ ~GamepadPlatformService();
+ template <class T>
+ void NotifyGamepadChange(GamepadHandle aHandle, const T& aInfo);
+
+ void Cleanup();
+
+ // mNextGamepadHandleValue can only be accessed by monitor thread
+ uint32_t mNextGamepadHandleValue;
+
+ // mChannelParents stores all the GamepadEventChannelParent instances
+ // which may be accessed by both background thread and monitor thread
+ // simultaneously, so we have a mutex to prevent race condition
+ nsTArray<RefPtr<GamepadEventChannelParent>> mChannelParents;
+
+ // This mutex protects mChannelParents from race condition
+ // between background and monitor thread
+ Mutex mMutex MOZ_UNANNOTATED;
+
+ std::map<GamepadHandle, GamepadAdded> mGamepadAdded;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/GamepadPose.cpp b/dom/gamepad/GamepadPose.cpp
new file mode 100644
index 0000000000..e2a5d09efc
--- /dev/null
+++ b/dom/gamepad/GamepadPose.cpp
@@ -0,0 +1,110 @@
+/* -*- 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/. */
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/GamepadPoseBinding.h"
+#include "mozilla/dom/GamepadPose.h"
+
+namespace mozilla::dom {
+
+GamepadPose::GamepadPose(nsISupports* aParent, const GamepadPoseState& aState)
+ : Pose(aParent), mPoseState(aState) {
+ mozilla::HoldJSObjects(this);
+}
+
+GamepadPose::GamepadPose(nsISupports* aParent) : Pose(aParent) {
+ mozilla::HoldJSObjects(this);
+ mPoseState.Clear();
+}
+
+GamepadPose::~GamepadPose() { mozilla::DropJSObjects(this); }
+
+/* virtual */
+JSObject* GamepadPose::WrapObject(JSContext* aJSContext,
+ JS::Handle<JSObject*> aGivenProto) {
+ return GamepadPose_Binding::Wrap(aJSContext, this, aGivenProto);
+}
+
+bool GamepadPose::HasOrientation() const {
+ return bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation);
+}
+
+bool GamepadPose::HasPosition() const {
+ return bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position) ||
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_PositionEmulated);
+}
+
+void GamepadPose::GetPosition(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ const bool valid =
+ mPoseState.isPositionValid &&
+ (bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position) ||
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_PositionEmulated));
+ SetFloat32Array(aJSContext, this, aRetval, mPosition,
+ valid ? mPoseState.position : nullptr, 3, aRv);
+}
+
+void GamepadPose::GetLinearVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ const bool valid =
+ mPoseState.isPositionValid &&
+ (bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position) ||
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_PositionEmulated));
+ SetFloat32Array(aJSContext, this, aRetval, mLinearVelocity,
+ valid ? mPoseState.linearVelocity : nullptr, 3, aRv);
+}
+
+void GamepadPose::GetLinearAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ const bool valid =
+ mPoseState.isPositionValid &&
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_LinearAcceleration);
+ SetFloat32Array(aJSContext, this, aRetval, mLinearAcceleration,
+ valid ? mPoseState.linearAcceleration : nullptr, 3, aRv);
+}
+
+void GamepadPose::GetOrientation(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ const bool valid =
+ mPoseState.isOrientationValid &&
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation);
+ SetFloat32Array(aJSContext, this, aRetval, mOrientation,
+ valid ? mPoseState.orientation : nullptr, 4, aRv);
+}
+
+void GamepadPose::GetAngularVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ const bool valid =
+ mPoseState.isOrientationValid &&
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation);
+ SetFloat32Array(aJSContext, this, aRetval, mAngularVelocity,
+ valid ? mPoseState.angularVelocity : nullptr, 3, aRv);
+}
+
+void GamepadPose::GetAngularAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ const bool valid =
+ mPoseState.isOrientationValid &&
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_AngularAcceleration);
+ SetFloat32Array(aJSContext, this, aRetval, mAngularAcceleration,
+ valid ? mPoseState.angularAcceleration : nullptr, 3, aRv);
+}
+
+void GamepadPose::SetPoseState(const GamepadPoseState& aPose) {
+ mPoseState = aPose;
+}
+
+const GamepadPoseState& GamepadPose::GetPoseState() { return mPoseState; }
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadPose.h b/dom/gamepad/GamepadPose.h
new file mode 100644
index 0000000000..2dabfa1117
--- /dev/null
+++ b/dom/gamepad/GamepadPose.h
@@ -0,0 +1,57 @@
+/* -*- 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_gamepad_GamepadPose_h
+#define mozilla_dom_gamepad_GamepadPose_h
+
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/dom/Pose.h"
+#include "mozilla/dom/GamepadPoseState.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::dom {
+
+class GamepadPose final : public Pose {
+ public:
+ GamepadPose(nsISupports* aParent, const GamepadPoseState& aState);
+ explicit GamepadPose(nsISupports* aParent);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool HasOrientation() const;
+ bool HasPosition() const;
+ virtual void GetPosition(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetLinearVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetLinearAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetOrientation(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetAngularVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetAngularAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ void SetPoseState(const GamepadPoseState& aPose);
+ const GamepadPoseState& GetPoseState();
+
+ private:
+ virtual ~GamepadPose();
+
+ nsCOMPtr<nsISupports> mParent;
+ GamepadPoseState mPoseState;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadPose_h
diff --git a/dom/gamepad/GamepadPoseState.h b/dom/gamepad/GamepadPoseState.h
new file mode 100644
index 0000000000..2272b84c76
--- /dev/null
+++ b/dom/gamepad/GamepadPoseState.h
@@ -0,0 +1,110 @@
+/* -*- 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_gamepad_GamepadPoseState_h_
+#define mozilla_dom_gamepad_GamepadPoseState_h_
+
+namespace mozilla::dom {
+
+enum class GamepadCapabilityFlags : uint16_t {
+ Cap_None = 0,
+ /**
+ * Cap_Position is set if the Gamepad is capable of tracking its position.
+ */
+ Cap_Position = 1 << 1,
+ /**
+ * Cap_Orientation is set if the Gamepad is capable of tracking its
+ * orientation.
+ */
+ Cap_Orientation = 1 << 2,
+ /**
+ * Cap_AngularAcceleration is set if the Gamepad is capable of tracking its
+ * angular acceleration.
+ */
+ Cap_AngularAcceleration = 1 << 3,
+ /**
+ * Cap_LinearAcceleration is set if the Gamepad is capable of tracking its
+ * linear acceleration.
+ */
+ Cap_LinearAcceleration = 1 << 4,
+ /**
+ * Cap_GripSpacePosition is set if the Gamepad has a grip space position.
+ */
+ Cap_GripSpacePosition = 1 << 5,
+ /**
+ * Cap_PositionEmulated is set if the VRDisplay is capable of setting a
+ * emulated position (e.g. neck model) even if still doesn't support 6DOF
+ * tracking.
+ */
+ Cap_PositionEmulated = 1 << 6,
+ /**
+ * Cap_All used for validity checking during IPC serialization
+ */
+ Cap_All = (1 << 7) - 1
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GamepadCapabilityFlags)
+
+struct GamepadPoseState {
+ GamepadCapabilityFlags flags;
+ float orientation[4];
+ float position[3];
+ float angularVelocity[3];
+ float angularAcceleration[3];
+ float linearVelocity[3];
+ float linearAcceleration[3];
+ bool isPositionValid;
+ bool isOrientationValid;
+
+ GamepadPoseState()
+ : flags(GamepadCapabilityFlags::Cap_None),
+ orientation{0, 0, 0, 0},
+ position{0, 0, 0},
+ angularVelocity{0, 0, 0},
+ angularAcceleration{0, 0, 0},
+ linearVelocity{0, 0, 0},
+ linearAcceleration{0, 0, 0},
+ isPositionValid(false),
+ isOrientationValid(false) {}
+
+ bool operator==(const GamepadPoseState& aPose) const {
+ return flags == aPose.flags && orientation[0] == aPose.orientation[0] &&
+ orientation[1] == aPose.orientation[1] &&
+ orientation[2] == aPose.orientation[2] &&
+ orientation[3] == aPose.orientation[3] &&
+ position[0] == aPose.position[0] &&
+ position[1] == aPose.position[1] &&
+ position[2] == aPose.position[2] &&
+ angularVelocity[0] == aPose.angularVelocity[0] &&
+ angularVelocity[1] == aPose.angularVelocity[1] &&
+ angularVelocity[2] == aPose.angularVelocity[2] &&
+ angularAcceleration[0] == aPose.angularAcceleration[0] &&
+ angularAcceleration[1] == aPose.angularAcceleration[1] &&
+ angularAcceleration[2] == aPose.angularAcceleration[2] &&
+ linearVelocity[0] == aPose.linearVelocity[0] &&
+ linearVelocity[1] == aPose.linearVelocity[1] &&
+ linearVelocity[2] == aPose.linearVelocity[2] &&
+ linearAcceleration[0] == aPose.linearAcceleration[0] &&
+ linearAcceleration[1] == aPose.linearAcceleration[1] &&
+ linearAcceleration[2] == aPose.linearAcceleration[2] &&
+ isPositionValid == aPose.isPositionValid &&
+ isOrientationValid == aPose.isOrientationValid;
+ }
+
+ bool operator!=(const GamepadPoseState& aPose) const {
+ return !(*this == aPose);
+ }
+
+ void Clear() {
+ memset(&flags, 0,
+ reinterpret_cast<char*>(&isOrientationValid) +
+ sizeof(isOrientationValid) - reinterpret_cast<char*>(&flags));
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadPoseState_h_
diff --git a/dom/gamepad/GamepadRemapping.cpp b/dom/gamepad/GamepadRemapping.cpp
new file mode 100644
index 0000000000..227d5fbf38
--- /dev/null
+++ b/dom/gamepad/GamepadRemapping.cpp
@@ -0,0 +1,2123 @@
+/* -*- 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"
+#include "mozilla/dom/GamepadPlatformService.h"
+
+#include <vector>
+#include <unordered_map>
+
+namespace mozilla::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
+};
+
+const float BUTTON_THRESHOLD_VALUE = 0.1f;
+
+float NormalizeTouch(long aValue, long aMin, long aMax) {
+ return (2.f * (aValue - aMin) / static_cast<float>(aMax - aMin)) - 1.f;
+}
+
+bool AxisNegativeAsButton(float input) {
+ const float value = (input < -0.5f) ? 1.f : 0.f;
+ return value > BUTTON_THRESHOLD_VALUE;
+}
+
+bool AxisPositiveAsButton(float input) {
+ const float value = (input > 0.5f) ? 1.f : 0.f;
+ return value > BUTTON_THRESHOLD_VALUE;
+}
+
+double AxisToButtonValue(double aValue) {
+ // Mapping axis value range from (-1, +1) to (0, +1).
+ return (aValue + 1.0f) * 0.5f;
+}
+
+void FetchDpadFromAxis(GamepadHandle aHandle, 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(aHandle, BUTTON_INDEX_DPAD_UP, up);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_RIGHT, right);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_DOWN, down);
+ service->NewButtonEvent(aHandle, 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 GamepadMappingType GetMappingType() const override {
+ return GamepadMappingType::_empty;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ if (GetAxisCount() <= aAxis) {
+ NS_WARNING(
+ nsPrintfCString("Axis idx '%d' doesn't support in DefaultRemapper().",
+ aAxis)
+ .get());
+ return;
+ }
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ service->NewAxisMoveEvent(aHandle, aAxis, aValue);
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in DefaultRemapper().", aButton)
+ .get());
+ return;
+ }
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+
+ private:
+ uint32_t numAxes;
+ uint32_t numButtons;
+};
+
+class ADT1Remapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString("Axis idx '%d' doesn't support in ADT1Remapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString("Button idx '%d' doesn't support in ADT1Remapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {12, BUTTON_INDEX_META},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class TwoAxesEightKeysRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return 0; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT - 1;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_LEFT,
+ AxisNegativeAsButton(aValue));
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_RIGHT,
+ AxisPositiveAsButton(aValue));
+ break;
+ case 1:
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_UP,
+ AxisNegativeAsButton(aValue));
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_DOWN,
+ AxisPositiveAsButton(aValue));
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in TwoAxesEightKeysRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in TwoAxesEightKeysRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {0, BUTTON_INDEX_QUATERNARY},
+ {2, BUTTON_INDEX_PRIMARY},
+ {3, BUTTON_INDEX_TERTIARY}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class StadiaControllerRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return STADIA_BUTTON_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in StadiaControllerRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (STADIA_BUTTON_COUNT <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in StadiaControllerRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+
+ private:
+ enum STADIAButtons {
+ STADIA_BUTTON_EXTRA1 = BUTTON_INDEX_COUNT,
+ STADIA_BUTTON_EXTRA2,
+ STADIA_BUTTON_COUNT
+ };
+};
+
+class Playstation3Remapper final : public GamepadRemapper {
+ public:
+ Playstation3Remapper() = default;
+
+ uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ uint32_t GetButtonCount() const override { return BUTTON_INDEX_COUNT; }
+
+ void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in Playstation3Remapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const std::vector<uint32_t> buttonMapping = {BUTTON_INDEX_BACK_SELECT,
+ BUTTON_INDEX_LEFT_THUMBSTICK,
+ BUTTON_INDEX_RIGHT_THUMBSTICK,
+ BUTTON_INDEX_START,
+ BUTTON_INDEX_DPAD_UP,
+ BUTTON_INDEX_DPAD_RIGHT,
+ BUTTON_INDEX_DPAD_DOWN,
+ BUTTON_INDEX_DPAD_LEFT,
+ BUTTON_INDEX_LEFT_TRIGGER,
+ BUTTON_INDEX_RIGHT_TRIGGER,
+ BUTTON_INDEX_LEFT_SHOULDER,
+ BUTTON_INDEX_RIGHT_SHOULDER,
+ BUTTON_INDEX_QUATERNARY,
+ BUTTON_INDEX_SECONDARY,
+ BUTTON_INDEX_PRIMARY,
+ BUTTON_INDEX_TERTIARY,
+ BUTTON_INDEX_META};
+
+ if (buttonMapping.size() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in Playstation3Remapper().",
+ aButton)
+ .get());
+ return;
+ }
+ service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
+ }
+};
+
+class Dualshock4Remapper final : public GamepadRemapper {
+ public:
+ Dualshock4Remapper() {
+ mLastTouches.SetLength(TOUCH_EVENT_COUNT);
+ mLastTouchId.SetLength(TOUCH_EVENT_COUNT);
+ }
+
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return DUALSHOCK_BUTTON_COUNT;
+ }
+
+ virtual uint32_t GetLightIndicatorCount() const override {
+ return LIGHT_INDICATOR_COUNT;
+ }
+
+ virtual void GetLightIndicators(
+ nsTArray<GamepadLightIndicatorType>& aTypes) const override {
+ const uint32_t len = GetLightIndicatorCount();
+ aTypes.SetLength(len);
+ for (uint32_t i = 0; i < len; ++i) {
+ aTypes[i] = GamepadLightIndicatorType::Rgb;
+ }
+ }
+
+ virtual uint32_t GetTouchEventCount() const override {
+ return TOUCH_EVENT_COUNT;
+ }
+
+ virtual void GetLightColorReport(
+ uint8_t aRed, uint8_t aGreen, uint8_t aBlue,
+ std::vector<uint8_t>& aReport) const override {
+ const size_t report_length = 32;
+ aReport.resize(report_length);
+ aReport.assign(report_length, 0);
+
+ aReport[0] = 0x05; // report ID USB only
+ aReport[1] = 0x02; // LED only
+ aReport[6] = aRed;
+ aReport[7] = aGreen;
+ aReport[8] = aBlue;
+ }
+
+ virtual uint32_t GetMaxInputReportLength() const override {
+ return MAX_INPUT_LEN;
+ }
+
+ virtual void ProcessTouchData(GamepadHandle aHandle, void* aInput) override {
+ nsTArray<GamepadTouchState> touches(TOUCH_EVENT_COUNT);
+ touches.SetLength(TOUCH_EVENT_COUNT);
+ uint8_t* rawData = (uint8_t*)aInput;
+
+ const uint32_t kTouchDimensionX = 1920;
+ const uint32_t kTouchDimensionY = 942;
+ bool touch0Pressed = (((rawData[35] & 0xff) >> 7) == 0);
+ bool touch1Pressed = (((rawData[39] & 0xff) >> 7) == 0);
+
+ if ((touch0Pressed && (rawData[35] & 0xff) < mLastTouchId[0]) ||
+ (touch1Pressed && (rawData[39] & 0xff) < mLastTouchId[1])) {
+ mTouchIdBase += 128;
+ }
+
+ if (touch0Pressed) {
+ touches[0].touchId = mTouchIdBase + (rawData[35] & 0x7f);
+ touches[0].surfaceId = 0;
+ touches[0].position[0] = NormalizeTouch(
+ ((rawData[37] & 0xf) << 8) | rawData[36], 0, (kTouchDimensionX - 1));
+ touches[0].position[1] =
+ NormalizeTouch(rawData[38] << 4 | ((rawData[37] & 0xf0) >> 4), 0,
+ (kTouchDimensionY - 1));
+ touches[0].surfaceDimensions[0] = kTouchDimensionX;
+ touches[0].surfaceDimensions[1] = kTouchDimensionY;
+ touches[0].isSurfaceDimensionsValid = true;
+ mLastTouchId[0] = rawData[35] & 0x7f;
+ }
+ if (touch1Pressed) {
+ touches[1].touchId = mTouchIdBase + (rawData[39] & 0x7f);
+ touches[1].surfaceId = 0;
+ touches[1].position[0] =
+ NormalizeTouch((((rawData[41] & 0xf) << 8) | rawData[40]) + 1, 0,
+ (kTouchDimensionX - 1));
+ touches[1].position[1] =
+ NormalizeTouch(rawData[42] << 4 | ((rawData[41] & 0xf0) >> 4), 0,
+ (kTouchDimensionY - 1));
+ touches[1].surfaceDimensions[0] = kTouchDimensionX;
+ touches[1].surfaceDimensions[1] = kTouchDimensionY;
+ touches[1].isSurfaceDimensionsValid = true;
+ mLastTouchId[1] = rawData[39] & 0x7f;
+ }
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ // Avoid to send duplicate untouched events to the gamepad service.
+ if ((mLastTouches[0] != touch0Pressed) || touch0Pressed) {
+ service->NewMultiTouchEvent(aHandle, 0, touches[0]);
+ }
+ if ((mLastTouches[1] != touch1Pressed) || touch1Pressed) {
+ service->NewMultiTouchEvent(aHandle, 1, touches[1]);
+ }
+ mLastTouches[0] = touch0Pressed;
+ mLastTouches[1] = touch1Pressed;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in Dualshock4Remapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, 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() <= aButton) {
+ NS_WARNING(nsPrintfCString(
+ "Button idx '%d' doesn't support in Dualshock4Remapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
+ }
+
+ private:
+ enum Dualshock4Buttons {
+ DUALSHOCK_BUTTON_TOUCHPAD = BUTTON_INDEX_COUNT,
+ DUALSHOCK_BUTTON_COUNT
+ };
+
+ static const uint32_t LIGHT_INDICATOR_COUNT = 1;
+ static const uint32_t TOUCH_EVENT_COUNT = 2;
+ static const uint32_t MAX_INPUT_LEN = 68;
+
+ nsTArray<unsigned long> mLastTouchId;
+ nsTArray<bool> mLastTouches;
+ unsigned long mTouchIdBase = 0;
+};
+
+class Xbox360Remapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 3:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 4:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 5: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in Xbox360Remapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in Xbox360Remapper().", aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {6, BUTTON_INDEX_LEFT_THUMBSTICK}, {7, BUTTON_INDEX_RIGHT_THUMBSTICK},
+ {8, BUTTON_INDEX_START}, {9, BUTTON_INDEX_BACK_SELECT},
+ {10, BUTTON_INDEX_META}, {11, BUTTON_INDEX_DPAD_UP},
+ {12, BUTTON_INDEX_DPAD_DOWN}, {13, BUTTON_INDEX_DPAD_LEFT},
+ {14, BUTTON_INDEX_DPAD_RIGHT}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class XboxOneSRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 3:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 4:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 5: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in XboxOneSRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in XboxOneSRemapper().", aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {6, BUTTON_INDEX_BACK_SELECT},
+ {7, BUTTON_INDEX_START},
+ {8, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {9, BUTTON_INDEX_RIGHT_THUMBSTICK},
+ {10, BUTTON_INDEX_META}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class XboxOneS2016FirmwareRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(nsPrintfCString("Axis idx '%d' doesn't support in "
+ "XboxOneS2016FirmwareRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(nsPrintfCString("Button idx '%d' doesn't support in "
+ "XboxOneS2016FirmwareRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ // kMicrosoftProductXboxOneSWireless2016 controller received a firmware
+ // update in 2019 that changed which field is populated with the meta button
+ // state. In order to cover the old and new cases, we have to check both
+ // fields of {12, 15} buttons.
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {0, BUTTON_INDEX_PRIMARY},
+ {1, BUTTON_INDEX_SECONDARY},
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {11, BUTTON_INDEX_START},
+ {12, BUTTON_INDEX_META},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK},
+ {15, BUTTON_INDEX_META},
+ {16, BUTTON_INDEX_BACK_SELECT}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class XboxOneRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ case 10: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 11: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in XboxOneRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in XboxOneRemapper().", aButton)
+ .get());
+ return;
+ }
+
+ // Accessing {30, 31} buttons looks strange to me
+ // and without an avilable device to help verify it.
+ // It is according to `MapperXboxOneBluetooth()` in
+ // https://cs.chromium.org/chromium/src/device/gamepad/gamepad_standard_mappings_mac.mm
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {0, BUTTON_INDEX_PRIMARY},
+ {1, BUTTON_INDEX_SECONDARY},
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {11, BUTTON_INDEX_START},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK},
+ {30, BUTTON_INDEX_META},
+ {31, BUTTON_INDEX_BACK_SELECT}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class LogitechDInputRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ // The Logitech button (BUTTON_INDEX_META) is not accessible through the
+ // device's D-mode.
+ return BUTTON_INDEX_COUNT - 1;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in LogitechDInputRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in LogitechDInputRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {0, BUTTON_INDEX_TERTIARY},
+ {1, BUTTON_INDEX_PRIMARY},
+ {2, BUTTON_INDEX_SECONDARY}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class SwitchJoyConRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return 2; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ if (GetAxisCount() <= aAxis) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in SwitchJoyConRemapper().", aAxis)
+ .get());
+ return;
+ }
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+};
+
+class SwitchProRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ // The Switch Pro controller has a Capture button that has no equivalent in
+ // the Standard Gamepad.
+ return SWITCHPRO_BUTTON_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ if (GetAxisCount() <= aAxis) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in SwitchProRemapper().", aAxis)
+ .get());
+ return;
+ }
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+
+ private:
+ enum SwitchProButtons {
+ SWITCHPRO_BUTTON_EXTRA = BUTTON_INDEX_COUNT,
+ SWITCHPRO_BUTTON_COUNT
+ };
+};
+
+class NvShieldRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return SHIELD_BUTTON_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in NvShieldRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in NvShieldRemapper().", aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {2, BUTTON_INDEX_META},
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {5, SHIELD_BUTTON_CIRCLE},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {9, BUTTON_INDEX_BACK_SELECT},
+ {11, BUTTON_INDEX_START},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+
+ private:
+ enum ShieldButtons {
+ SHIELD_BUTTON_CIRCLE = BUTTON_INDEX_COUNT,
+ SHIELD_BUTTON_COUNT
+ };
+};
+
+class NvShield2017Remapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return SHIELD2017_BUTTON_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in NvShield2017Remapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in NvShield2017Remapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {2, BUTTON_INDEX_META},
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {5, BUTTON_INDEX_START},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {8, BUTTON_INDEX_BACK_SELECT},
+ {11, SHIELD2017_BUTTON_PLAYPAUSE},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+
+ private:
+ enum Shield2017Buttons {
+ SHIELD2017_BUTTON_PLAYPAUSE = BUTTON_INDEX_COUNT,
+ SHIELD2017_BUTTON_COUNT
+ };
+};
+
+class IBuffaloRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return 2; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT - 1; /* no meta */
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_LEFT,
+ AxisNegativeAsButton(aValue));
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_RIGHT,
+ AxisPositiveAsButton(aValue));
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_UP,
+ AxisNegativeAsButton(aValue));
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_DOWN,
+ AxisPositiveAsButton(aValue));
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in IBuffaloRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in IBuffaloRemapper().", aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {0, BUTTON_INDEX_SECONDARY}, {1, BUTTON_INDEX_PRIMARY},
+ {2, BUTTON_INDEX_QUATERNARY}, {3, BUTTON_INDEX_TERTIARY},
+ {5, BUTTON_INDEX_RIGHT_TRIGGER}, {6, BUTTON_INDEX_BACK_SELECT},
+ {7, BUTTON_INDEX_START}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class XSkillsRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return GAMECUBE_BUTTON_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in XSkillsRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in XSkillsRemapper().", aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {0, BUTTON_INDEX_PRIMARY}, // A
+ {1, BUTTON_INDEX_TERTIARY}, // B
+ {2, BUTTON_INDEX_SECONDARY}, // X
+ {3, BUTTON_INDEX_QUATERNARY}, // Y
+ {4, GAMECUBE_BUTTON_LEFT_TRIGGER_CLICK},
+ {5, GAMECUBE_BUTTON_RIGHT_TRIGGER_CLICK},
+ {6, BUTTON_INDEX_RIGHT_SHOULDER},
+ {7, BUTTON_INDEX_START},
+ {8, BUTTON_INDEX_DPAD_LEFT},
+ {9, BUTTON_INDEX_DPAD_RIGHT},
+ {10, BUTTON_INDEX_DPAD_DOWN},
+ {11, BUTTON_INDEX_DPAD_UP}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+
+ private:
+ enum GamecubeButtons {
+ GAMECUBE_BUTTON_LEFT_TRIGGER_CLICK = BUTTON_INDEX_COUNT,
+ GAMECUBE_BUTTON_RIGHT_TRIGGER_CLICK,
+ GAMECUBE_BUTTON_COUNT
+ };
+};
+
+class BoomN64PsxRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT - 1; // no meta
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in BoomN64PsxRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const std::vector<uint32_t> buttonMapping = {
+ BUTTON_INDEX_QUATERNARY, BUTTON_INDEX_SECONDARY,
+ BUTTON_INDEX_PRIMARY, BUTTON_INDEX_TERTIARY,
+ BUTTON_INDEX_LEFT_TRIGGER, BUTTON_INDEX_RIGHT_TRIGGER,
+ BUTTON_INDEX_LEFT_SHOULDER, BUTTON_INDEX_RIGHT_SHOULDER,
+ BUTTON_INDEX_BACK_SELECT, BUTTON_INDEX_LEFT_THUMBSTICK,
+ BUTTON_INDEX_RIGHT_THUMBSTICK, BUTTON_INDEX_START,
+ BUTTON_INDEX_DPAD_UP, BUTTON_INDEX_DPAD_RIGHT,
+ BUTTON_INDEX_DPAD_DOWN, BUTTON_INDEX_DPAD_LEFT};
+
+ if (buttonMapping.size() <= aButton) {
+ NS_WARNING(nsPrintfCString(
+ "Button idx '%d' doesn't support in BoomN64PsxRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
+ }
+
+ private:
+ enum GamecubeButtons {
+ GAMECUBE_BUTTON_LEFT_TRIGGER_CLICK = BUTTON_INDEX_COUNT,
+ GAMECUBE_BUTTON_RIGHT_TRIGGER_CLICK,
+ GAMECUBE_BUTTON_COUNT
+ };
+};
+
+class StadiaControllerOldFirmwareRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return ANALOG_GAMEPAD_BUTTON_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in AnalogGamepadRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in AnalogGamepadRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {10, BUTTON_INDEX_BACK_SELECT},
+ {11, BUTTON_INDEX_META},
+ {12, BUTTON_INDEX_START},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK},
+ {16, ANALOG_GAMEPAD_BUTTON_EXTRA},
+ {17, ANALOG_GAMEPAD_BUTTON_EXTRA2}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+
+ private:
+ enum AnalogGamepadButtons {
+ ANALOG_GAMEPAD_BUTTON_EXTRA = BUTTON_INDEX_COUNT,
+ ANALOG_GAMEPAD_BUTTON_EXTRA2,
+ ANALOG_GAMEPAD_BUTTON_COUNT
+ };
+};
+
+class RazerServalRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT - 1; /* no meta */
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in RazerServalRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in RazerServalRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {3, BUTTON_INDEX_TERTIARY}, {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER}, {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {10, BUTTON_INDEX_BACK_SELECT}, {11, BUTTON_INDEX_START},
+ {12, BUTTON_INDEX_START}, {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class MogaProRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT - 1; /* no meta */
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 3: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 4: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 5:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in MogaProRemapper().", aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in MogaProRemapper().", aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {3, BUTTON_INDEX_TERTIARY}, {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER}, {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {11, BUTTON_INDEX_START}, {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class OnLiveWirelessRemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 3:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 4:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 5: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 9:
+ FetchDpadFromAxis(aHandle, aValue);
+ break;
+ default:
+ NS_WARNING(
+ nsPrintfCString(
+ "Axis idx '%d' doesn't support in OnLiveWirelessRemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Button idx '%d' doesn't support in OnLiveWirelessRemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {3, BUTTON_INDEX_TERTIARY},
+ {4, BUTTON_INDEX_QUATERNARY},
+ {6, BUTTON_INDEX_LEFT_SHOULDER},
+ {7, BUTTON_INDEX_RIGHT_SHOULDER},
+ {12, BUTTON_INDEX_META},
+ {13, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+class OUYARemapper final : public GamepadRemapper {
+ public:
+ virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
+
+ virtual uint32_t GetButtonCount() const override {
+ return BUTTON_INDEX_COUNT;
+ }
+
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ switch (aAxis) {
+ case 0:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
+ break;
+ case 1:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
+ break;
+ case 2: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ case 3:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
+ break;
+ case 4:
+ service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
+ break;
+ case 5: {
+ const double value = AxisToButtonValue(aValue);
+ service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
+ value > BUTTON_THRESHOLD_VALUE, value);
+ break;
+ }
+ default:
+ NS_WARNING(
+ nsPrintfCString("Axis idx '%d' doesn't support in OUYARemapper().",
+ aAxis)
+ .get());
+ break;
+ }
+ }
+
+ virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
+ bool aPressed) const override {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (GetButtonCount() <= aButton) {
+ NS_WARNING(
+ nsPrintfCString("Button idx '%d' doesn't support in OUYARemapper().",
+ aButton)
+ .get());
+ return;
+ }
+
+ const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
+ {1, BUTTON_INDEX_TERTIARY}, {2, BUTTON_INDEX_QUATERNARY},
+ {3, BUTTON_INDEX_SECONDARY}, {6, BUTTON_INDEX_LEFT_THUMBSTICK},
+ {7, BUTTON_INDEX_RIGHT_THUMBSTICK}, {8, BUTTON_INDEX_DPAD_UP},
+ {9, BUTTON_INDEX_DPAD_DOWN}, {10, BUTTON_INDEX_DPAD_LEFT},
+ {11, BUTTON_INDEX_DPAD_RIGHT}, {15, BUTTON_INDEX_META}};
+
+ auto find = buttonMapping.find(aButton);
+ if (find != buttonMapping.end()) {
+ service->NewButtonEvent(aHandle, find->second, aPressed);
+ } else {
+ service->NewButtonEvent(aHandle, aButton, aPressed);
+ }
+ }
+};
+
+already_AddRefed<GamepadRemapper> GetGamepadRemapper(const uint16_t aVendorId,
+ const uint16_t aProductId,
+ bool& aUsingDefault) {
+ const std::vector<GamepadRemappingData> remappingRules = {
+ {GamepadId::kAsusTekProduct4500, new ADT1Remapper()},
+ {GamepadId::kDragonRiseProduct0011, new TwoAxesEightKeysRemapper()},
+ {GamepadId::kGoogleProduct2c40, new ADT1Remapper()},
+ {GamepadId::kGoogleProduct9400, new StadiaControllerRemapper()},
+ {GamepadId::kLogitechProductc216, new LogitechDInputRemapper()},
+ {GamepadId::kLogitechProductc218, new LogitechDInputRemapper()},
+ {GamepadId::kLogitechProductc219, new LogitechDInputRemapper()},
+ {GamepadId::kMicrosoftProductXbox360Wireless, new Xbox360Remapper()},
+ {GamepadId::kMicrosoftProductXbox360Wireless2, new Xbox360Remapper()},
+ {GamepadId::kMicrosoftProductXboxOneElite2Wireless,
+ new XboxOneRemapper()},
+ {GamepadId::kMicrosoftProductXboxOneSWireless, new XboxOneSRemapper()},
+ {GamepadId::kMicrosoftProductXboxOneSWireless2016,
+ new XboxOneS2016FirmwareRemapper()},
+ {GamepadId::kMicrosoftProductXboxAdaptiveWireless, new XboxOneRemapper()},
+ {GamepadId::kNintendoProduct2006, new SwitchJoyConRemapper()},
+ {GamepadId::kNintendoProduct2007, new SwitchJoyConRemapper()},
+ {GamepadId::kNintendoProduct2009, new SwitchProRemapper()},
+ {GamepadId::kNintendoProduct200e, new SwitchProRemapper()},
+ {GamepadId::kNvidiaProduct7210, new NvShieldRemapper()},
+ {GamepadId::kNvidiaProduct7214, new NvShield2017Remapper()},
+ {GamepadId::kPadixProduct2060, new IBuffaloRemapper()},
+ {GamepadId::kPlayComProduct0005, new XSkillsRemapper()},
+ {GamepadId::kPrototypeVendorProduct0667, new BoomN64PsxRemapper()},
+ {GamepadId::kPrototypeVendorProduct9401,
+ new StadiaControllerOldFirmwareRemapper()},
+ {GamepadId::kRazer1532Product0900, new RazerServalRemapper()},
+ {GamepadId::kSonyProduct0268, new Playstation3Remapper()},
+ {GamepadId::kSonyProduct05c4, new Dualshock4Remapper()},
+ {GamepadId::kSonyProduct09cc, new Dualshock4Remapper()},
+ {GamepadId::kSonyProduct0ba0, new Dualshock4Remapper()},
+ {GamepadId::kVendor20d6Product6271, new MogaProRemapper()},
+ {GamepadId::kVendor2378Product1008, new OnLiveWirelessRemapper()},
+ {GamepadId::kVendor2378Product100a, new OnLiveWirelessRemapper()},
+ {GamepadId::kVendor2836Product0001, new OUYARemapper()}};
+ const GamepadId id = static_cast<GamepadId>((aVendorId << 16) | aProductId);
+
+ for (uint32_t i = 0; i < remappingRules.size(); ++i) {
+ if (id == remappingRules[i].id) {
+ aUsingDefault = false;
+ return do_AddRef(remappingRules[i].remapping.get());
+ }
+ }
+
+ RefPtr<GamepadRemapper> defaultRemapper = new DefaultRemapper();
+ aUsingDefault = true;
+ return do_AddRef(defaultRemapper.get());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadRemapping.h b/dom/gamepad/GamepadRemapping.h
new file mode 100644
index 0000000000..0d7eea5182
--- /dev/null
+++ b/dom/gamepad/GamepadRemapping.h
@@ -0,0 +1,140 @@
+/* -*- 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_
+
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadLightIndicator.h"
+#include "mozilla/dom/GamepadPoseState.h"
+#include "mozilla/dom/GamepadTouchState.h"
+
+namespace mozilla::dom {
+
+// GamepadId is (vendorId << 16) | productId)
+enum class GamepadId : uint32_t {
+ // Nexus Player Controller
+ kAsusTekProduct4500 = 0x0b054500,
+ // 2Axes 8Keys Game Pad
+ kDragonRiseProduct0011 = 0x00790011,
+ // ADT-1 Controller
+ kGoogleProduct2c40 = 0x18d12c40,
+ // Stadia Controller
+ kGoogleProduct9400 = 0x18d19400,
+ // Logitech F310, D-mode
+ kLogitechProductc216 = 0x046dc216,
+ // Logitech F510, D-mode
+ kLogitechProductc218 = 0x046dc218,
+ // Logitech F710, D-mode
+ kLogitechProductc219 = 0x046dc219,
+ // Microsoft Xbox 360
+ kMicrosoftProductXbox360 = 0x045e028e,
+ // Microsoft Xbox 360 Wireless
+ kMicrosoftProductXbox360Wireless = 0x045e028f,
+ // Microsoft Xbox 360 Wireless
+ kMicrosoftProductXbox360Wireless2 = 0x045e0719,
+ // Microsoft Xbox One 2013
+ kMicrosoftProductXbox2013 = 0x045e02d1,
+ // Microsoft Xbox One (2015 FW)
+ kMicrosoftProductXbox2015 = 0x045e02dd,
+ // Microsoft Xbox One S
+ kMicrosoftProductXboxOneS = 0x045e02ea,
+ // Microsoft Xbox One S Wireless
+ kMicrosoftProductXboxOneSWireless = 0x045e02e0,
+ // Microsoft Xbox One Elite
+ kMicrosoftProductXboxOneElite = 0x045e02e3,
+ // Microsoft Xbox One Elite 2
+ kMicrosoftProductXboxOneElite2 = 0x045e0b00,
+ // Microsoft Xbox One Elite 2 Wireless
+ kMicrosoftProductXboxOneElite2Wireless = 0x045e0b05,
+ // Xbox One S Wireless (2016 FW)
+ kMicrosoftProductXboxOneSWireless2016 = 0x045e02fd,
+ // Microsoft Xbox Adaptive
+ kMicrosoftProductXboxAdaptive = 0x045e0b0a,
+ // Microsoft Xbox Adaptive Wireless
+ kMicrosoftProductXboxAdaptiveWireless = 0x045e0b0c,
+ // Switch Joy-Con L
+ kNintendoProduct2006 = 0x057e2006,
+ // Switch Joy-Con R
+ kNintendoProduct2007 = 0x057e2007,
+ // Switch Pro Controller
+ kNintendoProduct2009 = 0x057e2009,
+ // Switch Charging Grip
+ kNintendoProduct200e = 0x057e200e,
+ // Nvidia Shield gamepad (2015)
+ kNvidiaProduct7210 = 0x09557210,
+ // Nvidia Shield gamepad (2017)
+ kNvidiaProduct7214 = 0x09557214,
+ // iBuffalo Classic
+ kPadixProduct2060 = 0x05832060,
+ // XSkills Gamecube USB adapter
+ kPlayComProduct0005 = 0x0b430005,
+ // boom PSX+N64 USB Converter
+ kPrototypeVendorProduct0667 = 0x66660667,
+ // Analog game controller
+ kPrototypeVendorProduct9401 = 0x66669401,
+ // Razer Serval Controller
+ kRazer1532Product0900 = 0x15320900,
+ // Playstation 3 Controller
+ kSonyProduct0268 = 0x054c0268,
+ // Playstation Dualshock 4
+ kSonyProduct05c4 = 0x054c05c4,
+ // Dualshock 4 (PS4 Slim)
+ kSonyProduct09cc = 0x054c09cc,
+ // Dualshock 4 USB receiver
+ kSonyProduct0ba0 = 0x054c0ba0,
+ // Moga Pro Controller (HID mode)
+ kVendor20d6Product6271 = 0x20d66271,
+ // OnLive Controller (Bluetooth)
+ kVendor2378Product1008 = 0x23781008,
+ // OnLive Controller (Wired)
+ kVendor2378Product100a = 0x2378100a,
+ // OUYA Controller
+ kVendor2836Product0001 = 0x28360001,
+};
+
+class GamepadRemapper {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadRemapper)
+
+ public:
+ virtual uint32_t GetAxisCount() const = 0;
+ virtual uint32_t GetButtonCount() const = 0;
+ virtual uint32_t GetLightIndicatorCount() const { return 0; }
+ virtual void GetLightIndicators(
+ nsTArray<GamepadLightIndicatorType>& aTypes) const {}
+ virtual uint32_t GetTouchEventCount() const { return 0; }
+ virtual void GetLightColorReport(uint8_t aRed, uint8_t aGreen, uint8_t aBlue,
+ std::vector<uint8_t>& aReport) const {}
+ virtual uint32_t GetMaxInputReportLength() const { return 0; }
+
+ virtual void SetAxisCount(uint32_t aButtonCount) {}
+ virtual void SetButtonCount(uint32_t aButtonCount) {}
+ virtual GamepadMappingType GetMappingType() const {
+ return GamepadMappingType::Standard;
+ }
+ virtual void ProcessTouchData(GamepadHandle aHandle, void* aInput) {}
+ virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
+ double aValue) const = 0;
+ virtual void RemapButtonEvent(GamepadHandle aHandle, 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,
+ bool& aUsingDefault);
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_GamepadRemapping_h_
diff --git a/dom/gamepad/GamepadServiceTest.cpp b/dom/gamepad/GamepadServiceTest.cpp
new file mode 100644
index 0000000000..253a7094b9
--- /dev/null
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -0,0 +1,392 @@
+/* -*- 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/. */
+
+#include "GamepadServiceTest.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/dom/GamepadServiceTestBinding.h"
+#include "mozilla/dom/GamepadTestChannelChild.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+namespace mozilla::dom {
+
+/*
+ * Implementation of the test service. This is just to provide a simple binding
+ * of the GamepadService to JavaScript via WebIDL so that we can write
+ * Mochitests that add and remove fake gamepads, avoiding the platform-specific
+ * backends.
+ */
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(GamepadServiceTest, DOMEventTargetHelper,
+ mWindow)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GamepadServiceTest)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(GamepadServiceTest, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(GamepadServiceTest, DOMEventTargetHelper)
+
+// static
+already_AddRefed<GamepadServiceTest> GamepadServiceTest::CreateTestService(
+ nsPIDOMWindowInner* aWindow) {
+ MOZ_ASSERT(aWindow);
+ RefPtr<GamepadServiceTest> service = new GamepadServiceTest(aWindow);
+ service->InitPBackgroundActor();
+ return service.forget();
+}
+
+void GamepadServiceTest::Shutdown() {
+ MOZ_ASSERT(!mShuttingDown);
+ mShuttingDown = true;
+ DestroyPBackgroundActor();
+ mWindow = nullptr;
+}
+
+GamepadServiceTest::GamepadServiceTest(nsPIDOMWindowInner* aWindow)
+ : mService(GamepadManager::GetService()),
+ mWindow(aWindow),
+ mEventNumber(0),
+ mShuttingDown(false),
+ mChild(nullptr) {}
+
+GamepadServiceTest::~GamepadServiceTest() {
+ MOZ_ASSERT(mPromiseList.IsEmpty());
+}
+
+void GamepadServiceTest::InitPBackgroundActor() {
+ MOZ_ASSERT(!mChild);
+
+ ::mozilla::ipc::PBackgroundChild* actor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actor)) {
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+ }
+
+ mChild = GamepadTestChannelChild::Create(this);
+ PGamepadTestChannelChild* initedChild =
+ actor->SendPGamepadTestChannelConstructor(mChild.get());
+ if (NS_WARN_IF(!initedChild)) {
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+ }
+}
+
+void GamepadServiceTest::ReplyGamepadHandle(uint32_t aPromiseId,
+ const GamepadHandle& aHandle) {
+ uint32_t handleSlot = AddGamepadHandle(aHandle);
+
+ RefPtr<Promise> p;
+ if (!mPromiseList.Get(aPromiseId, getter_AddRefs(p))) {
+ MOZ_CRASH("We should always have a promise.");
+ }
+
+ p->MaybeResolve(handleSlot);
+ mPromiseList.Remove(aPromiseId);
+}
+
+void GamepadServiceTest::DestroyPBackgroundActor() {
+ MOZ_ASSERT(mChild);
+ PGamepadTestChannelChild::Send__delete__(mChild);
+ mChild = nullptr;
+}
+
+already_AddRefed<Promise> GamepadServiceTest::AddGamepad(
+ const nsAString& aID, GamepadMappingType aMapping, GamepadHand aHand,
+ uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aNumHaptics,
+ uint32_t aNumLightIndicator, uint32_t aNumTouchEvents, ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ // The values here are ignored, the value just can't be zero to avoid an
+ // assertion
+ GamepadHandle gamepadHandle{1, GamepadHandleKind::GamepadPlatformManager};
+
+ // Only VR controllers has displayID, we give 0 to the general gamepads.
+ GamepadAdded a(nsString(aID), aMapping, aHand, 0, aNumButtons, aNumAxes,
+ aNumHaptics, aNumLightIndicator, aNumTouchEvents);
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ uint32_t id = ++mEventNumber;
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+
+ mChild->SendGamepadTestEvent(id, e);
+
+ return p.forget();
+}
+
+already_AddRefed<Promise> GamepadServiceTest::RemoveGamepad(
+ uint32_t aHandleSlot, ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ GamepadHandle gamepadHandle = GetHandleInSlot(aHandleSlot);
+
+ GamepadRemoved a;
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ uint32_t id = ++mEventNumber;
+
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+
+ mChild->SendGamepadTestEvent(id, e);
+ return p.forget();
+}
+
+already_AddRefed<Promise> GamepadServiceTest::NewButtonEvent(
+ uint32_t aHandleSlot, uint32_t aButton, bool aPressed, bool aTouched,
+ ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ GamepadHandle gamepadHandle = GetHandleInSlot(aHandleSlot);
+
+ GamepadButtonInformation a(aButton, aPressed ? 1.0 : 0, aPressed, aTouched);
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ uint32_t id = ++mEventNumber;
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+ mChild->SendGamepadTestEvent(id, e);
+ return p.forget();
+}
+
+already_AddRefed<Promise> GamepadServiceTest::NewButtonValueEvent(
+ uint32_t aHandleSlot, uint32_t aButton, bool aPressed, bool aTouched,
+ double aValue, ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ GamepadHandle gamepadHandle = GetHandleInSlot(aHandleSlot);
+
+ GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ uint32_t id = ++mEventNumber;
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+ mChild->SendGamepadTestEvent(id, e);
+ return p.forget();
+}
+
+already_AddRefed<Promise> GamepadServiceTest::NewAxisMoveEvent(
+ uint32_t aHandleSlot, uint32_t aAxis, double aValue, ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ GamepadHandle gamepadHandle = GetHandleInSlot(aHandleSlot);
+
+ GamepadAxisInformation a(aAxis, aValue);
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ uint32_t id = ++mEventNumber;
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+ mChild->SendGamepadTestEvent(id, e);
+ return p.forget();
+}
+
+already_AddRefed<Promise> GamepadServiceTest::NewPoseMove(
+ uint32_t aHandleSlot, const Nullable<Float32Array>& aOrient,
+ const Nullable<Float32Array>& aPos,
+ const Nullable<Float32Array>& aAngVelocity,
+ const Nullable<Float32Array>& aAngAcceleration,
+ const Nullable<Float32Array>& aLinVelocity,
+ const Nullable<Float32Array>& aLinAcceleration, ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ GamepadHandle gamepadHandle = GetHandleInSlot(aHandleSlot);
+
+ GamepadPoseState poseState;
+ poseState.flags = GamepadCapabilityFlags::Cap_Orientation |
+ GamepadCapabilityFlags::Cap_Position |
+ GamepadCapabilityFlags::Cap_AngularAcceleration |
+ GamepadCapabilityFlags::Cap_LinearAcceleration;
+ if (!aOrient.IsNull()) {
+ const Float32Array& value = aOrient.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 4);
+ poseState.orientation[0] = value.Data()[0];
+ poseState.orientation[1] = value.Data()[1];
+ poseState.orientation[2] = value.Data()[2];
+ poseState.orientation[3] = value.Data()[3];
+ poseState.isOrientationValid = true;
+ }
+ if (!aPos.IsNull()) {
+ const Float32Array& value = aPos.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 3);
+ poseState.position[0] = value.Data()[0];
+ poseState.position[1] = value.Data()[1];
+ poseState.position[2] = value.Data()[2];
+ poseState.isPositionValid = true;
+ }
+ if (!aAngVelocity.IsNull()) {
+ const Float32Array& value = aAngVelocity.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 3);
+ poseState.angularVelocity[0] = value.Data()[0];
+ poseState.angularVelocity[1] = value.Data()[1];
+ poseState.angularVelocity[2] = value.Data()[2];
+ }
+ if (!aAngAcceleration.IsNull()) {
+ const Float32Array& value = aAngAcceleration.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 3);
+ poseState.angularAcceleration[0] = value.Data()[0];
+ poseState.angularAcceleration[1] = value.Data()[1];
+ poseState.angularAcceleration[2] = value.Data()[2];
+ }
+ if (!aLinVelocity.IsNull()) {
+ const Float32Array& value = aLinVelocity.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 3);
+ poseState.linearVelocity[0] = value.Data()[0];
+ poseState.linearVelocity[1] = value.Data()[1];
+ poseState.linearVelocity[2] = value.Data()[2];
+ }
+ if (!aLinAcceleration.IsNull()) {
+ const Float32Array& value = aLinAcceleration.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 3);
+ poseState.linearAcceleration[0] = value.Data()[0];
+ poseState.linearAcceleration[1] = value.Data()[1];
+ poseState.linearAcceleration[2] = value.Data()[2];
+ }
+
+ GamepadPoseInformation a(poseState);
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ uint32_t id = ++mEventNumber;
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+ mChild->SendGamepadTestEvent(id, e);
+ return p.forget();
+}
+
+already_AddRefed<Promise> GamepadServiceTest::NewTouch(
+ uint32_t aHandleSlot, uint32_t aTouchArrayIndex, uint32_t aTouchId,
+ uint8_t aSurfaceId, const Float32Array& aPos,
+ const Nullable<Float32Array>& aSurfDim, ErrorResult& aRv) {
+ if (mShuttingDown) {
+ aRv.ThrowInvalidStateError("Shutting down");
+ return nullptr;
+ }
+
+ GamepadHandle gamepadHandle = GetHandleInSlot(aHandleSlot);
+
+ GamepadTouchState touchState;
+ touchState.touchId = aTouchId;
+ touchState.surfaceId = aSurfaceId;
+ const Float32Array& value = aPos;
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 2);
+ touchState.position[0] = value.Data()[0];
+ touchState.position[1] = value.Data()[1];
+
+ if (!aSurfDim.IsNull()) {
+ const Float32Array& value = aSurfDim.Value();
+ value.ComputeState();
+ MOZ_ASSERT(value.Length() == 2);
+ touchState.surfaceDimensions[0] = value.Data()[0];
+ touchState.surfaceDimensions[1] = value.Data()[1];
+ touchState.isSurfaceDimensionsValid = true;
+ }
+
+ GamepadTouchInformation a(aTouchArrayIndex, touchState);
+ GamepadChangeEventBody body(a);
+ GamepadChangeEvent e(gamepadHandle, body);
+
+ uint32_t id = ++mEventNumber;
+ RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mPromiseList.Contains(id));
+ mPromiseList.InsertOrUpdate(id, RefPtr{p});
+ mChild->SendGamepadTestEvent(id, e);
+ return p.forget();
+}
+
+JSObject* GamepadServiceTest::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return GamepadServiceTest_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+uint32_t GamepadServiceTest::AddGamepadHandle(GamepadHandle aHandle) {
+ uint32_t handleSlot = mGamepadHandles.Length();
+ mGamepadHandles.AppendElement(aHandle);
+ return handleSlot;
+}
+
+void GamepadServiceTest::RemoveGamepadHandle(uint32_t aHandleSlot) {
+ MOZ_ASSERT(aHandleSlot < mGamepadHandles.Length());
+ return mGamepadHandles.RemoveElementAt(aHandleSlot);
+}
+
+GamepadHandle GamepadServiceTest::GetHandleInSlot(uint32_t aHandleSlot) const {
+ MOZ_ASSERT(aHandleSlot < mGamepadHandles.Length());
+ return mGamepadHandles.ElementAt(aHandleSlot);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadServiceTest.h b/dom/gamepad/GamepadServiceTest.h
new file mode 100644
index 0000000000..3b88ff178a
--- /dev/null
+++ b/dom/gamepad/GamepadServiceTest.h
@@ -0,0 +1,115 @@
+/* -*- 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_GamepadServiceTest_h_
+#define mozilla_dom_GamepadServiceTest_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla::dom {
+
+class GamepadChangeEvent;
+class GamepadManager;
+class GamepadTestChannelChild;
+class Promise;
+
+// Service for testing purposes
+class GamepadServiceTest final : public DOMEventTargetHelper,
+ public SupportsWeakPtr {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GamepadServiceTest,
+ DOMEventTargetHelper)
+
+ GamepadMappingType NoMapping() const { return GamepadMappingType::_empty; }
+ GamepadMappingType StandardMapping() const {
+ return GamepadMappingType::Standard;
+ }
+ GamepadHand NoHand() const { return GamepadHand::_empty; }
+ GamepadHand LeftHand() const { return GamepadHand::Left; }
+ GamepadHand RightHand() const { return GamepadHand::Right; }
+
+ // IPC receiver
+ void ReplyGamepadHandle(uint32_t aPromiseId, const GamepadHandle& aHandle);
+
+ // Methods from GamepadServiceTest.webidl
+ already_AddRefed<Promise> AddGamepad(
+ const nsAString& aID, GamepadMappingType aMapping, GamepadHand aHand,
+ uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aNumHaptics,
+ uint32_t aNumLightIndicator, uint32_t aNumTouchEvents, ErrorResult& aRv);
+
+ already_AddRefed<Promise> RemoveGamepad(uint32_t aHandleSlot,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> NewButtonEvent(uint32_t aHandleSlot,
+ uint32_t aButton, bool aPressed,
+ bool aTouched, ErrorResult& aRv);
+
+ already_AddRefed<Promise> NewButtonValueEvent(uint32_t aHandleSlot,
+ uint32_t aButton, bool aPressed,
+ bool aTouched, double aValue,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> NewAxisMoveEvent(uint32_t aHandleSlot,
+ uint32_t aAxis, double aValue,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> NewPoseMove(
+ uint32_t aHandleSlot, const Nullable<Float32Array>& aOrient,
+ const Nullable<Float32Array>& aPos,
+ const Nullable<Float32Array>& aAngVelocity,
+ const Nullable<Float32Array>& aAngAcceleration,
+ const Nullable<Float32Array>& aLinVelocity,
+ const Nullable<Float32Array>& aLinAcceleration, ErrorResult& aRv);
+
+ already_AddRefed<Promise> NewTouch(uint32_t aHandleSlot,
+ uint32_t aTouchArrayIndex,
+ uint32_t aTouchId, uint8_t aSurfaceId,
+ const Float32Array& aPos,
+ const Nullable<Float32Array>& aSurfDim,
+ ErrorResult& aRv);
+
+ void Shutdown();
+
+ static already_AddRefed<GamepadServiceTest> CreateTestService(
+ nsPIDOMWindowInner* aWindow);
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ // Hold a reference to the gamepad service so we don't have to worry about
+ // execution order in tests.
+ RefPtr<GamepadManager> mService;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ uint32_t mEventNumber;
+ bool mShuttingDown;
+
+ // IPDL Channel for us to send test events to GamepadPlatformService, it
+ // will only be used in this singleton class and deleted during the IPDL
+ // shutdown chain
+ RefPtr<GamepadTestChannelChild> mChild;
+ nsTArray<GamepadHandle> mGamepadHandles;
+
+ nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mPromiseList;
+
+ explicit GamepadServiceTest(nsPIDOMWindowInner* aWindow);
+ ~GamepadServiceTest();
+ void InitPBackgroundActor();
+ void DestroyPBackgroundActor();
+
+ uint32_t AddGamepadHandle(GamepadHandle aHandle);
+ void RemoveGamepadHandle(uint32_t aHandleSlot);
+ GamepadHandle GetHandleInSlot(uint32_t aHandleSlot) const;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/GamepadTouch.cpp b/dom/gamepad/GamepadTouch.cpp
new file mode 100644
index 0000000000..2fdef5b12c
--- /dev/null
+++ b/dom/gamepad/GamepadTouch.cpp
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/GamepadTouch.h"
+
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(GamepadTouch, (mParent),
+ (mPosition,
+ mSurfaceDimensions))
+
+GamepadTouch::GamepadTouch(nsISupports* aParent)
+ : mParent(aParent), mPosition(nullptr), mSurfaceDimensions(nullptr) {
+ mozilla::HoldJSObjects(this);
+ mTouchState = GamepadTouchState();
+}
+
+GamepadTouch::~GamepadTouch() { mozilla::DropJSObjects(this); }
+
+/* virtual */ JSObject* GamepadTouch::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return GamepadTouch_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GamepadTouch::GetPosition(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ mPosition = Float32Array::Create(aCx, this, 2, mTouchState.position);
+ if (!mPosition) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aRetval.set(mPosition);
+}
+
+void GamepadTouch::GetSurfaceDimensions(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ mSurfaceDimensions = Uint32Array::Create(aCx, this, 2,
+ mTouchState.isSurfaceDimensionsValid
+ ? mTouchState.surfaceDimensions
+ : nullptr);
+
+ if (!mSurfaceDimensions) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aRetval.set(mSurfaceDimensions);
+}
+
+void GamepadTouch::SetTouchState(const GamepadTouchState& aTouch) {
+ mTouchState = aTouch;
+}
+
+void GamepadTouch::Set(const GamepadTouch* aOther) {
+ MOZ_ASSERT(aOther);
+ mTouchState = aOther->mTouchState;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/GamepadTouch.h b/dom/gamepad/GamepadTouch.h
new file mode 100644
index 0000000000..591905ed73
--- /dev/null
+++ b/dom/gamepad/GamepadTouch.h
@@ -0,0 +1,47 @@
+/* -*- 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_gamepad_GamepadTouch_h
+#define mozilla_dom_gamepad_GamepadTouch_h
+
+#include "mozilla/dom/GamepadTouchBinding.h"
+#include "mozilla/dom/GamepadTouchState.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class GamepadTouch final : public nsWrapperCache {
+ public:
+ explicit GamepadTouch(nsISupports* aParent);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(GamepadTouch)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(GamepadTouch)
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ uint32_t TouchId() const { return mTouchState.touchId; }
+ uint32_t SurfaceId() const { return mTouchState.surfaceId; }
+ void GetPosition(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void GetSurfaceDimensions(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void SetTouchState(const GamepadTouchState& aTouch);
+ void Set(const GamepadTouch* aOther);
+
+ private:
+ virtual ~GamepadTouch();
+
+ nsCOMPtr<nsISupports> mParent;
+ JS::Heap<JSObject*> mPosition;
+ JS::Heap<JSObject*> mSurfaceDimensions;
+ GamepadTouchState mTouchState;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadTouch_h
diff --git a/dom/gamepad/GamepadTouchState.h b/dom/gamepad/GamepadTouchState.h
new file mode 100644
index 0000000000..0af81a6451
--- /dev/null
+++ b/dom/gamepad/GamepadTouchState.h
@@ -0,0 +1,42 @@
+/* -*- 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_gamepad_GamepadTouchState_h_
+#define mozilla_dom_gamepad_GamepadTouchState_h_
+
+namespace mozilla::dom {
+
+struct GamepadTouchState {
+ uint32_t touchId;
+ uint32_t surfaceId;
+ float position[2];
+ uint32_t surfaceDimensions[2];
+ bool isSurfaceDimensionsValid;
+
+ GamepadTouchState()
+ : touchId(0),
+ surfaceId(0),
+ position{0, 0},
+ surfaceDimensions{0, 0},
+ isSurfaceDimensionsValid(false) {}
+
+ bool operator==(const GamepadTouchState& aTouch) const {
+ return touchId == aTouch.touchId && surfaceId == aTouch.surfaceId &&
+ position[0] == aTouch.position[0] &&
+ position[1] == aTouch.position[1] &&
+ surfaceDimensions[0] == aTouch.surfaceDimensions[0] &&
+ surfaceDimensions[1] == aTouch.surfaceDimensions[1] &&
+ isSurfaceDimensionsValid == aTouch.isSurfaceDimensionsValid;
+ }
+
+ bool operator!=(const GamepadTouchState& aTouch) const {
+ return !(*this == aTouch);
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_gamepad_GamepadTouchState_h_
diff --git a/dom/gamepad/android/AndroidGamepad.cpp b/dom/gamepad/android/AndroidGamepad.cpp
new file mode 100644
index 0000000000..ae8f927f35
--- /dev/null
+++ b/dom/gamepad/android/AndroidGamepad.cpp
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+// TODO: Bug 680289, implement gamepad haptics for Android.
+// TODO: Bug 1523355, implement gamepad lighindicator and touch for
+// Android.
+
+#include "mozilla/java/AndroidGamepadManagerNatives.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "nsThreadUtils.h"
+
+using mozilla::dom::GamepadHandle;
+
+namespace mozilla {
+namespace dom {
+
+class AndroidGamepadManager final
+ : public java::AndroidGamepadManager::Natives<AndroidGamepadManager> {
+ AndroidGamepadManager() = delete;
+
+ public:
+ static jni::ByteArray::LocalRef NativeAddGamepad() {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_RELEASE_ASSERT(service);
+
+ const GamepadHandle gamepadHandle = service->AddGamepad(
+ "android", GamepadMappingType::Standard, GamepadHand::_empty,
+ kStandardGamepadButtons, kStandardGamepadAxes, 0, 0, 0);
+
+ return mozilla::jni::ByteArray::New(
+ reinterpret_cast<const int8_t*>(&gamepadHandle), sizeof(gamepadHandle));
+ }
+
+ static void NativeRemoveGamepad(jni::ByteArray::Param aGamepadHandleBytes) {
+ GamepadHandle handle = JNIByteArrayToGamepadHandle(aGamepadHandleBytes);
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->RemoveGamepad(handle);
+ }
+
+ static void OnButtonChange(jni::ByteArray::Param aGamepadHandleBytes,
+ int32_t aButton, bool aPressed, float aValue) {
+ GamepadHandle handle = JNIByteArrayToGamepadHandle(aGamepadHandleBytes);
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->NewButtonEvent(handle, aButton, aPressed, aValue);
+ }
+
+ static void OnAxisChange(jni::ByteArray::Param aGamepadHandleBytes,
+ jni::BooleanArray::Param aValid,
+ jni::FloatArray::Param aValues) {
+ GamepadHandle handle = JNIByteArrayToGamepadHandle(aGamepadHandleBytes);
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const auto& valid = aValid->GetElements();
+ const auto& values = aValues->GetElements();
+ MOZ_ASSERT(valid.Length() == values.Length());
+
+ for (size_t i = 0; i < values.Length(); i++) {
+ if (valid[i]) {
+ service->NewAxisMoveEvent(handle, i, values[i]);
+ }
+ }
+ }
+
+ private:
+ static GamepadHandle JNIByteArrayToGamepadHandle(
+ jni::ByteArray::Param aGamepadHandleBytes) {
+ MOZ_ASSERT(aGamepadHandleBytes->Length() == sizeof(GamepadHandle));
+
+ GamepadHandle gamepadHandle;
+ aGamepadHandleBytes->CopyTo(reinterpret_cast<int8_t*>(&gamepadHandle),
+ sizeof(gamepadHandle));
+
+ return gamepadHandle;
+ }
+};
+
+void StartGamepadMonitoring() {
+ AndroidGamepadManager::Init();
+ java::AndroidGamepadManager::Start(
+ java::GeckoAppShell::GetApplicationContext());
+}
+
+void StopGamepadMonitoring() {
+ java::AndroidGamepadManager::Stop(
+ java::GeckoAppShell::GetApplicationContext());
+}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue) {
+ NS_WARNING("Android doesn't support gamepad light indicator.");
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp
new file mode 100644
index 0000000000..9647290652
--- /dev/null
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -0,0 +1,650 @@
+/* -*- 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/. */
+
+// mostly derived from the Allegro source code at:
+// http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
+
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/dom/GamepadRemapping.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Tainting.h"
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDBase.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#include <stdio.h>
+#include <vector>
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::dom;
+class DarwinGamepadService;
+
+DarwinGamepadService* gService = nullptr;
+
+struct Button {
+ int id;
+ bool analog;
+ IOHIDElementRef element;
+ CFIndex min;
+ CFIndex max;
+ bool pressed;
+
+ Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax)
+ : id(aId),
+ analog((aMax - aMin) > 1),
+ element(aElement),
+ min(aMin),
+ max(aMax),
+ pressed(false) {}
+};
+
+struct Axis {
+ int id;
+ IOHIDElementRef element;
+ uint32_t usagePage;
+ uint32_t usage;
+ CFIndex min;
+ CFIndex max;
+ double value;
+};
+
+// These values can be found in the USB HID Usage Tables:
+// http://www.usb.org/developers/hidpage
+const unsigned kDesktopUsagePage = 0x01;
+const unsigned kSimUsagePage = 0x02;
+const unsigned kAcceleratorUsage = 0xC4;
+const unsigned kBrakeUsage = 0xC5;
+const unsigned kJoystickUsage = 0x04;
+const unsigned kGamepadUsage = 0x05;
+const unsigned kAxisUsageMin = 0x30;
+const unsigned kAxisUsageMax = 0x35;
+const unsigned kDpadUsage = 0x39;
+const unsigned kButtonUsagePage = 0x09;
+const unsigned kConsumerPage = 0x0C;
+const unsigned kHomeUsage = 0x223;
+const unsigned kBackUsage = 0x224;
+
+// We poll it periodically,
+// 50ms is arbitrarily chosen.
+const uint32_t kDarwinGamepadPollInterval = 50;
+
+struct GamepadInputReportContext {
+ DarwinGamepadService* service;
+ size_t gamepadSlot;
+};
+
+class Gamepad {
+ private:
+ IOHIDDeviceRef mDevice;
+ nsTArray<Button> buttons;
+ nsTArray<Axis> axes;
+
+ public:
+ Gamepad() : mDevice(nullptr) {}
+
+ bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
+ bool empty() const { return mDevice == nullptr; }
+ void clear() {
+ mDevice = nullptr;
+ buttons.Clear();
+ axes.Clear();
+ mHandle = GamepadHandle{};
+ }
+ void init(IOHIDDeviceRef device, bool defaultRemapper);
+ void ReportChanged(uint8_t* report, CFIndex report_length);
+ size_t WriteOutputReport(const std::vector<uint8_t>& aReport) const;
+
+ size_t numButtons() { return buttons.Length(); }
+ size_t numAxes() { return axes.Length(); }
+
+ Button* lookupButton(IOHIDElementRef element) {
+ for (unsigned i = 0; i < buttons.Length(); i++) {
+ if (buttons[i].element == element) return &buttons[i];
+ }
+ return nullptr;
+ }
+
+ Axis* lookupAxis(IOHIDElementRef element) {
+ for (unsigned i = 0; i < axes.Length(); i++) {
+ if (axes[i].element == element) return &axes[i];
+ }
+ return nullptr;
+ }
+
+ GamepadHandle mHandle;
+ RefPtr<GamepadRemapper> mRemapper;
+ nsTArray<uint8_t> mInputReport;
+ UniquePtr<GamepadInputReportContext> mInputReportContext;
+};
+
+void Gamepad::init(IOHIDDeviceRef aDevice, bool aDefaultRemapper) {
+ clear();
+ mDevice = aDevice;
+
+ CFArrayRef elements =
+ IOHIDDeviceCopyMatchingElements(aDevice, nullptr, kIOHIDOptionsTypeNone);
+ CFIndex n = CFArrayGetCount(elements);
+ for (CFIndex i = 0; i < n; i++) {
+ IOHIDElementRef element =
+ (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
+ uint32_t usagePage = IOHIDElementGetUsagePage(element);
+ uint32_t usage = IOHIDElementGetUsage(element);
+
+ if (usagePage == kDesktopUsagePage && usage >= kAxisUsageMin &&
+ usage <= kAxisUsageMax) {
+ Axis axis = {aDefaultRemapper ? int(axes.Length())
+ : static_cast<int>(usage - kAxisUsageMin),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element)};
+ axes.AppendElement(axis);
+ } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
+ // Don't know how to handle d-pads that return weird values.
+ IOHIDElementGetLogicalMax(element) -
+ IOHIDElementGetLogicalMin(element) ==
+ 7) {
+ Axis axis = {aDefaultRemapper ? int(axes.Length())
+ : static_cast<int>(usage - kAxisUsageMin),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element)};
+ axes.AppendElement(axis);
+ } else if ((usagePage == kSimUsagePage &&
+ (usage == kAcceleratorUsage || usage == kBrakeUsage)) ||
+ (usagePage == kButtonUsagePage) ||
+ (usagePage == kConsumerPage &&
+ (usage == kHomeUsage || usage == kBackUsage))) {
+ Button button(int(buttons.Length()), element,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element));
+ buttons.AppendElement(button);
+ } else {
+ // TODO: handle other usage pages
+ }
+ }
+}
+
+// This service is created and destroyed in Background thread while
+// operates in a seperate thread(We call it Monitor thread here).
+class DarwinGamepadService {
+ private:
+ IOHIDManagerRef mManager;
+ nsTArray<Gamepad> mGamepads;
+
+ nsCOMPtr<nsIThread> mMonitorThread;
+ nsCOMPtr<nsIThread> mBackgroundThread;
+ nsCOMPtr<nsITimer> mPollingTimer;
+ bool mIsRunning;
+
+ static void DeviceAddedCallback(void* data, IOReturn result, void* sender,
+ IOHIDDeviceRef device);
+ static void DeviceRemovedCallback(void* data, IOReturn result, void* sender,
+ IOHIDDeviceRef device);
+ static void InputValueChangedCallback(void* data, IOReturn result,
+ void* sender, IOHIDValueRef newValue);
+ static void EventLoopOnceCallback(nsITimer* aTimer, void* aClosure);
+
+ void DeviceAdded(IOHIDDeviceRef device);
+ void DeviceRemoved(IOHIDDeviceRef device);
+ void InputValueChanged(IOHIDValueRef value);
+ void StartupInternal();
+ void RunEventLoopOnce();
+
+ public:
+ DarwinGamepadService();
+ ~DarwinGamepadService();
+
+ static void ReportChangedCallback(void* context, IOReturn result,
+ void* sender, IOHIDReportType report_type,
+ uint32_t report_id, uint8_t* report,
+ CFIndex report_length);
+
+ void Startup();
+ void Shutdown();
+ void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue);
+ friend class DarwinGamepadServiceStartupRunnable;
+ friend class DarwinGamepadServiceShutdownRunnable;
+};
+
+class DarwinGamepadServiceStartupRunnable final : public Runnable {
+ private:
+ ~DarwinGamepadServiceStartupRunnable() {}
+ // This Runnable schedules startup of DarwinGamepadService
+ // in a new thread, pointer to DarwinGamepadService is only
+ // used by this Runnable within its thread.
+ DarwinGamepadService MOZ_NON_OWNING_REF* mService;
+
+ public:
+ explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService* service)
+ : Runnable("DarwinGamepadServiceStartupRunnable"), mService(service) {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mService);
+ mService->StartupInternal();
+ return NS_OK;
+ }
+};
+
+class DarwinGamepadServiceShutdownRunnable final : public Runnable {
+ private:
+ ~DarwinGamepadServiceShutdownRunnable() {}
+
+ public:
+ // This Runnable schedules shutdown of DarwinGamepadService
+ // in background thread.
+ explicit DarwinGamepadServiceShutdownRunnable()
+ : Runnable("DarwinGamepadServiceStartupRunnable") {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(gService);
+ MOZ_ASSERT(NS_GetCurrentThread() == gService->mBackgroundThread);
+
+ IOHIDManagerRef manager = (IOHIDManagerRef)gService->mManager;
+
+ if (manager) {
+ IOHIDManagerClose(manager, 0);
+ CFRelease(manager);
+ gService->mManager = nullptr;
+ }
+ gService->mMonitorThread->Shutdown();
+ delete gService;
+ gService = nullptr;
+ return NS_OK;
+ }
+};
+
+void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ size_t slot = size_t(-1);
+ for (size_t i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i] == device) return;
+ if (slot == size_t(-1) && mGamepads[i].empty()) slot = i;
+ }
+
+ if (slot == size_t(-1)) {
+ slot = mGamepads.Length();
+ mGamepads.AppendElement(Gamepad());
+ }
+
+ // Gather some identifying information
+ CFNumberRef vendorIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
+ CFNumberRef productIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
+ CFStringRef productRef =
+ (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
+ int vendorId, productId;
+ CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
+ CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
+ char product_name[128];
+ CFStringGetCString(productRef, product_name, sizeof(product_name),
+ kCFStringEncodingASCII);
+ char buffer[256];
+ SprintfLiteral(buffer, "%x-%x-%s", vendorId, productId, product_name);
+
+ bool defaultRemapper = false;
+ RefPtr<GamepadRemapper> remapper =
+ GetGamepadRemapper(vendorId, productId, defaultRemapper);
+ MOZ_ASSERT(remapper);
+ mGamepads[slot].init(device, defaultRemapper);
+
+ remapper->SetAxisCount(mGamepads[slot].numAxes());
+ remapper->SetButtonCount(mGamepads[slot].numButtons());
+
+ GamepadHandle handle = service->AddGamepad(
+ buffer, remapper->GetMappingType(), mozilla::dom::GamepadHand::_empty,
+ remapper->GetButtonCount(), remapper->GetAxisCount(),
+ 0, // TODO: Bug 680289, implement gamepad haptics for cocoa.
+ remapper->GetLightIndicatorCount(), remapper->GetTouchEventCount());
+
+ nsTArray<GamepadLightIndicatorType> lightTypes;
+ remapper->GetLightIndicators(lightTypes);
+ for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
+ if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
+ service->NewLightIndicatorTypeEvent(handle, i, lightTypes[i]);
+ }
+ }
+
+ mGamepads[slot].mHandle = handle;
+ mGamepads[slot].mInputReport.SetLength(remapper->GetMaxInputReportLength());
+ mGamepads[slot].mInputReportContext = UniquePtr<GamepadInputReportContext>(
+ new GamepadInputReportContext{this, slot});
+ mGamepads[slot].mRemapper = remapper.forget();
+
+ IOHIDDeviceRegisterInputReportCallback(
+ device, mGamepads[slot].mInputReport.Elements(),
+ mGamepads[slot].mInputReport.Length(), ReportChangedCallback,
+ mGamepads[slot].mInputReportContext.get());
+}
+
+void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ for (Gamepad& gamepad : mGamepads) {
+ if (gamepad == device) {
+ IOHIDDeviceRegisterInputReportCallback(
+ device, gamepad.mInputReport.Elements(), 0, NULL,
+ gamepad.mInputReportContext.get());
+
+ gamepad.mInputReportContext.reset();
+
+ service->RemoveGamepad(gamepad.mHandle);
+ gamepad.clear();
+ return;
+ }
+ }
+}
+
+// Replace context to be Gamepad.
+void DarwinGamepadService::ReportChangedCallback(
+ void* context, IOReturn result, void* sender, IOHIDReportType report_type,
+ uint32_t report_id, uint8_t* report, CFIndex report_length) {
+ if (context && report_type == kIOHIDReportTypeInput && report_length) {
+ auto reportContext = static_cast<GamepadInputReportContext*>(context);
+ DarwinGamepadService* service = reportContext->service;
+ service->mGamepads[reportContext->gamepadSlot].ReportChanged(report,
+ report_length);
+ }
+}
+
+void Gamepad::ReportChanged(uint8_t* report, CFIndex report_len) {
+ MOZ_RELEASE_ASSERT(report_len <= mRemapper->GetMaxInputReportLength());
+ mRemapper->ProcessTouchData(mHandle, report);
+}
+
+size_t Gamepad::WriteOutputReport(const std::vector<uint8_t>& aReport) const {
+ IOReturn success =
+ IOHIDDeviceSetReport(mDevice, kIOHIDReportTypeOutput, aReport[0],
+ aReport.data(), aReport.size());
+
+ MOZ_ASSERT(success == kIOReturnSuccess);
+ return (success == kIOReturnSuccess) ? aReport.size() : 0;
+}
+
+void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ uint32_t value_length = IOHIDValueGetLength(value);
+ if (value_length > 4) {
+ // Workaround for bizarre issue with PS3 controllers that try to return
+ // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
+ return;
+ }
+ IOHIDElementRef element = IOHIDValueGetElement(value);
+ IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+
+ for (Gamepad& gamepad : mGamepads) {
+ if (gamepad == device) {
+ // Axis elements represent axes and d-pads.
+ if (Axis* axis = gamepad.lookupAxis(element)) {
+ const double d = IOHIDValueGetIntegerValue(value);
+ const double v =
+ 2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f;
+ if (axis->value != v) {
+ gamepad.mRemapper->RemapAxisMoveEvent(gamepad.mHandle, axis->id, v);
+ axis->value = v;
+ }
+ } else if (Button* button = gamepad.lookupButton(element)) {
+ const int iv = IOHIDValueGetIntegerValue(value);
+ const bool pressed = iv != 0;
+ if (button->pressed != pressed) {
+ gamepad.mRemapper->RemapButtonEvent(gamepad.mHandle, button->id,
+ pressed);
+ button->pressed = pressed;
+ }
+ }
+ return;
+ }
+ }
+}
+
+void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
+ void* sender,
+ IOHIDDeviceRef device) {
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceAdded(device);
+}
+
+void DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
+ void* sender,
+ IOHIDDeviceRef device) {
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceRemoved(device);
+}
+
+void DarwinGamepadService::InputValueChangedCallback(void* data,
+ IOReturn result,
+ void* sender,
+ IOHIDValueRef newValue) {
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->InputValueChanged(newValue);
+}
+
+void DarwinGamepadService::EventLoopOnceCallback(nsITimer* aTimer,
+ void* aClosure) {
+ DarwinGamepadService* service = static_cast<DarwinGamepadService*>(aClosure);
+ service->RunEventLoopOnce();
+}
+
+static CFMutableDictionaryRef MatchingDictionary(UInt32 inUsagePage,
+ UInt32 inUsage) {
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (!dict) return nullptr;
+ CFNumberRef number =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
+ CFRelease(number);
+
+ number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
+ CFRelease(number);
+
+ return dict;
+}
+
+DarwinGamepadService::DarwinGamepadService()
+ : mManager(nullptr), mIsRunning(false) {}
+
+DarwinGamepadService::~DarwinGamepadService() {
+ if (mManager != nullptr) CFRelease(mManager);
+ mMonitorThread = nullptr;
+ mBackgroundThread = nullptr;
+ if (mPollingTimer) {
+ mPollingTimer->Cancel();
+ mPollingTimer = nullptr;
+ }
+}
+
+void DarwinGamepadService::RunEventLoopOnce() {
+ MOZ_ASSERT(NS_GetCurrentThread() == mMonitorThread);
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
+
+ // This timer must be created in monitor thread
+ if (!mPollingTimer) {
+ mPollingTimer = NS_NewTimer();
+ }
+ mPollingTimer->Cancel();
+ if (mIsRunning) {
+ mPollingTimer->InitWithNamedFuncCallback(
+ EventLoopOnceCallback, this, kDarwinGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT, "EventLoopOnceCallback");
+ } else {
+ // We schedule a task shutdown and cleaning up resources to Background
+ // thread here to make sure no runloop is running to prevent potential race
+ // condition.
+ RefPtr<Runnable> shutdownTask = new DarwinGamepadServiceShutdownRunnable();
+ mBackgroundThread->Dispatch(shutdownTask.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+
+void DarwinGamepadService::StartupInternal() {
+ if (mManager != nullptr) return;
+
+ IOHIDManagerRef manager =
+ IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+
+ CFMutableDictionaryRef criteria_arr[2];
+ criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, kJoystickUsage);
+ if (!criteria_arr[0]) {
+ CFRelease(manager);
+ return;
+ }
+
+ criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, kGamepadUsage);
+ if (!criteria_arr[1]) {
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ CFArrayRef criteria = CFArrayCreate(kCFAllocatorDefault,
+ (const void**)criteria_arr, 2, nullptr);
+ if (!criteria) {
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
+ CFRelease(criteria);
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+
+ IOHIDManagerRegisterDeviceMatchingCallback(manager, DeviceAddedCallback,
+ this);
+ IOHIDManagerRegisterDeviceRemovalCallback(manager, DeviceRemovedCallback,
+ this);
+ IOHIDManagerRegisterInputValueCallback(manager, InputValueChangedCallback,
+ this);
+ IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
+ if (rv != kIOReturnSuccess) {
+ CFRelease(manager);
+ return;
+ }
+
+ mManager = manager;
+
+ mIsRunning = true;
+ RunEventLoopOnce();
+}
+
+void DarwinGamepadService::Startup() {
+ mBackgroundThread = NS_GetCurrentThread();
+ Unused << NS_NewNamedThread("Gamepad", getter_AddRefs(mMonitorThread),
+ new DarwinGamepadServiceStartupRunnable(this));
+}
+
+void DarwinGamepadService::Shutdown() {
+ // Flipping this flag will stop the eventloop in Monitor thread
+ // and dispatch a task destroying and cleaning up resources in
+ // Background thread
+ mIsRunning = false;
+}
+
+void DarwinGamepadService::SetLightIndicatorColor(
+ const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
+ const uint8_t& aGreen, const uint8_t& aBlue) {
+ // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
+ // It begins from 1 and is stored at Gamepad.id.
+ const Gamepad* gamepad = MOZ_FIND_AND_VALIDATE(
+ aGamepadHandle, list_item.mHandle == aGamepadHandle, mGamepads);
+ if (!gamepad) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ RefPtr<GamepadRemapper> remapper = gamepad->mRemapper;
+ if (!remapper ||
+ MOZ_IS_VALID(aLightColorIndex,
+ remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ std::vector<uint8_t> report;
+ remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
+ gamepad->WriteOutputReport(report);
+}
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+void StartGamepadMonitoring() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ if (gService) {
+ return;
+ }
+
+ gService = new DarwinGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ if (!gService) {
+ return;
+ }
+
+ // Calling Shutdown() will delete gService as well
+ gService->Shutdown();
+}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue) {
+ MOZ_ASSERT(gService);
+ if (!gService) {
+ return;
+ }
+ gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
+ aGreen, aBlue);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/fallback/FallbackGamepad.cpp b/dom/gamepad/fallback/FallbackGamepad.cpp
new file mode 100644
index 0000000000..4b37732f51
--- /dev/null
+++ b/dom/gamepad/fallback/FallbackGamepad.cpp
@@ -0,0 +1,22 @@
+/* -*- 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/. */
+
+#include <stdint.h>
+#include "mozilla/dom/GamepadHandle.h"
+
+namespace mozilla {
+namespace dom {
+
+void StartGamepadMonitoring() {}
+
+void StopGamepadMonitoring() {}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>&,
+ const Tainted<uint32_t>&, const uint8_t&,
+ const uint8_t&, const uint8_t&) {}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/ipc/GamepadEventChannelChild.cpp b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
new file mode 100644
index 0000000000..ef8528f565
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+#include "GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class GamepadUpdateRunnable final : public Runnable {
+ public:
+ explicit GamepadUpdateRunnable(const GamepadChangeEvent& aGamepadEvent)
+ : Runnable("dom::GamepadUpdateRunnable"), mEvent(aGamepadEvent) {}
+ NS_IMETHOD Run() override {
+ RefPtr<GamepadManager> svc(GamepadManager::GetService());
+ if (svc) {
+ svc->Update(mEvent);
+ }
+ return NS_OK;
+ }
+
+ protected:
+ GamepadChangeEvent mEvent;
+};
+
+} // namespace
+
+already_AddRefed<GamepadEventChannelChild> GamepadEventChannelChild::Create() {
+ return RefPtr<GamepadEventChannelChild>(new GamepadEventChannelChild())
+ .forget();
+}
+
+mozilla::ipc::IPCResult GamepadEventChannelChild::RecvGamepadUpdate(
+ const GamepadChangeEvent& aGamepadEvent) {
+ DebugOnly<nsresult> rv =
+ NS_DispatchToMainThread(new GamepadUpdateRunnable(aGamepadEvent));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ return IPC_OK();
+}
+
+void GamepadEventChannelChild::AddPromise(const uint32_t& aID,
+ dom::Promise* aPromise) {
+ MOZ_ASSERT(!mPromiseList.Contains(aID));
+ mPromiseList.InsertOrUpdate(aID, RefPtr{aPromise});
+}
+
+mozilla::ipc::IPCResult GamepadEventChannelChild::RecvReplyGamepadPromise(
+ const uint32_t& aPromiseID) {
+ RefPtr<dom::Promise> p;
+ if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
+ MOZ_CRASH("We should always have a promise.");
+ }
+
+ p->MaybeResolve(true);
+ mPromiseList.Remove(aPromiseID);
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/ipc/GamepadEventChannelChild.h b/dom/gamepad/ipc/GamepadEventChannelChild.h
new file mode 100644
index 0000000000..97fd0576a4
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+#include "mozilla/dom/PGamepadEventChannelChild.h"
+#include "nsRefPtrHashtable.h"
+
+#ifndef mozilla_dom_GamepadEventChannelChild_h_
+# define mozilla_dom_GamepadEventChannelChild_h_
+
+namespace mozilla::dom {
+
+class GamepadEventChannelChild final : public PGamepadEventChannelChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelChild, override)
+
+ static already_AddRefed<GamepadEventChannelChild> Create();
+
+ mozilla::ipc::IPCResult RecvGamepadUpdate(
+ const GamepadChangeEvent& aGamepadEvent);
+ mozilla::ipc::IPCResult RecvReplyGamepadPromise(const uint32_t& aPromiseID);
+ void AddPromise(const uint32_t& aID, dom::Promise* aPromise);
+
+ GamepadEventChannelChild(const GamepadEventChannelChild&) = delete;
+ GamepadEventChannelChild(GamepadEventChannelChild&&) = delete;
+ GamepadEventChannelChild& operator=(const GamepadEventChannelChild&) = delete;
+ GamepadEventChannelChild& operator=(GamepadEventChannelChild&&) = delete;
+
+ private:
+ GamepadEventChannelChild() = default;
+ ~GamepadEventChannelChild() = default;
+
+ nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mPromiseList;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/ipc/GamepadEventChannelParent.cpp b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
new file mode 100644
index 0000000000..6f98328329
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -0,0 +1,109 @@
+/* -*- 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/. */
+#include "GamepadEventChannelParent.h"
+#include "GamepadPlatformService.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+class SendGamepadUpdateRunnable final : public Runnable {
+ private:
+ ~SendGamepadUpdateRunnable() = default;
+ RefPtr<GamepadEventChannelParent> mParent;
+ GamepadChangeEvent mEvent;
+
+ public:
+ SendGamepadUpdateRunnable(GamepadEventChannelParent* aParent,
+ GamepadChangeEvent aEvent)
+ : Runnable("dom::SendGamepadUpdateRunnable"),
+ mParent(aParent),
+ mEvent(aEvent) {
+ MOZ_ASSERT(mParent);
+ }
+ NS_IMETHOD Run() override {
+ AssertIsOnBackgroundThread();
+ Unused << mParent->SendGamepadUpdate(mEvent);
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+already_AddRefed<GamepadEventChannelParent>
+GamepadEventChannelParent::Create() {
+ return RefPtr<GamepadEventChannelParent>(new GamepadEventChannelParent())
+ .forget();
+}
+
+GamepadEventChannelParent::GamepadEventChannelParent() : mIsShutdown{false} {
+ MOZ_DIAGNOSTIC_ASSERT(IsOnBackgroundThread());
+
+ mBackgroundEventTarget = GetCurrentEventTarget();
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+
+ service->AddChannelParent(this);
+}
+
+void GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_DIAGNOSTIC_ASSERT(IsOnBackgroundThread());
+ MOZ_DIAGNOSTIC_ASSERT(!mIsShutdown);
+
+ mIsShutdown = true;
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+ service->RemoveChannelParent(this);
+}
+
+mozilla::ipc::IPCResult GamepadEventChannelParent::RecvVibrateHaptic(
+ const Tainted<GamepadHandle>& aHandle,
+ const Tainted<uint32_t>& aHapticIndex, const Tainted<double>& aIntensity,
+ const Tainted<double>& aDuration, const uint32_t& aPromiseID) {
+ // TODO: Bug 680289, implement for standard gamepads
+
+ if (SendReplyGamepadPromise(aPromiseID)) {
+ return IPC_OK();
+ }
+
+ return IPC_FAIL(this, "SendReplyGamepadPromise fail.");
+}
+
+mozilla::ipc::IPCResult GamepadEventChannelParent::RecvStopVibrateHaptic(
+ const Tainted<GamepadHandle>& aHandle) {
+ // TODO: Bug 680289, implement for standard gamepads
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GamepadEventChannelParent::RecvLightIndicatorColor(
+ const Tainted<GamepadHandle>& aHandle,
+ const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
+ const uint8_t& aGreen, const uint8_t& aBlue, const uint32_t& aPromiseID) {
+ SetGamepadLightIndicatorColor(aHandle, aLightColorIndex, aRed, aGreen, aBlue);
+
+ if (SendReplyGamepadPromise(aPromiseID)) {
+ return IPC_OK();
+ }
+
+ return IPC_FAIL(this, "SendReplyGamepadPromise fail.");
+}
+
+void GamepadEventChannelParent::DispatchUpdateEvent(
+ const GamepadChangeEvent& aEvent) {
+ mBackgroundEventTarget->Dispatch(new SendGamepadUpdateRunnable(this, aEvent),
+ NS_DISPATCH_NORMAL);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/ipc/GamepadEventChannelParent.h b/dom/gamepad/ipc/GamepadEventChannelParent.h
new file mode 100644
index 0000000000..90bb7e7f4d
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+#include "mozilla/dom/PGamepadEventChannelParent.h"
+
+#ifndef mozilla_dom_GamepadEventChannelParent_h_
+# define mozilla_dom_GamepadEventChannelParent_h_
+
+namespace mozilla::dom {
+
+class GamepadEventChannelParent final : public PGamepadEventChannelParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent, override)
+
+ static already_AddRefed<GamepadEventChannelParent> Create();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvVibrateHaptic(
+ const Tainted<GamepadHandle>& aHandle,
+ const Tainted<uint32_t>& aHapticIndex, const Tainted<double>& aIntensity,
+ const Tainted<double>& aDuration, const uint32_t& aPromiseID);
+ mozilla::ipc::IPCResult RecvStopVibrateHaptic(
+ const Tainted<GamepadHandle>& aHandle);
+ mozilla::ipc::IPCResult RecvLightIndicatorColor(
+ const Tainted<GamepadHandle>& aHandle,
+ const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
+ const uint8_t& aGreen, const uint8_t& aBlue, const uint32_t& aPromiseID);
+ void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
+
+ GamepadEventChannelParent(const GamepadEventChannelParent&) = delete;
+ GamepadEventChannelParent(GamepadEventChannelParent&&) = delete;
+ GamepadEventChannelParent& operator=(const GamepadEventChannelParent&) =
+ delete;
+ GamepadEventChannelParent& operator=(GamepadEventChannelParent&&) = delete;
+
+ private:
+ GamepadEventChannelParent();
+ ~GamepadEventChannelParent() = default;
+
+ bool mIsShutdown;
+ nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/ipc/GamepadEventTypes.ipdlh b/dom/gamepad/ipc/GamepadEventTypes.ipdlh
new file mode 100644
index 0000000000..4ac1d7dc93
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventTypes.ipdlh
@@ -0,0 +1,78 @@
+/* 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/. */
+
+include "mozilla/dom/GamepadMessageUtils.h";
+
+using mozilla::dom::GamepadPoseState from "mozilla/dom/GamepadPoseState.h";
+using mozilla::dom::GamepadTouchState from "mozilla/dom/GamepadTouchState.h";
+using mozilla::dom::GamepadLightIndicatorType from "mozilla/dom/GamepadLightIndicatorBinding.h";
+using mozilla::dom::GamepadMappingType from "mozilla/dom/GamepadBinding.h";
+using mozilla::dom::GamepadHand from "mozilla/dom/GamepadBinding.h";
+using mozilla::dom::GamepadHandle from "mozilla/dom/GamepadHandle.h";
+
+namespace mozilla {
+namespace dom {
+
+struct GamepadAdded {
+ nsString id;
+ GamepadMappingType mapping;
+ GamepadHand hand;
+ uint32_t display_id;
+ uint32_t num_buttons;
+ uint32_t num_axes;
+ uint32_t num_haptics;
+ uint32_t num_lights;
+ uint32_t num_touches;
+};
+
+struct GamepadRemoved {};
+
+struct GamepadAxisInformation {
+ uint32_t axis;
+ double value;
+};
+
+struct GamepadButtonInformation {
+ uint32_t button;
+ double value;
+ bool pressed;
+ bool touched;
+};
+
+struct GamepadPoseInformation {
+ GamepadPoseState pose_state;
+};
+
+struct GamepadLightIndicatorTypeInformation {
+ uint32_t light;
+ GamepadLightIndicatorType type;
+};
+
+struct GamepadHandInformation {
+ GamepadHand hand;
+};
+
+struct GamepadTouchInformation {
+ uint32_t index;
+ GamepadTouchState touch_state;
+};
+
+union GamepadChangeEventBody {
+ GamepadAdded;
+ GamepadRemoved;
+ GamepadAxisInformation;
+ GamepadButtonInformation;
+ GamepadHandInformation;
+ GamepadLightIndicatorTypeInformation;
+ GamepadPoseInformation;
+ GamepadTouchInformation;
+};
+
+struct GamepadChangeEvent {
+ GamepadHandle handle;
+ GamepadChangeEventBody body;
+};
+
+} // namespace dom
+} // namespace mozilla \ No newline at end of file
diff --git a/dom/gamepad/ipc/GamepadMessageUtils.h b/dom/gamepad/ipc/GamepadMessageUtils.h
new file mode 100644
index 0000000000..c29293d3a5
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadMessageUtils.h
@@ -0,0 +1,154 @@
+/* -*- 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_gamepad_GamepadMessageUtils_h
+#define mozilla_dom_gamepad_GamepadMessageUtils_h
+
+#include "ipc/EnumSerializer.h"
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadLightIndicatorBinding.h"
+#include "mozilla/dom/GamepadPoseState.h"
+#include "mozilla/dom/GamepadTouchState.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadLightIndicatorType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::GamepadLightIndicatorType,
+ mozilla::dom::GamepadLightIndicatorType(0),
+ mozilla::dom::GamepadLightIndicatorType(
+ mozilla::dom::GamepadLightIndicatorType::EndGuard_)> {};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadMappingType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::GamepadMappingType, mozilla::dom::GamepadMappingType(0),
+ mozilla::dom::GamepadMappingType(
+ mozilla::dom::GamepadMappingType::EndGuard_)> {};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadHand>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::GamepadHand, mozilla::dom::GamepadHand(0),
+ mozilla::dom::GamepadHand(mozilla::dom::GamepadHand::EndGuard_)> {};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadCapabilityFlags>
+ : public BitFlagsEnumSerializer<
+ mozilla::dom::GamepadCapabilityFlags,
+ mozilla::dom::GamepadCapabilityFlags::Cap_All> {};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadPoseState> {
+ typedef mozilla::dom::GamepadPoseState paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.flags);
+ WriteParam(aWriter, aParam.orientation[0]);
+ WriteParam(aWriter, aParam.orientation[1]);
+ WriteParam(aWriter, aParam.orientation[2]);
+ WriteParam(aWriter, aParam.orientation[3]);
+ WriteParam(aWriter, aParam.position[0]);
+ WriteParam(aWriter, aParam.position[1]);
+ WriteParam(aWriter, aParam.position[2]);
+ WriteParam(aWriter, aParam.angularVelocity[0]);
+ WriteParam(aWriter, aParam.angularVelocity[1]);
+ WriteParam(aWriter, aParam.angularVelocity[2]);
+ WriteParam(aWriter, aParam.angularAcceleration[0]);
+ WriteParam(aWriter, aParam.angularAcceleration[1]);
+ WriteParam(aWriter, aParam.angularAcceleration[2]);
+ WriteParam(aWriter, aParam.linearVelocity[0]);
+ WriteParam(aWriter, aParam.linearVelocity[1]);
+ WriteParam(aWriter, aParam.linearVelocity[2]);
+ WriteParam(aWriter, aParam.linearAcceleration[0]);
+ WriteParam(aWriter, aParam.linearAcceleration[1]);
+ WriteParam(aWriter, aParam.linearAcceleration[2]);
+ WriteParam(aWriter, aParam.isPositionValid);
+ WriteParam(aWriter, aParam.isOrientationValid);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &(aResult->flags)) ||
+ !ReadParam(aReader, &(aResult->orientation[0])) ||
+ !ReadParam(aReader, &(aResult->orientation[1])) ||
+ !ReadParam(aReader, &(aResult->orientation[2])) ||
+ !ReadParam(aReader, &(aResult->orientation[3])) ||
+ !ReadParam(aReader, &(aResult->position[0])) ||
+ !ReadParam(aReader, &(aResult->position[1])) ||
+ !ReadParam(aReader, &(aResult->position[2])) ||
+ !ReadParam(aReader, &(aResult->angularVelocity[0])) ||
+ !ReadParam(aReader, &(aResult->angularVelocity[1])) ||
+ !ReadParam(aReader, &(aResult->angularVelocity[2])) ||
+ !ReadParam(aReader, &(aResult->angularAcceleration[0])) ||
+ !ReadParam(aReader, &(aResult->angularAcceleration[1])) ||
+ !ReadParam(aReader, &(aResult->angularAcceleration[2])) ||
+ !ReadParam(aReader, &(aResult->linearVelocity[0])) ||
+ !ReadParam(aReader, &(aResult->linearVelocity[1])) ||
+ !ReadParam(aReader, &(aResult->linearVelocity[2])) ||
+ !ReadParam(aReader, &(aResult->linearAcceleration[0])) ||
+ !ReadParam(aReader, &(aResult->linearAcceleration[1])) ||
+ !ReadParam(aReader, &(aResult->linearAcceleration[2])) ||
+ !ReadParam(aReader, &(aResult->isPositionValid)) ||
+ !ReadParam(aReader, &(aResult->isOrientationValid))) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadTouchState> {
+ typedef mozilla::dom::GamepadTouchState paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.touchId);
+ WriteParam(aWriter, aParam.surfaceId);
+ WriteParam(aWriter, aParam.position[0]);
+ WriteParam(aWriter, aParam.position[1]);
+ WriteParam(aWriter, aParam.surfaceDimensions[0]);
+ WriteParam(aWriter, aParam.surfaceDimensions[1]);
+ WriteParam(aWriter, aParam.isSurfaceDimensionsValid);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &(aResult->touchId)) ||
+ !ReadParam(aReader, &(aResult->surfaceId)) ||
+ !ReadParam(aReader, &(aResult->position[0])) ||
+ !ReadParam(aReader, &(aResult->position[1])) ||
+ !ReadParam(aReader, &(aResult->surfaceDimensions[0])) ||
+ !ReadParam(aReader, &(aResult->surfaceDimensions[1])) ||
+ !ReadParam(aReader, &(aResult->isSurfaceDimensionsValid))) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadHandleKind>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::dom::GamepadHandleKind,
+ mozilla::dom::GamepadHandleKind::GamepadPlatformManager,
+ mozilla::dom::GamepadHandleKind::VR> {};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadHandle> {
+ typedef mozilla::dom::GamepadHandle paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mValue);
+ WriteParam(aWriter, aParam.mKind);
+ }
+ static bool Read(MessageReader* aReader, paramType* aParam) {
+ return ReadParam(aReader, &aParam->mValue) &&
+ ReadParam(aReader, &aParam->mKind);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_dom_gamepad_GamepadMessageUtils_h
diff --git a/dom/gamepad/ipc/GamepadTestChannelChild.cpp b/dom/gamepad/ipc/GamepadTestChannelChild.cpp
new file mode 100644
index 0000000000..d74e0736ce
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.cpp
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#include "GamepadTestChannelChild.h"
+
+#include "mozilla/dom/GamepadServiceTest.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<GamepadTestChannelChild> GamepadTestChannelChild::Create(
+ GamepadServiceTest* aGamepadServiceTest) {
+ return RefPtr<GamepadTestChannelChild>(
+ new GamepadTestChannelChild(aGamepadServiceTest))
+ .forget();
+}
+
+GamepadTestChannelChild::GamepadTestChannelChild(
+ GamepadServiceTest* aGamepadServiceTest)
+ : mGamepadServiceTest(aGamepadServiceTest) {}
+
+mozilla::ipc::IPCResult GamepadTestChannelChild::RecvReplyGamepadHandle(
+ const uint32_t& aID, const GamepadHandle& aHandle) {
+ MOZ_RELEASE_ASSERT(
+ mGamepadServiceTest,
+ "Test channel should never outlive the owning GamepadServiceTest");
+
+ mGamepadServiceTest->ReplyGamepadHandle(aID, aHandle);
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/ipc/GamepadTestChannelChild.h b/dom/gamepad/ipc/GamepadTestChannelChild.h
new file mode 100644
index 0000000000..26d499ff8a
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.h
@@ -0,0 +1,45 @@
+/* -*- 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_GamepadTestChannelChild_h_
+#define mozilla_dom_GamepadTestChannelChild_h_
+
+#include "mozilla/dom/PGamepadTestChannelChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/WeakPtr.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla::dom {
+
+class GamepadServiceTest;
+
+class GamepadTestChannelChild final : public PGamepadTestChannelChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadTestChannelChild)
+
+ static already_AddRefed<GamepadTestChannelChild> Create(
+ GamepadServiceTest* aGamepadServiceTest);
+
+ GamepadTestChannelChild(const GamepadTestChannelChild&) = delete;
+ GamepadTestChannelChild(GamepadTestChannelChild&&) = delete;
+ GamepadTestChannelChild& operator=(const GamepadTestChannelChild&) = delete;
+ GamepadTestChannelChild& operator=(GamepadTestChannelChild&&) = delete;
+
+ private:
+ explicit GamepadTestChannelChild(GamepadServiceTest* aGamepadServiceTest);
+ ~GamepadTestChannelChild() = default;
+
+ mozilla::ipc::IPCResult RecvReplyGamepadHandle(const uint32_t& aID,
+ const GamepadHandle& aHandle);
+
+ WeakPtr<GamepadServiceTest> mGamepadServiceTest;
+
+ friend class PGamepadTestChannelChild;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/ipc/GamepadTestChannelParent.cpp b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
new file mode 100644
index 0000000000..1c8d67491e
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
@@ -0,0 +1,129 @@
+/* -*- 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/. */
+
+#include "GamepadTestChannelParent.h"
+
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<GamepadTestChannelParent> GamepadTestChannelParent::Create() {
+ // Refuse to create the parent actor if this pref is disabled
+ if (!StaticPrefs::dom_gamepad_test_enabled()) {
+ return nullptr;
+ }
+
+ return RefPtr<GamepadTestChannelParent>(new GamepadTestChannelParent())
+ .forget();
+}
+
+GamepadTestChannelParent::GamepadTestChannelParent() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ GamepadMonitoringState::GetSingleton().AddObserver(this);
+}
+
+GamepadTestChannelParent::~GamepadTestChannelParent() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ GamepadMonitoringState::GetSingleton().RemoveObserver(this);
+}
+
+void GamepadTestChannelParent::AddGamepadToPlatformService(
+ uint32_t aPromiseId, const GamepadAdded& aGamepadAdded) {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+
+ const GamepadAdded& a = aGamepadAdded;
+ nsCString gamepadID;
+ LossyCopyUTF16toASCII(a.id(), gamepadID);
+ GamepadHandle handle = service->AddGamepad(
+ gamepadID.get(), static_cast<GamepadMappingType>(a.mapping()), a.hand(),
+ a.num_buttons(), a.num_axes(), a.num_haptics(), a.num_lights(),
+ a.num_touches());
+
+ Unused << SendReplyGamepadHandle(aPromiseId, handle);
+}
+
+void GamepadTestChannelParent::OnMonitoringStateChanged(bool aNewState) {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (aNewState) {
+ for (auto& deferredGamepadAdd : mDeferredGamepadAdded) {
+ AddGamepadToPlatformService(deferredGamepadAdd.promiseId,
+ deferredGamepadAdd.gamepadAdded);
+ }
+ mDeferredGamepadAdded.Clear();
+ }
+}
+
+mozilla::ipc::IPCResult GamepadTestChannelParent::RecvGamepadTestEvent(
+ const uint32_t& aID, const GamepadChangeEvent& aEvent) {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+
+ const GamepadChangeEventBody& body = aEvent.body();
+
+ // GamepadAdded is special because it will be deferred if monitoring hasn't
+ // started. Everything else won't be deferred and will fail if monitoring
+ // isn't running
+ if (body.type() == GamepadChangeEventBody::TGamepadAdded) {
+ if (GamepadMonitoringState::GetSingleton().IsMonitoring()) {
+ AddGamepadToPlatformService(aID, body.get_GamepadAdded());
+ } else {
+ mDeferredGamepadAdded.AppendElement(
+ DeferredGamepadAdded{aID, body.get_GamepadAdded()});
+ }
+ return IPC_OK();
+ }
+
+ if (!GamepadMonitoringState::GetSingleton().IsMonitoring()) {
+ return IPC_FAIL(this, "Simulated message received while not monitoring");
+ }
+
+ GamepadHandle handle = aEvent.handle();
+
+ switch (body.type()) {
+ case GamepadChangeEventBody::TGamepadRemoved:
+ service->RemoveGamepad(handle);
+ break;
+ case GamepadChangeEventBody::TGamepadButtonInformation: {
+ const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
+ service->NewButtonEvent(handle, a.button(), a.pressed(), a.touched(),
+ a.value());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadAxisInformation: {
+ const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
+ service->NewAxisMoveEvent(handle, a.axis(), a.value());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadPoseInformation: {
+ const GamepadPoseInformation& a = body.get_GamepadPoseInformation();
+ service->NewPoseEvent(handle, a.pose_state());
+ break;
+ }
+ case GamepadChangeEventBody::TGamepadTouchInformation: {
+ const GamepadTouchInformation& a = body.get_GamepadTouchInformation();
+ service->NewMultiTouchEvent(handle, a.index(), a.touch_state());
+ break;
+ }
+ default:
+ NS_WARNING("Unknown event type.");
+ return IPC_FAIL_NO_REASON(this);
+ }
+ Unused << SendReplyGamepadHandle(aID, handle);
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/ipc/GamepadTestChannelParent.h b/dom/gamepad/ipc/GamepadTestChannelParent.h
new file mode 100644
index 0000000000..03567727e2
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.h
@@ -0,0 +1,49 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/PGamepadTestChannelParent.h"
+#include "mozilla/WeakPtr.h"
+
+#ifndef mozilla_dom_GamepadTestChannelParent_h_
+# define mozilla_dom_GamepadTestChannelParent_h_
+
+namespace mozilla::dom {
+
+class GamepadTestChannelParent final : public PGamepadTestChannelParent,
+ public SupportsWeakPtr {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadTestChannelParent)
+
+ static already_AddRefed<GamepadTestChannelParent> Create();
+
+ mozilla::ipc::IPCResult RecvGamepadTestEvent(
+ const uint32_t& aID, const GamepadChangeEvent& aGamepadEvent);
+
+ void OnMonitoringStateChanged(bool aNewState);
+
+ GamepadTestChannelParent(const GamepadTestChannelParent&) = delete;
+ GamepadTestChannelParent(GamepadTestChannelParent&&) = delete;
+ GamepadTestChannelParent& operator=(const GamepadTestChannelParent&) = delete;
+ GamepadTestChannelParent& operator=(GamepadTestChannelParent&&) = delete;
+
+ private:
+ struct DeferredGamepadAdded {
+ uint32_t promiseId;
+ GamepadAdded gamepadAdded;
+ };
+
+ GamepadTestChannelParent();
+ ~GamepadTestChannelParent();
+
+ void AddGamepadToPlatformService(uint32_t aPromiseId,
+ const GamepadAdded& aGamepadAdded);
+
+ nsTArray<DeferredGamepadAdded> mDeferredGamepadAdded;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/gamepad/ipc/PGamepadEventChannel.ipdl b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
new file mode 100644
index 0000000000..dba6725bd4
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -0,0 +1,31 @@
+/* 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/. */
+include protocol PBackground;
+include "mozilla/dom/GamepadMessageUtils.h";
+include GamepadEventTypes;
+
+using mozilla::dom::GamepadHandle from "mozilla/dom/GamepadHandle.h";
+
+namespace mozilla {
+namespace dom {
+
+protocol PGamepadEventChannel {
+ manager PBackground;
+ parent:
+ async __delete__();
+
+ [Tainted] async VibrateHaptic(GamepadHandle aHandle, uint32_t aHapticIndex,
+ double aIntensity, double aDuration, [NoTaint=passback] uint32_t aPromiseID);
+ [Tainted] async StopVibrateHaptic(GamepadHandle aHandle);
+ [Tainted] async LightIndicatorColor(GamepadHandle aHandle, uint32_t aLightColorIndex,
+ [NoTaint=allvalid] uint8_t aRed, [NoTaint=allvalid] uint8_t aGreen,
+ [NoTaint=allvalid] uint8_t aBlue, [NoTaint=passback] uint32_t aPromiseID);
+
+ child:
+ async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
+ async ReplyGamepadPromise(uint32_t aPromiseID);
+};
+
+}
+}
diff --git a/dom/gamepad/ipc/PGamepadTestChannel.ipdl b/dom/gamepad/ipc/PGamepadTestChannel.ipdl
new file mode 100644
index 0000000000..2be0819491
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadTestChannel.ipdl
@@ -0,0 +1,23 @@
+/* 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/. */
+include protocol PBackground;
+include "mozilla/dom/GamepadMessageUtils.h";
+include GamepadEventTypes;
+
+using mozilla::dom::GamepadHandle from "mozilla/dom/GamepadHandle.h";
+
+namespace mozilla {
+namespace dom {
+
+protocol PGamepadTestChannel {
+ manager PBackground;
+ parent:
+ async GamepadTestEvent(uint32_t aID, GamepadChangeEvent aGamepadEvent);
+ async __delete__();
+ child:
+ async ReplyGamepadHandle(uint32_t aID, GamepadHandle aHandle);
+};
+
+}
+}
diff --git a/dom/gamepad/linux/LinuxGamepad.cpp b/dom/gamepad/linux/LinuxGamepad.cpp
new file mode 100644
index 0000000000..deee47b9d2
--- /dev/null
+++ b/dom/gamepad/linux/LinuxGamepad.cpp
@@ -0,0 +1,362 @@
+/* -*- 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/. */
+
+/*
+ * LinuxGamepadService: A Linux backend for the GamepadService.
+ * Derived from the kernel documentation at
+ * http://www.kernel.org/doc/Documentation/input/joystick-api.txt
+ */
+#include <algorithm>
+#include <cstddef>
+
+#include <glib.h>
+#include <linux/joystick.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "nscore.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/Tainting.h"
+#include "mozilla/UniquePtr.h"
+#include "udev.h"
+
+namespace {
+
+using namespace mozilla::dom;
+using mozilla::MakeUnique;
+using mozilla::udev_device;
+using mozilla::udev_enumerate;
+using mozilla::udev_lib;
+using mozilla::udev_list_entry;
+using mozilla::udev_monitor;
+using mozilla::UniquePtr;
+
+static const float kMaxAxisValue = 32767.0;
+static const char kJoystickPath[] = "/dev/input/js";
+
+// TODO: should find a USB identifier for each device so we can
+// provide something that persists across connect/disconnect cycles.
+typedef struct {
+ GamepadHandle handle;
+ guint source_id;
+ int numAxes;
+ int numButtons;
+ char idstring[256];
+ char devpath[PATH_MAX];
+} Gamepad;
+
+class LinuxGamepadService {
+ public:
+ LinuxGamepadService() : mMonitor(nullptr), mMonitorSourceID(0) {}
+
+ void Startup();
+ void Shutdown();
+
+ private:
+ void AddDevice(struct udev_device* dev);
+ void RemoveDevice(struct udev_device* dev);
+ void ScanForDevices();
+ void AddMonitor();
+ void RemoveMonitor();
+ bool is_gamepad(struct udev_device* dev);
+ void ReadUdevChange();
+
+ // handler for data from /dev/input/jsN
+ static gboolean OnGamepadData(GIOChannel* source, GIOCondition condition,
+ gpointer data);
+
+ // handler for data from udev monitor
+ static gboolean OnUdevMonitor(GIOChannel* source, GIOCondition condition,
+ gpointer data);
+
+ udev_lib mUdev;
+ struct udev_monitor* mMonitor;
+ guint mMonitorSourceID;
+ // Information about currently connected gamepads.
+ AutoTArray<UniquePtr<Gamepad>, 4> mGamepads;
+};
+
+// singleton instance
+LinuxGamepadService* gService = nullptr;
+
+void LinuxGamepadService::AddDevice(struct udev_device* dev) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return;
+ }
+
+ // Ensure that this device hasn't already been added.
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (strcmp(mGamepads[i]->devpath, devpath) == 0) {
+ return;
+ }
+ }
+
+ auto gamepad = MakeUnique<Gamepad>();
+ snprintf(gamepad->devpath, sizeof(gamepad->devpath), "%s", devpath);
+ GIOChannel* channel = g_io_channel_new_file(devpath, "r", nullptr);
+ if (!channel) {
+ return;
+ }
+
+ g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, nullptr);
+ g_io_channel_set_encoding(channel, nullptr, nullptr);
+ g_io_channel_set_buffered(channel, FALSE);
+ int fd = g_io_channel_unix_get_fd(channel);
+ char name[128];
+ if (ioctl(fd, JSIOCGNAME(sizeof(name)), &name) == -1) {
+ strcpy(name, "unknown");
+ }
+ const char* vendor_id =
+ mUdev.udev_device_get_property_value(dev, "ID_VENDOR_ID");
+ const char* model_id =
+ mUdev.udev_device_get_property_value(dev, "ID_MODEL_ID");
+ if (!vendor_id || !model_id) {
+ struct udev_device* parent =
+ mUdev.udev_device_get_parent_with_subsystem_devtype(dev, "input",
+ nullptr);
+ if (parent) {
+ vendor_id = mUdev.udev_device_get_sysattr_value(parent, "id/vendor");
+ model_id = mUdev.udev_device_get_sysattr_value(parent, "id/product");
+ }
+ }
+ snprintf(gamepad->idstring, sizeof(gamepad->idstring), "%s-%s-%s",
+ vendor_id ? vendor_id : "unknown", model_id ? model_id : "unknown",
+ name);
+
+ char numAxes = 0, numButtons = 0;
+ ioctl(fd, JSIOCGAXES, &numAxes);
+ gamepad->numAxes = numAxes;
+ ioctl(fd, JSIOCGBUTTONS, &numButtons);
+ gamepad->numButtons = numButtons;
+
+ gamepad->handle = service->AddGamepad(
+ gamepad->idstring, mozilla::dom::GamepadMappingType::_empty,
+ mozilla::dom::GamepadHand::_empty, gamepad->numButtons, gamepad->numAxes,
+ 0, 0, 0); // TODO: Bug 680289, implement gamepad haptics for Linux.
+ // TODO: Bug 1523355, implement gamepad lighindicator and touch for Linux.
+
+ gamepad->source_id =
+ g_io_add_watch(channel, GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
+ OnGamepadData, gamepad.get());
+ g_io_channel_unref(channel);
+
+ mGamepads.AppendElement(std::move(gamepad));
+}
+
+void LinuxGamepadService::RemoveDevice(struct udev_device* dev) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return;
+ }
+
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (strcmp(mGamepads[i]->devpath, devpath) == 0) {
+ auto gamepad = std::move(mGamepads[i]);
+ mGamepads.RemoveElementAt(i);
+
+ g_source_remove(gamepad->source_id);
+ service->RemoveGamepad(gamepad->handle);
+
+ break;
+ }
+ }
+}
+
+void LinuxGamepadService::ScanForDevices() {
+ struct udev_enumerate* en = mUdev.udev_enumerate_new(mUdev.udev);
+ mUdev.udev_enumerate_add_match_subsystem(en, "input");
+ mUdev.udev_enumerate_scan_devices(en);
+
+ struct udev_list_entry* dev_list_entry;
+ for (dev_list_entry = mUdev.udev_enumerate_get_list_entry(en);
+ dev_list_entry != nullptr;
+ dev_list_entry = mUdev.udev_list_entry_get_next(dev_list_entry)) {
+ const char* path = mUdev.udev_list_entry_get_name(dev_list_entry);
+ struct udev_device* dev =
+ mUdev.udev_device_new_from_syspath(mUdev.udev, path);
+ if (is_gamepad(dev)) {
+ AddDevice(dev);
+ }
+
+ mUdev.udev_device_unref(dev);
+ }
+
+ mUdev.udev_enumerate_unref(en);
+}
+
+void LinuxGamepadService::AddMonitor() {
+ // Add a monitor to watch for device changes
+ mMonitor = mUdev.udev_monitor_new_from_netlink(mUdev.udev, "udev");
+ if (!mMonitor) {
+ // Not much we can do here.
+ return;
+ }
+ mUdev.udev_monitor_filter_add_match_subsystem_devtype(mMonitor, "input",
+ nullptr);
+
+ int monitor_fd = mUdev.udev_monitor_get_fd(mMonitor);
+ GIOChannel* monitor_channel = g_io_channel_unix_new(monitor_fd);
+ mMonitorSourceID = g_io_add_watch(monitor_channel,
+ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
+ OnUdevMonitor, nullptr);
+ g_io_channel_unref(monitor_channel);
+
+ mUdev.udev_monitor_enable_receiving(mMonitor);
+}
+
+void LinuxGamepadService::RemoveMonitor() {
+ if (mMonitorSourceID) {
+ g_source_remove(mMonitorSourceID);
+ mMonitorSourceID = 0;
+ }
+ if (mMonitor) {
+ mUdev.udev_monitor_unref(mMonitor);
+ mMonitor = nullptr;
+ }
+}
+
+void LinuxGamepadService::Startup() {
+ // Don't bother starting up if libudev couldn't be loaded or initialized.
+ if (!mUdev) return;
+
+ AddMonitor();
+ ScanForDevices();
+}
+
+void LinuxGamepadService::Shutdown() {
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ g_source_remove(mGamepads[i]->source_id);
+ }
+ mGamepads.Clear();
+ RemoveMonitor();
+}
+
+bool LinuxGamepadService::is_gamepad(struct udev_device* dev) {
+ if (!mUdev.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"))
+ return false;
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return false;
+ }
+ if (strncmp(kJoystickPath, devpath, sizeof(kJoystickPath) - 1) != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void LinuxGamepadService::ReadUdevChange() {
+ struct udev_device* dev = mUdev.udev_monitor_receive_device(mMonitor);
+ const char* action = mUdev.udev_device_get_action(dev);
+ if (is_gamepad(dev)) {
+ if (strcmp(action, "add") == 0) {
+ AddDevice(dev);
+ } else if (strcmp(action, "remove") == 0) {
+ RemoveDevice(dev);
+ }
+ }
+ mUdev.udev_device_unref(dev);
+}
+
+// static
+gboolean LinuxGamepadService::OnGamepadData(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return TRUE;
+ }
+ auto* gamepad = static_cast<Gamepad*>(data);
+
+ // TODO: remove gamepad?
+ if (condition & G_IO_ERR || condition & G_IO_HUP) return FALSE;
+
+ while (true) {
+ struct js_event event;
+ gsize count;
+ GError* err = nullptr;
+ if (g_io_channel_read_chars(source, (gchar*)&event, sizeof(event), &count,
+ &err) != G_IO_STATUS_NORMAL ||
+ count == 0) {
+ break;
+ }
+
+ // TODO: store device state?
+ if (event.type & JS_EVENT_INIT) {
+ continue;
+ }
+
+ switch (event.type) {
+ case JS_EVENT_BUTTON:
+ service->NewButtonEvent(gamepad->handle, event.number, !!event.value);
+ break;
+ case JS_EVENT_AXIS:
+ service->NewAxisMoveEvent(gamepad->handle, event.number,
+ ((float)event.value) / kMaxAxisValue);
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+// static
+gboolean LinuxGamepadService::OnUdevMonitor(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data) {
+ if (condition & G_IO_ERR || condition & G_IO_HUP) return FALSE;
+
+ gService->ReadUdevChange();
+ return TRUE;
+}
+
+} // namespace
+
+namespace mozilla::dom {
+
+void StartGamepadMonitoring() {
+ if (gService) {
+ return;
+ }
+ gService = new LinuxGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring() {
+ if (!gService) {
+ return;
+ }
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue) {
+ // TODO: Bug 1523355.
+ NS_WARNING("Linux doesn't support gamepad light indicator.");
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/linux/udev.h b/dom/gamepad/linux/udev.h
new file mode 100644
index 0000000000..4e3f0b7865
--- /dev/null
+++ b/dom/gamepad/linux/udev.h
@@ -0,0 +1,150 @@
+/* -*- 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/. */
+
+/*
+ * This file defines a wrapper around libudev so we can avoid
+ * linking directly to it and use dlopen instead.
+ */
+
+#ifndef HAL_LINUX_UDEV_H_
+#define HAL_LINUX_UDEV_H_
+
+#include <dlfcn.h>
+
+#include "mozilla/ArrayUtils.h"
+
+namespace mozilla {
+
+struct udev;
+struct udev_device;
+struct udev_enumerate;
+struct udev_list_entry;
+struct udev_monitor;
+
+class udev_lib {
+ public:
+ udev_lib() : lib(nullptr), udev(nullptr) {
+ // Be careful about ABI compat! 0 -> 1 didn't change any
+ // symbols this code relies on, per:
+ // https://lists.fedoraproject.org/pipermail/devel/2012-June/168227.html
+ const char* lib_names[] = {"libudev.so.0", "libudev.so.1"};
+ // Check whether a library is already loaded so we don't load two
+ // conflicting libs.
+ for (unsigned i = 0; i < ArrayLength(lib_names); i++) {
+ lib = dlopen(lib_names[i], RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
+ if (lib) {
+ break;
+ }
+ }
+ // If nothing loads the first time through, it means no version of libudev
+ // was already loaded.
+ if (!lib) {
+ for (unsigned i = 0; i < ArrayLength(lib_names); i++) {
+ lib = dlopen(lib_names[i], RTLD_LAZY | RTLD_GLOBAL);
+ if (lib) {
+ break;
+ }
+ }
+ }
+ if (lib && LoadSymbols()) {
+ udev = udev_new();
+ }
+ }
+
+ ~udev_lib() {
+ if (udev) {
+ udev_unref(udev);
+ }
+
+ if (lib) {
+ dlclose(lib);
+ }
+ }
+
+ explicit operator bool() { return udev; }
+
+ private:
+#define DLSYM(s) \
+ do { \
+ (s) = (decltype(s))dlsym(lib, #s); \
+ if (!(s)) return false; \
+ } while (0)
+
+ bool LoadSymbols() {
+ DLSYM(udev_new);
+ DLSYM(udev_unref);
+ DLSYM(udev_device_unref);
+ DLSYM(udev_device_new_from_syspath);
+ DLSYM(udev_device_get_devnode);
+ DLSYM(udev_device_get_parent_with_subsystem_devtype);
+ DLSYM(udev_device_get_property_value);
+ DLSYM(udev_device_get_action);
+ DLSYM(udev_device_get_sysattr_value);
+ DLSYM(udev_enumerate_new);
+ DLSYM(udev_enumerate_unref);
+ DLSYM(udev_enumerate_add_match_subsystem);
+ DLSYM(udev_enumerate_scan_devices);
+ DLSYM(udev_enumerate_get_list_entry);
+ DLSYM(udev_list_entry_get_next);
+ DLSYM(udev_list_entry_get_name);
+ DLSYM(udev_monitor_new_from_netlink);
+ DLSYM(udev_monitor_filter_add_match_subsystem_devtype);
+ DLSYM(udev_monitor_enable_receiving);
+ DLSYM(udev_monitor_get_fd);
+ DLSYM(udev_monitor_receive_device);
+ DLSYM(udev_monitor_unref);
+
+ return true;
+ }
+
+#undef DLSYM
+
+ void* lib;
+
+ public:
+ struct udev* udev;
+
+ // Function pointers returned from dlsym.
+ struct udev* (*udev_new)(void);
+ void (*udev_unref)(struct udev*);
+
+ void (*udev_device_unref)(struct udev_device*);
+ struct udev_device* (*udev_device_new_from_syspath)(struct udev*,
+ const char*);
+ const char* (*udev_device_get_devnode)(struct udev_device*);
+ struct udev_device* (*udev_device_get_parent_with_subsystem_devtype)(
+ struct udev_device*, const char*, const char*);
+ const char* (*udev_device_get_property_value)(struct udev_device*,
+ const char*);
+ const char* (*udev_device_get_action)(struct udev_device*);
+ const char* (*udev_device_get_sysattr_value)(struct udev_device*,
+ const char*);
+
+ struct udev_enumerate* (*udev_enumerate_new)(struct udev*);
+ void (*udev_enumerate_unref)(struct udev_enumerate*);
+ int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate*,
+ const char*);
+ int (*udev_enumerate_scan_devices)(struct udev_enumerate*);
+ struct udev_list_entry* (*udev_enumerate_get_list_entry)(
+ struct udev_enumerate*);
+
+ struct udev_list_entry* (*udev_list_entry_get_next)(struct udev_list_entry*);
+ const char* (*udev_list_entry_get_name)(struct udev_list_entry*);
+
+ struct udev_monitor* (*udev_monitor_new_from_netlink)(struct udev*,
+ const char*);
+ int (*udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor*,
+ const char*,
+ const char*);
+ int (*udev_monitor_enable_receiving)(struct udev_monitor*);
+ int (*udev_monitor_get_fd)(struct udev_monitor*);
+ struct udev_device* (*udev_monitor_receive_device)(struct udev_monitor*);
+ void (*udev_monitor_unref)(struct udev_monitor*);
+};
+
+} // namespace mozilla
+
+#endif // HAL_LINUX_UDEV_H_
diff --git a/dom/gamepad/moz.build b/dom/gamepad/moz.build
new file mode 100644
index 0000000000..eec526e771
--- /dev/null
+++ b/dom/gamepad/moz.build
@@ -0,0 +1,82 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+IPDL_SOURCES += [
+ "ipc/GamepadEventTypes.ipdlh",
+ "ipc/PGamepadEventChannel.ipdl",
+ "ipc/PGamepadTestChannel.ipdl",
+]
+
+EXPORTS.mozilla.dom += [
+ "Gamepad.h",
+ "GamepadButton.h",
+ "GamepadHandle.h",
+ "GamepadHapticActuator.h",
+ "GamepadLightIndicator.h",
+ "GamepadManager.h",
+ "GamepadMonitoring.h",
+ "GamepadPlatformService.h",
+ "GamepadPose.h",
+ "GamepadPoseState.h",
+ "GamepadRemapping.h",
+ "GamepadServiceTest.h",
+ "GamepadTouch.h",
+ "GamepadTouchState.h",
+ "ipc/GamepadEventChannelChild.h",
+ "ipc/GamepadEventChannelParent.h",
+ "ipc/GamepadMessageUtils.h",
+ "ipc/GamepadTestChannelChild.h",
+ "ipc/GamepadTestChannelParent.h",
+]
+
+UNIFIED_SOURCES = [
+ "Gamepad.cpp",
+ "GamepadButton.cpp",
+ "GamepadHandle.cpp",
+ "GamepadHapticActuator.cpp",
+ "GamepadLightIndicator.cpp",
+ "GamepadManager.cpp",
+ "GamepadPlatformService.cpp",
+ "GamepadPose.cpp",
+ "GamepadRemapping.cpp",
+ "GamepadServiceTest.cpp",
+ "GamepadTouch.cpp",
+ "ipc/GamepadEventChannelChild.cpp",
+ "ipc/GamepadEventChannelParent.cpp",
+ "ipc/GamepadTestChannelChild.cpp",
+ "ipc/GamepadTestChannelParent.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ UNIFIED_SOURCES += ["cocoa/CocoaGamepad.cpp"]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ REQUIRES_UNIFIED_BUILD = True
+ UNIFIED_SOURCES += ["windows/WindowsGamepad.cpp"]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ UNIFIED_SOURCES += ["android/AndroidGamepad.cpp"]
+elif CONFIG["OS_ARCH"] == "Linux":
+ UNIFIED_SOURCES += ["linux/LinuxGamepad.cpp"]
+else:
+ UNIFIED_SOURCES += ["fallback/FallbackGamepad.cpp"]
+
+LOCAL_INCLUDES += [
+ "ipc",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/dom/base",
+]
+
+CFLAGS += CONFIG["GLIB_CFLAGS"]
+CFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
diff --git a/dom/gamepad/windows/WindowsGamepad.cpp b/dom/gamepad/windows/WindowsGamepad.cpp
new file mode 100644
index 0000000000..a73d5afc97
--- /dev/null
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -0,0 +1,1136 @@
+/* -*- 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/. */
+
+#include <algorithm>
+#include <cstddef>
+
+#ifndef UNICODE
+# define UNICODE
+#endif
+#include <windows.h>
+#include <hidsdi.h>
+#include <stdio.h>
+#include <xinput.h>
+
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/ArrayUtils.h"
+
+#include "mozilla/ipc/BackgroundParent.h"
+#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 uint32_t kDpadMinimumUsageNumber = 0x39;
+const uint32_t kAxesLengthCap = 16;
+
+// USB HID usage tables
+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.
+// 50ms is arbitrarily chosen.
+const uint32_t kWindowsGamepadPollInterval = 50;
+
+const UINT kRawInputError = (UINT)-1;
+
+// In XInputGetState, we can't get the state of Xbox Guide button.
+// We need to go through the undocumented XInputGetStateEx method
+// to get that button's state.
+const LPCSTR kXInputGetStateExOrdinal = (LPCSTR)100;
+// Bitmask for the Guide button in XInputGamepadEx.wButtons.
+const int XINPUT_GAMEPAD_Guide = 0x0400;
+
+#ifndef XUSER_MAX_COUNT
+# define XUSER_MAX_COUNT 4
+#endif
+
+const struct {
+ int usagePage;
+ int usage;
+} kUsagePages[] = {
+ // USB HID usage tables, page 1
+ {kDesktopUsagePage, 4}, // Joystick
+ {kDesktopUsagePage, 5} // Gamepad
+};
+
+const struct {
+ WORD button;
+ int mapped;
+} kXIButtonMap[] = {{XINPUT_GAMEPAD_DPAD_UP, 12},
+ {XINPUT_GAMEPAD_DPAD_DOWN, 13},
+ {XINPUT_GAMEPAD_DPAD_LEFT, 14},
+ {XINPUT_GAMEPAD_DPAD_RIGHT, 15},
+ {XINPUT_GAMEPAD_START, 9},
+ {XINPUT_GAMEPAD_BACK, 8},
+ {XINPUT_GAMEPAD_LEFT_THUMB, 10},
+ {XINPUT_GAMEPAD_RIGHT_THUMB, 11},
+ {XINPUT_GAMEPAD_LEFT_SHOULDER, 4},
+ {XINPUT_GAMEPAD_RIGHT_SHOULDER, 5},
+ {XINPUT_GAMEPAD_Guide, 16},
+ {XINPUT_GAMEPAD_A, 0},
+ {XINPUT_GAMEPAD_B, 1},
+ {XINPUT_GAMEPAD_X, 2},
+ {XINPUT_GAMEPAD_Y, 3}};
+const size_t kNumMappings = ArrayLength(kXIButtonMap);
+
+enum GamepadType { kNoGamepad = 0, kRawInputGamepad, kXInputGamepad };
+
+class WindowsGamepadService;
+// This pointer holds a windows gamepad backend service,
+// it will be created and destroyed by background thread and
+// used by gMonitorThread
+WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
+nsCOMPtr<nsIThread> gMonitorThread = nullptr;
+static bool sIsShutdown = false;
+
+class Gamepad {
+ public:
+ GamepadType type;
+
+ // Handle to raw input device
+ HANDLE handle;
+
+ // XInput Index of the user's controller. Passed to XInputGetState.
+ DWORD userIndex;
+
+ // Last-known state of the controller.
+ XINPUT_STATE state;
+
+ // Handle from the GamepadService
+ GamepadHandle gamepadHandle;
+
+ // Information about the physical device.
+ unsigned numAxes;
+ unsigned numButtons;
+
+ nsTArray<bool> buttons;
+ struct axisValue {
+ HIDP_VALUE_CAPS caps;
+ double value;
+ bool active;
+
+ axisValue() : value(0.0f), active(false) {}
+ 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) {
+ buttons.SetLength(numButtons);
+ axes.SetLength(numAxes);
+ }
+
+ private:
+ Gamepad() {}
+};
+
+// Drop this in favor of decltype when we require a new enough SDK.
+using XInputEnable_func = void(WINAPI*)(BOOL);
+
+// RAII class to wrap loading the XInput DLL
+class XInputLoader {
+ public:
+ XInputLoader()
+ : module(nullptr), mXInputGetState(nullptr), mXInputEnable(nullptr) {
+ // xinput1_4.dll exists on Windows 8
+ // xinput9_1_0.dll exists on Windows 7 and Vista
+ // xinput1_3.dll shipped with the DirectX SDK
+ const wchar_t* dlls[] = {L"xinput1_4.dll", L"xinput9_1_0.dll",
+ L"xinput1_3.dll"};
+ const size_t kNumDLLs = ArrayLength(dlls);
+ for (size_t i = 0; i < kNumDLLs; ++i) {
+ module = LoadLibraryW(dlls[i]);
+ if (module) {
+ mXInputEnable = reinterpret_cast<XInputEnable_func>(
+ GetProcAddress(module, "XInputEnable"));
+ // Checking if `XInputGetStateEx` is available. If not,
+ // we will fallback to use `XInputGetState`.
+ mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
+ GetProcAddress(module, kXInputGetStateExOrdinal));
+ if (!mXInputGetState) {
+ mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
+ GetProcAddress(module, "XInputGetState"));
+ }
+ MOZ_ASSERT(mXInputGetState &&
+ "XInputGetState must be linked successfully.");
+
+ if (mXInputEnable) {
+ mXInputEnable(TRUE);
+ }
+ break;
+ }
+ }
+ }
+
+ ~XInputLoader() {
+ mXInputEnable = nullptr;
+ mXInputGetState = nullptr;
+
+ if (module) {
+ FreeLibrary(module);
+ }
+ }
+
+ explicit operator bool() { return module && mXInputGetState; }
+
+ HMODULE module;
+ decltype(XInputGetState)* mXInputGetState;
+ XInputEnable_func mXInputEnable;
+};
+
+bool GetPreparsedData(HANDLE handle, nsTArray<uint8_t>& data) {
+ UINT size;
+ if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) ==
+ kRawInputError) {
+ return false;
+ }
+ data.SetLength(size);
+ return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, data.Elements(),
+ &size) > 0;
+}
+
+/*
+ * Given an axis value and a minimum and maximum range,
+ * scale it to be in the range -1.0 .. 1.0.
+ */
+double ScaleAxis(ULONG value, LONG min, LONG max) {
+ return 2.0 * (value - min) / (max - min) - 1.0;
+}
+
+/*
+ * Return true if this USB HID usage page and usage are of a type we
+ * know how to handle.
+ */
+bool SupportedUsage(USHORT page, USHORT usage) {
+ for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) {
+ if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
+ return true;
+ }
+ }
+ return false;
+}
+
+class HIDLoader {
+ public:
+ HIDLoader()
+ : mHidD_GetProductString(nullptr),
+ mHidP_GetCaps(nullptr),
+ mHidP_GetButtonCaps(nullptr),
+ mHidP_GetValueCaps(nullptr),
+ mHidP_GetUsages(nullptr),
+ mHidP_GetUsageValue(nullptr),
+ mHidP_GetScaledUsageValue(nullptr),
+ mModule(LoadLibraryW(L"hid.dll")) {
+ if (mModule) {
+ mHidD_GetProductString =
+ reinterpret_cast<decltype(HidD_GetProductString)*>(
+ GetProcAddress(mModule, "HidD_GetProductString"));
+ mHidP_GetCaps = reinterpret_cast<decltype(HidP_GetCaps)*>(
+ GetProcAddress(mModule, "HidP_GetCaps"));
+ mHidP_GetButtonCaps = reinterpret_cast<decltype(HidP_GetButtonCaps)*>(
+ GetProcAddress(mModule, "HidP_GetButtonCaps"));
+ mHidP_GetValueCaps = reinterpret_cast<decltype(HidP_GetValueCaps)*>(
+ GetProcAddress(mModule, "HidP_GetValueCaps"));
+ mHidP_GetUsages = reinterpret_cast<decltype(HidP_GetUsages)*>(
+ GetProcAddress(mModule, "HidP_GetUsages"));
+ mHidP_GetUsageValue = reinterpret_cast<decltype(HidP_GetUsageValue)*>(
+ GetProcAddress(mModule, "HidP_GetUsageValue"));
+ mHidP_GetScaledUsageValue =
+ reinterpret_cast<decltype(HidP_GetScaledUsageValue)*>(
+ GetProcAddress(mModule, "HidP_GetScaledUsageValue"));
+ }
+ }
+
+ ~HIDLoader() {
+ if (mModule) {
+ FreeLibrary(mModule);
+ }
+ }
+
+ explicit operator bool() {
+ return mModule && mHidD_GetProductString && mHidP_GetCaps &&
+ mHidP_GetButtonCaps && mHidP_GetValueCaps && mHidP_GetUsages &&
+ mHidP_GetUsageValue && mHidP_GetScaledUsageValue;
+ }
+
+ decltype(HidD_GetProductString)* mHidD_GetProductString;
+ decltype(HidP_GetCaps)* mHidP_GetCaps;
+ decltype(HidP_GetButtonCaps)* mHidP_GetButtonCaps;
+ decltype(HidP_GetValueCaps)* mHidP_GetValueCaps;
+ decltype(HidP_GetUsages)* mHidP_GetUsages;
+ decltype(HidP_GetUsageValue)* mHidP_GetUsageValue;
+ decltype(HidP_GetScaledUsageValue)* mHidP_GetScaledUsageValue;
+
+ private:
+ HMODULE mModule;
+};
+
+HWND sHWnd = nullptr;
+
+static void DirectInputMessageLoopOnceCallback(nsITimer* aTimer,
+ void* aClosure) {
+ MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+ MSG msg;
+ while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ aTimer->Cancel();
+ if (!sIsShutdown) {
+ aTimer->InitWithNamedFuncCallback(DirectInputMessageLoopOnceCallback,
+ nullptr, kWindowsGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT,
+ "DirectInputMessageLoopOnceCallback");
+ }
+}
+
+class WindowsGamepadService {
+ public:
+ WindowsGamepadService() {
+ mDirectInputTimer = NS_NewTimer();
+ mXInputTimer = NS_NewTimer();
+ mDeviceChangeTimer = NS_NewTimer();
+ }
+ virtual ~WindowsGamepadService() { Cleanup(); }
+
+ void DevicesChanged(bool aIsStablizing);
+
+ void StartMessageLoop() {
+ MOZ_ASSERT(mDirectInputTimer);
+ mDirectInputTimer->InitWithNamedFuncCallback(
+ DirectInputMessageLoopOnceCallback, nullptr,
+ kWindowsGamepadPollInterval, nsITimer::TYPE_ONE_SHOT,
+ "DirectInputMessageLoopOnceCallback");
+ }
+
+ void Startup();
+ void Shutdown();
+ // Parse gamepad input from a WM_INPUT message.
+ bool HandleRawInput(HRAWINPUT handle);
+ void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue);
+ size_t WriteOutputReport(const std::vector<uint8_t>& aReport);
+ static void XInputMessageLoopOnceCallback(nsITimer* aTimer, void* aClosure);
+ static void DevicesChangeCallback(nsITimer* aTimer, void* aService);
+
+ private:
+ void ScanForDevices();
+ // Look for connected raw input devices.
+ void ScanForRawInputDevices();
+ // Look for connected XInput devices.
+ bool ScanForXInputDevices();
+ bool HaveXInputGamepad(unsigned int userIndex);
+
+ bool mIsXInputMonitoring;
+ void PollXInput();
+ void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);
+
+ // Get information about a raw input gamepad.
+ bool GetRawGamepad(HANDLE handle);
+ void Cleanup();
+
+ // List of connected devices.
+ nsTArray<Gamepad> mGamepads;
+
+ HIDLoader mHID;
+ nsAutoHandle mHidHandle;
+ XInputLoader mXInput;
+
+ nsCOMPtr<nsITimer> mDirectInputTimer;
+ nsCOMPtr<nsITimer> mXInputTimer;
+ nsCOMPtr<nsITimer> mDeviceChangeTimer;
+};
+
+void WindowsGamepadService::ScanForRawInputDevices() {
+ if (!mHID) {
+ return;
+ }
+
+ UINT numDevices;
+ if (GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST)) ==
+ kRawInputError) {
+ return;
+ }
+ nsTArray<RAWINPUTDEVICELIST> devices(numDevices);
+ devices.SetLength(numDevices);
+ if (GetRawInputDeviceList(devices.Elements(), &numDevices,
+ sizeof(RAWINPUTDEVICELIST)) == kRawInputError) {
+ return;
+ }
+
+ for (unsigned i = 0; i < devices.Length(); i++) {
+ if (devices[i].dwType == RIM_TYPEHID) {
+ GetRawGamepad(devices[i].hDevice);
+ }
+ }
+}
+
+// static
+void WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer* aTimer,
+ void* aService) {
+ MOZ_ASSERT(aService);
+ WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+ self->PollXInput();
+ if (self->mIsXInputMonitoring) {
+ aTimer->Cancel();
+ aTimer->InitWithNamedFuncCallback(
+ XInputMessageLoopOnceCallback, self, kWindowsGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT, "XInputMessageLoopOnceCallback");
+ }
+}
+
+// static
+void WindowsGamepadService::DevicesChangeCallback(nsITimer* aTimer,
+ void* aService) {
+ MOZ_ASSERT(aService);
+ WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+ self->DevicesChanged(false);
+}
+
+bool WindowsGamepadService::HaveXInputGamepad(unsigned int userIndex) {
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type == kXInputGamepad &&
+ mGamepads[i].userIndex == userIndex) {
+ mGamepads[i].present = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WindowsGamepadService::ScanForXInputDevices() {
+ MOZ_ASSERT(mXInput, "XInput should be present!");
+
+ bool found = false;
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return found;
+ }
+
+ for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++) {
+ XINPUT_STATE state = {};
+
+ if (!mXInput.mXInputGetState ||
+ mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
+ continue;
+ }
+
+ found = true;
+ // See if this device is already present in our list.
+ if (HaveXInputGamepad(i)) {
+ continue;
+ }
+
+ // Not already present, add it.
+ Gamepad gamepad(kStandardGamepadAxes, kStandardGamepadButtons,
+ kXInputGamepad);
+ gamepad.userIndex = i;
+ gamepad.state = state;
+ gamepad.gamepadHandle = service->AddGamepad(
+ "xinput", GamepadMappingType::Standard, GamepadHand::_empty,
+ kStandardGamepadButtons, kStandardGamepadAxes, 0, 0,
+ 0); // TODO: Bug 680289, implement gamepad haptics for Windows.
+ mGamepads.AppendElement(std::move(gamepad));
+ }
+
+ return found;
+}
+
+void WindowsGamepadService::ScanForDevices() {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+ mGamepads[i].present = false;
+ }
+
+ if (mHID) {
+ ScanForRawInputDevices();
+ }
+ if (mXInput) {
+ mXInputTimer->Cancel();
+ if (ScanForXInputDevices()) {
+ mIsXInputMonitoring = true;
+ mXInputTimer->InitWithNamedFuncCallback(
+ XInputMessageLoopOnceCallback, this, kWindowsGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT, "XInputMessageLoopOnceCallback");
+ } else {
+ mIsXInputMonitoring = false;
+ }
+ }
+
+ // Look for devices that are no longer present and remove them.
+ for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+ if (!mGamepads[i].present) {
+ service->RemoveGamepad(mGamepads[i].gamepadHandle);
+ mGamepads.RemoveElementAt(i);
+ }
+ }
+}
+
+void WindowsGamepadService::PollXInput() {
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type != kXInputGamepad) {
+ continue;
+ }
+
+ XINPUT_STATE state = {};
+
+ if (!mXInput.mXInputGetState ||
+ mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
+ continue;
+ }
+
+ if (state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) {
+ CheckXInputChanges(mGamepads[i], state);
+ }
+ }
+}
+
+void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
+ XINPUT_STATE& state) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ // Handle digital buttons first
+ for (size_t b = 0; b < kNumMappings; b++) {
+ if (state.Gamepad.wButtons & kXIButtonMap[b].button &&
+ !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) {
+ // Button pressed
+ service->NewButtonEvent(gamepad.gamepadHandle, kXIButtonMap[b].mapped,
+ true);
+ } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) &&
+ gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) {
+ // Button released
+ service->NewButtonEvent(gamepad.gamepadHandle, kXIButtonMap[b].mapped,
+ false);
+ }
+ }
+
+ // Then triggers
+ if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
+ const bool pressed =
+ state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
+ service->NewButtonEvent(gamepad.gamepadHandle, kButtonLeftTrigger, pressed,
+ state.Gamepad.bLeftTrigger / 255.0);
+ }
+ if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
+ const bool pressed =
+ state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
+ service->NewButtonEvent(gamepad.gamepadHandle, kButtonRightTrigger, pressed,
+ state.Gamepad.bRightTrigger / 255.0);
+ }
+
+ // Finally deal with analog sticks
+ // TODO: bug 1001955 - Support deadzones.
+ if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) {
+ const float div = state.Gamepad.sThumbLX > 0 ? 32767.0 : 32768.0;
+ service->NewAxisMoveEvent(gamepad.gamepadHandle, kLeftStickXAxis,
+ state.Gamepad.sThumbLX / div);
+ }
+ if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
+ const float div = state.Gamepad.sThumbLY > 0 ? 32767.0 : 32768.0;
+ service->NewAxisMoveEvent(gamepad.gamepadHandle, kLeftStickYAxis,
+ -1.0 * state.Gamepad.sThumbLY / div);
+ }
+ if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
+ const float div = state.Gamepad.sThumbRX > 0 ? 32767.0 : 32768.0;
+ service->NewAxisMoveEvent(gamepad.gamepadHandle, kRightStickXAxis,
+ state.Gamepad.sThumbRX / div);
+ }
+ if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
+ const float div = state.Gamepad.sThumbRY > 0 ? 32767.0 : 32768.0;
+ service->NewAxisMoveEvent(gamepad.gamepadHandle, kRightStickYAxis,
+ -1.0 * state.Gamepad.sThumbRY / div);
+ }
+ gamepad.state = state;
+}
+
+// Used to sort a list of axes by HID usage.
+class HidValueComparator {
+ public:
+ 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 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.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) {
+ return false;
+ }
+
+ for (unsigned i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type == kRawInputGamepad &&
+ mGamepads[i].handle == handle) {
+ mGamepads[i].present = true;
+ return true;
+ }
+ }
+
+ RID_DEVICE_INFO rdi = {};
+ UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO);
+ if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) ==
+ kRawInputError) {
+ return false;
+ }
+ // Ensure that this is a device we care about
+ if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
+ return false;
+ }
+
+ // Device name is a mostly-opaque string.
+ if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) ==
+ kRawInputError) {
+ return false;
+ }
+
+ nsTArray<wchar_t> devname(size);
+ devname.SetLength(size);
+ if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(),
+ &size) == kRawInputError) {
+ return false;
+ }
+
+ // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
+ // device names containing "IG_" are XInput controllers. Ignore those
+ // devices since we'll handle them with XInput.
+ if (wcsstr(devname.Elements(), L"IG_")) {
+ return false;
+ }
+
+ // 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;
+ // Creating this file with FILE_FLAG_OVERLAPPED to perform
+ // an asynchronous request in WriteOutputReport.
+ mHidHandle.own(CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
+ if (mHidHandle != INVALID_HANDLE_VALUE) {
+ if (mHID.mHidD_GetProductString(mHidHandle, &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,
+ nullptr, nullptr);
+ }
+ }
+ if (gamepad_name.Length() == 0 || !gamepad_name[0]) {
+ const char kUnknown[] = "Unknown Gamepad";
+ gamepad_name.SetLength(ArrayLength(kUnknown));
+ strcpy_s(gamepad_name.Elements(), gamepad_name.Length(), kUnknown);
+ }
+
+ char gamepad_id[256] = {0};
+ _snprintf_s(gamepad_id, _TRUNCATE, "%04x-%04x-%s", rdi.hid.dwVendorId,
+ rdi.hid.dwProductId, gamepad_name.Elements());
+
+ nsTArray<uint8_t> preparsedbytes;
+ if (!GetPreparsedData(handle, preparsedbytes)) {
+ return false;
+ }
+
+ PHIDP_PREPARSED_DATA parsed =
+ reinterpret_cast<PHIDP_PREPARSED_DATA>(preparsedbytes.Elements());
+ HIDP_CAPS caps;
+ if (mHID.mHidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+
+ // Enumerate buttons.
+ USHORT count = caps.NumberInputButtonCaps;
+ nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count);
+ buttonCaps.SetLength(count);
+ if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count,
+ parsed) != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+ uint32_t numButtons = 0;
+ 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> axisCaps(count);
+ axisCaps.SetLength(count);
+ if (mHID.mHidP_GetValueCaps(HidP_Input, axisCaps.Elements(), &count,
+ parsed) != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+
+ 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);
+
+ // Looking for the exisiting ramapping rule.
+ bool defaultRemapper = false;
+ RefPtr<GamepadRemapper> remapper = GetGamepadRemapper(
+ rdi.hid.dwVendorId, rdi.hid.dwProductId, defaultRemapper);
+ 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);
+ }
+ }
+
+ // Not already present, add it.
+
+ 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] = axes[i];
+ }
+
+ gamepad.remapper = remapper.forget();
+ // TODO: Bug 680289, implement gamepad haptics for Windows.
+ gamepad.gamepadHandle = service->AddGamepad(
+ gamepad_id, gamepad.remapper->GetMappingType(), GamepadHand::_empty,
+ gamepad.remapper->GetButtonCount(), gamepad.remapper->GetAxisCount(), 0,
+ gamepad.remapper->GetLightIndicatorCount(),
+ gamepad.remapper->GetTouchEventCount());
+
+ nsTArray<GamepadLightIndicatorType> lightTypes;
+ gamepad.remapper->GetLightIndicators(lightTypes);
+ for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
+ if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
+ service->NewLightIndicatorTypeEvent(gamepad.gamepadHandle, i,
+ lightTypes[i]);
+ }
+ }
+
+ mGamepads.AppendElement(std::move(gamepad));
+ return true;
+}
+
+bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle) {
+ if (!mHID) {
+ return false;
+ }
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return false;
+ }
+
+ // First, get data from the handle
+ UINT size;
+ GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
+ nsTArray<uint8_t> data(size);
+ data.SetLength(size);
+ if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
+ sizeof(RAWINPUTHEADER)) == kRawInputError) {
+ return false;
+ }
+ PRAWINPUT raw = reinterpret_cast<PRAWINPUT>(data.Elements());
+
+ Gamepad* gamepad = nullptr;
+ for (unsigned i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type == kRawInputGamepad &&
+ mGamepads[i].handle == raw->header.hDevice) {
+ gamepad = &mGamepads[i];
+ break;
+ }
+ }
+ if (gamepad == nullptr) {
+ return false;
+ }
+
+ // Second, get the preparsed data
+ nsTArray<uint8_t> parsedbytes;
+ if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) {
+ return false;
+ }
+ PHIDP_PREPARSED_DATA parsed =
+ reinterpret_cast<PHIDP_PREPARSED_DATA>(parsedbytes.Elements());
+
+ // Get all the pressed buttons.
+ nsTArray<USAGE> usages(gamepad->numButtons);
+ usages.SetLength(gamepad->numButtons);
+ ULONG usageLength = gamepad->numButtons;
+ if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
+ &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
+ raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+
+ nsTArray<bool> buttons(gamepad->numButtons);
+ buttons.SetLength(gamepad->numButtons);
+ // If we don't zero out the buttons array first, sometimes it can reuse
+ // values.
+ memset(buttons.Elements(), 0, gamepad->numButtons * sizeof(bool));
+
+ for (unsigned i = 0; i < usageLength; i++) {
+ // The button index in usages may be larger than what we detected when
+ // enumerating gamepads. If so, warn and continue.
+ //
+ // Usage ID of 0 is reserved, so it should always be 1 or higher.
+ 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]) {
+ gamepad->remapper->RemapButtonEvent(gamepad->gamepadHandle, 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) {
+ LONG value;
+ if (mHID.mHidP_GetScaledUsageValue(
+ HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
+ gamepad->axes[i].caps.Range.UsageMin, &value, parsed,
+ (PCHAR)raw->data.hid.bRawData,
+ raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
+ continue;
+ }
+ new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
+ gamepad->axes[i].caps.LogicalMax);
+ } else {
+ ULONG value;
+ if (mHID.mHidP_GetUsageValue(
+ HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
+ gamepad->axes[i].caps.Range.UsageMin, &value, parsed,
+ (PCHAR)raw->data.hid.bRawData,
+ 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) {
+ gamepad->remapper->RemapAxisMoveEvent(gamepad->gamepadHandle, i,
+ new_value);
+ gamepad->axes[i].value = new_value;
+ }
+ }
+
+ BYTE* rawData = raw->data.hid.bRawData;
+ gamepad->remapper->ProcessTouchData(gamepad->gamepadHandle, rawData);
+
+ return true;
+}
+
+void WindowsGamepadService::SetLightIndicatorColor(
+ const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
+ const uint8_t& aGreen, const uint8_t& aBlue) {
+ // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
+ // It begins from 1 and is stored at Gamepad.id.
+ const Gamepad* gamepad = (MOZ_FIND_AND_VALIDATE(
+ aGamepadHandle, list_item.gamepadHandle == aGamepadHandle, mGamepads));
+ if (!gamepad) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ RefPtr<GamepadRemapper> remapper = gamepad->remapper;
+ if (!remapper ||
+ MOZ_IS_VALID(aLightColorIndex,
+ remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ std::vector<uint8_t> report;
+ remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
+ WriteOutputReport(report);
+}
+
+size_t WindowsGamepadService::WriteOutputReport(
+ const std::vector<uint8_t>& aReport) {
+ DCHECK(static_cast<const void*>(aReport.data()));
+ DCHECK_GE(aReport.size(), 1U);
+ if (!mHidHandle) return 0;
+
+ nsAutoHandle eventHandle(::CreateEvent(nullptr, FALSE, FALSE, nullptr));
+ OVERLAPPED overlapped = {0};
+ overlapped.hEvent = eventHandle;
+
+ // Doing an asynchronous write to allows us to time out
+ // if the write takes too long.
+ DWORD bytesWritten = 0;
+ BOOL writeSuccess =
+ ::WriteFile(mHidHandle, static_cast<const void*>(aReport.data()),
+ aReport.size(), &bytesWritten, &overlapped);
+ if (!writeSuccess) {
+ DWORD error = ::GetLastError();
+ if (error == ERROR_IO_PENDING) {
+ // Wait for the write to complete. This causes WriteOutputReport to behave
+ // synchronously but with a timeout.
+ DWORD wait_object = ::WaitForSingleObject(overlapped.hEvent, 100);
+ if (wait_object == WAIT_OBJECT_0) {
+ if (!::GetOverlappedResult(mHidHandle, &overlapped, &bytesWritten,
+ TRUE)) {
+ return 0;
+ }
+ } else {
+ // Wait failed, or the timeout was exceeded before the write completed.
+ // Cancel the write request.
+ if (::CancelIo(mHidHandle)) {
+ wait_object = ::WaitForSingleObject(overlapped.hEvent, INFINITE);
+ MOZ_ASSERT(wait_object == WAIT_OBJECT_0);
+ }
+ }
+ }
+ }
+ return writeSuccess ? bytesWritten : 0;
+}
+
+void WindowsGamepadService::Startup() { ScanForDevices(); }
+
+void WindowsGamepadService::Shutdown() { Cleanup(); }
+
+void WindowsGamepadService::Cleanup() {
+ mIsXInputMonitoring = false;
+ if (mDirectInputTimer) {
+ mDirectInputTimer->Cancel();
+ }
+ if (mXInputTimer) {
+ mXInputTimer->Cancel();
+ }
+ if (mDeviceChangeTimer) {
+ mDeviceChangeTimer->Cancel();
+ }
+
+ mGamepads.Clear();
+}
+
+void WindowsGamepadService::DevicesChanged(bool aIsStablizing) {
+ if (aIsStablizing) {
+ mDeviceChangeTimer->Cancel();
+ mDeviceChangeTimer->InitWithNamedFuncCallback(
+ DevicesChangeCallback, this, kDevicesChangedStableDelay,
+ nsITimer::TYPE_ONE_SHOT, "DevicesChangeCallback");
+ } else {
+ ScanForDevices();
+ }
+}
+
+bool RegisterRawInput(HWND hwnd, bool enable) {
+ nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages));
+ rid.SetLength(ArrayLength(kUsagePages));
+
+ for (unsigned i = 0; i < rid.Length(); i++) {
+ rid[i].usUsagePage = kUsagePages[i].usagePage;
+ rid[i].usUsage = kUsagePages[i].usage;
+ rid[i].dwFlags =
+ enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
+ rid[i].hwndTarget = hwnd;
+ }
+
+ if (!RegisterRawInputDevices(rid.Elements(), rid.Length(),
+ sizeof(RAWINPUTDEVICE))) {
+ return false;
+ }
+ return true;
+}
+
+static LRESULT CALLBACK GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ const unsigned int DBT_DEVICEARRIVAL = 0x8000;
+ const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
+ const unsigned int DBT_DEVNODES_CHANGED = 0x7;
+
+ switch (msg) {
+ case WM_DEVICECHANGE:
+ if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE ||
+ wParam == DBT_DEVNODES_CHANGED) {
+ if (gService) {
+ gService->DevicesChanged(true);
+ }
+ }
+ break;
+ case WM_INPUT:
+ if (gService) {
+ gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+class StartWindowsGamepadServiceRunnable final : public Runnable {
+ public:
+ StartWindowsGamepadServiceRunnable()
+ : Runnable("StartWindowsGamepadServiceRunnable") {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+ gService = new WindowsGamepadService();
+ gService->Startup();
+
+ if (sHWnd == nullptr) {
+ WNDCLASSW wc;
+ HMODULE hSelf = GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
+ ZeroMemory(&wc, sizeof(WNDCLASSW));
+ wc.hInstance = hSelf;
+ wc.lpfnWndProc = GamepadWindowProc;
+ wc.lpszClassName = L"MozillaGamepadClass";
+ RegisterClassW(&wc);
+ }
+
+ sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", 0, 0, 0,
+ 0, 0, nullptr, nullptr, hSelf, nullptr);
+ RegisterRawInput(sHWnd, true);
+ }
+
+ // Explicitly start the message loop
+ gService->StartMessageLoop();
+
+ return NS_OK;
+ }
+
+ private:
+ ~StartWindowsGamepadServiceRunnable() {}
+};
+
+class StopWindowsGamepadServiceRunnable final : public Runnable {
+ public:
+ StopWindowsGamepadServiceRunnable()
+ : Runnable("StopWindowsGamepadServiceRunnable") {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+ if (sHWnd) {
+ RegisterRawInput(sHWnd, false);
+ DestroyWindow(sHWnd);
+ sHWnd = nullptr;
+ }
+
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+
+ return NS_OK;
+ }
+
+ private:
+ ~StopWindowsGamepadServiceRunnable() {}
+};
+
+} // namespace
+
+namespace mozilla::dom {
+
+using namespace mozilla::ipc;
+
+void StartGamepadMonitoring() {
+ AssertIsOnBackgroundThread();
+
+ if (gMonitorThread || gService) {
+ return;
+ }
+ sIsShutdown = false;
+ NS_NewNamedThread("Gamepad", getter_AddRefs(gMonitorThread));
+ gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(),
+ NS_DISPATCH_NORMAL);
+}
+
+void StopGamepadMonitoring() {
+ AssertIsOnBackgroundThread();
+
+ if (sIsShutdown) {
+ return;
+ }
+ sIsShutdown = true;
+ gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(),
+ NS_DISPATCH_NORMAL);
+ gMonitorThread->Shutdown();
+ gMonitorThread = nullptr;
+}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue) {
+ MOZ_ASSERT(gService);
+ if (!gService) {
+ return;
+ }
+ gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
+ aGreen, aBlue);
+}
+
+} // namespace mozilla::dom