123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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 "nsIDOMEvent.h"
- #include "nsIDOMDocument.h"
- #include "nsIDOMWindow.h"
- #include "nsIObserver.h"
- #include "nsIObserverService.h"
- #include "nsIServiceManager.h"
- #include "nsThreadUtils.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();
- }
- 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;
- }
- 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);
- }
- //Override nsIIPCBackgroundChildCreateCallback
- void
- GamepadManager::ActorFailed()
- {
- MOZ_CRASH("Gamepad IPC actor create failed!");
- }
- } // namespace dom
- } // namespace mozilla
|