Index: device/gamepad/gamepad_provider.cc |
diff --git a/device/gamepad/gamepad_provider.cc b/device/gamepad/gamepad_provider.cc |
index c37c38a7dbc78ccaca92b5df5c3fa2ccfbbc87b0..23fa47b72858ed6b25b724a7392773368f17c999 100644 |
--- a/device/gamepad/gamepad_provider.cc |
+++ b/device/gamepad/gamepad_provider.cc |
@@ -7,7 +7,6 @@ |
#include <stddef.h> |
#include <string.h> |
#include <cmath> |
-#include <set> |
#include <utility> |
#include <vector> |
@@ -21,12 +20,18 @@ |
#include "base/threading/thread_task_runner_handle.h" |
#include "build/build_config.h" |
#include "device/gamepad/gamepad_data_fetcher.h" |
-#include "device/gamepad/gamepad_platform_data_fetcher.h" |
+#include "device/gamepad/gamepad_data_fetcher_manager.h" |
#include "device/gamepad/gamepad_user_gesture.h" |
using blink::WebGamepad; |
using blink::WebGamepads; |
+namespace { |
+ |
+const float kMinAxisResetValue = 0.1f; |
+ |
+} // namespace |
+ |
namespace device { |
GamepadProvider::ClosureAndThread::ClosureAndThread( |
@@ -46,6 +51,7 @@ GamepadProvider::GamepadProvider( |
have_scheduled_do_poll_(false), |
devices_changed_(true), |
ever_had_user_gesture_(false), |
+ sanitize_(true), |
gamepad_shared_buffer_(std::move(buffer)), |
connection_change_client_(connection_change_client) { |
Initialize(std::unique_ptr<GamepadDataFetcher>()); |
@@ -59,12 +65,15 @@ GamepadProvider::GamepadProvider( |
have_scheduled_do_poll_(false), |
devices_changed_(true), |
ever_had_user_gesture_(false), |
+ sanitize_(true), |
gamepad_shared_buffer_(std::move(buffer)), |
connection_change_client_(connection_change_client) { |
Initialize(std::move(fetcher)); |
} |
GamepadProvider::~GamepadProvider() { |
+ GamepadDataFetcherManager::GetInstance()->ClearProvider(); |
+ |
base::SystemMonitor* monitor = base::SystemMonitor::Get(); |
if (monitor) |
monitor->RemoveDevicesChangedObserver(this); |
@@ -72,7 +81,6 @@ GamepadProvider::~GamepadProvider() { |
// Use Stop() to join the polling thread, as there may be pending callbacks |
// which dereference |polling_thread_|. |
polling_thread_->Stop(); |
- data_fetcher_.reset(); |
} |
base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess( |
@@ -135,6 +143,9 @@ void GamepadProvider::Initialize(std::unique_ptr<GamepadDataFetcher> fetcher) { |
pad_states_.reset(new PadState[WebGamepads::itemsLengthCap]); |
+ for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) |
+ ClearPadState(pad_states_.get()[i]); |
+ |
polling_thread_.reset(new base::Thread("Gamepad polling thread")); |
#if defined(OS_LINUX) |
// On Linux, the data fetcher needs to watch file descriptors, so the message |
@@ -152,58 +163,53 @@ void GamepadProvider::Initialize(std::unique_ptr<GamepadDataFetcher> fetcher) { |
#endif |
polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); |
+ if (fetcher) { |
+ AddGamepadDataFetcher(std::move(fetcher)); |
+ } else { |
+ GamepadDataFetcherManager::GetInstance()->InitializeProvider(this); |
+ } |
+} |
+ |
+void GamepadProvider::AddGamepadDataFetcher( |
+ std::unique_ptr<GamepadDataFetcher> fetcher) { |
polling_thread_->task_runner()->PostTask( |
- FROM_HERE, base::Bind(&GamepadProvider::DoInitializePollingThread, |
+ FROM_HERE, base::Bind(&GamepadProvider::DoAddGamepadDataFetcher, |
base::Unretained(this), base::Passed(&fetcher))); |
} |
-void GamepadProvider::DoInitializePollingThread( |
+void GamepadProvider::RemoveSourceGamepadDataFetcher(GamepadSource source) { |
+ polling_thread_->task_runner()->PostTask( |
+ FROM_HERE, base::Bind(&GamepadProvider::DoRemoveSourceGamepadDataFetcher, |
+ base::Unretained(this), source)); |
+} |
+ |
+void GamepadProvider::DoAddGamepadDataFetcher( |
std::unique_ptr<GamepadDataFetcher> fetcher) { |
DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
- DCHECK(!data_fetcher_.get()); // Should only initialize once. |
if (!fetcher) |
- fetcher.reset(new GamepadPlatformDataFetcher); |
- data_fetcher_ = std::move(fetcher); |
-} |
- |
-void GamepadProvider::SendPauseHint(bool paused) { |
- DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
- if (data_fetcher_) |
- data_fetcher_->PauseHint(paused); |
-} |
+ return; |
-bool GamepadProvider::PadState::Match(const WebGamepad& pad) const { |
- return connected_ == pad.connected && axes_length_ == pad.axesLength && |
- buttons_length_ == pad.buttonsLength && |
- memcmp(id_, pad.id, sizeof(id_)) == 0 && |
- memcmp(mapping_, pad.mapping, sizeof(mapping_)) == 0; |
+ fetcher->InitializeProvider(this); |
+ data_fetchers_.push_back(std::move(fetcher)); |
} |
-void GamepadProvider::PadState::SetPad(const WebGamepad& pad) { |
- connected_ = pad.connected; |
- axes_length_ = pad.axesLength; |
- buttons_length_ = pad.buttonsLength; |
- memcpy(id_, pad.id, sizeof(id_)); |
- memcpy(mapping_, pad.mapping, sizeof(mapping_)); |
-} |
+void GamepadProvider::DoRemoveSourceGamepadDataFetcher(GamepadSource source) { |
+ DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
-void GamepadProvider::PadState::SetDisconnected() { |
- connected_ = false; |
- axes_length_ = 0; |
- buttons_length_ = 0; |
- memset(id_, 0, sizeof(id_)); |
- memset(mapping_, 0, sizeof(mapping_)); |
+ for (GamepadFetcherVector::iterator it = data_fetchers_.begin(); |
+ it != data_fetchers_.end(); ++it) { |
+ if ((*it)->source() == source) { |
+ data_fetchers_.erase(it); |
+ } |
+ } |
} |
-void GamepadProvider::PadState::AsWebGamepad(WebGamepad* pad) { |
- pad->connected = connected_; |
- pad->axesLength = axes_length_; |
- pad->buttonsLength = buttons_length_; |
- memcpy(pad->id, id_, sizeof(id_)); |
- memcpy(pad->mapping, mapping_, sizeof(mapping_)); |
- memset(pad->axes, 0, sizeof(pad->axes)); |
- memset(pad->buttons, 0, sizeof(pad->buttons)); |
+void GamepadProvider::SendPauseHint(bool paused) { |
+ DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
+ for (const auto& it : data_fetchers_) { |
+ it->PauseHint(paused); |
+ } |
} |
void GamepadProvider::DoPoll() { |
@@ -222,29 +228,54 @@ void GamepadProvider::DoPoll() { |
devices_changed_ = false; |
} |
+ // Loop through each registered data fetcher and poll it's gamepad data. |
+ // It's expected that GetGamepadData will mark each gamepad as active (via |
+ // GetPadState). If a gamepad is not marked as active during the calls to |
+ // GetGamepadData then it's assumed to be disconnected. |
+ for (const auto& it : data_fetchers_) { |
+ it->GetGamepadData(changed); |
+ } |
+ |
+ blink::WebGamepads* buffer = gamepad_shared_buffer_->buffer(); |
+ |
+ // Send out disconnect events using the last polled data before we wipe it out |
+ // in the mapping step. |
+ if (ever_had_user_gesture_) { |
+ for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ PadState& state = pad_states_.get()[i]; |
+ |
+ if (!state.active_state && state.source != GAMEPAD_SOURCE_NONE) { |
+ OnGamepadConnectionChange(false, i, buffer->items[i]); |
+ ClearPadState(state); |
+ } |
+ } |
+ } |
+ |
{ |
base::AutoLock lock(shared_memory_lock_); |
// Acquire the SeqLock. There is only ever one writer to this data. |
// See gamepad_hardware_buffer.h. |
gamepad_shared_buffer_->WriteBegin(); |
- data_fetcher_->GetGamepadData(gamepad_shared_buffer_->buffer(), changed); |
+ buffer->length = 0; |
+ for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ PadState& state = pad_states_.get()[i]; |
+ // Must run through the map+sanitize here or CheckForUserGesture may fail. |
+ MapAndSanitizeGamepadData(&state, &buffer->items[i]); |
+ if (state.active_state) |
+ buffer->length++; |
+ } |
gamepad_shared_buffer_->WriteEnd(); |
} |
if (ever_had_user_gesture_) { |
for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
- WebGamepad& pad = gamepad_shared_buffer_->buffer()->items[i]; |
PadState& state = pad_states_.get()[i]; |
- if (pad.connected && !state.connected()) { |
- OnGamepadConnectionChange(true, i, pad); |
- } else if (!pad.connected && state.connected()) { |
- OnGamepadConnectionChange(false, i, pad); |
- } else if (pad.connected && state.connected() && !state.Match(pad)) { |
- WebGamepad old_pad; |
- state.AsWebGamepad(&old_pad); |
- OnGamepadConnectionChange(false, i, old_pad); |
- OnGamepadConnectionChange(true, i, pad); |
+ |
+ if (state.active_state) { |
+ if (state.active_state == GAMEPAD_NEWLY_ACTIVE) |
+ OnGamepadConnectionChange(true, i, buffer->items[i]); |
+ state.active_state = GAMEPAD_INACTIVE; |
} |
} |
} |
@@ -275,12 +306,6 @@ void GamepadProvider::ScheduleDoPoll() { |
void GamepadProvider::OnGamepadConnectionChange(bool connected, |
int index, |
const WebGamepad& pad) { |
- PadState& state = pad_states_.get()[index]; |
- if (connected) |
- state.SetPad(pad); |
- else |
- state.SetDisconnected(); |
- |
if (connection_change_client_) |
connection_change_client_->OnGamepadConnectionChange(connected, index, pad); |
} |
@@ -290,7 +315,6 @@ void GamepadProvider::CheckForUserGesture() { |
if (user_gesture_observers_.empty() && ever_had_user_gesture_) |
return; |
- bool had_gesture_before = ever_had_user_gesture_; |
const WebGamepads* pads = gamepad_shared_buffer_->buffer(); |
if (GamepadsHaveUserGesture(*pads)) { |
ever_had_user_gesture_ = true; |
@@ -300,10 +324,91 @@ void GamepadProvider::CheckForUserGesture() { |
} |
user_gesture_observers_.clear(); |
} |
- if (!had_gesture_before && ever_had_user_gesture_) { |
- // Initialize pad_states_ for the first time. |
- for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
- pad_states_.get()[i].SetPad(pads->items[i]); |
+} |
+ |
+void GamepadProvider::ClearPadState(PadState& state) { |
+ memset(&state, 0, sizeof(PadState)); |
+} |
+ |
+PadState* GamepadProvider::GetPadState(GamepadSource source, int source_id) { |
+ // Check to see if the device already has a reserved slot |
+ PadState* empty_slot = nullptr; |
+ for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
+ PadState& state = pad_states_.get()[i]; |
+ if (state.source == source && state.source_id == source_id) { |
+ // Retrieving the pad state marks this gamepad as active. |
+ state.active_state = GAMEPAD_ACTIVE; |
+ return &state; |
+ } |
+ if (!empty_slot && state.source == GAMEPAD_SOURCE_NONE) |
+ empty_slot = &state; |
+ } |
+ if (empty_slot) { |
+ empty_slot->source = source; |
+ empty_slot->source_id = source_id; |
+ empty_slot->active_state = GAMEPAD_NEWLY_ACTIVE; |
+ } |
+ return empty_slot; |
+} |
+ |
+void GamepadProvider::MapAndSanitizeGamepadData(PadState* pad_state, |
+ WebGamepad* pad) { |
+ DCHECK(pad_state); |
+ DCHECK(pad); |
+ |
+ if (!pad_state->data.connected) { |
+ memset(pad, 0, sizeof(WebGamepad)); |
+ return; |
+ } |
+ |
+ // Copy the current state to the output buffer, using the mapping |
+ // function, if there is one available. |
+ if (pad_state->mapper) |
+ pad_state->mapper(pad_state->data, pad); |
+ else |
+ *pad = pad_state->data; |
+ |
+ pad->connected = true; |
+ |
+ if (!sanitize_) |
+ return; |
+ |
+ // About sanitization: Gamepads may report input event if the user is not |
+ // interacting with it, due to hardware problems or environmental ones (pad |
+ // has something heavy leaning against an axis.) This may cause user gestures |
+ // to be detected erroniously, exposing gamepad information when the user had |
+ // no intention of doing so. To avoid this we require that each button or axis |
+ // report being at rest (zero) at least once before exposing its value to the |
+ // Gamepad API. This state is tracked by the axis_mask and button_mask |
+ // bitfields. If the bit for an axis or button is 0 it means the axis has |
+ // never reported being at rest, and the value will be forced to zero. |
+ |
+ // We can skip axis sanitation if all available axes have been masked. |
+ uint32_t full_axis_mask = (1 << pad->axesLength) - 1; |
+ if (pad_state->axis_mask != full_axis_mask) { |
+ for (size_t axis = 0; axis < pad->axesLength; ++axis) { |
+ if (!(pad_state->axis_mask & 1 << axis)) { |
+ if (fabs(pad->axes[axis]) < kMinAxisResetValue) { |
+ pad_state->axis_mask |= 1 << axis; |
+ } else { |
+ pad->axes[axis] = 0.0f; |
+ } |
+ } |
+ } |
+ } |
+ |
+ // We can skip button sanitation if all available buttons have been masked. |
+ uint32_t full_button_mask = (1 << pad->buttonsLength) - 1; |
+ if (pad_state->button_mask != full_button_mask) { |
+ for (size_t button = 0; button < pad->buttonsLength; ++button) { |
+ if (!(pad_state->button_mask & 1 << button)) { |
+ if (!pad->buttons[button].pressed) { |
+ pad_state->button_mask |= 1 << button; |
+ } else { |
+ pad->buttons[button].pressed = false; |
+ pad->buttons[button].value = 0.0f; |
+ } |
+ } |
} |
} |
} |