| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "device/gamepad/gamepad_provider.h" | 5 #include "device/gamepad/gamepad_provider.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <string.h> | 8 #include <string.h> |
| 9 #include <cmath> | 9 #include <cmath> |
| 10 #include <set> | |
| 11 #include <utility> | 10 #include <utility> |
| 12 #include <vector> | 11 #include <vector> |
| 13 | 12 |
| 14 #include "base/bind.h" | 13 #include "base/bind.h" |
| 15 #include "base/location.h" | 14 #include "base/location.h" |
| 16 #include "base/logging.h" | 15 #include "base/logging.h" |
| 17 #include "base/single_thread_task_runner.h" | 16 #include "base/single_thread_task_runner.h" |
| 18 #include "base/third_party/dynamic_annotations/dynamic_annotations.h" | 17 #include "base/third_party/dynamic_annotations/dynamic_annotations.h" |
| 19 #include "base/threading/thread.h" | 18 #include "base/threading/thread.h" |
| 20 #include "base/threading/thread_restrictions.h" | 19 #include "base/threading/thread_restrictions.h" |
| 21 #include "base/threading/thread_task_runner_handle.h" | 20 #include "base/threading/thread_task_runner_handle.h" |
| 22 #include "build/build_config.h" | 21 #include "build/build_config.h" |
| 23 #include "device/gamepad/gamepad_data_fetcher.h" | 22 #include "device/gamepad/gamepad_data_fetcher.h" |
| 24 #include "device/gamepad/gamepad_platform_data_fetcher.h" | 23 #include "device/gamepad/gamepad_data_fetcher_manager.h" |
| 25 #include "device/gamepad/gamepad_user_gesture.h" | 24 #include "device/gamepad/gamepad_user_gesture.h" |
| 26 | 25 |
| 27 using blink::WebGamepad; | 26 using blink::WebGamepad; |
| 28 using blink::WebGamepads; | 27 using blink::WebGamepads; |
| 29 | 28 |
| 30 namespace device { | 29 namespace device { |
| 31 | 30 |
| 32 GamepadProvider::ClosureAndThread::ClosureAndThread( | 31 GamepadProvider::ClosureAndThread::ClosureAndThread( |
| 33 const base::Closure& c, | 32 const base::Closure& c, |
| 34 const scoped_refptr<base::SingleThreadTaskRunner>& m) | 33 const scoped_refptr<base::SingleThreadTaskRunner>& m) |
| 35 : closure(c), task_runner(m) {} | 34 : closure(c), task_runner(m) {} |
| 36 | 35 |
| 37 GamepadProvider::ClosureAndThread::ClosureAndThread( | 36 GamepadProvider::ClosureAndThread::ClosureAndThread( |
| 38 const ClosureAndThread& other) = default; | 37 const ClosureAndThread& other) = default; |
| 39 | 38 |
| 40 GamepadProvider::ClosureAndThread::~ClosureAndThread() {} | 39 GamepadProvider::ClosureAndThread::~ClosureAndThread() {} |
| 41 | 40 |
| 42 GamepadProvider::GamepadProvider( | 41 GamepadProvider::GamepadProvider( |
| 43 std::unique_ptr<GamepadSharedBuffer> buffer, | 42 std::unique_ptr<GamepadSharedBuffer> buffer, |
| 44 GamepadConnectionChangeClient* connection_change_client) | 43 GamepadConnectionChangeClient* connection_change_client) |
| 45 : is_paused_(true), | 44 : is_paused_(true), |
| 46 have_scheduled_do_poll_(false), | 45 have_scheduled_do_poll_(false), |
| 47 devices_changed_(true), | 46 devices_changed_(true), |
| 48 ever_had_user_gesture_(false), | 47 ever_had_user_gesture_(false), |
| 48 sanitize_(true), |
| 49 gamepad_shared_buffer_(std::move(buffer)), | 49 gamepad_shared_buffer_(std::move(buffer)), |
| 50 connection_change_client_(connection_change_client) { | 50 connection_change_client_(connection_change_client) { |
| 51 Initialize(std::unique_ptr<GamepadDataFetcher>()); | 51 Initialize(std::unique_ptr<GamepadDataFetcher>()); |
| 52 } | 52 } |
| 53 | 53 |
| 54 GamepadProvider::GamepadProvider( | 54 GamepadProvider::GamepadProvider( |
| 55 std::unique_ptr<GamepadSharedBuffer> buffer, | 55 std::unique_ptr<GamepadSharedBuffer> buffer, |
| 56 GamepadConnectionChangeClient* connection_change_client, | 56 GamepadConnectionChangeClient* connection_change_client, |
| 57 std::unique_ptr<GamepadDataFetcher> fetcher) | 57 std::unique_ptr<GamepadDataFetcher> fetcher) |
| 58 : is_paused_(true), | 58 : is_paused_(true), |
| 59 have_scheduled_do_poll_(false), | 59 have_scheduled_do_poll_(false), |
| 60 devices_changed_(true), | 60 devices_changed_(true), |
| 61 ever_had_user_gesture_(false), | 61 ever_had_user_gesture_(false), |
| 62 sanitize_(true), |
| 62 gamepad_shared_buffer_(std::move(buffer)), | 63 gamepad_shared_buffer_(std::move(buffer)), |
| 63 connection_change_client_(connection_change_client) { | 64 connection_change_client_(connection_change_client) { |
| 64 Initialize(std::move(fetcher)); | 65 Initialize(std::move(fetcher)); |
| 65 } | 66 } |
| 66 | 67 |
| 67 GamepadProvider::~GamepadProvider() { | 68 GamepadProvider::~GamepadProvider() { |
| 69 GamepadDataFetcherManager::GetInstance()->ClearProvider(); |
| 70 |
| 68 base::SystemMonitor* monitor = base::SystemMonitor::Get(); | 71 base::SystemMonitor* monitor = base::SystemMonitor::Get(); |
| 69 if (monitor) | 72 if (monitor) |
| 70 monitor->RemoveDevicesChangedObserver(this); | 73 monitor->RemoveDevicesChangedObserver(this); |
| 71 | 74 |
| 72 // Use Stop() to join the polling thread, as there may be pending callbacks | 75 // Use Stop() to join the polling thread, as there may be pending callbacks |
| 73 // which dereference |polling_thread_|. | 76 // which dereference |polling_thread_|. |
| 74 polling_thread_->Stop(); | 77 polling_thread_->Stop(); |
| 75 data_fetcher_.reset(); | |
| 76 } | 78 } |
| 77 | 79 |
| 78 base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess( | 80 base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess( |
| 79 base::ProcessHandle process) { | 81 base::ProcessHandle process) { |
| 80 base::SharedMemoryHandle renderer_handle; | 82 base::SharedMemoryHandle renderer_handle; |
| 81 gamepad_shared_buffer_->shared_memory()->ShareToProcess(process, | 83 gamepad_shared_buffer_->shared_memory()->ShareToProcess(process, |
| 82 &renderer_handle); | 84 &renderer_handle); |
| 83 return renderer_handle; | 85 return renderer_handle; |
| 84 } | 86 } |
| 85 | 87 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 126 void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) { | 128 void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) { |
| 127 base::AutoLock lock(devices_changed_lock_); | 129 base::AutoLock lock(devices_changed_lock_); |
| 128 devices_changed_ = true; | 130 devices_changed_ = true; |
| 129 } | 131 } |
| 130 | 132 |
| 131 void GamepadProvider::Initialize(std::unique_ptr<GamepadDataFetcher> fetcher) { | 133 void GamepadProvider::Initialize(std::unique_ptr<GamepadDataFetcher> fetcher) { |
| 132 base::SystemMonitor* monitor = base::SystemMonitor::Get(); | 134 base::SystemMonitor* monitor = base::SystemMonitor::Get(); |
| 133 if (monitor) | 135 if (monitor) |
| 134 monitor->AddDevicesChangedObserver(this); | 136 monitor->AddDevicesChangedObserver(this); |
| 135 | 137 |
| 136 pad_states_.reset(new PadState[WebGamepads::itemsLengthCap]); | |
| 137 | |
| 138 polling_thread_.reset(new base::Thread("Gamepad polling thread")); | 138 polling_thread_.reset(new base::Thread("Gamepad polling thread")); |
| 139 #if defined(OS_LINUX) | 139 #if defined(OS_LINUX) |
| 140 // On Linux, the data fetcher needs to watch file descriptors, so the message | 140 // On Linux, the data fetcher needs to watch file descriptors, so the message |
| 141 // loop needs to be a libevent loop. | 141 // loop needs to be a libevent loop. |
| 142 const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO; | 142 const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO; |
| 143 #elif defined(OS_ANDROID) | 143 #elif defined(OS_ANDROID) |
| 144 // On Android, keeping a message loop of default type. | 144 // On Android, keeping a message loop of default type. |
| 145 const base::MessageLoop::Type kMessageLoopType = | 145 const base::MessageLoop::Type kMessageLoopType = |
| 146 base::MessageLoop::TYPE_DEFAULT; | 146 base::MessageLoop::TYPE_DEFAULT; |
| 147 #else | 147 #else |
| 148 // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the | 148 // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the |
| 149 // message loop needs to be a UI-type loop. On Windows it must be a UI loop | 149 // message loop needs to be a UI-type loop. On Windows it must be a UI loop |
| 150 // to properly pump the MessageWindow that captures device state. | 150 // to properly pump the MessageWindow that captures device state. |
| 151 const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI; | 151 const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI; |
| 152 #endif | 152 #endif |
| 153 polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); | 153 polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); |
| 154 | 154 |
| 155 if (fetcher) { |
| 156 AddGamepadDataFetcher(std::move(fetcher)); |
| 157 } else { |
| 158 GamepadDataFetcherManager::GetInstance()->InitializeProvider(this); |
| 159 } |
| 160 } |
| 161 |
| 162 void GamepadProvider::AddGamepadDataFetcher( |
| 163 std::unique_ptr<GamepadDataFetcher> fetcher) { |
| 155 polling_thread_->task_runner()->PostTask( | 164 polling_thread_->task_runner()->PostTask( |
| 156 FROM_HERE, base::Bind(&GamepadProvider::DoInitializePollingThread, | 165 FROM_HERE, base::Bind(&GamepadProvider::DoAddGamepadDataFetcher, |
| 157 base::Unretained(this), base::Passed(&fetcher))); | 166 base::Unretained(this), base::Passed(&fetcher))); |
| 158 } | 167 } |
| 159 | 168 |
| 160 void GamepadProvider::DoInitializePollingThread( | 169 void GamepadProvider::RemoveSourceGamepadDataFetcher(GamepadSource source) { |
| 170 polling_thread_->task_runner()->PostTask( |
| 171 FROM_HERE, base::Bind(&GamepadProvider::DoRemoveSourceGamepadDataFetcher, |
| 172 base::Unretained(this), source)); |
| 173 } |
| 174 |
| 175 void GamepadProvider::DoAddGamepadDataFetcher( |
| 161 std::unique_ptr<GamepadDataFetcher> fetcher) { | 176 std::unique_ptr<GamepadDataFetcher> fetcher) { |
| 162 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); | 177 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
| 163 DCHECK(!data_fetcher_.get()); // Should only initialize once. | |
| 164 | 178 |
| 165 if (!fetcher) | 179 if (!fetcher) |
| 166 fetcher.reset(new GamepadPlatformDataFetcher); | 180 return; |
| 167 data_fetcher_ = std::move(fetcher); | 181 |
| 182 InitializeDataFetcher(fetcher.get()); |
| 183 data_fetchers_.push_back(std::move(fetcher)); |
| 184 } |
| 185 |
| 186 void GamepadProvider::DoRemoveSourceGamepadDataFetcher(GamepadSource source) { |
| 187 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
| 188 |
| 189 for (GamepadFetcherVector::iterator it = data_fetchers_.begin(); |
| 190 it != data_fetchers_.end(); ++it) { |
| 191 if ((*it)->source() == source) { |
| 192 data_fetchers_.erase(it); |
| 193 } |
| 194 } |
| 168 } | 195 } |
| 169 | 196 |
| 170 void GamepadProvider::SendPauseHint(bool paused) { | 197 void GamepadProvider::SendPauseHint(bool paused) { |
| 171 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); | 198 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
| 172 if (data_fetcher_) | 199 for (const auto& it : data_fetchers_) { |
| 173 data_fetcher_->PauseHint(paused); | 200 it->PauseHint(paused); |
| 174 } | 201 } |
| 175 | |
| 176 bool GamepadProvider::PadState::Match(const WebGamepad& pad) const { | |
| 177 return connected_ == pad.connected && axes_length_ == pad.axesLength && | |
| 178 buttons_length_ == pad.buttonsLength && | |
| 179 memcmp(id_, pad.id, sizeof(id_)) == 0 && | |
| 180 memcmp(mapping_, pad.mapping, sizeof(mapping_)) == 0; | |
| 181 } | |
| 182 | |
| 183 void GamepadProvider::PadState::SetPad(const WebGamepad& pad) { | |
| 184 connected_ = pad.connected; | |
| 185 axes_length_ = pad.axesLength; | |
| 186 buttons_length_ = pad.buttonsLength; | |
| 187 memcpy(id_, pad.id, sizeof(id_)); | |
| 188 memcpy(mapping_, pad.mapping, sizeof(mapping_)); | |
| 189 } | |
| 190 | |
| 191 void GamepadProvider::PadState::SetDisconnected() { | |
| 192 connected_ = false; | |
| 193 axes_length_ = 0; | |
| 194 buttons_length_ = 0; | |
| 195 memset(id_, 0, sizeof(id_)); | |
| 196 memset(mapping_, 0, sizeof(mapping_)); | |
| 197 } | |
| 198 | |
| 199 void GamepadProvider::PadState::AsWebGamepad(WebGamepad* pad) { | |
| 200 pad->connected = connected_; | |
| 201 pad->axesLength = axes_length_; | |
| 202 pad->buttonsLength = buttons_length_; | |
| 203 memcpy(pad->id, id_, sizeof(id_)); | |
| 204 memcpy(pad->mapping, mapping_, sizeof(mapping_)); | |
| 205 memset(pad->axes, 0, sizeof(pad->axes)); | |
| 206 memset(pad->buttons, 0, sizeof(pad->buttons)); | |
| 207 } | 202 } |
| 208 | 203 |
| 209 void GamepadProvider::DoPoll() { | 204 void GamepadProvider::DoPoll() { |
| 210 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); | 205 DCHECK(polling_thread_->task_runner()->BelongsToCurrentThread()); |
| 211 DCHECK(have_scheduled_do_poll_); | 206 DCHECK(have_scheduled_do_poll_); |
| 212 have_scheduled_do_poll_ = false; | 207 have_scheduled_do_poll_ = false; |
| 213 | 208 |
| 214 bool changed; | 209 bool changed; |
| 215 | 210 |
| 216 ANNOTATE_BENIGN_RACE_SIZED(gamepad_shared_buffer_->buffer(), | 211 ANNOTATE_BENIGN_RACE_SIZED(gamepad_shared_buffer_->buffer(), |
| 217 sizeof(WebGamepads), "Racey reads are discarded"); | 212 sizeof(WebGamepads), "Racey reads are discarded"); |
| 218 | 213 |
| 219 { | 214 { |
| 220 base::AutoLock lock(devices_changed_lock_); | 215 base::AutoLock lock(devices_changed_lock_); |
| 221 changed = devices_changed_; | 216 changed = devices_changed_; |
| 222 devices_changed_ = false; | 217 devices_changed_ = false; |
| 223 } | 218 } |
| 224 | 219 |
| 220 // Loop through each registered data fetcher and poll it's gamepad data. |
| 221 // It's expected that GetGamepadData will mark each gamepad as active (via |
| 222 // GetPadState). If a gamepad is not marked as active during the calls to |
| 223 // GetGamepadData then it's assumed to be disconnected. |
| 224 for (const auto& it : data_fetchers_) { |
| 225 it->GetGamepadData(changed); |
| 226 } |
| 227 |
| 228 blink::WebGamepads* buffer = gamepad_shared_buffer_->buffer(); |
| 229 |
| 230 // Send out disconnect events using the last polled data before we wipe it out |
| 231 // in the mapping step. |
| 232 if (ever_had_user_gesture_) { |
| 233 for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| 234 PadState& state = pad_states_.get()[i]; |
| 235 |
| 236 if (!state.active_state && state.source != GAMEPAD_SOURCE_NONE) { |
| 237 OnGamepadConnectionChange(false, i, buffer->items[i]); |
| 238 ClearPadState(state); |
| 239 } |
| 240 } |
| 241 } |
| 242 |
| 225 { | 243 { |
| 226 base::AutoLock lock(shared_memory_lock_); | 244 base::AutoLock lock(shared_memory_lock_); |
| 227 | 245 |
| 228 // Acquire the SeqLock. There is only ever one writer to this data. | 246 // Acquire the SeqLock. There is only ever one writer to this data. |
| 229 // See gamepad_hardware_buffer.h. | 247 // See gamepad_hardware_buffer.h. |
| 230 gamepad_shared_buffer_->WriteBegin(); | 248 gamepad_shared_buffer_->WriteBegin(); |
| 231 data_fetcher_->GetGamepadData(gamepad_shared_buffer_->buffer(), changed); | 249 buffer->length = 0; |
| 250 for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| 251 PadState& state = pad_states_.get()[i]; |
| 252 // Must run through the map+sanitize here or CheckForUserGesture may fail. |
| 253 MapAndSanitizeGamepadData(&state, &buffer->items[i], sanitize_); |
| 254 if (state.active_state) |
| 255 buffer->length++; |
| 256 } |
| 232 gamepad_shared_buffer_->WriteEnd(); | 257 gamepad_shared_buffer_->WriteEnd(); |
| 233 } | 258 } |
| 234 | 259 |
| 235 if (ever_had_user_gesture_) { | 260 if (ever_had_user_gesture_) { |
| 236 for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { | 261 for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| 237 WebGamepad& pad = gamepad_shared_buffer_->buffer()->items[i]; | |
| 238 PadState& state = pad_states_.get()[i]; | 262 PadState& state = pad_states_.get()[i]; |
| 239 if (pad.connected && !state.connected()) { | 263 |
| 240 OnGamepadConnectionChange(true, i, pad); | 264 if (state.active_state) { |
| 241 } else if (!pad.connected && state.connected()) { | 265 if (state.active_state == GAMEPAD_NEWLY_ACTIVE) |
| 242 OnGamepadConnectionChange(false, i, pad); | 266 OnGamepadConnectionChange(true, i, buffer->items[i]); |
| 243 } else if (pad.connected && state.connected() && !state.Match(pad)) { | 267 state.active_state = GAMEPAD_INACTIVE; |
| 244 WebGamepad old_pad; | |
| 245 state.AsWebGamepad(&old_pad); | |
| 246 OnGamepadConnectionChange(false, i, old_pad); | |
| 247 OnGamepadConnectionChange(true, i, pad); | |
| 248 } | 268 } |
| 249 } | 269 } |
| 250 } | 270 } |
| 251 | 271 |
| 252 CheckForUserGesture(); | 272 CheckForUserGesture(); |
| 253 | 273 |
| 254 // Schedule our next interval of polling. | 274 // Schedule our next interval of polling. |
| 255 ScheduleDoPoll(); | 275 ScheduleDoPoll(); |
| 256 } | 276 } |
| 257 | 277 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 268 | 288 |
| 269 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 289 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| 270 FROM_HERE, base::Bind(&GamepadProvider::DoPoll, Unretained(this)), | 290 FROM_HERE, base::Bind(&GamepadProvider::DoPoll, Unretained(this)), |
| 271 base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs)); | 291 base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs)); |
| 272 have_scheduled_do_poll_ = true; | 292 have_scheduled_do_poll_ = true; |
| 273 } | 293 } |
| 274 | 294 |
| 275 void GamepadProvider::OnGamepadConnectionChange(bool connected, | 295 void GamepadProvider::OnGamepadConnectionChange(bool connected, |
| 276 int index, | 296 int index, |
| 277 const WebGamepad& pad) { | 297 const WebGamepad& pad) { |
| 278 PadState& state = pad_states_.get()[index]; | |
| 279 if (connected) | |
| 280 state.SetPad(pad); | |
| 281 else | |
| 282 state.SetDisconnected(); | |
| 283 | |
| 284 if (connection_change_client_) | 298 if (connection_change_client_) |
| 285 connection_change_client_->OnGamepadConnectionChange(connected, index, pad); | 299 connection_change_client_->OnGamepadConnectionChange(connected, index, pad); |
| 286 } | 300 } |
| 287 | 301 |
| 288 void GamepadProvider::CheckForUserGesture() { | 302 void GamepadProvider::CheckForUserGesture() { |
| 289 base::AutoLock lock(user_gesture_lock_); | 303 base::AutoLock lock(user_gesture_lock_); |
| 290 if (user_gesture_observers_.empty() && ever_had_user_gesture_) | 304 if (user_gesture_observers_.empty() && ever_had_user_gesture_) |
| 291 return; | 305 return; |
| 292 | 306 |
| 293 bool had_gesture_before = ever_had_user_gesture_; | |
| 294 const WebGamepads* pads = gamepad_shared_buffer_->buffer(); | 307 const WebGamepads* pads = gamepad_shared_buffer_->buffer(); |
| 295 if (GamepadsHaveUserGesture(*pads)) { | 308 if (GamepadsHaveUserGesture(*pads)) { |
| 296 ever_had_user_gesture_ = true; | 309 ever_had_user_gesture_ = true; |
| 297 for (size_t i = 0; i < user_gesture_observers_.size(); i++) { | 310 for (size_t i = 0; i < user_gesture_observers_.size(); i++) { |
| 298 user_gesture_observers_[i].task_runner->PostTask( | 311 user_gesture_observers_[i].task_runner->PostTask( |
| 299 FROM_HERE, user_gesture_observers_[i].closure); | 312 FROM_HERE, user_gesture_observers_[i].closure); |
| 300 } | 313 } |
| 301 user_gesture_observers_.clear(); | 314 user_gesture_observers_.clear(); |
| 302 } | 315 } |
| 303 if (!had_gesture_before && ever_had_user_gesture_) { | |
| 304 // Initialize pad_states_ for the first time. | |
| 305 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
| 306 pad_states_.get()[i].SetPad(pads->items[i]); | |
| 307 } | |
| 308 } | |
| 309 } | 316 } |
| 310 | 317 |
| 311 } // namespace device | 318 } // namespace device |
| OLD | NEW |