summaryrefslogtreecommitdiff
path: root/dom/gamepad
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/gamepad
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/gamepad')
-rw-r--r--dom/gamepad/Gamepad.cpp147
-rw-r--r--dom/gamepad/Gamepad.h134
-rw-r--r--dom/gamepad/GamepadButton.cpp30
-rw-r--r--dom/gamepad/GamepadButton.h69
-rw-r--r--dom/gamepad/GamepadManager.cpp699
-rw-r--r--dom/gamepad/GamepadManager.h156
-rw-r--r--dom/gamepad/GamepadMonitoring.cpp32
-rw-r--r--dom/gamepad/GamepadMonitoring.h24
-rw-r--r--dom/gamepad/GamepadPlatformService.cpp265
-rw-r--r--dom/gamepad/GamepadPlatformService.h104
-rw-r--r--dom/gamepad/GamepadPose.cpp120
-rw-r--r--dom/gamepad/GamepadPose.h58
-rw-r--r--dom/gamepad/GamepadPoseState.h88
-rw-r--r--dom/gamepad/GamepadServiceTest.cpp283
-rw-r--r--dom/gamepad/GamepadServiceTest.h89
-rw-r--r--dom/gamepad/android/AndroidGamepad.cpp84
-rw-r--r--dom/gamepad/cocoa/CocoaGamepad.cpp589
-rw-r--r--dom/gamepad/fallback/FallbackGamepad.cpp20
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelChild.cpp42
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelChild.h24
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelParent.cpp101
-rw-r--r--dom/gamepad/ipc/GamepadEventChannelParent.h31
-rw-r--r--dom/gamepad/ipc/GamepadEventTypes.ipdlh59
-rw-r--r--dom/gamepad/ipc/GamepadMessageUtils.h82
-rw-r--r--dom/gamepad/ipc/GamepadServiceType.h20
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelChild.cpp32
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelChild.h29
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelParent.cpp63
-rw-r--r--dom/gamepad/ipc/GamepadTestChannelParent.h33
-rw-r--r--dom/gamepad/ipc/PGamepadEventChannel.ipdl21
-rw-r--r--dom/gamepad/ipc/PGamepadTestChannel.ipdl21
-rw-r--r--dom/gamepad/linux/LinuxGamepad.cpp390
-rw-r--r--dom/gamepad/linux/udev.h150
-rw-r--r--dom/gamepad/moz.build83
-rw-r--r--dom/gamepad/windows/WindowsGamepad.cpp1096
35 files changed, 5268 insertions, 0 deletions
diff --git a/dom/gamepad/Gamepad.cpp b/dom/gamepad/Gamepad.cpp
new file mode 100644
index 0000000000..0c59b190e0
--- /dev/null
+++ b/dom/gamepad/Gamepad.cpp
@@ -0,0 +1,147 @@
+/* -*- 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/dom/GamepadBinding.h"
+
+namespace mozilla {
+namespace 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)
+
+void
+Gamepad::UpdateTimestamp()
+{
+ nsCOMPtr<nsPIDOMWindowInner> newWindow(do_QueryInterface(mParent));
+ if(newWindow) {
+ Performance* perf = newWindow->GetPerformance();
+ if (perf) {
+ mTimestamp = perf->Now();
+ }
+ }
+}
+
+Gamepad::Gamepad(nsISupports* aParent,
+ const nsAString& aID, uint32_t aIndex,
+ GamepadMappingType aMapping,
+ uint32_t aNumButtons, uint32_t aNumAxes)
+ : mParent(aParent),
+ mID(aID),
+ mIndex(aIndex),
+ mMapping(aMapping),
+ 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);
+ UpdateTimestamp();
+}
+
+void
+Gamepad::SetIndex(uint32_t aIndex)
+{
+ mIndex = aIndex;
+}
+
+void
+Gamepad::SetConnected(bool aConnected)
+{
+ mConnected = aConnected;
+}
+
+void
+Gamepad::SetButton(uint32_t aButton, bool aPressed, double aValue)
+{
+ MOZ_ASSERT(aButton < mButtons.Length());
+ mButtons[aButton]->SetPressed(aPressed);
+ 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;
+ GamepadBinding::ClearCachedAxesValue(this);
+ }
+ UpdateTimestamp();
+}
+
+void
+Gamepad::SetPose(const GamepadPoseState& aPose)
+{
+ mPose->SetPoseState(aPose);
+}
+
+void
+Gamepad::SyncState(Gamepad* aOther)
+{
+ const char* kGamepadExtEnabledPref = "dom.gamepad.extensions.enabled";
+
+ 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]->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) {
+ GamepadBinding::ClearCachedAxesValue(this);
+ }
+
+ if (Preferences::GetBool(kGamepadExtEnabledPref)) {
+ MOZ_ASSERT(aOther->GetPose());
+ mPose->SetPoseState(aOther->GetPose()->GetPoseState());
+ }
+
+ UpdateTimestamp();
+}
+
+already_AddRefed<Gamepad>
+Gamepad::Clone(nsISupports* aParent)
+{
+ RefPtr<Gamepad> out =
+ new Gamepad(aParent, mID, mIndex, mMapping,
+ mButtons.Length(), mAxes.Length());
+ out->SyncState(this);
+ return out.forget();
+}
+
+/* virtual */ JSObject*
+Gamepad::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return GamepadBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/Gamepad.h b/dom/gamepad/Gamepad.h
new file mode 100644
index 0000000000..e388348f0f
--- /dev/null
+++ b/dom/gamepad/Gamepad.h
@@ -0,0 +1,134 @@
+/* -*- 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/ErrorResult.h"
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadButton.h"
+#include "mozilla/dom/GamepadPose.h"
+#include "mozilla/dom/Performance.h"
+#include <stdint.h>
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+// 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, uint32_t aIndex,
+ GamepadMappingType aMapping,
+ uint32_t aNumButtons, uint32_t aNumAxes);
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Gamepad)
+
+ void SetConnected(bool aConnected);
+ void SetButton(uint32_t aButton, bool aPressed, double aValue);
+ void SetAxis(uint32_t aAxis, double aValue);
+ void SetIndex(uint32_t aIndex);
+ void SetPose(const GamepadPoseState& aPose);
+
+ // Make the state of this gamepad equivalent to other.
+ void SyncState(Gamepad* aOther);
+
+ // Return a new Gamepad containing the same data as this object,
+ // parented to aParent.
+ already_AddRefed<Gamepad> Clone(nsISupports* aParent);
+
+ nsISupports* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetId(nsAString& aID) const
+ {
+ aID = mID;
+ }
+
+ DOMHighResTimeStamp Timestamp() const
+ {
+ return mTimestamp;
+ }
+
+ GamepadMappingType Mapping()
+ {
+ return mMapping;
+ }
+
+ bool Connected() const
+ {
+ return mConnected;
+ }
+
+ uint32_t Index() const
+ {
+ return mIndex;
+ }
+
+ void GetButtons(nsTArray<RefPtr<GamepadButton>>& aButtons) const
+ {
+ aButtons = mButtons;
+ }
+
+ void GetAxes(nsTArray<double>& aAxes) const
+ {
+ aAxes = mAxes;
+ }
+
+ GamepadPose* GetPose() const
+ {
+ return mPose;
+ }
+
+private:
+ virtual ~Gamepad() {}
+ void UpdateTimestamp();
+
+protected:
+ nsCOMPtr<nsISupports> mParent;
+ nsString mID;
+ uint32_t mIndex;
+
+ // The mapping in use.
+ GamepadMappingType mMapping;
+
+ // true if this gamepad is currently connected.
+ bool mConnected;
+
+ // Current state of buttons, axes.
+ nsTArray<RefPtr<GamepadButton>> mButtons;
+ nsTArray<double> mAxes;
+ DOMHighResTimeStamp mTimestamp;
+ RefPtr<GamepadPose> mPose;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_gamepad_Gamepad_h
diff --git a/dom/gamepad/GamepadButton.cpp b/dom/gamepad/GamepadButton.cpp
new file mode 100644
index 0000000000..154a139853
--- /dev/null
+++ b/dom/gamepad/GamepadButton.cpp
@@ -0,0 +1,30 @@
+/* -*- 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 {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GamepadButton)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GamepadButton)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GamepadButton)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GamepadButton, mParent)
+
+/* virtual */ JSObject*
+GamepadButton::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return GamepadButtonBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/GamepadButton.h b/dom/gamepad/GamepadButton.h
new file mode 100644
index 0000000000..2855959e58
--- /dev/null
+++ b/dom/gamepad/GamepadButton.h
@@ -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/. */
+
+#ifndef mozilla_dom_gamepad_GamepadButton_h
+#define mozilla_dom_gamepad_GamepadButton_h
+
+#include <stdint.h>
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class GamepadButton : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ explicit GamepadButton(nsISupports* aParent) : mParent(aParent),
+ mPressed(false),
+ mValue(0)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GamepadButton)
+
+ nsISupports* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetPressed(bool aPressed)
+ {
+ mPressed = aPressed;
+ }
+
+ void SetValue(double aValue)
+ {
+ mValue = aValue;
+ }
+
+ bool Pressed() const
+ {
+ return mPressed;
+ }
+
+ double Value() const
+ {
+ return mValue;
+ }
+
+private:
+ virtual ~GamepadButton() {}
+
+protected:
+ nsCOMPtr<nsISupports> mParent;
+ bool mPressed;
+ double mValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_gamepad_GamepadButton_h
diff --git a/dom/gamepad/GamepadManager.cpp b/dom/gamepad/GamepadManager.cpp
new file mode 100644
index 0000000000..dde71dd0a9
--- /dev/null
+++ b/dom/gamepad/GamepadManager.cpp
@@ -0,0 +1,699 @@
+/* -*- 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/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsAutoPtr.h"
+#include "nsGlobalWindow.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsThreadUtils.h"
+#include "VRManagerChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+
+#include <cstddef>
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+const char* kGamepadEnabledPref = "dom.gamepad.enabled";
+const char* kGamepadEventsEnabledPref =
+ "dom.gamepad.non_standard_events.enabled";
+
+const nsTArray<RefPtr<nsGlobalWindow>>::index_type NoIndex =
+ nsTArray<RefPtr<nsGlobalWindow>>::NoIndex;
+
+bool sShutdown = false;
+
+StaticRefPtr<GamepadManager> gGamepadManagerSingleton;
+const uint32_t VR_GAMEPAD_IDX_OFFSET = 0x01 << 16;
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver)
+
+GamepadManager::GamepadManager()
+ : mEnabled(false),
+ mNonstandardEventsEnabled(false),
+ mShuttingDown(false)
+{}
+
+nsresult
+GamepadManager::Init()
+{
+ mEnabled = IsAPIEnabled();
+ mNonstandardEventsEnabled =
+ Preferences::GetBool(kGamepadEventsEnabledPref, false);
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ rv = observerService->AddObserver(this,
+ NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+ false);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GamepadManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ }
+ BeginShutdown();
+ return NS_OK;
+}
+
+void
+GamepadManager::StopMonitoring()
+{
+ for (uint32_t i = 0; i < mChannelChildren.Length(); ++i) {
+ mChannelChildren[i]->SendGamepadListenerRemoved();
+ }
+ mChannelChildren.Clear();
+ mGamepads.Clear();
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+ mVRChannelChild = gfx::VRManagerChild::Get();
+ mVRChannelChild->SendControllerListenerRemoved();
+#endif
+}
+
+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(nsGlobalWindow* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mEnabled || mShuttingDown) {
+ return;
+ }
+
+ if (mListeners.IndexOf(aWindow) != NoIndex) {
+ return; // already exists
+ }
+
+ mListeners.AppendElement(aWindow);
+
+ // IPDL child has been created
+ if (!mChannelChildren.IsEmpty()) {
+ return;
+ }
+
+ PBackgroundChild *actor = BackgroundChild::GetForCurrentThread();
+ //Try to get the PBackground Child actor
+ if (actor) {
+ ActorCreated(actor);
+ } else {
+ Unused << BackgroundChild::GetOrCreateForCurrentThread(this);
+ }
+}
+
+void
+GamepadManager::RemoveListener(nsGlobalWindow* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+
+ 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 (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
+ aWindow->RemoveGamepad(iter.Key());
+ }
+
+ mListeners.RemoveElement(aWindow);
+
+ if (mListeners.IsEmpty()) {
+ StopMonitoring();
+ }
+}
+
+already_AddRefed<Gamepad>
+GamepadManager::GetGamepad(uint32_t aIndex) const
+{
+ RefPtr<Gamepad> gamepad;
+ if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
+ return gamepad.forget();
+ }
+
+ return nullptr;
+}
+
+uint32_t GamepadManager::GetGamepadIndexWithServiceType(uint32_t aIndex,
+ GamepadServiceType aServiceType)
+{
+ uint32_t newIndex = 0;
+
+ switch (aServiceType) {
+ case GamepadServiceType::Standard:
+ {
+ MOZ_ASSERT(aIndex <= VR_GAMEPAD_IDX_OFFSET);
+ newIndex = aIndex;
+ break;
+ }
+ case GamepadServiceType::VR:
+ {
+ newIndex = aIndex + VR_GAMEPAD_IDX_OFFSET;
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ return newIndex;
+}
+
+void
+GamepadManager::AddGamepad(uint32_t aIndex,
+ const nsAString& aId,
+ GamepadMappingType aMapping,
+ GamepadServiceType aServiceType,
+ uint32_t aNumButtons,
+ uint32_t aNumAxes)
+{
+ //TODO: bug 852258: get initial button/axis state
+ RefPtr<Gamepad> gamepad =
+ new Gamepad(nullptr,
+ aId,
+ 0, // index is set by global window
+ aMapping,
+ aNumButtons,
+ aNumAxes);
+
+ uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
+
+ // We store the gamepad related to its index given by the parent process,
+ // and no duplicate index is allowed.
+ MOZ_ASSERT(!mGamepads.Get(newIndex, nullptr));
+ mGamepads.Put(newIndex, gamepad);
+ NewConnectionEvent(newIndex, true);
+}
+
+void
+GamepadManager::RemoveGamepad(uint32_t aIndex, GamepadServiceType aServiceType)
+{
+ uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
+
+ RefPtr<Gamepad> gamepad = GetGamepad(newIndex);
+ if (!gamepad) {
+ NS_WARNING("Trying to delete gamepad with invalid index");
+ return;
+ }
+ gamepad->SetConnected(false);
+ NewConnectionEvent(newIndex, false);
+ mGamepads.Remove(newIndex);
+}
+
+void
+GamepadManager::NewButtonEvent(uint32_t aIndex, GamepadServiceType aServiceType,
+ uint32_t aButton, bool aPressed, double aValue)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
+
+ RefPtr<Gamepad> gamepad = GetGamepad(newIndex);
+ if (!gamepad) {
+ return;
+ }
+
+ gamepad->SetButton(aButton, aPressed, aValue);
+
+ // Hold on to listeners in a separate array because firing events
+ // can mutate the mListeners array.
+ nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+ MOZ_ASSERT(!listeners.IsEmpty());
+
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+ MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+ // Only send events to non-background windows
+ if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+ listeners[i]->GetOuterWindow()->IsBackground()) {
+ continue;
+ }
+
+ bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], newIndex);
+
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(newIndex);
+ if (listenerGamepad) {
+ listenerGamepad->SetButton(aButton, aPressed, aValue);
+ if (firstTime) {
+ FireConnectionEvent(listeners[i], listenerGamepad, true);
+ }
+ if (mNonstandardEventsEnabled) {
+ // Fire event
+ FireButtonEvent(listeners[i], listenerGamepad, aButton, aValue);
+ }
+ }
+ }
+}
+
+void
+GamepadManager::FireButtonEvent(EventTarget* aTarget,
+ Gamepad* aGamepad,
+ uint32_t aButton,
+ double aValue)
+{
+ nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") :
+ NS_LITERAL_STRING("gamepadbuttonup");
+ GamepadButtonEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mGamepad = aGamepad;
+ init.mButton = aButton;
+ RefPtr<GamepadButtonEvent> event =
+ GamepadButtonEvent::Constructor(aTarget, name, init);
+
+ event->SetTrusted(true);
+
+ bool defaultActionEnabled = true;
+ aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::NewAxisMoveEvent(uint32_t aIndex, GamepadServiceType aServiceType,
+ uint32_t aAxis, double aValue)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
+
+ RefPtr<Gamepad> gamepad = GetGamepad(newIndex);
+ if (!gamepad) {
+ return;
+ }
+ gamepad->SetAxis(aAxis, aValue);
+
+ // Hold on to listeners in a separate array because firing events
+ // can mutate the mListeners array.
+ nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+ MOZ_ASSERT(!listeners.IsEmpty());
+
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+ MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+ // Only send events to non-background windows
+ if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+ listeners[i]->GetOuterWindow()->IsBackground()) {
+ continue;
+ }
+
+ bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], newIndex);
+
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(newIndex);
+ if (listenerGamepad) {
+ listenerGamepad->SetAxis(aAxis, aValue);
+ if (firstTime) {
+ FireConnectionEvent(listeners[i], listenerGamepad, true);
+ }
+ if (mNonstandardEventsEnabled) {
+ // Fire event
+ FireAxisMoveEvent(listeners[i], listenerGamepad, aAxis, aValue);
+ }
+ }
+ }
+}
+
+void
+GamepadManager::FireAxisMoveEvent(EventTarget* aTarget,
+ Gamepad* aGamepad,
+ uint32_t aAxis,
+ double aValue)
+{
+ GamepadAxisMoveEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mGamepad = aGamepad;
+ init.mAxis = aAxis;
+ init.mValue = aValue;
+ RefPtr<GamepadAxisMoveEvent> event =
+ GamepadAxisMoveEvent::Constructor(aTarget,
+ NS_LITERAL_STRING("gamepadaxismove"),
+ init);
+
+ event->SetTrusted(true);
+
+ bool defaultActionEnabled = true;
+ aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::NewPoseEvent(uint32_t aIndex, GamepadServiceType aServiceType,
+ const GamepadPoseState& aPose)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
+
+ RefPtr<Gamepad> gamepad = GetGamepad(newIndex);
+ if (!gamepad) {
+ return;
+ }
+ gamepad->SetPose(aPose);
+
+ // Hold on to listeners in a separate array because firing events
+ // can mutate the mListeners array.
+ nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+ MOZ_ASSERT(!listeners.IsEmpty());
+
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+ MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+ // Only send events to non-background windows
+ if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+ listeners[i]->GetOuterWindow()->IsBackground()) {
+ continue;
+ }
+
+ bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], newIndex);
+
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(newIndex);
+ if (listenerGamepad) {
+ listenerGamepad->SetPose(aPose);
+ if (firstTime) {
+ FireConnectionEvent(listeners[i], listenerGamepad, true);
+ }
+ }
+ }
+}
+
+void
+GamepadManager::NewConnectionEvent(uint32_t aIndex, bool aConnected)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+ if (!gamepad) {
+ return;
+ }
+
+ // Hold on to listeners in a separate array because firing events
+ // can mutate the mListeners array.
+ nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+ MOZ_ASSERT(!listeners.IsEmpty());
+
+ if (aConnected) {
+ for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+ MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+ // Only send events to non-background windows
+ if (!listeners[i]->AsInner()->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], aIndex);
+
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+ 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.
+
+ if (WindowHasSeenGamepad(listeners[i], aIndex)) {
+ RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+ if (listenerGamepad) {
+ listenerGamepad->SetConnected(false);
+ // Fire event
+ FireConnectionEvent(listeners[i], listenerGamepad, false);
+ listeners[i]->RemoveGamepad(aIndex);
+ }
+ }
+ }
+ }
+}
+
+void
+GamepadManager::FireConnectionEvent(EventTarget* aTarget,
+ Gamepad* aGamepad,
+ bool aConnected)
+{
+ nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") :
+ NS_LITERAL_STRING("gamepaddisconnected");
+ GamepadEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mGamepad = aGamepad;
+ RefPtr<GamepadEvent> event =
+ GamepadEvent::Constructor(aTarget, name, init);
+
+ event->SetTrusted(true);
+
+ bool defaultActionEnabled = true;
+ aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad)
+{
+ if (mShuttingDown || !mEnabled) {
+ return;
+ }
+
+ RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+ if (!gamepad) {
+ return;
+ }
+
+ aGamepad->SyncState(gamepad);
+}
+
+// static
+bool
+GamepadManager::IsServiceRunning()
+{
+ return !!gGamepadManagerSingleton;
+}
+
+// static
+already_AddRefed<GamepadManager>
+GamepadManager::GetService()
+{
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ if (!gGamepadManagerSingleton) {
+ RefPtr<GamepadManager> manager = new GamepadManager();
+ nsresult rv = manager->Init();
+ if(NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ gGamepadManagerSingleton = manager;
+ ClearOnShutdown(&gGamepadManagerSingleton);
+ }
+
+ RefPtr<GamepadManager> service(gGamepadManagerSingleton);
+ return service.forget();
+}
+
+// static
+bool
+GamepadManager::IsAPIEnabled() {
+ return Preferences::GetBool(kGamepadEnabledPref, false);
+}
+
+bool
+GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex)
+{
+ if (!WindowHasSeenGamepad(aWindow, aIndex)) {
+ // This window hasn't seen this gamepad before, so
+ // send a connection event first.
+ SetWindowHasSeenGamepad(aWindow, aIndex);
+ return true;
+ }
+ return false;
+}
+
+bool
+GamepadManager::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const
+{
+ RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aIndex);
+ return gamepad != nullptr;
+}
+
+void
+GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow,
+ uint32_t aIndex,
+ bool aHasSeen)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+
+ if (mListeners.IndexOf(aWindow) == NoIndex) {
+ // This window isn't even listening for gamepad events.
+ return;
+ }
+
+ if (aHasSeen) {
+ aWindow->SetHasSeenGamepadInput(true);
+ nsCOMPtr<nsISupports> window = ToSupports(aWindow);
+ RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+ if (!gamepad) {
+ return;
+ }
+ RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window);
+ aWindow->AddGamepad(aIndex, clonedGamepad);
+ } else {
+ aWindow->RemoveGamepad(aIndex);
+ }
+}
+
+void
+GamepadManager::Update(const GamepadChangeEvent& aEvent)
+{
+ if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
+ const GamepadAdded& a = aEvent.get_GamepadAdded();
+ AddGamepad(a.index(), a.id(),
+ static_cast<GamepadMappingType>(a.mapping()),
+ a.service_type(),
+ a.num_buttons(), a.num_axes());
+ return;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
+ const GamepadRemoved& a = aEvent.get_GamepadRemoved();
+ RemoveGamepad(a.index(), a.service_type());
+ return;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
+ const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
+ NewButtonEvent(a.index(), a.service_type(), a.button(),
+ a.pressed(), a.value());
+ return;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
+ const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
+ NewAxisMoveEvent(a.index(), a.service_type(), a.axis(), a.value());
+ return;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadPoseInformation) {
+ const GamepadPoseInformation& a = aEvent.get_GamepadPoseInformation();
+ NewPoseEvent(a.index(), a.service_type(), a.pose_state());
+ return;
+ }
+
+ MOZ_CRASH("We shouldn't be here!");
+
+}
+
+//Override nsIIPCBackgroundChildCreateCallback
+void
+GamepadManager::ActorCreated(PBackgroundChild *aActor)
+{
+ MOZ_ASSERT(aActor);
+ GamepadEventChannelChild *child = new GamepadEventChannelChild();
+ PGamepadEventChannelChild *initedChild =
+ aActor->SendPGamepadEventChannelConstructor(child);
+ if (NS_WARN_IF(!initedChild)) {
+ ActorFailed();
+ return;
+ }
+ MOZ_ASSERT(initedChild == child);
+ child->SendGamepadListenerAdded();
+ mChannelChildren.AppendElement(child);
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+ // Construct VRManagerChannel and ask adding the connected
+ // VR controllers to GamepadManager
+ mVRChannelChild = gfx::VRManagerChild::Get();
+ mVRChannelChild->SendControllerListenerAdded();
+#endif
+}
+
+//Override nsIIPCBackgroundChildCreateCallback
+void
+GamepadManager::ActorFailed()
+{
+ MOZ_CRASH("Gamepad IPC actor create failed!");
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/GamepadManager.h b/dom/gamepad/GamepadManager.h
new file mode 100644
index 0000000000..1bb437d8ff
--- /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 "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsIObserver.h"
+// Needed for GamepadMappingType
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadServiceType.h"
+
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace gfx {
+class VRManagerChild;
+} // namespace gfx
+namespace dom {
+
+class EventTarget;
+class Gamepad;
+class GamepadChangeEvent;
+class GamepadEventChannelChild;
+
+class GamepadManager final : public nsIObserver,
+ public nsIIPCBackgroundChildCreateCallback
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+
+ // Returns true if we actually have a service up and running
+ static bool IsServiceRunning();
+ // Get the singleton service
+ static already_AddRefed<GamepadManager> GetService();
+ // Return true if the API is preffed on.
+ static bool IsAPIEnabled();
+
+ void BeginShutdown();
+ void StopMonitoring();
+
+ // Indicate that |aWindow| wants to receive gamepad events.
+ void AddListener(nsGlobalWindow* aWindow);
+ // Indicate that |aWindow| should no longer receive gamepad events.
+ void RemoveListener(nsGlobalWindow* aWindow);
+
+ // Add a gamepad to the list of known gamepads.
+ void AddGamepad(uint32_t aIndex, const nsAString& aID, GamepadMappingType aMapping,
+ GamepadServiceType aServiceType, uint32_t aNumButtons, uint32_t aNumAxes);
+
+ // Remove the gamepad at |aIndex| from the list of known gamepads.
+ void RemoveGamepad(uint32_t aIndex, GamepadServiceType aServiceType);
+
+ // Update the state of |aButton| for the gamepad at |aIndex| 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, aValue is for analog buttons.
+ void NewButtonEvent(uint32_t aIndex, GamepadServiceType aServiceType, uint32_t aButton,
+ bool aPressed, double aValue);
+
+ // Update the state of |aAxis| for the gamepad at |aIndex| for all
+ // windows that are listening and visible, and fire a gamepadaxismove
+ // event at them as well.
+ void NewAxisMoveEvent(uint32_t aIndex, GamepadServiceType aServiceType,
+ uint32_t aAxis, double aValue);
+
+ // Update the state of |aState| for the gamepad at |aIndex| for all
+ // windows that are listening and visible.
+ void NewPoseEvent(uint32_t aIndex, GamepadServiceType aServiceType,
+ const GamepadPoseState& aState);
+
+ // Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
+ void SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad);
+
+ // Returns gamepad object if index exists, null otherwise
+ already_AddRefed<Gamepad> GetGamepad(uint32_t aIndex) const;
+
+ // Receive GamepadChangeEvent messages from parent process to fire DOM events
+ void Update(const GamepadChangeEvent& aGamepadEvent);
+
+ protected:
+ GamepadManager();
+ ~GamepadManager() {};
+
+ // Fire a gamepadconnected or gamepaddisconnected event for the gamepad
+ // at |aIndex| to all windows that are listening and have received
+ // gamepad input.
+ void NewConnectionEvent(uint32_t aIndex, 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;
+
+ // Gamepad IPDL child
+ // This pointer is only used by this singleton instance and
+ // will be destroyed during the IPDL shutdown chain, so we
+ // don't need to refcount it here.
+ nsTArray<GamepadEventChannelChild *> mChannelChildren;
+ gfx::VRManagerChild* mVRChannelChild;
+
+ private:
+
+ nsresult Init();
+
+ bool MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex);
+ // 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(nsGlobalWindow* aWindow, uint32_t aIndex) const;
+ // Indicate that a window has received data from a gamepad.
+ void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex,
+ bool aHasSeen = true);
+ // Our gamepad index has VR_GAMEPAD_IDX_OFFSET while GamepadChannelType
+ // is from VRManager.
+ uint32_t GetGamepadIndexWithServiceType(uint32_t aIndex, GamepadServiceType aServiceType);
+
+ // Gamepads connected to the system. Copies of these are handed out
+ // to each window.
+ nsRefPtrHashtable<nsUint32HashKey, Gamepad> mGamepads;
+ // Inner windows that are listening for gamepad events.
+ // has been sent to that window.
+ nsTArray<RefPtr<nsGlobalWindow>> mListeners;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GamepadManager_h_
diff --git a/dom/gamepad/GamepadMonitoring.cpp b/dom/gamepad/GamepadMonitoring.cpp
new file mode 100644
index 0000000000..ac45aea571
--- /dev/null
+++ b/dom/gamepad/GamepadMonitoring.cpp
@@ -0,0 +1,32 @@
+/* -*- 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/GamepadMonitoring.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+void
+MaybeStopGamepadMonitoring()
+{
+ AssertIsOnBackgroundThread();
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+ if(service->HasGamepadListeners()) {
+ return;
+ }
+ StopGamepadMonitoring();
+ service->ResetGamepadIndexes();
+ service->MaybeShutdown();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/GamepadMonitoring.h b/dom/gamepad/GamepadMonitoring.h
new file mode 100644
index 0000000000..bf231c4dda
--- /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_
+
+namespace mozilla {
+namespace dom {
+// Functions for platform specific gamepad monitoring.
+
+void MaybeStopGamepadMonitoring();
+
+// These two functions are implemented in the platform specific service files
+// (linux/LinuxGamepad.cpp, cocoa/CocoaGamepad.cpp, etc)
+void StartGamepadMonitoring();
+void StopGamepadMonitoring();
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/gamepad/GamepadPlatformService.cpp b/dom/gamepad/GamepadPlatformService.cpp
new file mode 100644
index 0000000000..3df967aec4
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.cpp
@@ -0,0 +1,265 @@
+/* -*- 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/ipc/BackgroundParent.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIThread.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This is the singleton instance of GamepadPlatformService, can be called
+// by both background and monitor thread.
+StaticRefPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton;
+
+} //namepsace
+
+GamepadPlatformService::GamepadPlatformService()
+ : mGamepadIndex(0),
+ mMutex("mozilla::dom::GamepadPlatformService")
+{}
+
+GamepadPlatformService::~GamepadPlatformService()
+{
+ Cleanup();
+}
+
+// static
+already_AddRefed<GamepadPlatformService>
+GamepadPlatformService::GetParentService()
+{
+ //GamepadPlatformService can only be accessed in parent process
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!gGamepadPlatformServiceSingleton) {
+ // Only Background Thread can create new GamepadPlatformService instance.
+ if (IsOnBackgroundThread()) {
+ gGamepadPlatformServiceSingleton = new GamepadPlatformService();
+ } else {
+ return nullptr;
+ }
+ }
+ RefPtr<GamepadPlatformService> service(gGamepadPlatformServiceSingleton);
+ return service.forget();
+}
+
+template<class T>
+void
+GamepadPlatformService::NotifyGamepadChange(const T& aInfo)
+{
+ // This method is called by monitor populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ GamepadChangeEvent e(aInfo);
+
+ // mChannelParents may be accessed by background thread in the
+ // same time, we use mutex to prevent possible race condtion
+ MutexAutoLock autoLock(mMutex);
+
+ // Buffer all events if we have no Channel to dispatch, which
+ // may happen when performing Mochitest.
+ if (mChannelParents.IsEmpty()) {
+ mPendingEvents.AppendElement(e);
+ return;
+ }
+
+ for(uint32_t i = 0; i < mChannelParents.Length(); ++i) {
+ mChannelParents[i]->DispatchUpdateEvent(e);
+ }
+}
+
+uint32_t
+GamepadPlatformService::AddGamepad(const char* aID,
+ GamepadMappingType aMapping,
+ uint32_t aNumButtons, uint32_t aNumAxes)
+{
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ uint32_t index = ++mGamepadIndex;
+ GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
+ static_cast<uint32_t>(aMapping), GamepadServiceType::Standard, aNumButtons, aNumAxes);
+ NotifyGamepadChange<GamepadAdded>(a);
+ return index;
+}
+
+void
+GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
+{
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ GamepadRemoved a(aIndex, GamepadServiceType::Standard);
+ NotifyGamepadChange<GamepadRemoved>(a);
+}
+
+void
+GamepadPlatformService::NewButtonEvent(uint32_t aIndex, 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());
+ GamepadButtonInformation a(aIndex, GamepadServiceType::Standard,
+ aButton, aPressed, aValue);
+ NotifyGamepadChange<GamepadButtonInformation>(a);
+}
+
+void
+GamepadPlatformService::NewButtonEvent(uint32_t aIndex, 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(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L);
+}
+
+void
+GamepadPlatformService::NewAxisMoveEvent(uint32_t aIndex, 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(aIndex, GamepadServiceType::Standard,
+ aAxis, aValue);
+ NotifyGamepadChange<GamepadAxisInformation>(a);
+}
+
+void
+GamepadPlatformService::ResetGamepadIndexes()
+{
+ // This method is called by monitor thread populated in
+ // platform-dependent backends
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NS_IsMainThread());
+ mGamepadIndex = 0;
+}
+
+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);
+ FlushPendingEvents();
+}
+
+void
+GamepadPlatformService::FlushPendingEvents()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mChannelParents.IsEmpty());
+
+ if (mPendingEvents.IsEmpty()) {
+ return;
+ }
+
+ // NOTE: This method must be called with mMutex held because it accesses
+ // mChannelParents.
+ for (uint32_t i=0; i<mChannelParents.Length(); ++i) {
+ for (uint32_t j=0; j<mPendingEvents.Length();++j) {
+ mChannelParents[i]->DispatchUpdateEvent(mPendingEvents[j]);
+ }
+ }
+ mPendingEvents.Clear();
+}
+
+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);
+}
+
+bool
+GamepadPlatformService::HasGamepadListeners()
+{
+ // mChannelParents may be accessed by background thread in the
+ // same time, we use mutex to prevent possible race condtion
+ AssertIsOnBackgroundThread();
+
+ // We use mutex here to prevent race condition with monitor thread
+ MutexAutoLock autoLock(mMutex);
+ for (uint32_t i = 0; i < mChannelParents.Length(); i++) {
+ if(mChannelParents[i]->HasGamepadListener()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+GamepadPlatformService::MaybeShutdown()
+{
+ // This method is invoked in MaybeStopGamepadMonitoring when
+ // an IPDL channel is going to be destroyed
+ AssertIsOnBackgroundThread();
+
+ // We have to release gGamepadPlatformServiceSingleton ouside
+ // the mutex as well as making upcoming GetParentService() call
+ // recreate new singleton, so we use this RefPtr to temporarily
+ // hold the reference, postponing the release process until this
+ // method ends.
+ RefPtr<GamepadPlatformService> kungFuDeathGrip;
+
+ bool isChannelParentEmpty;
+ {
+ MutexAutoLock autoLock(mMutex);
+ isChannelParentEmpty = mChannelParents.IsEmpty();
+ if(isChannelParentEmpty) {
+ kungFuDeathGrip = gGamepadPlatformServiceSingleton;
+ gGamepadPlatformServiceSingleton = nullptr;
+ }
+ }
+}
+
+void
+GamepadPlatformService::Cleanup()
+{
+ // This method is called when GamepadPlatformService is
+ // successfully distructed in background thread
+ AssertIsOnBackgroundThread();
+
+ MutexAutoLock autoLock(mMutex);
+ mChannelParents.Clear();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/GamepadPlatformService.h b/dom/gamepad/GamepadPlatformService.h
new file mode 100644
index 0000000000..0a61281e42
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.h
@@ -0,0 +1,104 @@
+/* -*- 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/Mutex.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class GamepadEventChannelParent;
+
+// Platform Service for building and transmitting IPDL messages
+// through the HAL sandbox. Used by platform specific
+// Gamepad implementations
+//
+// This class can be accessed by the following 2 threads :
+// 1. Background thread:
+// This thread takes charge of IPDL communications
+// between here and DOM side
+//
+// 2. Monitor Thread:
+// This thread is populated in platform-dependent backends, which
+// is in charge of processing gamepad hardware events from OS
+class GamepadPlatformService final
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadPlatformService)
+ public:
+ //Get the singleton service
+ static already_AddRefed<GamepadPlatformService> GetParentService();
+
+ // Add a gamepad to the list of known gamepads, and return its index.
+ uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping,
+ uint32_t aNumButtons, uint32_t aNumAxes);
+ // Remove the gamepad at |aIndex| from the list of known gamepads.
+ void RemoveGamepad(uint32_t aIndex);
+
+ // Update the state of |aButton| for the gamepad at |aIndex| 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, aValue is for analog buttons.
+ void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+ double aValue);
+ // When only a digital button is available the value will be synthesized.
+ void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+
+ // Update the state of |aAxis| for the gamepad at |aIndex| for all
+ // windows that are listening and visible, and fire a gamepadaxismove
+ // event at them as well.
+ void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+
+ // 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);
+
+ bool HasGamepadListeners();
+
+ void MaybeShutdown();
+
+ private:
+ GamepadPlatformService();
+ ~GamepadPlatformService();
+ template<class T> void NotifyGamepadChange(const T& aInfo);
+
+ // Flush all pending events buffered in mPendingEvents, must be called
+ // with mMutex held
+ void FlushPendingEvents();
+ void Cleanup();
+
+ // mGamepadIndex can only be accessed by monitor thread
+ uint32_t mGamepadIndex;
+
+ // mChannelParents stores all the GamepadEventChannelParent instances
+ // which may be accessed by both background thread and monitor thread
+ // simultaneously, so we have a mutex to prevent race condition
+ nsTArray<RefPtr<GamepadEventChannelParent>> mChannelParents;
+
+ // This mutex protects mChannelParents from race condition
+ // between background and monitor thread
+ Mutex mMutex;
+
+ // In mochitest, it is possible that the test Events is synthesized
+ // before GamepadEventChannel created, we need to buffer all events
+ // until the channel is created if that happens.
+ nsTArray<GamepadChangeEvent> mPendingEvents;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/gamepad/GamepadPose.cpp b/dom/gamepad/GamepadPose.cpp
new file mode 100644
index 0000000000..25d157b8b3
--- /dev/null
+++ b/dom/gamepad/GamepadPose.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 {
+namespace dom {
+
+GamepadPose::GamepadPose(nsISupports* aParent, const GamepadPoseState& aState)
+ : Pose(aParent),
+ mPoseState(aState)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+GamepadPose::GamepadPose(nsISupports* aParent)
+ : Pose(aParent)
+{
+ mozilla::HoldJSObjects(this);
+ mPoseState.Clear();
+}
+
+GamepadPose::~GamepadPose()
+{
+ mozilla::DropJSObjects(this);
+}
+
+/* virtual */ JSObject*
+GamepadPose::WrapObject(JSContext* aJSContext, JS::Handle<JSObject*> aGivenProto)
+{
+ return GamepadPoseBinding::Wrap(aJSContext, this, aGivenProto);
+}
+
+bool
+GamepadPose::HasOrientation() const
+{
+ return bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position);
+}
+
+bool
+GamepadPose::HasPosition() const
+{
+ return bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation);
+}
+
+void
+GamepadPose::GetPosition(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aJSContext, aRetval, mPosition, mPoseState.position, 3,
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position), aRv);
+}
+
+void
+GamepadPose::GetLinearVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aJSContext, aRetval, mLinearVelocity, mPoseState.linearVelocity, 3,
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position), aRv);
+}
+
+void
+GamepadPose::GetLinearAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aJSContext, aRetval, mLinearAcceleration, mPoseState.linearAcceleration, 3,
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_LinearAcceleration), aRv);
+}
+
+void
+GamepadPose::GetOrientation(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aJSContext, aRetval, mOrientation, mPoseState.orientation, 4,
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation), aRv);
+}
+
+void
+GamepadPose::GetAngularVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aJSContext, aRetval, mAngularVelocity, mPoseState.angularVelocity, 3,
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation), aRv);
+}
+
+void
+GamepadPose::GetAngularAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aJSContext, aRetval, mAngularAcceleration, mPoseState.angularAcceleration, 3,
+ bool(mPoseState.flags & GamepadCapabilityFlags::Cap_AngularAcceleration), aRv);
+}
+
+void
+GamepadPose::SetPoseState(const GamepadPoseState& aPose)
+{
+ mPoseState = aPose;
+}
+
+const GamepadPoseState&
+GamepadPose::GetPoseState()
+{
+ return mPoseState;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/GamepadPose.h b/dom/gamepad/GamepadPose.h
new file mode 100644
index 0000000000..fa32df3fce
--- /dev/null
+++ b/dom/gamepad/GamepadPose.h
@@ -0,0 +1,58 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace dom {
+
+class GamepadPose final : public Pose
+{
+public:
+ GamepadPose(nsISupports* aParent, const GamepadPoseState& aState);
+ explicit GamepadPose(nsISupports* aParent);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool HasOrientation() const;
+ bool HasPosition() const;
+ virtual void GetPosition(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetLinearVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetLinearAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetOrientation(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetAngularVelocity(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetAngularAcceleration(JSContext* aJSContext,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ void SetPoseState(const GamepadPoseState& aPose);
+ const GamepadPoseState& GetPoseState();
+
+private:
+ virtual ~GamepadPose();
+
+ nsCOMPtr<nsISupports> mParent;
+ GamepadPoseState mPoseState;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_gamepad_GamepadPose_h
diff --git a/dom/gamepad/GamepadPoseState.h b/dom/gamepad/GamepadPoseState.h
new file mode 100644
index 0000000000..958b26139f
--- /dev/null
+++ b/dom/gamepad/GamepadPoseState.h
@@ -0,0 +1,88 @@
+
+#ifndef mozilla_dom_gamepad_GamepadPoseState_h_
+#define mozilla_dom_gamepad_GamepadPoseState_h_
+
+namespace mozilla{
+namespace 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_All used for validity checking during IPC serialization
+ */
+ Cap_All = (1 << 5) - 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];
+
+ GamepadPoseState()
+ {
+ Clear();
+ }
+
+ 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];
+ }
+
+ bool operator!=(const GamepadPoseState& aPose) const
+ {
+ return !(*this == aPose);
+ }
+
+ void Clear() {
+ memset(this, 0, sizeof(GamepadPoseState));
+ }
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif // mozilla_dom_gamepad_GamepadPoseState_h_ \ No newline at end of file
diff --git a/dom/gamepad/GamepadServiceTest.cpp b/dom/gamepad/GamepadServiceTest.cpp
new file mode 100644
index 0000000000..a6fde58f01
--- /dev/null
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -0,0 +1,283 @@
+/* -*- 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"
+
+#include "mozilla/Unused.h"
+
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+
+namespace mozilla {
+namespace 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_CLASS(GamepadServiceTest)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(GamepadServiceTest,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GamepadServiceTest,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GamepadServiceTest)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(GamepadServiceTest, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(GamepadServiceTest, DOMEventTargetHelper)
+
+// static
+already_AddRefed<GamepadServiceTest>
+GamepadServiceTest::CreateTestService(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ RefPtr<GamepadServiceTest> service = new GamepadServiceTest(aWindow);
+ service->InitPBackgroundActor();
+ return service.forget();
+}
+
+void
+GamepadServiceTest::Shutdown()
+{
+ MOZ_ASSERT(!mShuttingDown);
+ mShuttingDown = true;
+ DestroyPBackgroundActor();
+ mWindow = nullptr;
+}
+
+GamepadServiceTest::GamepadServiceTest(nsPIDOMWindowInner* aWindow)
+ : mService(GamepadManager::GetService()),
+ mWindow(aWindow),
+ mEventNumber(0),
+ mShuttingDown(false),
+ mChild(nullptr)
+{}
+
+GamepadServiceTest::~GamepadServiceTest() {}
+
+void
+GamepadServiceTest::InitPBackgroundActor()
+{
+ MOZ_ASSERT(!mChild);
+ PBackgroundChild *actor = BackgroundChild::GetForCurrentThread();
+ //Try to get the PBackground Child actor
+ if (actor) {
+ ActorCreated(actor);
+ } else {
+ Unused << BackgroundChild::GetOrCreateForCurrentThread(this);
+ }
+}
+
+void
+GamepadServiceTest::DestroyPBackgroundActor()
+{
+ if (mChild) {
+ // If mChild exists, which means that IPDL channel
+ // has been created, our pending operations should
+ // be empty.
+ MOZ_ASSERT(mPendingOperations.IsEmpty());
+ mChild->SendShutdownChannel();
+ mChild = nullptr;
+ } else {
+ // If the IPDL channel has not been created and we
+ // want to destroy it now, just cancel all pending
+ // operations.
+ mPendingOperations.Clear();
+ }
+}
+
+already_AddRefed<Promise>
+GamepadServiceTest::AddGamepad(const nsAString& aID,
+ uint32_t aMapping,
+ uint32_t aNumButtons,
+ uint32_t aNumAxes,
+ ErrorResult& aRv)
+{
+ if (mShuttingDown) {
+ return nullptr;
+ }
+
+ GamepadAdded a(nsString(aID), 0,
+ aMapping,
+ GamepadServiceType::Standard,
+ aNumButtons, aNumAxes);
+ GamepadChangeEvent e(a);
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+
+ RefPtr<Promise> p = Promise::Create(go, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ uint32_t id = ++mEventNumber;
+ if (mChild) {
+ mChild->AddPromise(id, p);
+ mChild->SendGamepadTestEvent(id, e);
+ } else {
+ PendingOperation op(id, e, p);
+ mPendingOperations.AppendElement(op);
+ }
+ return p.forget();
+}
+
+void
+GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ GamepadRemoved a(aIndex, GamepadServiceType::Standard);
+ GamepadChangeEvent e(a);
+
+ uint32_t id = ++mEventNumber;
+ if (mChild) {
+ mChild->SendGamepadTestEvent(id, e);
+ } else {
+ PendingOperation op(id, e);
+ mPendingOperations.AppendElement(op);
+ }
+}
+
+void
+GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
+ uint32_t aButton,
+ bool aPressed)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ GamepadButtonInformation a(aIndex, GamepadServiceType::Standard,
+ aButton, aPressed, aPressed ? 1.0 : 0);
+ GamepadChangeEvent e(a);
+
+ uint32_t id = ++mEventNumber;
+ if (mChild) {
+ mChild->SendGamepadTestEvent(id, e);
+ } else {
+ PendingOperation op(id, e);
+ mPendingOperations.AppendElement(op);
+ }
+}
+
+void
+GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex,
+ uint32_t aButton,
+ bool aPressed,
+ double aValue)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ GamepadButtonInformation a(aIndex, GamepadServiceType::Standard,
+ aButton, aPressed, aValue);
+ GamepadChangeEvent e(a);
+
+ uint32_t id = ++mEventNumber;
+ if (mChild) {
+ mChild->SendGamepadTestEvent(id, e);
+ } else {
+ PendingOperation op(id, e);
+ mPendingOperations.AppendElement(op);
+ }
+}
+
+void
+GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
+ uint32_t aAxis,
+ double aValue)
+{
+ if (mShuttingDown) {
+ return;
+ }
+
+ GamepadAxisInformation a(aIndex, GamepadServiceType::Standard,
+ aAxis, aValue);
+ GamepadChangeEvent e(a);
+
+ uint32_t id = ++mEventNumber;
+ if (mChild) {
+ mChild->SendGamepadTestEvent(id, e);
+ } else {
+ PendingOperation op(id, e);
+ mPendingOperations.AppendElement(op);
+ }
+}
+
+void
+GamepadServiceTest::FlushPendingOperations()
+{
+ for (uint32_t i=0; i < mPendingOperations.Length(); ++i) {
+ PendingOperation op = mPendingOperations[i];
+ if (op.mPromise) {
+ mChild->AddPromise(op.mID, op.mPromise);
+ }
+ mChild->SendGamepadTestEvent(op.mID, op.mEvent);
+ }
+ mPendingOperations.Clear();
+}
+
+void
+GamepadServiceTest::ActorCreated(PBackgroundChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+ // If we are shutting down, we don't need to create the
+ // IPDL child/parent pair anymore.
+ if (mShuttingDown) {
+ // mPendingOperations should be cleared in
+ // DestroyPBackgroundActor()
+ MOZ_ASSERT(mPendingOperations.IsEmpty());
+ return;
+ }
+
+ mChild = new GamepadTestChannelChild();
+ PGamepadTestChannelChild* initedChild =
+ aActor->SendPGamepadTestChannelConstructor(mChild);
+ if (NS_WARN_IF(!initedChild)) {
+ ActorFailed();
+ return;
+ }
+ FlushPendingOperations();
+}
+
+void
+GamepadServiceTest::ActorFailed()
+{
+ MOZ_CRASH("Failed to create background child actor!");
+}
+
+JSObject*
+GamepadServiceTest::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+ return GamepadServiceTestBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // dom
+} // mozilla
diff --git a/dom/gamepad/GamepadServiceTest.h b/dom/gamepad/GamepadServiceTest.h
new file mode 100644
index 0000000000..6ef9fcc19a
--- /dev/null
+++ b/dom/gamepad/GamepadServiceTest.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/. */
+
+#ifndef mozilla_dom_GamepadServiceTest_h_
+#define mozilla_dom_GamepadServiceTest_h_
+
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/GamepadBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class GamepadChangeEvent;
+class GamepadManager;
+class GamepadTestChannelChild;
+class Promise;
+
+// Service for testing purposes
+class GamepadServiceTest final : public DOMEventTargetHelper,
+ public nsIIPCBackgroundChildCreateCallback
+{
+public:
+ NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GamepadServiceTest,
+ DOMEventTargetHelper)
+
+ uint32_t NoMapping() const { return 0; }
+ uint32_t StandardMapping() const { return 1; }
+
+ already_AddRefed<Promise> AddGamepad(const nsAString& aID,
+ uint32_t aMapping,
+ uint32_t aNumButtons,
+ uint32_t aNumAxes,
+ ErrorResult& aRv);
+ void RemoveGamepad(uint32_t aIndex);
+ void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+ void NewButtonValueEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, double aValue);
+ void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+ void Shutdown();
+
+ static already_AddRefed<GamepadServiceTest> CreateTestService(nsPIDOMWindowInner* aWindow);
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+ JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
+private:
+
+ // We need to asynchronously create IPDL channel, it is possible that
+ // we send commands before the channel is created, so we have to buffer
+ // them until the channel is created in that case.
+ struct PendingOperation {
+ explicit PendingOperation(const uint32_t& aID,
+ const GamepadChangeEvent& aEvent,
+ Promise* aPromise = nullptr)
+ : mID(aID), mEvent(aEvent), mPromise(aPromise) {}
+ uint32_t mID;
+ const GamepadChangeEvent& mEvent;
+ RefPtr<Promise> mPromise;
+ };
+
+ // Hold a reference to the gamepad service so we don't have to worry about
+ // execution order in tests.
+ RefPtr<GamepadManager> mService;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsTArray<PendingOperation> mPendingOperations;
+ 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
+ GamepadTestChannelChild* MOZ_NON_OWNING_REF mChild;
+
+ explicit GamepadServiceTest(nsPIDOMWindowInner* aWindow);
+ ~GamepadServiceTest();
+ void InitPBackgroundActor();
+ void DestroyPBackgroundActor();
+ void FlushPendingOperations();
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/gamepad/android/AndroidGamepad.cpp b/dom/gamepad/android/AndroidGamepad.cpp
new file mode 100644
index 0000000000..706d02617b
--- /dev/null
+++ b/dom/gamepad/android/AndroidGamepad.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 "GeneratedJNIWrappers.h"
+#include "GeneratedJNINatives.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+class AndroidGamepadManager final
+ : public java::AndroidGamepadManager::Natives<AndroidGamepadManager>
+{
+ AndroidGamepadManager() = delete;
+
+public:
+ static void
+ OnGamepadChange(int32_t aID, bool aAdded)
+ {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ if (aAdded) {
+ const int svc_id = service->AddGamepad(
+ "android", GamepadMappingType::Standard,
+ kStandardGamepadButtons, kStandardGamepadAxes);
+ java::AndroidGamepadManager::OnGamepadAdded(aID, svc_id);
+
+ } else {
+ service->RemoveGamepad(aID);
+ }
+ }
+
+ static void
+ OnButtonChange(int32_t aID, int32_t aButton, bool aPressed, float aValue)
+ {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ service->NewButtonEvent(aID, aButton, aPressed, aValue);
+ }
+
+ static void
+ OnAxisChange(int32_t aID, jni::BooleanArray::Param aValid,
+ jni::FloatArray::Param aValues)
+ {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const auto& valid = aValid->GetElements();
+ const auto& values = aValues->GetElements();
+ MOZ_ASSERT(valid.Length() == values.Length());
+
+ for (size_t i = 0; i < values.Length(); i++) {
+ service->NewAxisMoveEvent(aID, i, values[i]);
+ }
+ }
+};
+
+void StartGamepadMonitoring()
+{
+ AndroidGamepadManager::Init();
+ java::AndroidGamepadManager::Start();
+}
+
+void StopGamepadMonitoring()
+{
+ java::AndroidGamepadManager::Stop();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp
new file mode 100644
index 0000000000..e7c986e22a
--- /dev/null
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -0,0 +1,589 @@
+/* -*- 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/GamepadPlatformService.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsThreadUtils.h"
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDBase.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#include <stdio.h>
+#include <vector>
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using std::vector;
+
+struct Button {
+ int id;
+ bool analog;
+ IOHIDElementRef element;
+ CFIndex min;
+ CFIndex max;
+
+ Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax) :
+ id(aId),
+ analog((aMax - aMin) > 1),
+ element(aElement),
+ min(aMin),
+ max(aMax) {}
+};
+
+struct Axis {
+ int id;
+ IOHIDElementRef element;
+ uint32_t usagePage;
+ uint32_t usage;
+ CFIndex min;
+ CFIndex max;
+};
+
+typedef bool dpad_buttons[4];
+
+// 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;
+
+
+class Gamepad {
+ private:
+ IOHIDDeviceRef mDevice;
+ nsTArray<Button> buttons;
+ nsTArray<Axis> axes;
+ IOHIDElementRef mDpad;
+ dpad_buttons mDpadState;
+
+ public:
+ Gamepad() : mDevice(nullptr), mDpad(nullptr), mSuperIndex(-1) {}
+ bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
+ bool empty() const { return mDevice == nullptr; }
+ void clear()
+ {
+ mDevice = nullptr;
+ buttons.Clear();
+ axes.Clear();
+ mDpad = nullptr;
+ mSuperIndex = -1;
+ }
+ void init(IOHIDDeviceRef device);
+ size_t numButtons() { return buttons.Length() + (mDpad ? 4 : 0); }
+ size_t numAxes() { return axes.Length(); }
+
+ // Index given by our superclass.
+ uint32_t mSuperIndex;
+
+ bool isDpad(IOHIDElementRef element) const
+ {
+ return element == mDpad;
+ }
+
+ const dpad_buttons& getDpadState() const
+ {
+ return mDpadState;
+ }
+
+ void setDpadState(const dpad_buttons& dpadState)
+ {
+ for (unsigned i = 0; i < ArrayLength(mDpadState); i++) {
+ mDpadState[i] = dpadState[i];
+ }
+ }
+
+ const Button* lookupButton(IOHIDElementRef element) const
+ {
+ for (unsigned i = 0; i < buttons.Length(); i++) {
+ if (buttons[i].element == element)
+ return &buttons[i];
+ }
+ return nullptr;
+ }
+
+ const Axis* lookupAxis(IOHIDElementRef element) const
+ {
+ for (unsigned i = 0; i < axes.Length(); i++) {
+ if (axes[i].element == element)
+ return &axes[i];
+ }
+ return nullptr;
+ }
+};
+
+class AxisComparator {
+public:
+ bool Equals(const Axis& a1, const Axis& a2) const
+ {
+ return a1.usagePage == a2.usagePage && a1.usage == a2.usage;
+ }
+ bool LessThan(const Axis& a1, const Axis& a2) const
+ {
+ if (a1.usagePage == a2.usagePage) {
+ return a1.usage < a2.usage;
+ }
+ return a1.usagePage < a2.usagePage;
+ }
+};
+
+void Gamepad::init(IOHIDDeviceRef device)
+{
+ clear();
+ mDevice = device;
+
+ CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device,
+ nullptr,
+ kIOHIDOptionsTypeNone);
+ CFIndex n = CFArrayGetCount(elements);
+ for (CFIndex i = 0; i < n; i++) {
+ IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements,
+ i);
+ uint32_t usagePage = IOHIDElementGetUsagePage(element);
+ uint32_t usage = IOHIDElementGetUsage(element);
+
+ if (usagePage == kDesktopUsagePage &&
+ usage >= kAxisUsageMin &&
+ usage <= kAxisUsageMax)
+ {
+ Axis axis = { int(axes.Length()),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element) };
+ axes.AppendElement(axis);
+ } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
+ // Don't know how to handle d-pads that return weird values.
+ IOHIDElementGetLogicalMax(element) - IOHIDElementGetLogicalMin(element) == 7) {
+ mDpad = element;
+ } else if ((usagePage == kSimUsagePage &&
+ (usage == kAcceleratorUsage ||
+ usage == kBrakeUsage)) ||
+ (usagePage == kButtonUsagePage) ||
+ (usagePage == kConsumerPage &&
+ (usage == kHomeUsage ||
+ usage == kBackUsage))) {
+ Button button(int(buttons.Length()), element, IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element));
+ buttons.AppendElement(button);
+ } else {
+ //TODO: handle other usage pages
+ }
+ }
+
+ AxisComparator comparator;
+ axes.Sort(comparator);
+ for (unsigned i = 0; i < axes.Length(); i++) {
+ axes[i].id = i;
+ }
+}
+
+class DarwinGamepadService {
+ private:
+ IOHIDManagerRef mManager;
+ vector<Gamepad> mGamepads;
+
+ //Workaround to support running in background thread
+ CFRunLoopRef mMonitorRunLoop;
+ nsCOMPtr<nsIThread> mMonitorThread;
+
+ static void DeviceAddedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device);
+ static void DeviceRemovedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device);
+ static void InputValueChangedCallback(void* data, IOReturn result,
+ void* sender, IOHIDValueRef newValue);
+
+ void DeviceAdded(IOHIDDeviceRef device);
+ void DeviceRemoved(IOHIDDeviceRef device);
+ void InputValueChanged(IOHIDValueRef value);
+ void StartupInternal();
+
+ public:
+ DarwinGamepadService();
+ ~DarwinGamepadService();
+ void Startup();
+ void Shutdown();
+ friend class DarwinGamepadServiceStartupRunnable;
+};
+
+class DarwinGamepadServiceStartupRunnable final : public Runnable
+{
+ private:
+ ~DarwinGamepadServiceStartupRunnable() {}
+ // This Runnable schedules startup of DarwinGamepadService
+ // in a new thread, pointer to DarwinGamepadService is only
+ // used by this Runnable within its thread.
+ DarwinGamepadService MOZ_NON_OWNING_REF *mService;
+ public:
+ explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService *service)
+ : mService(service) {}
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mService);
+ mService->StartupInternal();
+ return NS_OK;
+ }
+};
+
+void
+DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ size_t slot = size_t(-1);
+ for (size_t i = 0; i < mGamepads.size(); i++) {
+ if (mGamepads[i] == device)
+ return;
+ if (slot == size_t(-1) && mGamepads[i].empty())
+ slot = i;
+ }
+
+ if (slot == size_t(-1)) {
+ slot = mGamepads.size();
+ mGamepads.push_back(Gamepad());
+ }
+ mGamepads[slot].init(device);
+
+ // Gather some identifying information
+ CFNumberRef vendorIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
+ CFNumberRef productIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
+ CFStringRef productRef =
+ (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
+ int vendorId, productId;
+ CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
+ CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
+ char product_name[128];
+ CFStringGetCString(productRef, product_name,
+ sizeof(product_name), kCFStringEncodingASCII);
+ char buffer[256];
+ sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
+ uint32_t index = service->AddGamepad(buffer,
+ mozilla::dom::GamepadMappingType::_empty,
+ (int)mGamepads[slot].numButtons(),
+ (int)mGamepads[slot].numAxes());
+ mGamepads[slot].mSuperIndex = index;
+}
+
+void
+DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ for (size_t i = 0; i < mGamepads.size(); i++) {
+ if (mGamepads[i] == device) {
+ service->RemoveGamepad(mGamepads[i].mSuperIndex);
+ mGamepads[i].clear();
+ return;
+ }
+ }
+}
+
+/*
+ * Given a value from a d-pad (POV hat in USB HID terminology),
+ * represent it as 4 buttons, one for each cardinal direction.
+ */
+static void
+UnpackDpad(int dpad_value, int min, int max, dpad_buttons& buttons)
+{
+ const unsigned kUp = 0;
+ const unsigned kDown = 1;
+ const unsigned kLeft = 2;
+ const unsigned kRight = 3;
+
+ // Different controllers have different ways of representing
+ // "nothing is pressed", but they're all outside the range of values.
+ if (dpad_value < min || dpad_value > max) {
+ // Nothing is pressed.
+ return;
+ }
+
+ // Normalize value to start at 0.
+ int value = dpad_value - min;
+
+ // Value will be in the range 0-7. The value represents the
+ // position of the d-pad around a circle, with 0 being straight up,
+ // 2 being right, 4 being straight down, and 6 being left.
+ if (value < 2 || value > 6) {
+ buttons[kUp] = true;
+ }
+ if (value > 2 && value < 6) {
+ buttons[kDown] = true;
+ }
+ if (value > 4) {
+ buttons[kLeft] = true;
+ }
+ if (value > 0 && value < 4) {
+ buttons[kRight] = true;
+ }
+}
+
+void
+DarwinGamepadService::InputValueChanged(IOHIDValueRef value)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ uint32_t value_length = IOHIDValueGetLength(value);
+ if (value_length > 4) {
+ // Workaround for bizarre issue with PS3 controllers that try to return
+ // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
+ return;
+ }
+ IOHIDElementRef element = IOHIDValueGetElement(value);
+ IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+
+ for (unsigned i = 0; i < mGamepads.size(); i++) {
+ Gamepad &gamepad = mGamepads[i];
+ if (gamepad == device) {
+ if (gamepad.isDpad(element)) {
+ const dpad_buttons& oldState = gamepad.getDpadState();
+ dpad_buttons newState = { false, false, false, false };
+ UnpackDpad(IOHIDValueGetIntegerValue(value),
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element),
+ newState);
+ const int numButtons = gamepad.numButtons();
+ for (unsigned b = 0; b < ArrayLength(newState); b++) {
+ if (newState[b] != oldState[b]) {
+ service->NewButtonEvent(gamepad.mSuperIndex,
+ numButtons - 4 + b,
+ newState[b]);
+ }
+ }
+ gamepad.setDpadState(newState);
+ } else if (const Axis* axis = gamepad.lookupAxis(element)) {
+ double d = IOHIDValueGetIntegerValue(value);
+ double v = 2.0f * (d - axis->min) /
+ (double)(axis->max - axis->min) - 1.0f;
+ service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
+ } else if (const Button* button = gamepad.lookupButton(element)) {
+ int iv = IOHIDValueGetIntegerValue(value);
+ bool pressed = iv != 0;
+ double v = 0;
+ if (button->analog) {
+ double dv = iv;
+ v = (dv - button->min) / (double)(button->max - button->min);
+ } else {
+ v = pressed ? 1.0 : 0.0;
+ }
+ service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
+ }
+ return;
+ }
+ }
+}
+
+void
+DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device)
+{
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceAdded(device);
+}
+
+void
+DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device)
+{
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceRemoved(device);
+}
+
+void
+DarwinGamepadService::InputValueChangedCallback(void* data,
+ IOReturn result,
+ void* sender,
+ IOHIDValueRef newValue)
+{
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->InputValueChanged(newValue);
+}
+
+static CFMutableDictionaryRef
+MatchingDictionary(UInt32 inUsagePage, UInt32 inUsage)
+{
+ CFMutableDictionaryRef dict =
+ CFDictionaryCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (!dict)
+ return nullptr;
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberIntType,
+ &inUsagePage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
+ CFRelease(number);
+
+ number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
+ CFRelease(number);
+
+ return dict;
+}
+
+DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
+
+DarwinGamepadService::~DarwinGamepadService()
+{
+ if (mManager != nullptr)
+ CFRelease(mManager);
+}
+
+void
+DarwinGamepadService::StartupInternal()
+{
+ if (mManager != nullptr)
+ return;
+
+ IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault,
+ kIOHIDOptionsTypeNone);
+
+ CFMutableDictionaryRef criteria_arr[2];
+ criteria_arr[0] = MatchingDictionary(kDesktopUsagePage,
+ kJoystickUsage);
+ if (!criteria_arr[0]) {
+ CFRelease(manager);
+ return;
+ }
+
+ criteria_arr[1] = MatchingDictionary(kDesktopUsagePage,
+ kGamepadUsage);
+ if (!criteria_arr[1]) {
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ CFArrayRef criteria =
+ CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr);
+ if (!criteria) {
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
+ CFRelease(criteria);
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+
+ IOHIDManagerRegisterDeviceMatchingCallback(manager,
+ DeviceAddedCallback,
+ this);
+ IOHIDManagerRegisterDeviceRemovalCallback(manager,
+ DeviceRemovedCallback,
+ this);
+ IOHIDManagerRegisterInputValueCallback(manager,
+ InputValueChangedCallback,
+ this);
+ IOHIDManagerScheduleWithRunLoop(manager,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
+ if (rv != kIOReturnSuccess) {
+ CFRelease(manager);
+ return;
+ }
+
+ mManager = manager;
+
+ // We held the handle of the CFRunLoop to make sure we
+ // can shut it down explicitly by CFRunLoopStop in another
+ // thread.
+ mMonitorRunLoop = CFRunLoopGetCurrent();
+
+ // CFRunLoopRun() is a blocking message loop when it's called in
+ // non-main thread so this thread cannot receive any other runnables
+ // and nsITimer timeout events after it's called.
+ CFRunLoopRun();
+}
+
+void DarwinGamepadService::Startup()
+{
+ Unused << NS_NewThread(getter_AddRefs(mMonitorThread),
+ new DarwinGamepadServiceStartupRunnable(this));
+}
+
+void DarwinGamepadService::Shutdown()
+{
+ IOHIDManagerRef manager = (IOHIDManagerRef)mManager;
+ CFRunLoopStop(mMonitorRunLoop);
+ if (manager) {
+ IOHIDManagerClose(manager, 0);
+ CFRelease(manager);
+ mManager = nullptr;
+ }
+ mMonitorThread->Shutdown();
+}
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+DarwinGamepadService* gService = nullptr;
+
+void StartGamepadMonitoring()
+{
+ if (gService) {
+ return;
+ }
+
+ gService = new DarwinGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring()
+{
+ if (!gService) {
+ return;
+ }
+
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/fallback/FallbackGamepad.cpp b/dom/gamepad/fallback/FallbackGamepad.cpp
new file mode 100644
index 0000000000..f843a243c4
--- /dev/null
+++ b/dom/gamepad/fallback/FallbackGamepad.cpp
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+
+namespace mozilla {
+namespace dom {
+
+void StartGamepadMonitoring()
+{
+}
+
+void StopGamepadMonitoring()
+{
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/ipc/GamepadEventChannelChild.cpp b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
new file mode 100644
index 0000000000..4cdb55a92b
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadManager.h"
+
+namespace mozilla {
+namespace dom{
+
+namespace {
+
+class GamepadUpdateRunnable final : public Runnable
+{
+ public:
+ explicit GamepadUpdateRunnable(const GamepadChangeEvent& aGamepadEvent)
+ : mEvent(aGamepadEvent) {}
+ NS_IMETHOD Run() override
+ {
+ RefPtr<GamepadManager> svc(GamepadManager::GetService());
+ if (svc) {
+ svc->Update(mEvent);
+ }
+ return NS_OK;
+ }
+ protected:
+ GamepadChangeEvent mEvent;
+};
+
+} // namespace
+
+bool
+GamepadEventChannelChild::RecvGamepadUpdate(
+ const GamepadChangeEvent& aGamepadEvent)
+{
+ DebugOnly<nsresult> rv =
+ NS_DispatchToMainThread(new GamepadUpdateRunnable(aGamepadEvent));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/ipc/GamepadEventChannelChild.h b/dom/gamepad/ipc/GamepadEventChannelChild.h
new file mode 100644
index 0000000000..686cbd6401
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/PGamepadEventChannelChild.h"
+
+#ifndef mozilla_dom_GamepadEventChannelChild_h_
+#define mozilla_dom_GamepadEventChannelChild_h_
+
+namespace mozilla{
+namespace dom{
+
+class GamepadEventChannelChild final : public PGamepadEventChannelChild
+{
+ public:
+ GamepadEventChannelChild() {}
+ ~GamepadEventChannelChild() {}
+ virtual bool
+ RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
diff --git a/dom/gamepad/ipc/GamepadEventChannelParent.cpp b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
new file mode 100644
index 0000000000..c3c8fd2c8b
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "GamepadEventChannelParent.h"
+#include "GamepadPlatformService.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+class SendGamepadUpdateRunnable final : public Runnable
+{
+ private:
+ ~SendGamepadUpdateRunnable() {}
+ RefPtr<GamepadEventChannelParent> mParent;
+ GamepadChangeEvent mEvent;
+ public:
+ SendGamepadUpdateRunnable(GamepadEventChannelParent* aParent,
+ GamepadChangeEvent aEvent)
+ : mEvent(aEvent)
+ {
+ MOZ_ASSERT(aParent);
+ mParent = aParent;
+ }
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnBackgroundThread();
+ if(mParent->HasGamepadListener()) {
+ Unused << mParent->SendGamepadUpdate(mEvent);
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+GamepadEventChannelParent::GamepadEventChannelParent()
+ : mHasGamepadListener(false)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+ service->AddChannelParent(this);
+ mBackgroundThread = NS_GetCurrentThread();
+}
+
+bool
+GamepadEventChannelParent::RecvGamepadListenerAdded()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mHasGamepadListener);
+ mHasGamepadListener = true;
+ StartGamepadMonitoring();
+ return true;
+}
+
+bool
+GamepadEventChannelParent::RecvGamepadListenerRemoved()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mHasGamepadListener);
+ mHasGamepadListener = false;
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+ service->RemoveChannelParent(this);
+ Unused << Send__delete__(this);
+ return true;
+}
+
+void
+GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ // It may be called because IPDL child side crashed, we'll
+ // not receive RecvGamepadListenerRemoved in that case
+ if (mHasGamepadListener) {
+ mHasGamepadListener = false;
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+ service->RemoveChannelParent(this);
+ }
+ MaybeStopGamepadMonitoring();
+}
+
+void
+GamepadEventChannelParent::DispatchUpdateEvent(const GamepadChangeEvent& aEvent)
+{
+ mBackgroundThread->Dispatch(new SendGamepadUpdateRunnable(this, aEvent),
+ NS_DISPATCH_NORMAL);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/ipc/GamepadEventChannelParent.h b/dom/gamepad/ipc/GamepadEventChannelParent.h
new file mode 100644
index 0000000000..2ba005c358
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/PGamepadEventChannelParent.h"
+
+#ifndef mozilla_dom_GamepadEventChannelParent_h_
+#define mozilla_dom_GamepadEventChannelParent_h_
+
+namespace mozilla{
+namespace dom{
+
+class GamepadEventChannelParent final : public PGamepadEventChannelParent
+{
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent)
+ GamepadEventChannelParent();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ virtual bool RecvGamepadListenerAdded() override;
+ virtual bool RecvGamepadListenerRemoved() override;
+ void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
+ bool HasGamepadListener() const { return mHasGamepadListener; }
+ private:
+ ~GamepadEventChannelParent() {}
+ bool mHasGamepadListener;
+ nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
diff --git a/dom/gamepad/ipc/GamepadEventTypes.ipdlh b/dom/gamepad/ipc/GamepadEventTypes.ipdlh
new file mode 100644
index 0000000000..68ca360166
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventTypes.ipdlh
@@ -0,0 +1,59 @@
+/* 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/. */
+
+using mozilla::dom::GamepadServiceType from "mozilla/dom/GamepadMessageUtils.h";
+using mozilla::dom::GamepadPoseState from "mozilla/dom/GamepadMessageUtils.h";
+
+
+namespace mozilla {
+namespace dom {
+
+struct GamepadAdded {
+ nsString id;
+ uint32_t index;
+ // Ideally, mapping should be a GamepadMappingType
+ // But, we have dependency problems in non MOZ_GAMEPAD
+ // platforms. Therefore, we make it as an uint32_t here.
+ uint32_t mapping;
+ GamepadServiceType service_type;
+ uint32_t num_buttons;
+ uint32_t num_axes;
+};
+
+struct GamepadRemoved {
+ uint32_t index;
+ GamepadServiceType service_type;
+};
+
+struct GamepadAxisInformation {
+ uint32_t index;
+ GamepadServiceType service_type;
+ uint32_t axis;
+ double value;
+};
+
+struct GamepadButtonInformation {
+ uint32_t index;
+ GamepadServiceType service_type;
+ uint32_t button;
+ bool pressed;
+ double value;
+};
+
+struct GamepadPoseInformation {
+ uint32_t index;
+ GamepadServiceType service_type;
+ GamepadPoseState pose_state;
+};
+
+union GamepadChangeEvent {
+ GamepadAdded;
+ GamepadRemoved;
+ GamepadAxisInformation;
+ GamepadButtonInformation;
+ GamepadPoseInformation;
+};
+
+} // namespace dom
+} // namespace mozilla \ No newline at end of file
diff --git a/dom/gamepad/ipc/GamepadMessageUtils.h b/dom/gamepad/ipc/GamepadMessageUtils.h
new file mode 100644
index 0000000000..b9af6db9fa
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadMessageUtils.h
@@ -0,0 +1,82 @@
+
+#ifndef mozilla_dom_gamepad_GamepadMessageUtils_h
+#define mozilla_dom_gamepad_GamepadMessageUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/dom/GamepadServiceType.h"
+#include "mozilla/dom/GamepadPoseState.h"
+
+namespace IPC {
+
+template<>
+struct ParamTraits<mozilla::dom::GamepadServiceType> :
+ public ContiguousEnumSerializer<mozilla::dom::GamepadServiceType,
+ mozilla::dom::GamepadServiceType(0),
+ mozilla::dom::GamepadServiceType(
+ mozilla::dom::GamepadServiceType::NumGamepadServiceType)> {};
+
+template<>
+struct ParamTraits<mozilla::dom::GamepadCapabilityFlags> :
+ public BitFlagsEnumSerializer<mozilla::dom::GamepadCapabilityFlags,
+ mozilla::dom::GamepadCapabilityFlags::Cap_All> {};
+
+template <>
+struct ParamTraits<mozilla::dom::GamepadPoseState>
+{
+ typedef mozilla::dom::GamepadPoseState paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.flags);
+ WriteParam(aMsg, aParam.orientation[0]);
+ WriteParam(aMsg, aParam.orientation[1]);
+ WriteParam(aMsg, aParam.orientation[2]);
+ WriteParam(aMsg, aParam.orientation[3]);
+ WriteParam(aMsg, aParam.position[0]);
+ WriteParam(aMsg, aParam.position[1]);
+ WriteParam(aMsg, aParam.position[2]);
+ WriteParam(aMsg, aParam.angularVelocity[0]);
+ WriteParam(aMsg, aParam.angularVelocity[1]);
+ WriteParam(aMsg, aParam.angularVelocity[2]);
+ WriteParam(aMsg, aParam.angularAcceleration[0]);
+ WriteParam(aMsg, aParam.angularAcceleration[1]);
+ WriteParam(aMsg, aParam.angularAcceleration[2]);
+ WriteParam(aMsg, aParam.linearVelocity[0]);
+ WriteParam(aMsg, aParam.linearVelocity[1]);
+ WriteParam(aMsg, aParam.linearVelocity[2]);
+ WriteParam(aMsg, aParam.linearAcceleration[0]);
+ WriteParam(aMsg, aParam.linearAcceleration[1]);
+ WriteParam(aMsg, aParam.linearAcceleration[2]);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (!ReadParam(aMsg, aIter, &(aResult->flags)) ||
+ !ReadParam(aMsg, aIter, &(aResult->orientation[0])) ||
+ !ReadParam(aMsg, aIter, &(aResult->orientation[1])) ||
+ !ReadParam(aMsg, aIter, &(aResult->orientation[2])) ||
+ !ReadParam(aMsg, aIter, &(aResult->orientation[3])) ||
+ !ReadParam(aMsg, aIter, &(aResult->position[0])) ||
+ !ReadParam(aMsg, aIter, &(aResult->position[1])) ||
+ !ReadParam(aMsg, aIter, &(aResult->position[2])) ||
+ !ReadParam(aMsg, aIter, &(aResult->angularVelocity[0])) ||
+ !ReadParam(aMsg, aIter, &(aResult->angularVelocity[1])) ||
+ !ReadParam(aMsg, aIter, &(aResult->angularVelocity[2])) ||
+ !ReadParam(aMsg, aIter, &(aResult->angularAcceleration[0])) ||
+ !ReadParam(aMsg, aIter, &(aResult->angularAcceleration[1])) ||
+ !ReadParam(aMsg, aIter, &(aResult->angularAcceleration[2])) ||
+ !ReadParam(aMsg, aIter, &(aResult->linearVelocity[0])) ||
+ !ReadParam(aMsg, aIter, &(aResult->linearVelocity[1])) ||
+ !ReadParam(aMsg, aIter, &(aResult->linearVelocity[2])) ||
+ !ReadParam(aMsg, aIter, &(aResult->linearAcceleration[0])) ||
+ !ReadParam(aMsg, aIter, &(aResult->linearAcceleration[1])) ||
+ !ReadParam(aMsg, aIter, &(aResult->linearAcceleration[2]))) {
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_dom_gamepad_GamepadMessageUtils_h \ No newline at end of file
diff --git a/dom/gamepad/ipc/GamepadServiceType.h b/dom/gamepad/ipc/GamepadServiceType.h
new file mode 100644
index 0000000000..acc0967d1e
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadServiceType.h
@@ -0,0 +1,20 @@
+
+#ifndef mozilla_dom_GamepadServiceType_h_
+#define mozilla_dom_GamepadServiceType_h_
+
+namespace mozilla{
+namespace dom{
+
+// Standard channel is used for managing gamepads that
+// are from GamepadPlatformService. VR channel
+// is for gamepads that are from VRManagerChild.
+enum class GamepadServiceType : uint16_t {
+ Standard,
+ VR,
+ NumGamepadServiceType
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif // mozilla_dom_GamepadServiceType_h_ \ No newline at end of file
diff --git a/dom/gamepad/ipc/GamepadTestChannelChild.cpp b/dom/gamepad/ipc/GamepadTestChannelChild.cpp
new file mode 100644
index 0000000000..3fec9a7eaa
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.cpp
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GamepadTestChannelChild.h"
+
+namespace mozilla {
+namespace dom {
+
+void
+GamepadTestChannelChild::AddPromise(const uint32_t& aID, Promise* aPromise)
+{
+ MOZ_ASSERT(!mPromiseList.Get(aID, nullptr));
+ mPromiseList.Put(aID, aPromise);
+}
+
+bool
+GamepadTestChannelChild::RecvReplyGamepadIndex(const uint32_t& aID,
+ const uint32_t& aIndex)
+{
+ RefPtr<Promise> p;
+ if (!mPromiseList.Get(aID, getter_AddRefs(p))) {
+ MOZ_CRASH("We should always have a promise.");
+ }
+
+ p->MaybeResolve(aIndex);
+ mPromiseList.Remove(aID);
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/ipc/GamepadTestChannelChild.h b/dom/gamepad/ipc/GamepadTestChannelChild.h
new file mode 100644
index 0000000000..f2c771fe30
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.h
@@ -0,0 +1,29 @@
+/* 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/PGamepadTestChannelChild.h"
+#include "mozilla/dom/Promise.h"
+
+#ifndef mozilla_dom_GamepadTestChannelChild_h_
+#define mozilla_dom_GamepadTestChannelChild_h_
+
+namespace mozilla {
+namespace dom {
+
+class GamepadTestChannelChild final : public PGamepadTestChannelChild
+{
+ public:
+ GamepadTestChannelChild() {}
+ ~GamepadTestChannelChild() {}
+ void AddPromise(const uint32_t& aID, Promise* aPromise);
+ private:
+ virtual bool RecvReplyGamepadIndex(const uint32_t& aID,
+ const uint32_t& aIndex) override;
+ nsRefPtrHashtable<nsUint32HashKey, Promise> mPromiseList;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
diff --git a/dom/gamepad/ipc/GamepadTestChannelParent.cpp b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
new file mode 100644
index 0000000000..421447fe06
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GamepadTestChannelParent.h"
+
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace dom {
+
+bool
+GamepadTestChannelParent::RecvGamepadTestEvent(const uint32_t& aID,
+ const GamepadChangeEvent& aEvent)
+{
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ MOZ_ASSERT(service);
+ if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
+ const GamepadAdded& a = aEvent.get_GamepadAdded();
+ nsCString gamepadID;
+ LossyCopyUTF16toASCII(a.id(), gamepadID);
+ uint32_t index = service->AddGamepad(gamepadID.get(),
+ static_cast<GamepadMappingType>(a.mapping()),
+ a.num_buttons(),
+ a.num_axes());
+ if (!mShuttingdown) {
+ Unused << SendReplyGamepadIndex(aID, index);
+ }
+ return true;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
+ const GamepadRemoved& a = aEvent.get_GamepadRemoved();
+ service->RemoveGamepad(a.index());
+ return true;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
+ const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
+ service->NewButtonEvent(a.index(), a.button(), a.pressed(), a.value());
+ return true;
+ }
+ if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
+ const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
+ service->NewAxisMoveEvent(a.index(), a.axis(), a.value());
+ return true;
+ }
+
+ NS_WARNING("Unknown event type.");
+ return false;
+}
+
+bool
+GamepadTestChannelParent::RecvShutdownChannel()
+{
+ mShuttingdown = true;
+ Unused << Send__delete__(this);
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/ipc/GamepadTestChannelParent.h b/dom/gamepad/ipc/GamepadTestChannelParent.h
new file mode 100644
index 0000000000..ce66667b88
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PGamepadTestChannelParent.h"
+
+#ifndef mozilla_dom_GamepadTestChannelParent_h_
+#define mozilla_dom_GamepadTestChannelParent_h_
+
+namespace mozilla {
+namespace dom {
+
+class GamepadTestChannelParent final : public PGamepadTestChannelParent
+{
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadTestChannelParent)
+ GamepadTestChannelParent()
+ : mShuttingdown(false) {}
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override {}
+ virtual bool
+ RecvGamepadTestEvent(const uint32_t& aID,
+ const GamepadChangeEvent& aGamepadEvent) override;
+ virtual bool
+ RecvShutdownChannel() override;
+ private:
+ ~GamepadTestChannelParent() {}
+ bool mShuttingdown;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
diff --git a/dom/gamepad/ipc/PGamepadEventChannel.ipdl b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
new file mode 100644
index 0000000000..78e389a91e
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+include protocol PBackground;
+include GamepadEventTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PGamepadEventChannel {
+ manager PBackground;
+ parent:
+ async GamepadListenerAdded();
+ async GamepadListenerRemoved();
+ child:
+ async __delete__();
+ async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
+};
+
+}
+}
diff --git a/dom/gamepad/ipc/PGamepadTestChannel.ipdl b/dom/gamepad/ipc/PGamepadTestChannel.ipdl
new file mode 100644
index 0000000000..57b694acc3
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadTestChannel.ipdl
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+include protocol PBackground;
+include GamepadEventTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PGamepadTestChannel {
+ manager PBackground;
+ parent:
+ async GamepadTestEvent(uint32_t aID, GamepadChangeEvent aGamepadEvent);
+ async ShutdownChannel();
+ child:
+ async __delete__();
+ async ReplyGamepadIndex(uint32_t aID, uint32_t aIndex);
+};
+
+}
+}
diff --git a/dom/gamepad/linux/LinuxGamepad.cpp b/dom/gamepad/linux/LinuxGamepad.cpp
new file mode 100644
index 0000000000..c45f4174a2
--- /dev/null
+++ b/dom/gamepad/linux/LinuxGamepad.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * LinuxGamepadService: A Linux backend for the GamepadService.
+ * Derived from the kernel documentation at
+ * http://www.kernel.org/doc/Documentation/input/joystick-api.txt
+ */
+#include <algorithm>
+#include <cstddef>
+
+#include <glib.h>
+#include <linux/joystick.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "nscore.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "udev.h"
+
+namespace {
+
+using namespace mozilla::dom;
+using mozilla::udev_lib;
+using mozilla::udev_device;
+using mozilla::udev_list_entry;
+using mozilla::udev_enumerate;
+using mozilla::udev_monitor;
+
+static const float kMaxAxisValue = 32767.0;
+static const char kJoystickPath[] = "/dev/input/js";
+
+//TODO: should find a USB identifier for each device so we can
+// provide something that persists across connect/disconnect cycles.
+typedef struct {
+ int index;
+ guint source_id;
+ int numAxes;
+ int numButtons;
+ char idstring[128];
+ char devpath[PATH_MAX];
+} Gamepad;
+
+class LinuxGamepadService {
+public:
+ LinuxGamepadService() : mMonitor(nullptr),
+ mMonitorSourceID(0) {
+ }
+
+ void Startup();
+ void Shutdown();
+
+private:
+ void AddDevice(struct udev_device* dev);
+ void RemoveDevice(struct udev_device* dev);
+ void ScanForDevices();
+ void AddMonitor();
+ void RemoveMonitor();
+ bool is_gamepad(struct udev_device* dev);
+ void ReadUdevChange();
+
+ // handler for data from /dev/input/jsN
+ static gboolean OnGamepadData(GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+
+ // handler for data from udev monitor
+ static gboolean OnUdevMonitor(GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+
+ udev_lib mUdev;
+ struct udev_monitor* mMonitor;
+ guint mMonitorSourceID;
+ // Information about currently connected gamepads.
+ AutoTArray<Gamepad,4> mGamepads;
+};
+
+// singleton instance
+LinuxGamepadService* gService = nullptr;
+
+void
+LinuxGamepadService::AddDevice(struct udev_device* dev)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return;
+ }
+
+ // Ensure that this device hasn't already been added.
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (strcmp(mGamepads[i].devpath, devpath) == 0) {
+ return;
+ }
+ }
+
+ Gamepad gamepad;
+ snprintf(gamepad.devpath, sizeof(gamepad.devpath), "%s", devpath);
+ GIOChannel* channel = g_io_channel_new_file(devpath, "r", nullptr);
+ if (!channel) {
+ return;
+ }
+
+ g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, nullptr);
+ g_io_channel_set_encoding(channel, nullptr, nullptr);
+ g_io_channel_set_buffered(channel, FALSE);
+ int fd = g_io_channel_unix_get_fd(channel);
+ char name[128];
+ if (ioctl(fd, JSIOCGNAME(sizeof(name)), &name) == -1) {
+ strcpy(name, "unknown");
+ }
+ const char* vendor_id =
+ mUdev.udev_device_get_property_value(dev, "ID_VENDOR_ID");
+ const char* model_id =
+ mUdev.udev_device_get_property_value(dev, "ID_MODEL_ID");
+ if (!vendor_id || !model_id) {
+ struct udev_device* parent =
+ mUdev.udev_device_get_parent_with_subsystem_devtype(dev,
+ "input",
+ nullptr);
+ if (parent) {
+ vendor_id = mUdev.udev_device_get_sysattr_value(parent, "id/vendor");
+ model_id = mUdev.udev_device_get_sysattr_value(parent, "id/product");
+ }
+ }
+ snprintf(gamepad.idstring, sizeof(gamepad.idstring),
+ "%s-%s-%s",
+ vendor_id ? vendor_id : "unknown",
+ model_id ? model_id : "unknown",
+ name);
+
+ char numAxes = 0, numButtons = 0;
+ ioctl(fd, JSIOCGAXES, &numAxes);
+ gamepad.numAxes = numAxes;
+ ioctl(fd, JSIOCGBUTTONS, &numButtons);
+ gamepad.numButtons = numButtons;
+
+ gamepad.index = service->AddGamepad(gamepad.idstring,
+ mozilla::dom::GamepadMappingType::_empty,
+ gamepad.numButtons,
+ gamepad.numAxes);
+
+ gamepad.source_id =
+ g_io_add_watch(channel,
+ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
+ OnGamepadData,
+ GINT_TO_POINTER(gamepad.index));
+ g_io_channel_unref(channel);
+
+ mGamepads.AppendElement(gamepad);
+}
+
+void
+LinuxGamepadService::RemoveDevice(struct udev_device* dev)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return;
+ }
+
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (strcmp(mGamepads[i].devpath, devpath) == 0) {
+ g_source_remove(mGamepads[i].source_id);
+ service->RemoveGamepad(mGamepads[i].index);
+ mGamepads.RemoveElementAt(i);
+ break;
+ }
+ }
+}
+
+void
+LinuxGamepadService::ScanForDevices()
+{
+ struct udev_enumerate* en = mUdev.udev_enumerate_new(mUdev.udev);
+ mUdev.udev_enumerate_add_match_subsystem(en, "input");
+ mUdev.udev_enumerate_scan_devices(en);
+
+ struct udev_list_entry* dev_list_entry;
+ for (dev_list_entry = mUdev.udev_enumerate_get_list_entry(en);
+ dev_list_entry != nullptr;
+ dev_list_entry = mUdev.udev_list_entry_get_next(dev_list_entry)) {
+ const char* path = mUdev.udev_list_entry_get_name(dev_list_entry);
+ struct udev_device* dev = mUdev.udev_device_new_from_syspath(mUdev.udev,
+ path);
+ if (is_gamepad(dev)) {
+ AddDevice(dev);
+ }
+
+ mUdev.udev_device_unref(dev);
+ }
+
+ mUdev.udev_enumerate_unref(en);
+}
+
+void
+LinuxGamepadService::AddMonitor()
+{
+ // Add a monitor to watch for device changes
+ mMonitor =
+ mUdev.udev_monitor_new_from_netlink(mUdev.udev, "udev");
+ if (!mMonitor) {
+ // Not much we can do here.
+ return;
+ }
+ mUdev.udev_monitor_filter_add_match_subsystem_devtype(mMonitor,
+ "input",
+ nullptr);
+
+ int monitor_fd = mUdev.udev_monitor_get_fd(mMonitor);
+ GIOChannel* monitor_channel = g_io_channel_unix_new(monitor_fd);
+ mMonitorSourceID =
+ g_io_add_watch(monitor_channel,
+ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
+ OnUdevMonitor,
+ nullptr);
+ g_io_channel_unref(monitor_channel);
+
+ mUdev.udev_monitor_enable_receiving(mMonitor);
+}
+
+void
+LinuxGamepadService::RemoveMonitor()
+{
+ if (mMonitorSourceID) {
+ g_source_remove(mMonitorSourceID);
+ mMonitorSourceID = 0;
+ }
+ if (mMonitor) {
+ mUdev.udev_monitor_unref(mMonitor);
+ mMonitor = nullptr;
+ }
+}
+
+void
+LinuxGamepadService::Startup()
+{
+ // Don't bother starting up if libudev couldn't be loaded or initialized.
+ if (!mUdev)
+ return;
+
+ AddMonitor();
+ ScanForDevices();
+}
+
+void
+LinuxGamepadService::Shutdown()
+{
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ g_source_remove(mGamepads[i].source_id);
+ }
+ mGamepads.Clear();
+ RemoveMonitor();
+}
+
+bool
+LinuxGamepadService::is_gamepad(struct udev_device* dev)
+{
+ if (!mUdev.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"))
+ return false;
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return false;
+ }
+ if (strncmp(kJoystickPath, devpath, sizeof(kJoystickPath) - 1) != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+LinuxGamepadService::ReadUdevChange()
+{
+ struct udev_device* dev =
+ mUdev.udev_monitor_receive_device(mMonitor);
+ const char* action = mUdev.udev_device_get_action(dev);
+ if (is_gamepad(dev)) {
+ if (strcmp(action, "add") == 0) {
+ AddDevice(dev);
+ } else if (strcmp(action, "remove") == 0) {
+ RemoveDevice(dev);
+ }
+ }
+ mUdev.udev_device_unref(dev);
+}
+
+// static
+gboolean
+LinuxGamepadService::OnGamepadData(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return TRUE;
+ }
+ int index = GPOINTER_TO_INT(data);
+ //TODO: remove gamepad?
+ if (condition & G_IO_ERR || condition & G_IO_HUP)
+ return FALSE;
+
+ while (true) {
+ struct js_event event;
+ gsize count;
+ GError* err = nullptr;
+ if (g_io_channel_read_chars(source,
+ (gchar*)&event,
+ sizeof(event),
+ &count,
+ &err) != G_IO_STATUS_NORMAL ||
+ count == 0) {
+ break;
+ }
+
+ //TODO: store device state?
+ if (event.type & JS_EVENT_INIT) {
+ continue;
+ }
+
+ switch (event.type) {
+ case JS_EVENT_BUTTON:
+ service->NewButtonEvent(index, event.number, !!event.value);
+ break;
+ case JS_EVENT_AXIS:
+ service->NewAxisMoveEvent(index, event.number,
+ ((float)event.value) / kMaxAxisValue);
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+// static
+gboolean
+LinuxGamepadService::OnUdevMonitor(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data)
+{
+ if (condition & G_IO_ERR || condition & G_IO_HUP)
+ return FALSE;
+
+ gService->ReadUdevChange();
+ return TRUE;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+void StartGamepadMonitoring()
+{
+ if (gService) {
+ return;
+ }
+ gService = new LinuxGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring()
+{
+ if (!gService) {
+ return;
+ }
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/linux/udev.h b/dom/gamepad/linux/udev.h
new file mode 100644
index 0000000000..18da9f7dc7
--- /dev/null
+++ b/dom/gamepad/linux/udev.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file defines a wrapper around libudev so we can avoid
+ * linking directly to it and use dlopen instead.
+ */
+
+#ifndef HAL_LINUX_UDEV_H_
+#define HAL_LINUX_UDEV_H_
+
+#include <dlfcn.h>
+
+#include "mozilla/ArrayUtils.h"
+
+namespace mozilla {
+
+struct udev;
+struct udev_device;
+struct udev_enumerate;
+struct udev_list_entry;
+struct udev_monitor;
+
+class udev_lib {
+ public:
+ udev_lib() : lib(nullptr),
+ udev(nullptr) {
+ // Be careful about ABI compat! 0 -> 1 didn't change any
+ // symbols this code relies on, per:
+ // https://lists.fedoraproject.org/pipermail/devel/2012-June/168227.html
+ const char* lib_names[] = {"libudev.so.0", "libudev.so.1"};
+ // Check whether a library is already loaded so we don't load two
+ // conflicting libs.
+ for (unsigned i = 0; i < ArrayLength(lib_names); i++) {
+ lib = dlopen(lib_names[i], RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
+ if (lib) {
+ break;
+ }
+ }
+ // If nothing loads the first time through, it means no version of libudev
+ // was already loaded.
+ if (!lib) {
+ for (unsigned i = 0; i < ArrayLength(lib_names); i++) {
+ lib = dlopen(lib_names[i], RTLD_LAZY | RTLD_GLOBAL);
+ if (lib) {
+ break;
+ }
+ }
+ }
+ if (lib && LoadSymbols()) {
+ udev = udev_new();
+ }
+ }
+
+ ~udev_lib() {
+ if (udev) {
+ udev_unref(udev);
+ }
+
+ if (lib) {
+ dlclose(lib);
+ }
+ }
+
+ explicit operator bool() {
+ return udev;
+ }
+
+ private:
+ bool LoadSymbols() {
+#define DLSYM(s) \
+ do { \
+ s = (typeof(s))dlsym(lib, #s); \
+ if (!s) return false; \
+ } while (0)
+
+ DLSYM(udev_new);
+ DLSYM(udev_unref);
+ DLSYM(udev_device_unref);
+ DLSYM(udev_device_new_from_syspath);
+ DLSYM(udev_device_get_devnode);
+ DLSYM(udev_device_get_parent_with_subsystem_devtype);
+ DLSYM(udev_device_get_property_value);
+ DLSYM(udev_device_get_action);
+ DLSYM(udev_device_get_sysattr_value);
+ DLSYM(udev_enumerate_new);
+ DLSYM(udev_enumerate_unref);
+ DLSYM(udev_enumerate_add_match_subsystem);
+ DLSYM(udev_enumerate_scan_devices);
+ DLSYM(udev_enumerate_get_list_entry);
+ DLSYM(udev_list_entry_get_next);
+ DLSYM(udev_list_entry_get_name);
+ DLSYM(udev_monitor_new_from_netlink);
+ DLSYM(udev_monitor_filter_add_match_subsystem_devtype);
+ DLSYM(udev_monitor_enable_receiving);
+ DLSYM(udev_monitor_get_fd);
+ DLSYM(udev_monitor_receive_device);
+ DLSYM(udev_monitor_unref);
+#undef DLSYM
+ return true;
+ }
+
+ void* lib;
+
+ public:
+ struct udev* udev;
+
+ // Function pointers returned from dlsym.
+ struct udev* (*udev_new)(void);
+ void (*udev_unref)(struct udev*);
+
+ void (*udev_device_unref)(struct udev_device*);
+ struct udev_device* (*udev_device_new_from_syspath)(struct udev*,
+ const char*);
+ const char* (*udev_device_get_devnode)(struct udev_device*);
+ struct udev_device* (*udev_device_get_parent_with_subsystem_devtype)
+ (struct udev_device*, const char*, const char*);
+ const char* (*udev_device_get_property_value)(struct udev_device*,
+ const char*);
+ const char* (*udev_device_get_action)(struct udev_device*);
+ const char* (*udev_device_get_sysattr_value)(struct udev_device*,
+ const char*);
+
+ struct udev_enumerate* (*udev_enumerate_new)(struct udev*);
+ void (*udev_enumerate_unref)(struct udev_enumerate*);
+ int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate*,
+ const char*);
+ int (*udev_enumerate_scan_devices)(struct udev_enumerate*);
+ struct udev_list_entry* (*udev_enumerate_get_list_entry)
+ (struct udev_enumerate*);
+
+ struct udev_list_entry* (*udev_list_entry_get_next)(struct udev_list_entry *);
+ const char* (*udev_list_entry_get_name)(struct udev_list_entry*);
+
+ struct udev_monitor* (*udev_monitor_new_from_netlink)(struct udev*,
+ const char*);
+ int (*udev_monitor_filter_add_match_subsystem_devtype)
+ (struct udev_monitor*, const char*, const char*);
+ int (*udev_monitor_enable_receiving)(struct udev_monitor*);
+ int (*udev_monitor_get_fd)(struct udev_monitor*);
+ struct udev_device* (*udev_monitor_receive_device)(struct udev_monitor*);
+ void (*udev_monitor_unref)(struct udev_monitor*);
+};
+
+} // namespace mozilla
+
+#endif // HAL_LINUX_UDEV_H_
diff --git a/dom/gamepad/moz.build b/dom/gamepad/moz.build
new file mode 100644
index 0000000000..a809d1ebaa
--- /dev/null
+++ b/dom/gamepad/moz.build
@@ -0,0 +1,83 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+IPDL_SOURCES += [
+ 'ipc/GamepadEventTypes.ipdlh',
+ 'ipc/PGamepadEventChannel.ipdl',
+ 'ipc/PGamepadTestChannel.ipdl'
+]
+
+EXPORTS.mozilla.dom += [
+ 'GamepadPoseState.h',
+ 'ipc/GamepadMessageUtils.h',
+ 'ipc/GamepadServiceType.h'
+]
+
+if CONFIG['MOZ_GAMEPAD']:
+ EXPORTS.mozilla.dom += [
+ 'Gamepad.h',
+ 'GamepadButton.h',
+ 'GamepadManager.h',
+ 'GamepadMonitoring.h',
+ 'GamepadPlatformService.h',
+ 'GamepadPose.h',
+ 'GamepadServiceTest.h',
+ 'ipc/GamepadEventChannelChild.h',
+ 'ipc/GamepadEventChannelParent.h',
+ 'ipc/GamepadTestChannelChild.h',
+ 'ipc/GamepadTestChannelParent.h'
+ ]
+
+ UNIFIED_SOURCES = [
+ 'Gamepad.cpp',
+ 'GamepadButton.cpp',
+ 'GamepadManager.cpp',
+ 'GamepadMonitoring.cpp',
+ 'GamepadPlatformService.cpp',
+ 'GamepadPose.cpp',
+ 'GamepadServiceTest.cpp',
+ 'ipc/GamepadEventChannelChild.cpp',
+ 'ipc/GamepadEventChannelParent.cpp',
+ 'ipc/GamepadTestChannelChild.cpp',
+ 'ipc/GamepadTestChannelParent.cpp'
+ ]
+
+ if CONFIG['MOZ_GAMEPAD_BACKEND'] == 'stub':
+ UNIFIED_SOURCES += [
+ 'fallback/FallbackGamepad.cpp'
+ ]
+ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'cocoa/CocoaGamepad.cpp'
+ ]
+ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'windows':
+ UNIFIED_SOURCES += [
+ 'windows/WindowsGamepad.cpp'
+ ]
+ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'linux':
+ UNIFIED_SOURCES += [
+ 'linux/LinuxGamepad.cpp'
+ ]
+ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'android':
+ UNIFIED_SOURCES += [
+ 'android/AndroidGamepad.cpp'
+ ]
+
+ LOCAL_INCLUDES += [
+ 'ipc',
+ ]
+
+ include('/ipc/chromium/chromium-config.mozbuild')
+
+ FINAL_LIBRARY = 'xul'
+ LOCAL_INCLUDES += [
+ '/dom/base',
+ ]
+
+ CFLAGS += CONFIG['GLIB_CFLAGS']
+ CFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
+ CXXFLAGS += CONFIG['GLIB_CFLAGS']
+ CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
diff --git a/dom/gamepad/windows/WindowsGamepad.cpp b/dom/gamepad/windows/WindowsGamepad.cpp
new file mode 100644
index 0000000000..e1965c00c8
--- /dev/null
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -0,0 +1,1096 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include <cstddef>
+
+#ifndef UNICODE
+#define UNICODE
+#endif
+#include <windows.h>
+#include <hidsdi.h>
+#include <stdio.h>
+#include <xinput.h>
+
+#include "nsIComponentManager.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::ArrayLength;
+
+// USB HID usage tables, page 1 (Hat switch)
+const unsigned kUsageDpad = 0x39;
+// USB HID usage tables, page 1, 0x30 = X
+const unsigned kFirstAxis = 0x30;
+
+// USB HID usage tables
+const unsigned kDesktopUsagePage = 0x1;
+const unsigned kButtonUsagePage = 0x9;
+
+// Multiple devices-changed notifications can be sent when a device
+// is connected, because USB devices consist of multiple logical devices.
+// Therefore, we wait a bit after receiving one before looking for
+// device changes.
+const uint32_t kDevicesChangedStableDelay = 200;
+// Both DirectInput and XInput are polling-driven here,
+// so we need to poll it periodically.
+// 50ms is arbitrarily chosen.
+const uint32_t kWindowsGamepadPollInterval = 50;
+
+const UINT kRawInputError = (UINT)-1;
+
+#ifndef XUSER_MAX_COUNT
+#define XUSER_MAX_COUNT 4
+#endif
+
+const struct {
+ int usagePage;
+ int usage;
+} kUsagePages[] = {
+ // USB HID usage tables, page 1
+ { kDesktopUsagePage, 4 }, // Joystick
+ { kDesktopUsagePage, 5 } // Gamepad
+};
+
+const struct {
+ WORD button;
+ int mapped;
+} kXIButtonMap[] = {
+ { XINPUT_GAMEPAD_DPAD_UP, 12 },
+ { XINPUT_GAMEPAD_DPAD_DOWN, 13 },
+ { XINPUT_GAMEPAD_DPAD_LEFT, 14 },
+ { XINPUT_GAMEPAD_DPAD_RIGHT, 15 },
+ { XINPUT_GAMEPAD_START, 9 },
+ { XINPUT_GAMEPAD_BACK, 8 },
+ { XINPUT_GAMEPAD_LEFT_THUMB, 10 },
+ { XINPUT_GAMEPAD_RIGHT_THUMB, 11 },
+ { XINPUT_GAMEPAD_LEFT_SHOULDER, 4 },
+ { XINPUT_GAMEPAD_RIGHT_SHOULDER, 5 },
+ { XINPUT_GAMEPAD_A, 0 },
+ { XINPUT_GAMEPAD_B, 1 },
+ { XINPUT_GAMEPAD_X, 2 },
+ { XINPUT_GAMEPAD_Y, 3 }
+};
+const size_t kNumMappings = ArrayLength(kXIButtonMap);
+
+enum GamepadType {
+ kNoGamepad = 0,
+ kRawInputGamepad,
+ kXInputGamepad
+};
+
+class WindowsGamepadService;
+// This pointer holds a windows gamepad backend service,
+// it will be created and destroyed by background thread and
+// used by gMonitorThread
+WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
+nsCOMPtr<nsIThread> gMonitorThread = nullptr;
+static bool sIsShutdown = false;
+
+class Gamepad {
+public:
+ GamepadType type;
+
+ // Handle to raw input device
+ HANDLE handle;
+
+ // XInput Index of the user's controller. Passed to XInputGetState.
+ DWORD userIndex;
+
+ // Last-known state of the controller.
+ XINPUT_STATE state;
+
+ // ID from the GamepadService, also used as the index into
+ // WindowsGamepadService::mGamepads.
+ int id;
+
+
+ // Information about the physical device.
+ unsigned numAxes;
+ unsigned numButtons;
+ bool hasDpad;
+ HIDP_VALUE_CAPS dpadCaps;
+
+ nsTArray<bool> buttons;
+ struct axisValue {
+ HIDP_VALUE_CAPS caps;
+ double value;
+ };
+ nsTArray<axisValue> axes;
+
+ // Used during rescan to find devices that were disconnected.
+ bool present;
+
+ Gamepad(uint32_t aNumAxes,
+ uint32_t aNumButtons,
+ bool aHasDpad,
+ GamepadType aType) :
+ numAxes(aNumAxes),
+ numButtons(aNumButtons),
+ hasDpad(aHasDpad),
+ type(aType),
+ present(true)
+ {
+ buttons.SetLength(numButtons);
+ axes.SetLength(numAxes);
+ }
+private:
+ Gamepad() {}
+};
+
+// Drop this in favor of decltype when we require a new enough SDK.
+typedef void (WINAPI *XInputEnable_func)(BOOL);
+
+// RAII class to wrap loading the XInput DLL
+class XInputLoader {
+public:
+ XInputLoader() : module(nullptr),
+ mXInputEnable(nullptr),
+ mXInputGetState(nullptr) {
+ // xinput1_4.dll exists on Windows 8
+ // xinput9_1_0.dll exists on Windows 7 and Vista
+ // xinput1_3.dll shipped with the DirectX SDK
+ const wchar_t* dlls[] = {L"xinput1_4.dll",
+ L"xinput9_1_0.dll",
+ L"xinput1_3.dll"};
+ const size_t kNumDLLs = ArrayLength(dlls);
+ for (size_t i = 0; i < kNumDLLs; ++i) {
+ module = LoadLibraryW(dlls[i]);
+ if (module) {
+ mXInputEnable = reinterpret_cast<XInputEnable_func>(
+ GetProcAddress(module, "XInputEnable"));
+ mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
+ GetProcAddress(module, "XInputGetState"));
+ if (mXInputEnable) {
+ mXInputEnable(TRUE);
+ }
+ break;
+ }
+ }
+ }
+
+ ~XInputLoader() {
+ //mXInputEnable = nullptr;
+ mXInputGetState = nullptr;
+
+ if (module) {
+ FreeLibrary(module);
+ }
+ }
+
+ operator bool() {
+ return module && mXInputGetState;
+ }
+
+ HMODULE module;
+ decltype(XInputGetState) *mXInputGetState;
+ XInputEnable_func mXInputEnable;
+};
+
+bool
+GetPreparsedData(HANDLE handle, nsTArray<uint8_t>& data)
+{
+ UINT size;
+ if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) == kRawInputError) {
+ return false;
+ }
+ data.SetLength(size);
+ return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA,
+ data.Elements(), &size) > 0;
+}
+
+/*
+ * Given an axis value and a minimum and maximum range,
+ * scale it to be in the range -1.0 .. 1.0.
+ */
+double
+ScaleAxis(ULONG value, LONG min, LONG max)
+{
+ return 2.0 * (value - min) / (max - min) - 1.0;
+}
+
+/*
+ * Given a value from a d-pad (POV hat in USB HID terminology),
+ * represent it as 4 buttons, one for each cardinal direction.
+ */
+void
+UnpackDpad(LONG dpad_value, const Gamepad* gamepad, nsTArray<bool>& buttons)
+{
+ const unsigned kUp = gamepad->numButtons - 4;
+ const unsigned kDown = gamepad->numButtons - 3;
+ const unsigned kLeft = gamepad->numButtons - 2;
+ const unsigned kRight = gamepad->numButtons - 1;
+
+ // Different controllers have different ways of representing
+ // "nothing is pressed", but they're all outside the range of values.
+ if (dpad_value < gamepad->dpadCaps.LogicalMin
+ || dpad_value > gamepad->dpadCaps.LogicalMax) {
+ // Nothing is pressed.
+ return;
+ }
+
+ // Normalize value to start at 0.
+ int value = dpad_value - gamepad->dpadCaps.LogicalMin;
+
+ // Value will be in the range 0-7. The value represents the
+ // position of the d-pad around a circle, with 0 being straight up,
+ // 2 being right, 4 being straight down, and 6 being left.
+ if (value < 2 || value > 6) {
+ buttons[kUp] = true;
+ }
+ if (value > 2 && value < 6) {
+ buttons[kDown] = true;
+ }
+ if (value > 4) {
+ buttons[kLeft] = true;
+ }
+ if (value > 0 && value < 4) {
+ buttons[kRight] = true;
+ }
+}
+
+/*
+ * Return true if this USB HID usage page and usage are of a type we
+ * know how to handle.
+ */
+bool
+SupportedUsage(USHORT page, USHORT usage)
+{
+ for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) {
+ if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
+ return true;
+ }
+ }
+ return false;
+}
+
+class HIDLoader {
+public:
+ HIDLoader() : mModule(LoadLibraryW(L"hid.dll")),
+ mHidD_GetProductString(nullptr),
+ mHidP_GetCaps(nullptr),
+ mHidP_GetButtonCaps(nullptr),
+ mHidP_GetValueCaps(nullptr),
+ mHidP_GetUsages(nullptr),
+ mHidP_GetUsageValue(nullptr),
+ mHidP_GetScaledUsageValue(nullptr)
+ {
+ if (mModule) {
+ mHidD_GetProductString = reinterpret_cast<decltype(HidD_GetProductString)*>(GetProcAddress(mModule, "HidD_GetProductString"));
+ mHidP_GetCaps = reinterpret_cast<decltype(HidP_GetCaps)*>(GetProcAddress(mModule, "HidP_GetCaps"));
+ mHidP_GetButtonCaps = reinterpret_cast<decltype(HidP_GetButtonCaps)*>(GetProcAddress(mModule, "HidP_GetButtonCaps"));
+ mHidP_GetValueCaps = reinterpret_cast<decltype(HidP_GetValueCaps)*>(GetProcAddress(mModule, "HidP_GetValueCaps"));
+ mHidP_GetUsages = reinterpret_cast<decltype(HidP_GetUsages)*>(GetProcAddress(mModule, "HidP_GetUsages"));
+ mHidP_GetUsageValue = reinterpret_cast<decltype(HidP_GetUsageValue)*>(GetProcAddress(mModule, "HidP_GetUsageValue"));
+ mHidP_GetScaledUsageValue = reinterpret_cast<decltype(HidP_GetScaledUsageValue)*>(GetProcAddress(mModule, "HidP_GetScaledUsageValue"));
+ }
+ }
+
+ ~HIDLoader() {
+ if (mModule) {
+ FreeLibrary(mModule);
+ }
+ }
+
+ operator bool() {
+ return mModule &&
+ mHidD_GetProductString &&
+ mHidP_GetCaps &&
+ mHidP_GetButtonCaps &&
+ mHidP_GetValueCaps &&
+ mHidP_GetUsages &&
+ mHidP_GetUsageValue &&
+ mHidP_GetScaledUsageValue;
+ }
+
+ decltype(HidD_GetProductString) *mHidD_GetProductString;
+ decltype(HidP_GetCaps) *mHidP_GetCaps;
+ decltype(HidP_GetButtonCaps) *mHidP_GetButtonCaps;
+ decltype(HidP_GetValueCaps) *mHidP_GetValueCaps;
+ decltype(HidP_GetUsages) *mHidP_GetUsages;
+ decltype(HidP_GetUsageValue) *mHidP_GetUsageValue;
+ decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue;
+
+private:
+ HMODULE mModule;
+};
+
+HWND sHWnd = nullptr;
+
+static void
+DirectInputMessageLoopOnceCallback(nsITimer *aTimer, void* aClosure)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+ MSG msg;
+ while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ aTimer->Cancel();
+ if (!sIsShutdown) {
+ aTimer->InitWithFuncCallback(DirectInputMessageLoopOnceCallback,
+ nullptr, kWindowsGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+class WindowsGamepadService
+{
+ public:
+ WindowsGamepadService()
+ {
+ mDirectInputTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mXInputTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mDeviceChangeTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+ virtual ~WindowsGamepadService()
+ {
+ Cleanup();
+ }
+
+ void DevicesChanged(bool aIsStablizing);
+
+ void StartMessageLoop()
+ {
+ MOZ_ASSERT(mDirectInputTimer);
+ mDirectInputTimer->InitWithFuncCallback(DirectInputMessageLoopOnceCallback,
+ nullptr, kWindowsGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ void Startup();
+ void Shutdown();
+ // Parse gamepad input from a WM_INPUT message.
+ bool HandleRawInput(HRAWINPUT handle);
+
+ static void XInputMessageLoopOnceCallback(nsITimer *aTimer, void* aClosure);
+ static void DevicesChangeCallback(nsITimer *aTimer, void* aService);
+
+ private:
+ void ScanForDevices();
+ // Look for connected raw input devices.
+ void ScanForRawInputDevices();
+ // Look for connected XInput devices.
+ bool ScanForXInputDevices();
+ bool HaveXInputGamepad(int userIndex);
+
+ bool mIsXInputMonitoring;
+ void PollXInput();
+ void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);
+
+ // Get information about a raw input gamepad.
+ bool GetRawGamepad(HANDLE handle);
+ void Cleanup();
+
+ // List of connected devices.
+ nsTArray<Gamepad> mGamepads;
+
+ HIDLoader mHID;
+ XInputLoader mXInput;
+
+ nsCOMPtr<nsITimer> mDirectInputTimer;
+ nsCOMPtr<nsITimer> mXInputTimer;
+ nsCOMPtr<nsITimer> mDeviceChangeTimer;
+};
+
+
+void
+WindowsGamepadService::ScanForRawInputDevices()
+{
+ if (!mHID) {
+ return;
+ }
+
+ UINT numDevices;
+ if (GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST))
+ == kRawInputError) {
+ return;
+ }
+ nsTArray<RAWINPUTDEVICELIST> devices(numDevices);
+ devices.SetLength(numDevices);
+ if (GetRawInputDeviceList(devices.Elements(), &numDevices,
+ sizeof(RAWINPUTDEVICELIST)) == kRawInputError) {
+ return;
+ }
+
+ for (unsigned i = 0; i < devices.Length(); i++) {
+ if (devices[i].dwType == RIM_TYPEHID) {
+ GetRawGamepad(devices[i].hDevice);
+ }
+ }
+}
+
+// static
+void
+WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer *aTimer,
+ void* aService)
+{
+ MOZ_ASSERT(aService);
+ WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+ self->PollXInput();
+ if (self->mIsXInputMonitoring) {
+ aTimer->Cancel();
+ aTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, self,
+ kWindowsGamepadPollInterval, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+// static
+void
+WindowsGamepadService::DevicesChangeCallback(nsITimer *aTimer, void* aService)
+{
+ MOZ_ASSERT(aService);
+ WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+ self->DevicesChanged(false);
+}
+
+bool
+WindowsGamepadService::HaveXInputGamepad(int userIndex)
+{
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type == kXInputGamepad
+ && mGamepads[i].userIndex == userIndex) {
+ mGamepads[i].present = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+WindowsGamepadService::ScanForXInputDevices()
+{
+ MOZ_ASSERT(mXInput, "XInput should be present!");
+
+ bool found = false;
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return found;
+ }
+
+ for (int i = 0; i < XUSER_MAX_COUNT; i++) {
+ XINPUT_STATE state = {};
+ if (mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
+ continue;
+ }
+ found = true;
+ // See if this device is already present in our list.
+ if (HaveXInputGamepad(i)) {
+ continue;
+ }
+
+ // Not already present, add it.
+ Gamepad gamepad(kStandardGamepadAxes,
+ kStandardGamepadButtons,
+ true,
+ kXInputGamepad);
+ gamepad.userIndex = i;
+ gamepad.state = state;
+ gamepad.id = service->AddGamepad("xinput",
+ GamepadMappingType::Standard,
+ kStandardGamepadButtons,
+ kStandardGamepadAxes);
+ mGamepads.AppendElement(gamepad);
+ }
+
+ return found;
+}
+
+void
+WindowsGamepadService::ScanForDevices()
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+ mGamepads[i].present = false;
+ }
+
+ if (mHID) {
+ ScanForRawInputDevices();
+ }
+ if (mXInput) {
+ mXInputTimer->Cancel();
+ if (ScanForXInputDevices()) {
+ mIsXInputMonitoring = true;
+ mXInputTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, this,
+ kWindowsGamepadPollInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ mIsXInputMonitoring = false;
+ }
+ }
+
+ // Look for devices that are no longer present and remove them.
+ for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+ if (!mGamepads[i].present) {
+ service->RemoveGamepad(mGamepads[i].id);
+ mGamepads.RemoveElementAt(i);
+ }
+ }
+}
+
+void
+WindowsGamepadService::PollXInput()
+{
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type != kXInputGamepad) {
+ continue;
+ }
+
+ XINPUT_STATE state = {};
+ DWORD res = mXInput.mXInputGetState(mGamepads[i].userIndex, &state);
+ if (res == ERROR_SUCCESS
+ && state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) {
+ CheckXInputChanges(mGamepads[i], state);
+ }
+ }
+}
+
+void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
+ XINPUT_STATE& state) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ // Handle digital buttons first
+ for (size_t b = 0; b < kNumMappings; b++) {
+ if (state.Gamepad.wButtons & kXIButtonMap[b].button &&
+ !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) {
+ // Button pressed
+ service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true);
+ } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) &&
+ gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) {
+ // Button released
+ service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false);
+ }
+ }
+
+ // Then triggers
+ if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
+ bool pressed =
+ state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
+ service->NewButtonEvent(gamepad.id, kButtonLeftTrigger,
+ pressed, state.Gamepad.bLeftTrigger / 255.0);
+ }
+ if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
+ bool pressed =
+ state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
+ service->NewButtonEvent(gamepad.id, kButtonRightTrigger,
+ pressed, state.Gamepad.bRightTrigger / 255.0);
+ }
+
+ // Finally deal with analog sticks
+ // TODO: bug 1001955 - Support deadzones.
+ if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) {
+ service->NewAxisMoveEvent(gamepad.id, kLeftStickXAxis,
+ state.Gamepad.sThumbLX / 32767.0);
+ }
+ if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
+ service->NewAxisMoveEvent(gamepad.id, kLeftStickYAxis,
+ -1.0 * state.Gamepad.sThumbLY / 32767.0);
+ }
+ if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
+ service->NewAxisMoveEvent(gamepad.id, kRightStickXAxis,
+ state.Gamepad.sThumbRX / 32767.0);
+ }
+ if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
+ service->NewAxisMoveEvent(gamepad.id, kRightStickYAxis,
+ -1.0 * state.Gamepad.sThumbRY / 32767.0);
+ }
+ gamepad.state = state;
+}
+
+// Used to sort a list of axes by HID usage.
+class HidValueComparator {
+public:
+ bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
+ {
+ return c1.UsagePage == c2.UsagePage && c1.Range.UsageMin == c2.Range.UsageMin;
+ }
+ bool LessThan(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
+ {
+ if (c1.UsagePage == c2.UsagePage) {
+ return c1.Range.UsageMin < c2.Range.UsageMin;
+ }
+ return c1.UsagePage < c2.UsagePage;
+ }
+};
+
+bool
+WindowsGamepadService::GetRawGamepad(HANDLE handle)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return true;
+ }
+
+ if (!mHID) {
+ return false;
+ }
+
+ for (unsigned i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type == kRawInputGamepad && mGamepads[i].handle == handle) {
+ mGamepads[i].present = true;
+ return true;
+ }
+ }
+
+ RID_DEVICE_INFO rdi = {};
+ UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO);
+ if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) == kRawInputError) {
+ return false;
+ }
+ // Ensure that this is a device we care about
+ if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
+ return false;
+ }
+
+ // Device name is a mostly-opaque string.
+ if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) == kRawInputError) {
+ return false;
+ }
+
+ nsTArray<wchar_t> devname(size);
+ devname.SetLength(size);
+ if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(), &size) == kRawInputError) {
+ return false;
+ }
+
+ // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
+ // device names containing "IG_" are XInput controllers. Ignore those
+ // devices since we'll handle them with XInput.
+ if (wcsstr(devname.Elements(), L"IG_")) {
+ return false;
+ }
+
+ // Product string is a human-readable name.
+ // Per http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx
+ // "For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character)."
+ wchar_t name[128] = { 0 };
+ size = sizeof(name);
+ nsTArray<char> gamepad_name;
+ HANDLE hid_handle = CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hid_handle) {
+ if (mHID.mHidD_GetProductString(hid_handle, &name, size)) {
+ int bytes = WideCharToMultiByte(CP_UTF8, 0, name, -1, nullptr, 0, nullptr,
+ nullptr);
+ gamepad_name.SetLength(bytes);
+ WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad_name.Elements(),
+ bytes, nullptr, nullptr);
+ }
+ CloseHandle(hid_handle);
+ }
+ if (gamepad_name.Length() == 0 || !gamepad_name[0]) {
+ const char kUnknown[] = "Unknown Gamepad";
+ gamepad_name.SetLength(ArrayLength(kUnknown));
+ strcpy_s(gamepad_name.Elements(), gamepad_name.Length(), kUnknown);
+ }
+
+ char gamepad_id[256] = { 0 };
+ _snprintf_s(gamepad_id, _TRUNCATE, "%04x-%04x-%s", rdi.hid.dwVendorId,
+ rdi.hid.dwProductId, gamepad_name.Elements());
+
+ nsTArray<uint8_t> preparsedbytes;
+ if (!GetPreparsedData(handle, preparsedbytes)) {
+ return false;
+ }
+
+ PHIDP_PREPARSED_DATA parsed =
+ reinterpret_cast<PHIDP_PREPARSED_DATA>(preparsedbytes.Elements());
+ HIDP_CAPS caps;
+ if (mHID.mHidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+
+ // Enumerate buttons.
+ USHORT count = caps.NumberInputButtonCaps;
+ nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count);
+ buttonCaps.SetLength(count);
+ if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count, parsed)
+ != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+ uint32_t numButtons = 0;
+ for (unsigned i = 0; i < count; i++) {
+ // Each buttonCaps is typically a range of buttons.
+ numButtons +=
+ buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
+ }
+
+ // Enumerate value caps, which represent axes and d-pads.
+ count = caps.NumberInputValueCaps;
+ nsTArray<HIDP_VALUE_CAPS> valueCaps(count);
+ valueCaps.SetLength(count);
+ if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count, parsed)
+ != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+ nsTArray<HIDP_VALUE_CAPS> axes;
+ // Sort the axes by usagePage and usage to expose a consistent ordering.
+ bool hasDpad;
+ HIDP_VALUE_CAPS dpadCaps;
+
+ HidValueComparator comparator;
+ for (unsigned i = 0; i < count; i++) {
+ if (valueCaps[i].UsagePage == kDesktopUsagePage
+ && valueCaps[i].Range.UsageMin == kUsageDpad
+ // Don't know how to handle d-pads that return weird values.
+ && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7) {
+ // d-pad gets special handling.
+ // Ostensibly HID devices can expose multiple d-pads, but this
+ // doesn't happen in practice.
+ hasDpad = true;
+ dpadCaps = valueCaps[i];
+ // Expose d-pad as 4 additional buttons.
+ numButtons += 4;
+ } else {
+ axes.InsertElementSorted(valueCaps[i], comparator);
+ }
+ }
+
+ uint32_t numAxes = axes.Length();
+
+ // Not already present, add it.
+ Gamepad gamepad(numAxes,
+ numButtons,
+ true,
+ kRawInputGamepad);
+
+ gamepad.handle = handle;
+
+ for (unsigned i = 0; i < gamepad.numAxes; i++) {
+ gamepad.axes[i].caps = axes[i];
+ }
+
+ gamepad.id = service->AddGamepad(gamepad_id,
+ GamepadMappingType::_empty,
+ gamepad.numButtons,
+ gamepad.numAxes);
+ mGamepads.AppendElement(gamepad);
+ return true;
+}
+
+bool
+WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
+{
+ if (!mHID) {
+ return false;
+ }
+
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return false;
+ }
+
+ // First, get data from the handle
+ UINT size;
+ GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
+ nsTArray<uint8_t> data(size);
+ data.SetLength(size);
+ if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
+ sizeof(RAWINPUTHEADER)) == kRawInputError) {
+ return false;
+ }
+ PRAWINPUT raw = reinterpret_cast<PRAWINPUT>(data.Elements());
+
+ Gamepad* gamepad = nullptr;
+ for (unsigned i = 0; i < mGamepads.Length(); i++) {
+ if (mGamepads[i].type == kRawInputGamepad
+ && mGamepads[i].handle == raw->header.hDevice) {
+ gamepad = &mGamepads[i];
+ break;
+ }
+ }
+ if (gamepad == nullptr) {
+ return false;
+ }
+
+ // Second, get the preparsed data
+ nsTArray<uint8_t> parsedbytes;
+ if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) {
+ return false;
+ }
+ PHIDP_PREPARSED_DATA parsed =
+ reinterpret_cast<PHIDP_PREPARSED_DATA>(parsedbytes.Elements());
+
+ // Get all the pressed buttons.
+ nsTArray<USAGE> usages(gamepad->numButtons);
+ usages.SetLength(gamepad->numButtons);
+ ULONG usageLength = gamepad->numButtons;
+ if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
+ &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
+ raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+
+ nsTArray<bool> buttons(gamepad->numButtons);
+ buttons.SetLength(gamepad->numButtons);
+ // If we don't zero out the buttons array first, sometimes it can reuse values.
+ memset(buttons.Elements(), 0, gamepad->numButtons * sizeof(bool));
+
+ for (unsigned i = 0; i < usageLength; i++) {
+ buttons[usages[i] - 1] = true;
+ }
+
+ if (gamepad->hasDpad) {
+ // Get d-pad position as 4 buttons.
+ ULONG value;
+ if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
+ UnpackDpad(static_cast<LONG>(value), gamepad, buttons);
+ }
+ }
+
+ for (unsigned i = 0; i < gamepad->numButtons; i++) {
+ if (gamepad->buttons[i] != buttons[i]) {
+ service->NewButtonEvent(gamepad->id, i, buttons[i]);
+ gamepad->buttons[i] = buttons[i];
+ }
+ }
+
+ // Get all axis values.
+ for (unsigned i = 0; i < gamepad->numAxes; i++) {
+ double new_value;
+ if (gamepad->axes[i].caps.LogicalMin < 0) {
+LONG value;
+ if (mHID.mHidP_GetScaledUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage,
+ 0, gamepad->axes[i].caps.Range.UsageMin,
+ &value, parsed,
+ (PCHAR)raw->data.hid.bRawData,
+ raw->data.hid.dwSizeHid)
+ != HIDP_STATUS_SUCCESS) {
+ continue;
+ }
+ new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
+ gamepad->axes[i].caps.LogicalMax);
+ } else {
+ ULONG value;
+ if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
+ gamepad->axes[i].caps.Range.UsageMin, &value,
+ parsed, (PCHAR)raw->data.hid.bRawData,
+ raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
+ continue;
+ }
+
+ new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
+ gamepad->axes[i].caps.LogicalMax);
+ }
+ if (gamepad->axes[i].value != new_value) {
+ service->NewAxisMoveEvent(gamepad->id, i, new_value);
+ gamepad->axes[i].value = new_value;
+ }
+ }
+
+ return true;
+}
+
+void
+WindowsGamepadService::Startup()
+{
+ ScanForDevices();
+}
+
+void
+WindowsGamepadService::Shutdown()
+{
+ Cleanup();
+}
+
+void
+WindowsGamepadService::Cleanup()
+{
+ mIsXInputMonitoring = false;
+ if (mDirectInputTimer) {
+ mDirectInputTimer->Cancel();
+ }
+ if (mXInputTimer) {
+ mXInputTimer->Cancel();
+ }
+ if (mDeviceChangeTimer) {
+ mDeviceChangeTimer->Cancel();
+ }
+ mGamepads.Clear();
+}
+
+void
+WindowsGamepadService::DevicesChanged(bool aIsStablizing)
+{
+ if (aIsStablizing) {
+ mDeviceChangeTimer->Cancel();
+ mDeviceChangeTimer->InitWithFuncCallback(DevicesChangeCallback, this,
+ kDevicesChangedStableDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ ScanForDevices();
+ }
+}
+
+bool
+RegisterRawInput(HWND hwnd, bool enable)
+{
+ nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages));
+ rid.SetLength(ArrayLength(kUsagePages));
+
+ for (unsigned i = 0; i < rid.Length(); i++) {
+ rid[i].usUsagePage = kUsagePages[i].usagePage;
+ rid[i].usUsage = kUsagePages[i].usage;
+ rid[i].dwFlags =
+ enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
+ rid[i].hwndTarget = hwnd;
+ }
+
+ if (!RegisterRawInputDevices(rid.Elements(), rid.Length(),
+ sizeof(RAWINPUTDEVICE))) {
+ return false;
+ }
+ return true;
+}
+
+static
+LRESULT CALLBACK
+GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ const unsigned int DBT_DEVICEARRIVAL = 0x8000;
+ const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
+ const unsigned int DBT_DEVNODES_CHANGED = 0x7;
+
+ switch (msg) {
+ case WM_DEVICECHANGE:
+ if (wParam == DBT_DEVICEARRIVAL ||
+ wParam == DBT_DEVICEREMOVECOMPLETE ||
+ wParam == DBT_DEVNODES_CHANGED) {
+ if (gService) {
+ gService->DevicesChanged(true);
+ }
+ }
+ break;
+ case WM_INPUT:
+ if (gService) {
+ gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+class StartWindowsGamepadServiceRunnable final : public Runnable
+{
+public:
+ StartWindowsGamepadServiceRunnable() {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+ gService = new WindowsGamepadService();
+ gService->Startup();
+
+ if (sHWnd == nullptr) {
+ WNDCLASSW wc;
+ HMODULE hSelf = GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
+ ZeroMemory(&wc, sizeof(WNDCLASSW));
+ wc.hInstance = hSelf;
+ wc.lpfnWndProc = GamepadWindowProc;
+ wc.lpszClassName = L"MozillaGamepadClass";
+ RegisterClassW(&wc);
+ }
+
+ sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
+ 0, 0, 0, 0, 0,
+ nullptr, nullptr, hSelf, nullptr);
+ RegisterRawInput(sHWnd, true);
+ }
+
+ // Explicitly start the message loop
+ gService->StartMessageLoop();
+
+ return NS_OK;
+ }
+private:
+ ~StartWindowsGamepadServiceRunnable() {}
+};
+
+class StopWindowsGamepadServiceRunnable final : public Runnable
+{
+ public:
+ StopWindowsGamepadServiceRunnable() {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+ if (sHWnd) {
+ RegisterRawInput(sHWnd, false);
+ DestroyWindow(sHWnd);
+ sHWnd = nullptr;
+ }
+
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+
+ return NS_OK;
+ }
+ private:
+ ~StopWindowsGamepadServiceRunnable() {}
+};
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::ipc;
+
+void
+StartGamepadMonitoring()
+{
+ AssertIsOnBackgroundThread();
+
+ if (gMonitorThread || gService) {
+ return;
+ }
+ sIsShutdown = false;
+ NS_NewThread(getter_AddRefs(gMonitorThread));
+ gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(),
+ NS_DISPATCH_NORMAL);
+}
+
+void
+StopGamepadMonitoring()
+{
+ AssertIsOnBackgroundThread();
+
+ if (sIsShutdown) {
+ return;
+ }
+ sIsShutdown = true;
+ gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(), NS_DISPATCH_NORMAL);
+ gMonitorThread->Shutdown();
+ gMonitorThread = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla