Index: content/browser/gamepad/gamepad_provider.cc |
diff --git a/content/browser/gamepad/gamepad_provider.cc b/content/browser/gamepad/gamepad_provider.cc |
index 682afc51d353a73cc0759ec433c5f9e7ecae1772..430af56de642becf1a063d751ab8917f0e352a59 100644 |
--- a/content/browser/gamepad/gamepad_provider.cc |
+++ b/content/browser/gamepad/gamepad_provider.cc |
@@ -7,7 +7,6 @@ |
#include <stddef.h> |
#include <string.h> |
#include <cmath> |
-#include <set> |
#include <utility> |
#include <vector> |
@@ -31,6 +30,12 @@ |
using blink::WebGamepad; |
using blink::WebGamepads; |
+namespace { |
+ |
+const float kMinAxisResetValue = 0.1f; |
+ |
+} // namespace |
+ |
namespace content { |
GamepadProvider::ClosureAndThread::ClosureAndThread( |
@@ -46,7 +51,8 @@ GamepadProvider::GamepadProvider() |
: is_paused_(true), |
have_scheduled_do_poll_(false), |
devices_changed_(true), |
- ever_had_user_gesture_(false) { |
+ ever_had_user_gesture_(false), |
+ sanitize_(true) { |
Initialize(scoped_ptr<GamepadDataFetcher>()); |
} |
@@ -66,7 +72,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( |
@@ -132,6 +137,9 @@ void GamepadProvider::Initialize(scoped_ptr<GamepadDataFetcher> fetcher) { |
memset(hwbuf, 0, sizeof(GamepadHardwareBuffer)); |
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 |
@@ -149,59 +157,34 @@ void GamepadProvider::Initialize(scoped_ptr<GamepadDataFetcher> fetcher) { |
#endif |
polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); |
+ if (fetcher) { |
+ AddGamepadDataFetcher(std::move(fetcher)); |
+ } else { |
+ AddGamepadPlatformDataFetchers(this); |
+ } |
+} |
+ |
+void GamepadProvider::AddGamepadDataFetcher( |
+ scoped_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::DoAddGamepadDataFetcher( |
scoped_ptr<GamepadDataFetcher> fetcher) { |
DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); |
- DCHECK(!data_fetcher_.get()); // Should only initialize once. |
- if (!fetcher) |
- fetcher.reset(new GamepadPlatformDataFetcher); |
- data_fetcher_ = std::move(fetcher); |
+ fetcher->InitializeProvider(this); |
+ |
+ data_fetchers_.push_back(std::move(fetcher)); |
} |
void GamepadProvider::SendPauseHint(bool paused) { |
DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); |
- if (data_fetcher_) |
- data_fetcher_->PauseHint(paused); |
-} |
- |
-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; |
-} |
- |
-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::PadState::SetDisconnected() { |
- connected_ = false; |
- axes_length_ = 0; |
- buttons_length_ = 0; |
- memset(id_, 0, sizeof(id_)); |
- memset(mapping_, 0, sizeof(mapping_)); |
-} |
- |
-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)); |
+ for (const auto& it : data_fetchers_) { |
+ it->PauseHint(paused); |
+ } |
} |
void GamepadProvider::DoPoll() { |
@@ -223,29 +206,52 @@ void GamepadProvider::DoPoll() { |
devices_changed_ = false; |
} |
+ // Loop through each registered data fetcher and poll its 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); |
+ } |
+ |
+ // 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, hwbuf->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. |
hwbuf->sequence.WriteBegin(); |
- data_fetcher_->GetGamepadData(&hwbuf->buffer, changed); |
+ hwbuf->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, &hwbuf->buffer.items[i]); |
+ if (state.active_state) |
+ hwbuf->buffer.length++; |
+ } |
hwbuf->sequence.WriteEnd(); |
} |
if (ever_had_user_gesture_) { |
for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
- WebGamepad& pad = hwbuf->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, hwbuf->buffer.items[i]); |
+ state.active_state = GAMEPAD_INACTIVE; |
} |
} |
} |
@@ -275,12 +281,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(); |
- |
BrowserThread::PostTask( |
BrowserThread::IO, |
FROM_HERE, |
@@ -310,7 +310,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 = SharedMemoryAsHardwareBuffer()->buffer; |
if (GamepadsHaveUserGesture(pads)) { |
ever_had_user_gesture_ = true; |
@@ -320,10 +319,92 @@ 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->active_state) { |
+ 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; |
+ } |
+ } |
} |
} |
} |