// 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 "content/browser/gamepad/gamepad_provider.h" #include #include #include #include #include #include #include "base/bind.h" #include "base/location.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "content/browser/gamepad/gamepad_data_fetcher.h" #include "content/browser/gamepad/gamepad_platform_data_fetcher.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& m) : closure(c), task_runner(m) { } GamepadProvider::ClosureAndThread::ClosureAndThread( const ClosureAndThread& other) = default; GamepadProvider::ClosureAndThread::~ClosureAndThread() { } GamepadProvider::GamepadProvider() : is_paused_(true), have_scheduled_do_poll_(false), devices_changed_(true), ever_had_user_gesture_(false) { Initialize(std::unique_ptr()); } GamepadProvider::GamepadProvider(std::unique_ptr fetcher) : is_paused_(true), have_scheduled_do_poll_(false), devices_changed_(true), ever_had_user_gesture_(false) { Initialize(std::move(fetcher)); } 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->task_runner()->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->task_runner()->PostTask( FROM_HERE, base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false)); polling_loop->task_runner()->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()->task_runner())); } void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) { base::AutoLock lock(devices_changed_lock_); devices_changed_ = true; } void GamepadProvider::Initialize(std::unique_ptr 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_->task_runner()->PostTask( FROM_HERE, base::Bind(&GamepadProvider::DoInitializePollingThread, base::Unretained(this), base::Passed(&fetcher))); } void GamepadProvider::DoInitializePollingThread( std::unique_ptr 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); } 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)); } 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::ThreadTaskRunnerHandle::Get()->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(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].task_runner->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