From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/gamepad/Gamepad.cpp | 147 ++++ dom/gamepad/Gamepad.h | 134 +++ dom/gamepad/GamepadButton.cpp | 30 + dom/gamepad/GamepadButton.h | 69 ++ dom/gamepad/GamepadManager.cpp | 699 ++++++++++++++++ dom/gamepad/GamepadManager.h | 156 ++++ dom/gamepad/GamepadMonitoring.cpp | 32 + dom/gamepad/GamepadMonitoring.h | 24 + dom/gamepad/GamepadPlatformService.cpp | 265 ++++++ dom/gamepad/GamepadPlatformService.h | 104 +++ dom/gamepad/GamepadPose.cpp | 120 +++ dom/gamepad/GamepadPose.h | 58 ++ dom/gamepad/GamepadPoseState.h | 88 ++ dom/gamepad/GamepadServiceTest.cpp | 283 +++++++ dom/gamepad/GamepadServiceTest.h | 89 ++ dom/gamepad/android/AndroidGamepad.cpp | 84 ++ dom/gamepad/cocoa/CocoaGamepad.cpp | 589 +++++++++++++ dom/gamepad/fallback/FallbackGamepad.cpp | 20 + dom/gamepad/ipc/GamepadEventChannelChild.cpp | 42 + dom/gamepad/ipc/GamepadEventChannelChild.h | 24 + dom/gamepad/ipc/GamepadEventChannelParent.cpp | 101 +++ dom/gamepad/ipc/GamepadEventChannelParent.h | 31 + dom/gamepad/ipc/GamepadEventTypes.ipdlh | 59 ++ dom/gamepad/ipc/GamepadMessageUtils.h | 82 ++ dom/gamepad/ipc/GamepadServiceType.h | 20 + dom/gamepad/ipc/GamepadTestChannelChild.cpp | 32 + dom/gamepad/ipc/GamepadTestChannelChild.h | 29 + dom/gamepad/ipc/GamepadTestChannelParent.cpp | 63 ++ dom/gamepad/ipc/GamepadTestChannelParent.h | 33 + dom/gamepad/ipc/PGamepadEventChannel.ipdl | 21 + dom/gamepad/ipc/PGamepadTestChannel.ipdl | 21 + dom/gamepad/linux/LinuxGamepad.cpp | 390 +++++++++ dom/gamepad/linux/udev.h | 150 ++++ dom/gamepad/moz.build | 83 ++ dom/gamepad/windows/WindowsGamepad.cpp | 1096 +++++++++++++++++++++++++ 35 files changed, 5268 insertions(+) create mode 100644 dom/gamepad/Gamepad.cpp create mode 100644 dom/gamepad/Gamepad.h create mode 100644 dom/gamepad/GamepadButton.cpp create mode 100644 dom/gamepad/GamepadButton.h create mode 100644 dom/gamepad/GamepadManager.cpp create mode 100644 dom/gamepad/GamepadManager.h create mode 100644 dom/gamepad/GamepadMonitoring.cpp create mode 100644 dom/gamepad/GamepadMonitoring.h create mode 100644 dom/gamepad/GamepadPlatformService.cpp create mode 100644 dom/gamepad/GamepadPlatformService.h create mode 100644 dom/gamepad/GamepadPose.cpp create mode 100644 dom/gamepad/GamepadPose.h create mode 100644 dom/gamepad/GamepadPoseState.h create mode 100644 dom/gamepad/GamepadServiceTest.cpp create mode 100644 dom/gamepad/GamepadServiceTest.h create mode 100644 dom/gamepad/android/AndroidGamepad.cpp create mode 100644 dom/gamepad/cocoa/CocoaGamepad.cpp create mode 100644 dom/gamepad/fallback/FallbackGamepad.cpp create mode 100644 dom/gamepad/ipc/GamepadEventChannelChild.cpp create mode 100644 dom/gamepad/ipc/GamepadEventChannelChild.h create mode 100644 dom/gamepad/ipc/GamepadEventChannelParent.cpp create mode 100644 dom/gamepad/ipc/GamepadEventChannelParent.h create mode 100644 dom/gamepad/ipc/GamepadEventTypes.ipdlh create mode 100644 dom/gamepad/ipc/GamepadMessageUtils.h create mode 100644 dom/gamepad/ipc/GamepadServiceType.h create mode 100644 dom/gamepad/ipc/GamepadTestChannelChild.cpp create mode 100644 dom/gamepad/ipc/GamepadTestChannelChild.h create mode 100644 dom/gamepad/ipc/GamepadTestChannelParent.cpp create mode 100644 dom/gamepad/ipc/GamepadTestChannelParent.h create mode 100644 dom/gamepad/ipc/PGamepadEventChannel.ipdl create mode 100644 dom/gamepad/ipc/PGamepadTestChannel.ipdl create mode 100644 dom/gamepad/linux/LinuxGamepad.cpp create mode 100644 dom/gamepad/linux/udev.h create mode 100644 dom/gamepad/moz.build create mode 100644 dom/gamepad/windows/WindowsGamepad.cpp (limited to 'dom/gamepad') diff --git a/dom/gamepad/Gamepad.cpp b/dom/gamepad/Gamepad.cpp new file mode 100644 index 000000000..0c59b190e --- /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 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::Clone(nsISupports* aParent) +{ + RefPtr 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 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 000000000..e388348f0 --- /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 +#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 Clone(nsISupports* aParent); + + nsISupports* GetParentObject() const + { + return mParent; + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + void GetId(nsAString& aID) const + { + aID = mID; + } + + DOMHighResTimeStamp Timestamp() const + { + return mTimestamp; + } + + GamepadMappingType Mapping() + { + return mMapping; + } + + bool Connected() const + { + return mConnected; + } + + uint32_t Index() const + { + return mIndex; + } + + void GetButtons(nsTArray>& aButtons) const + { + aButtons = mButtons; + } + + void GetAxes(nsTArray& aAxes) const + { + aAxes = mAxes; + } + + GamepadPose* GetPose() const + { + return mPose; + } + +private: + virtual ~Gamepad() {} + void UpdateTimestamp(); + +protected: + nsCOMPtr 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> mButtons; + nsTArray mAxes; + DOMHighResTimeStamp mTimestamp; + RefPtr 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 000000000..154a13985 --- /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 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 000000000..2855959e5 --- /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 +#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 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 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 000000000..dde71dd0a --- /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 + +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>::index_type NoIndex = + nsTArray>::NoIndex; + +bool sShutdown = false; + +StaticRefPtr 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 observerService = + mozilla::services::GetObserverService(); + + if (NS_WARN_IF(!observerService)) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + rv = observerService->AddObserver(this, + NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, + false); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +GamepadManager::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + BeginShutdown(); + return NS_OK; +} + +void +GamepadManager::StopMonitoring() +{ + 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 +GamepadManager::GetGamepad(uint32_t aIndex) const +{ + RefPtr 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 = + 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 = 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 = 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> 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 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 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 = 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> 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 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 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 = 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> 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 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 = GetGamepad(aIndex); + if (!gamepad) { + return; + } + + // Hold on to listeners in a separate array because firing events + // can mutate the mListeners array. + nsTArray> 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 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 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 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 = GetGamepad(aIndex); + if (!gamepad) { + return; + } + + aGamepad->SyncState(gamepad); +} + +// static +bool +GamepadManager::IsServiceRunning() +{ + return !!gGamepadManagerSingleton; +} + +// static +already_AddRefed +GamepadManager::GetService() +{ + if (sShutdown) { + return nullptr; + } + + if (!gGamepadManagerSingleton) { + RefPtr manager = new GamepadManager(); + nsresult rv = manager->Init(); + if(NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + gGamepadManagerSingleton = manager; + ClearOnShutdown(&gGamepadManagerSingleton); + } + + RefPtr service(gGamepadManagerSingleton); + return service.forget(); +} + +// 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 = 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 window = ToSupports(aWindow); + RefPtr gamepad = GetGamepad(aIndex); + if (!gamepad) { + return; + } + RefPtr 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(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 000000000..1bb437d8f --- /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 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 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 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 mGamepads; + // Inner windows that are listening for gamepad events. + // has been sent to that window. + nsTArray> 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 000000000..ac45aea57 --- /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 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 000000000..bf231c4dd --- /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 000000000..3df967aec --- /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 gGamepadPlatformServiceSingleton; + +} //namepsace + +GamepadPlatformService::GamepadPlatformService() + : mGamepadIndex(0), + mMutex("mozilla::dom::GamepadPlatformService") +{} + +GamepadPlatformService::~GamepadPlatformService() +{ + Cleanup(); +} + +// static +already_AddRefed +GamepadPlatformService::GetParentService() +{ + //GamepadPlatformService can only be accessed in parent process + MOZ_ASSERT(XRE_IsParentProcess()); + if (!gGamepadPlatformServiceSingleton) { + // Only Background Thread can create new GamepadPlatformService instance. + if (IsOnBackgroundThread()) { + gGamepadPlatformServiceSingleton = new GamepadPlatformService(); + } else { + return nullptr; + } + } + RefPtr service(gGamepadPlatformServiceSingleton); + return service.forget(); +} + +template +void +GamepadPlatformService::NotifyGamepadChange(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(aMapping), GamepadServiceType::Standard, aNumButtons, aNumAxes); + NotifyGamepadChange(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(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(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(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; iDispatchUpdateEvent(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 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 000000000..0a61281e4 --- /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 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 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> 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 mPendingEvents; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/gamepad/GamepadPose.cpp b/dom/gamepad/GamepadPose.cpp new file mode 100644 index 000000000..25d157b8b --- /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 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 aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aJSContext, aRetval, mPosition, mPoseState.position, 3, + bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position), aRv); +} + +void +GamepadPose::GetLinearVelocity(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aJSContext, aRetval, mLinearVelocity, mPoseState.linearVelocity, 3, + bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position), aRv); +} + +void +GamepadPose::GetLinearAcceleration(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aJSContext, aRetval, mLinearAcceleration, mPoseState.linearAcceleration, 3, + bool(mPoseState.flags & GamepadCapabilityFlags::Cap_LinearAcceleration), aRv); +} + +void +GamepadPose::GetOrientation(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aJSContext, aRetval, mOrientation, mPoseState.orientation, 4, + bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation), aRv); +} + +void +GamepadPose::GetAngularVelocity(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aJSContext, aRetval, mAngularVelocity, mPoseState.angularVelocity, 3, + bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation), aRv); +} + +void +GamepadPose::GetAngularAcceleration(JSContext* aJSContext, + JS::MutableHandle 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 000000000..fa32df3fc --- /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 aGivenProto) override; + + bool HasOrientation() const; + bool HasPosition() const; + virtual void GetPosition(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetLinearVelocity(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetLinearAcceleration(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetOrientation(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetAngularVelocity(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + virtual void GetAngularAcceleration(JSContext* aJSContext, + JS::MutableHandle aRetval, + ErrorResult& aRv) override; + void SetPoseState(const GamepadPoseState& aPose); + const GamepadPoseState& GetPoseState(); + +private: + virtual ~GamepadPose(); + + nsCOMPtr mParent; + GamepadPoseState mPoseState; +}; + +} // namespace 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 000000000..958b26139 --- /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 000000000..a6fde58f0 --- /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::CreateTestService(nsPIDOMWindowInner* aWindow) +{ + MOZ_ASSERT(aWindow); + RefPtr service = new GamepadServiceTest(aWindow); + service->InitPBackgroundActor(); + return service.forget(); +} + +void +GamepadServiceTest::Shutdown() +{ + MOZ_ASSERT(!mShuttingDown); + mShuttingDown = true; + DestroyPBackgroundActor(); + mWindow = nullptr; +} + +GamepadServiceTest::GamepadServiceTest(nsPIDOMWindowInner* aWindow) + : mService(GamepadManager::GetService()), + mWindow(aWindow), + mEventNumber(0), + mShuttingDown(false), + mChild(nullptr) +{} + +GamepadServiceTest::~GamepadServiceTest() {} + +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 +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 go = do_QueryInterface(mWindow); + + RefPtr 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 000000000..6ef9fcc19 --- /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 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 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 mPromise; + }; + + // Hold a reference to the gamepad service so we don't have to worry about + // execution order in tests. + RefPtr mService; + nsCOMPtr mWindow; + nsTArray 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 000000000..706d02617 --- /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() = delete; + +public: + static void + OnGamepadChange(int32_t aID, bool aAdded) + { + RefPtr 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 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 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 000000000..e7c986e22 --- /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 +#include +#include +#include + +#include +#include + +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