From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/gamepad/Gamepad.cpp | 188 +++ dom/gamepad/Gamepad.h | 145 ++ dom/gamepad/GamepadButton.cpp | 28 + dom/gamepad/GamepadButton.h | 53 + dom/gamepad/GamepadHandle.cpp | 40 + dom/gamepad/GamepadHandle.h | 89 + dom/gamepad/GamepadHapticActuator.cpp | 80 + dom/gamepad/GamepadHapticActuator.h | 51 + dom/gamepad/GamepadLightIndicator.cpp | 69 + dom/gamepad/GamepadLightIndicator.h | 53 + dom/gamepad/GamepadManager.cpp | 660 ++++++++ dom/gamepad/GamepadManager.h | 156 ++ dom/gamepad/GamepadMonitoring.h | 24 + dom/gamepad/GamepadPlatformService.cpp | 336 ++++ dom/gamepad/GamepadPlatformService.h | 150 ++ dom/gamepad/GamepadPose.cpp | 110 ++ dom/gamepad/GamepadPose.h | 57 + dom/gamepad/GamepadPoseState.h | 110 ++ dom/gamepad/GamepadRemapping.cpp | 2218 +++++++++++++++++++++++++ dom/gamepad/GamepadRemapping.h | 142 ++ dom/gamepad/GamepadServiceTest.cpp | 392 +++++ dom/gamepad/GamepadServiceTest.h | 115 ++ dom/gamepad/GamepadTouch.cpp | 70 + dom/gamepad/GamepadTouch.h | 47 + dom/gamepad/GamepadTouchState.h | 42 + dom/gamepad/android/AndroidGamepad.cpp | 118 ++ dom/gamepad/cocoa/CocoaGamepad.cpp | 665 ++++++++ dom/gamepad/fallback/FallbackGamepad.cpp | 22 + dom/gamepad/ipc/GamepadEventChannelChild.cpp | 63 + dom/gamepad/ipc/GamepadEventChannelChild.h | 39 + dom/gamepad/ipc/GamepadEventChannelParent.cpp | 109 ++ dom/gamepad/ipc/GamepadEventChannelParent.h | 48 + dom/gamepad/ipc/GamepadEventTypes.ipdlh | 78 + dom/gamepad/ipc/GamepadMessageUtils.h | 154 ++ dom/gamepad/ipc/GamepadTestChannelChild.cpp | 34 + dom/gamepad/ipc/GamepadTestChannelChild.h | 45 + dom/gamepad/ipc/GamepadTestChannelParent.cpp | 129 ++ dom/gamepad/ipc/GamepadTestChannelParent.h | 49 + dom/gamepad/ipc/PGamepadEventChannel.ipdl | 31 + dom/gamepad/ipc/PGamepadTestChannel.ipdl | 23 + dom/gamepad/linux/LinuxGamepad.cpp | 362 ++++ dom/gamepad/linux/udev.h | 150 ++ dom/gamepad/moz.build | 81 + dom/gamepad/windows/WindowsGamepad.cpp | 1136 +++++++++++++ 44 files changed, 8761 insertions(+) create mode 100644 dom/gamepad/Gamepad.cpp create mode 100644 dom/gamepad/Gamepad.h create mode 100644 dom/gamepad/GamepadButton.cpp create mode 100644 dom/gamepad/GamepadButton.h create mode 100644 dom/gamepad/GamepadHandle.cpp create mode 100644 dom/gamepad/GamepadHandle.h create mode 100644 dom/gamepad/GamepadHapticActuator.cpp create mode 100644 dom/gamepad/GamepadHapticActuator.h create mode 100644 dom/gamepad/GamepadLightIndicator.cpp create mode 100644 dom/gamepad/GamepadLightIndicator.h create mode 100644 dom/gamepad/GamepadManager.cpp create mode 100644 dom/gamepad/GamepadManager.h create mode 100644 dom/gamepad/GamepadMonitoring.h create mode 100644 dom/gamepad/GamepadPlatformService.cpp create mode 100644 dom/gamepad/GamepadPlatformService.h create mode 100644 dom/gamepad/GamepadPose.cpp create mode 100644 dom/gamepad/GamepadPose.h create mode 100644 dom/gamepad/GamepadPoseState.h create mode 100644 dom/gamepad/GamepadRemapping.cpp create mode 100644 dom/gamepad/GamepadRemapping.h create mode 100644 dom/gamepad/GamepadServiceTest.cpp create mode 100644 dom/gamepad/GamepadServiceTest.h create mode 100644 dom/gamepad/GamepadTouch.cpp create mode 100644 dom/gamepad/GamepadTouch.h create mode 100644 dom/gamepad/GamepadTouchState.h create mode 100644 dom/gamepad/android/AndroidGamepad.cpp create mode 100644 dom/gamepad/cocoa/CocoaGamepad.cpp create mode 100644 dom/gamepad/fallback/FallbackGamepad.cpp create mode 100644 dom/gamepad/ipc/GamepadEventChannelChild.cpp create mode 100644 dom/gamepad/ipc/GamepadEventChannelChild.h create mode 100644 dom/gamepad/ipc/GamepadEventChannelParent.cpp create mode 100644 dom/gamepad/ipc/GamepadEventChannelParent.h create mode 100644 dom/gamepad/ipc/GamepadEventTypes.ipdlh create mode 100644 dom/gamepad/ipc/GamepadMessageUtils.h create mode 100644 dom/gamepad/ipc/GamepadTestChannelChild.cpp create mode 100644 dom/gamepad/ipc/GamepadTestChannelChild.h create mode 100644 dom/gamepad/ipc/GamepadTestChannelParent.cpp create mode 100644 dom/gamepad/ipc/GamepadTestChannelParent.h create mode 100644 dom/gamepad/ipc/PGamepadEventChannel.ipdl create mode 100644 dom/gamepad/ipc/PGamepadTestChannel.ipdl create mode 100644 dom/gamepad/linux/LinuxGamepad.cpp create mode 100644 dom/gamepad/linux/udev.h create mode 100644 dom/gamepad/moz.build create mode 100644 dom/gamepad/windows/WindowsGamepad.cpp (limited to 'dom/gamepad') 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 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::Clone(nsISupports* aParent) { + RefPtr 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 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 +#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 Clone(nsISupports* aParent); + + nsISupports* GetParentObject() const { return mParent; } + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle 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>& aButtons) const { + aButtons = mButtons.Clone(); + } + + void GetAxes(nsTArray& aAxes) const { aAxes = mAxes.Clone(); } + + GamepadPose* GetPose() const { return mPose; } + + void GetHapticActuators( + nsTArray>& aHapticActuators) const { + aHapticActuators = mHapticActuators.Clone(); + } + + void GetLightIndicators( + nsTArray>& aLightIndicators) const { + aLightIndicators = mLightIndicators.Clone(); + } + + void GetTouchEvents(nsTArray>& aTouchEvents) const { + aTouchEvents = mTouchEvents.Clone(); + } + + GamepadHandle GetHandle() const { return mHandle; } + + private: + virtual ~Gamepad() = default; + void UpdateTimestamp(); + + protected: + nsCOMPtr 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> mButtons; + nsTArray mAxes; + DOMHighResTimeStamp mTimestamp; + RefPtr mPose; + nsTArray> mHapticActuators; + nsTArray> mLightIndicators; + nsTArray> mTouchEvents; + nsTHashMap 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 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 +#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 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 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..c13e2b912e --- /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 "PLDHashTable.h" +#include +#include + +namespace IPC { + +template +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; +}; + +static_assert(std::is_trivially_copyable::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 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 GamepadHapticActuator::Pulse(double aValue, + double aDuration, + ErrorResult& aRv) { + nsCOMPtr global = do_QueryInterface(GetParentObject()); + MOZ_ASSERT(global); + + RefPtr 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 = 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 aGivenProto) override; + + already_AddRefed Pulse(double aValue, double aDuration, + ErrorResult& aRv); + + GamepadHapticActuatorType Type() const; + + void Set(const GamepadHapticActuator* aOther); + + private: + virtual ~GamepadHapticActuator() = default; + + protected: + nsCOMPtr 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 aGivenProto) { + return GamepadLightIndicator_Binding::Wrap(aCx, this, aGivenProto); +} + +nsISupports* GamepadLightIndicator::GetParentObject() const { return mParent; } + +already_AddRefed GamepadLightIndicator::SetColor( + const GamepadLightColor& color, ErrorResult& aRv) { + nsCOMPtr global = do_QueryInterface(GetParentObject()); + MOZ_ASSERT(global); + + RefPtr gamepadManager(GamepadManager::GetService()); + MOZ_ASSERT(gamepadManager); + + RefPtr 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 aGivenProto) override; + + already_AddRefed SetColor(const GamepadLightColor& color, + ErrorResult& aRv); + + void SetType(GamepadLightIndicatorType aType) { mType = aType; } + + GamepadLightIndicatorType Type() const; + + void Set(const GamepadLightIndicator* aOther); + + private: + virtual ~GamepadLightIndicator(); + + nsCOMPtr 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..2f643b952b --- /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 + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +namespace { + +const nsTArray>::index_type NoIndex = + nsTArray>::NoIndex; + +bool sShutdown = false; + +StaticRefPtr 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 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 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 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 GamepadManager::GetGamepad( + GamepadHandle aHandle) const { + RefPtr 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 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 = 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 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 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 = GetGamepad(aHandle); + if (!gamepad) { + return; + } + + // Hold on to listeners in a separate array because firing events + // can mutate the mListeners array. + nsTArray> 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 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 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 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 = GetGamepad(aHandle); + if (!gamepad) { + return; + } + + aGamepad->SyncState(gamepad); +} + +// static +bool GamepadManager::IsServiceRunning() { return !!gGamepadManagerSingleton; } + +// static +already_AddRefed GamepadManager::GetService() { + if (sShutdown) { + return nullptr; + } + + if (!gGamepadManagerSingleton) { + RefPtr manager = new GamepadManager(); + nsresult rv = manager->Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + gGamepadManagerSingleton = manager; + ClearOnShutdown(&gGamepadManagerSingleton); + } + + RefPtr 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 = 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 window = ToSupports(aWindow); + RefPtr gamepad = GetGamepad(aHandle); + if (!gamepad) { + return; + } + RefPtr clonedGamepad = gamepad->Clone(window); + aWindow->AddGamepad(aHandle, clonedGamepad); + } else { + aWindow->RemoveGamepad(aHandle); + } +} + +void GamepadManager::Update(const GamepadChangeEvent& aEvent) { + if (!mEnabled || mShuttingDown) { + 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(a.mapping()), + static_cast(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> 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() || + listeners[i]->ShouldResistFingerprinting()) { + 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 = 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 = + 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 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 GamepadManager::VibrateHaptic( + GamepadHandle aHandle, uint32_t aHapticIndex, double aIntensity, + double aDuration, nsIGlobalObject* aGlobal, ErrorResult& aRv) { + RefPtr 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 GamepadManager::SetLightIndicatorColor( + GamepadHandle aHandle, uint32_t aLightColorIndex, uint8_t aRed, + uint8_t aGreen, uint8_t aBlue, nsIGlobalObject* aGlobal, ErrorResult& aRv) { + RefPtr 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 + +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 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 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 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 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 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, Gamepad> mGamepads; + // Inner windows that are listening for gamepad events. + // has been sent to that window. + nsTArray> 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& aGamepadHandle, + const Tainted& 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 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* 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::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 service(gGamepadPlatformServiceSingleton); + return service.forget(); +} + +template +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(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(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(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(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(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(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(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 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 +#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> 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 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 + 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> mChannelParents; + + // This mutex protects mChannelParents from race condition + // between background and monitor thread + Mutex mMutex MOZ_UNANNOTATED; + + std::map 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 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 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 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 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 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 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 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 aGivenProto) override; + + bool HasOrientation() const; + bool HasPosition() const; + virtual void GetPosition(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetLinearVelocity(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetLinearAcceleration(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetOrientation(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetAngularVelocity(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetAngularAcceleration(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + void SetPoseState(const GamepadPoseState& aPose); + const GamepadPoseState& GetPoseState(); + + private: + virtual ~GamepadPose(); + + nsCOMPtr 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(&isOrientationValid) + + sizeof(isOrientationValid) - reinterpret_cast(&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..7c5a858bcc --- /dev/null +++ b/dom/gamepad/GamepadRemapping.cpp @@ -0,0 +1,2218 @@ +/* -*- 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 +#include + +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(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 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 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 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 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 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 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 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 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 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 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 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 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 service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + + const std::vector 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& 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& 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 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 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 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 service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + + const std::vector 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 mLastTouchId; + nsTArray 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 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 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 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 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 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 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 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 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 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 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 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 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 XboxSeriesXRemapper 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 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; + case 148: { + const double value = AxisToButtonValue(aValue); + service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER, + value > BUTTON_THRESHOLD_VALUE, value); + break; + } + case 149: { + const double value = AxisToButtonValue(aValue); + service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER, + value > BUTTON_THRESHOLD_VALUE, value); + break; + } + default: + NS_WARNING( + nsPrintfCString( + "Axis idx '%d' doesn't support in XboxSeriesXRemapper().", + aAxis) + .get()); + break; + } + } + + virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton, + bool aPressed) const override { + RefPtr service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + + if (GetButtonCount() <= aButton) { + NS_WARNING( + nsPrintfCString( + "Button idx '%d' doesn't support in XboxSeriesXRemapper().", + aButton) + .get()); + return; + } + + const std::unordered_map 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}, + {10, BUTTON_INDEX_BACK_SELECT}, + {11, BUTTON_INDEX_START}, + {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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 service = + GamepadPlatformService::GetParentService(); + if (!service) { + return; + } + + const std::vector 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 GetGamepadRemapper(const uint16_t aVendorId, + const uint16_t aProductId, + bool& aUsingDefault) { + const std::vector 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::kMicrosoftProductXboxSeriesXWireless, + new XboxSeriesXRemapper()}, + {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((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 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..8b2038889f --- /dev/null +++ b/dom/gamepad/GamepadRemapping.h @@ -0,0 +1,142 @@ +/* -*- 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, + // Microsoft Xbox Series X Wireless + kMicrosoftProductXboxSeriesXWireless = 0x045e0b13, + // 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& aTypes) const {} + virtual uint32_t GetTouchEventCount() const { return 0; } + virtual void GetLightColorReport(uint8_t aRed, uint8_t aGreen, uint8_t aBlue, + std::vector& 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 remapping; +}; + +already_AddRefed 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::CreateTestService( + nsPIDOMWindowInner* aWindow) { + MOZ_ASSERT(aWindow); + RefPtr 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 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 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 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 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 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 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 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 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 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 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 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 GamepadServiceTest::NewPoseMove( + uint32_t aHandleSlot, const Nullable& aOrient, + const Nullable& aPos, + const Nullable& aAngVelocity, + const Nullable& aAngAcceleration, + const Nullable& aLinVelocity, + const Nullable& 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 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 GamepadServiceTest::NewTouch( + uint32_t aHandleSlot, uint32_t aTouchArrayIndex, uint32_t aTouchId, + uint8_t aSurfaceId, const Float32Array& aPos, + const Nullable& 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 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 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 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 RemoveGamepad(uint32_t aHandleSlot, + ErrorResult& aRv); + + already_AddRefed NewButtonEvent(uint32_t aHandleSlot, + uint32_t aButton, bool aPressed, + bool aTouched, ErrorResult& aRv); + + already_AddRefed NewButtonValueEvent(uint32_t aHandleSlot, + uint32_t aButton, bool aPressed, + bool aTouched, double aValue, + ErrorResult& aRv); + + already_AddRefed NewAxisMoveEvent(uint32_t aHandleSlot, + uint32_t aAxis, double aValue, + ErrorResult& aRv); + + already_AddRefed NewPoseMove( + uint32_t aHandleSlot, const Nullable& aOrient, + const Nullable& aPos, + const Nullable& aAngVelocity, + const Nullable& aAngAcceleration, + const Nullable& aLinVelocity, + const Nullable& aLinAcceleration, ErrorResult& aRv); + + already_AddRefed NewTouch(uint32_t aHandleSlot, + uint32_t aTouchArrayIndex, + uint32_t aTouchId, uint8_t aSurfaceId, + const Float32Array& aPos, + const Nullable& aSurfDim, + ErrorResult& aRv); + + void Shutdown(); + + static already_AddRefed CreateTestService( + nsPIDOMWindowInner* aWindow); + nsPIDOMWindowInner* GetParentObject() const { return mWindow; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + private: + // Hold a reference to the gamepad service so we don't have to worry about + // execution order in tests. + RefPtr mService; + nsCOMPtr 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 mChild; + nsTArray mGamepadHandles; + + nsRefPtrHashtable 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 aGivenProto) { + return GamepadTouch_Binding::Wrap(aCx, this, aGivenProto); +} + +void GamepadTouch::GetPosition(JSContext* aCx, + JS::MutableHandle 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 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 aGivenProto) override; + uint32_t TouchId() const { return mTouchState.touchId; } + uint32_t SurfaceId() const { return mTouchState.surfaceId; } + void GetPosition(JSContext* aCx, JS::MutableHandle aRetval, + ErrorResult& aRv); + void GetSurfaceDimensions(JSContext* aCx, + JS::MutableHandle aRetval, + ErrorResult& aRv); + void SetTouchState(const GamepadTouchState& aTouch); + void Set(const GamepadTouch* aOther); + + private: + virtual ~GamepadTouch(); + + nsCOMPtr mParent; + JS::Heap mPosition; + JS::Heap 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() = delete; + + public: + static jni::ByteArray::LocalRef NativeAddGamepad() { + RefPtr 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(&gamepadHandle), sizeof(gamepadHandle)); + } + + static void NativeRemoveGamepad(jni::ByteArray::Param aGamepadHandleBytes) { + GamepadHandle handle = JNIByteArrayToGamepadHandle(aGamepadHandleBytes); + + RefPtr 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 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 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(&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& aGamepadHandle, + const Tainted& 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..544c2e42b7 --- /dev/null +++ b/dom/gamepad/cocoa/CocoaGamepad.cpp @@ -0,0 +1,665 @@ +/* -*- 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 +#include +#include +#include + +#include +#include + +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