| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cmath> |
| #include <set> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "content/browser/gamepad/gamepad_data_fetcher.h" |
| #include "content/browser/gamepad/gamepad_platform_data_fetcher.h" |
| #include "content/browser/gamepad/gamepad_provider.h" |
| #include "content/browser/gamepad/gamepad_service.h" |
| #include "content/common/gamepad_hardware_buffer.h" |
| #include "content/common/gamepad_messages.h" |
| #include "content/common/gamepad_user_gesture.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| using blink::WebGamepad; |
| using blink::WebGamepads; |
| |
| namespace content { |
| |
| GamepadProvider::ClosureAndThread::ClosureAndThread( |
| const base::Closure& c, |
| const scoped_refptr<base::MessageLoopProxy>& m) |
| : closure(c), |
| message_loop(m) { |
| } |
| |
| GamepadProvider::ClosureAndThread::~ClosureAndThread() { |
| } |
| |
| GamepadProvider::GamepadProvider() |
| : is_paused_(true), |
| have_scheduled_do_poll_(false), |
| devices_changed_(true), |
| ever_had_user_gesture_(false) { |
| Initialize(scoped_ptr<GamepadDataFetcher>()); |
| } |
| |
| GamepadProvider::GamepadProvider(scoped_ptr<GamepadDataFetcher> fetcher) |
| : is_paused_(true), |
| have_scheduled_do_poll_(false), |
| devices_changed_(true), |
| ever_had_user_gesture_(false) { |
| Initialize(fetcher.Pass()); |
| } |
| |
| GamepadProvider::~GamepadProvider() { |
| base::SystemMonitor* monitor = base::SystemMonitor::Get(); |
| if (monitor) |
| monitor->RemoveDevicesChangedObserver(this); |
| |
| // 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( |
| base::ProcessHandle process) { |
| base::SharedMemoryHandle renderer_handle; |
| gamepad_shared_memory_.ShareToProcess(process, &renderer_handle); |
| return renderer_handle; |
| } |
| |
| void GamepadProvider::GetCurrentGamepadData(WebGamepads* data) { |
| const WebGamepads& pads = SharedMemoryAsHardwareBuffer()->buffer; |
| base::AutoLock lock(shared_memory_lock_); |
| *data = pads; |
| } |
| |
| void GamepadProvider::Pause() { |
| { |
| base::AutoLock lock(is_paused_lock_); |
| is_paused_ = true; |
| } |
| base::MessageLoop* polling_loop = polling_thread_->message_loop(); |
| polling_loop->PostTask( |
| FROM_HERE, |
| base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), true)); |
| } |
| |
| void GamepadProvider::Resume() { |
| { |
| base::AutoLock lock(is_paused_lock_); |
| if (!is_paused_) |
| return; |
| is_paused_ = false; |
| } |
| |
| base::MessageLoop* polling_loop = polling_thread_->message_loop(); |
| polling_loop->PostTask( |
| FROM_HERE, |
| base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false)); |
| polling_loop->PostTask( |
| FROM_HERE, |
| base::Bind(&GamepadProvider::ScheduleDoPoll, Unretained(this))); |
| } |
| |
| void GamepadProvider::RegisterForUserGesture(const base::Closure& closure) { |
| base::AutoLock lock(user_gesture_lock_); |
| user_gesture_observers_.push_back(ClosureAndThread( |
| closure, base::MessageLoop::current()->message_loop_proxy())); |
| } |
| |
| void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) { |
| base::AutoLock lock(devices_changed_lock_); |
| devices_changed_ = true; |
| } |
| |
| void GamepadProvider::Initialize(scoped_ptr<GamepadDataFetcher> fetcher) { |
| size_t data_size = sizeof(GamepadHardwareBuffer); |
| base::SystemMonitor* monitor = base::SystemMonitor::Get(); |
| if (monitor) |
| monitor->AddDevicesChangedObserver(this); |
| bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size); |
| CHECK(res); |
| GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); |
| memset(hwbuf, 0, sizeof(GamepadHardwareBuffer)); |
| pad_states_.reset(new PadState[WebGamepads::itemsLengthCap]); |
| |
| 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 |
| // loop needs to be a libevent loop. |
| const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO; |
| #elif defined(OS_ANDROID) |
| // On Android, keeping a message loop of default type. |
| const base::MessageLoop::Type kMessageLoopType = |
| base::MessageLoop::TYPE_DEFAULT; |
| #else |
| // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the |
| // message loop needs to be a UI-type loop. On Windows it must be a UI loop |
| // to properly pump the MessageWindow that captures device state. |
| const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI; |
| #endif |
| polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); |
| |
| polling_thread_->message_loop()->PostTask( |
| FROM_HERE, |
| base::Bind(&GamepadProvider::DoInitializePollingThread, |
| base::Unretained(this), |
| base::Passed(&fetcher))); |
| } |
| |
| void GamepadProvider::DoInitializePollingThread( |
| 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_ = fetcher.Pass(); |
| } |
| |
| 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, arraysize(id_)) == 0 && |
| memcmp(mapping_, pad.mapping, arraysize(mapping_)) == 0; |
| } |
| |
| void GamepadProvider::PadState::SetPad(const WebGamepad& pad) { |
| connected_ = pad.connected; |
| axes_length_ = pad.axesLength; |
| buttons_length_ = pad.buttonsLength; |
| memcpy(id_, pad.id, arraysize(id_)); |
| memcpy(mapping_, pad.mapping, arraysize(mapping_)); |
| } |
| |
| void GamepadProvider::PadState::SetDisconnected() { |
| connected_ = false; |
| axes_length_ = 0; |
| buttons_length_ = 0; |
| memset(id_, 0, arraysize(id_)); |
| memset(mapping_, 0, arraysize(mapping_)); |
| } |
| |
| void GamepadProvider::PadState::AsWebGamepad(WebGamepad* pad) { |
| pad->connected = connected_; |
| pad->axesLength = axes_length_; |
| pad->buttonsLength = buttons_length_; |
| memcpy(pad->id, id_, arraysize(id_)); |
| memcpy(pad->mapping, mapping_, arraysize(mapping_)); |
| memset(pad->axes, 0, arraysize(pad->axes)); |
| memset(pad->buttons, 0, arraysize(pad->buttons)); |
| } |
| |
| void GamepadProvider::DoPoll() { |
| DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); |
| DCHECK(have_scheduled_do_poll_); |
| have_scheduled_do_poll_ = false; |
| |
| bool changed; |
| GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); |
| |
| ANNOTATE_BENIGN_RACE_SIZED( |
| &hwbuf->buffer, |
| sizeof(WebGamepads), |
| "Racey reads are discarded"); |
| |
| { |
| base::AutoLock lock(devices_changed_lock_); |
| changed = devices_changed_; |
| devices_changed_ = false; |
| } |
| |
| { |
| 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->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); |
| } |
| } |
| } |
| |
| CheckForUserGesture(); |
| |
| // Schedule our next interval of polling. |
| ScheduleDoPoll(); |
| } |
| |
| void GamepadProvider::ScheduleDoPoll() { |
| DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); |
| if (have_scheduled_do_poll_) |
| return; |
| |
| { |
| base::AutoLock lock(is_paused_lock_); |
| if (is_paused_) |
| return; |
| } |
| |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&GamepadProvider::DoPoll, Unretained(this)), |
| base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs)); |
| have_scheduled_do_poll_ = true; |
| } |
| |
| 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, |
| base::Bind(&GamepadProvider::DispatchGamepadConnectionChange, |
| base::Unretained(this), |
| connected, |
| index, |
| pad)); |
| } |
| |
| void GamepadProvider::DispatchGamepadConnectionChange( |
| bool connected, int index, const WebGamepad& pad) { |
| if (connected) |
| GamepadService::GetInstance()->OnGamepadConnected(index, pad); |
| else |
| GamepadService::GetInstance()->OnGamepadDisconnected(index, pad); |
| } |
| |
| GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() { |
| void* mem = gamepad_shared_memory_.memory(); |
| CHECK(mem); |
| return static_cast<GamepadHardwareBuffer*>(mem); |
| } |
| |
| void GamepadProvider::CheckForUserGesture() { |
| base::AutoLock lock(user_gesture_lock_); |
| 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; |
| for (size_t i = 0; i < user_gesture_observers_.size(); i++) { |
| user_gesture_observers_[i].message_loop->PostTask(FROM_HERE, |
| user_gesture_observers_[i].closure); |
| } |
| 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]); |
| } |
| } |
| } |
| |
| } // namespace content |