OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/browser/gamepad/gamepad_provider.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <string.h> | |
9 #include <cmath> | |
10 #include <set> | |
11 #include <utility> | |
12 #include <vector> | |
13 | |
14 #include "base/bind.h" | |
15 #include "base/location.h" | |
16 #include "base/logging.h" | |
17 #include "base/single_thread_task_runner.h" | |
18 #include "base/third_party/dynamic_annotations/dynamic_annotations.h" | |
19 #include "base/threading/thread.h" | |
20 #include "base/threading/thread_restrictions.h" | |
21 #include "base/threading/thread_task_runner_handle.h" | |
22 #include "build/build_config.h" | |
23 #include "content/browser/gamepad/gamepad_data_fetcher.h" | |
24 #include "content/browser/gamepad/gamepad_platform_data_fetcher.h" | |
25 #include "content/browser/gamepad/gamepad_service.h" | |
26 #include "content/common/gamepad_hardware_buffer.h" | |
27 #include "content/common/gamepad_messages.h" | |
28 #include "content/common/gamepad_user_gesture.h" | |
29 #include "content/public/browser/browser_thread.h" | |
30 | |
31 using blink::WebGamepad; | |
32 using blink::WebGamepads; | |
33 | |
34 namespace content { | |
35 | |
36 GamepadProvider::ClosureAndThread::ClosureAndThread( | |
37 const base::Closure& c, | |
38 const scoped_refptr<base::SingleThreadTaskRunner>& m) | |
39 : closure(c), task_runner(m) { | |
40 } | |
41 | |
42 GamepadProvider::ClosureAndThread::ClosureAndThread( | |
43 const ClosureAndThread& other) = default; | |
44 | |
45 GamepadProvider::ClosureAndThread::~ClosureAndThread() { | |
46 } | |
47 | |
48 GamepadProvider::GamepadProvider() | |
49 : is_paused_(true), | |
50 have_scheduled_do_poll_(false), | |
51 devices_changed_(true), | |
52 ever_had_user_gesture_(false) { | |
53 Initialize(std::unique_ptr<GamepadDataFetcher>()); | |
54 } | |
55 | |
56 GamepadProvider::GamepadProvider(std::unique_ptr<GamepadDataFetcher> fetcher) | |
57 : is_paused_(true), | |
58 have_scheduled_do_poll_(false), | |
59 devices_changed_(true), | |
60 ever_had_user_gesture_(false) { | |
61 Initialize(std::move(fetcher)); | |
62 } | |
63 | |
64 GamepadProvider::~GamepadProvider() { | |
65 base::SystemMonitor* monitor = base::SystemMonitor::Get(); | |
66 if (monitor) | |
67 monitor->RemoveDevicesChangedObserver(this); | |
68 | |
69 // Use Stop() to join the polling thread, as there may be pending callbacks | |
70 // which dereference |polling_thread_|. | |
71 polling_thread_->Stop(); | |
72 data_fetcher_.reset(); | |
73 } | |
74 | |
75 base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess( | |
76 base::ProcessHandle process) { | |
77 base::SharedMemoryHandle renderer_handle; | |
78 gamepad_shared_memory_.ShareToProcess(process, &renderer_handle); | |
79 return renderer_handle; | |
80 } | |
81 | |
82 void GamepadProvider::GetCurrentGamepadData(WebGamepads* data) { | |
83 const WebGamepads& pads = SharedMemoryAsHardwareBuffer()->buffer; | |
84 base::AutoLock lock(shared_memory_lock_); | |
85 *data = pads; | |
86 } | |
87 | |
88 void GamepadProvider::Pause() { | |
89 { | |
90 base::AutoLock lock(is_paused_lock_); | |
91 is_paused_ = true; | |
92 } | |
93 base::MessageLoop* polling_loop = polling_thread_->message_loop(); | |
94 polling_loop->task_runner()->PostTask( | |
95 FROM_HERE, | |
96 base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), true)); | |
97 } | |
98 | |
99 void GamepadProvider::Resume() { | |
100 { | |
101 base::AutoLock lock(is_paused_lock_); | |
102 if (!is_paused_) | |
103 return; | |
104 is_paused_ = false; | |
105 } | |
106 | |
107 base::MessageLoop* polling_loop = polling_thread_->message_loop(); | |
108 polling_loop->task_runner()->PostTask( | |
109 FROM_HERE, | |
110 base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false)); | |
111 polling_loop->task_runner()->PostTask( | |
112 FROM_HERE, | |
113 base::Bind(&GamepadProvider::ScheduleDoPoll, Unretained(this))); | |
114 } | |
115 | |
116 void GamepadProvider::RegisterForUserGesture(const base::Closure& closure) { | |
117 base::AutoLock lock(user_gesture_lock_); | |
118 user_gesture_observers_.push_back( | |
119 ClosureAndThread(closure, base::ThreadTaskRunnerHandle::Get())); | |
120 } | |
121 | |
122 void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) { | |
123 base::AutoLock lock(devices_changed_lock_); | |
124 devices_changed_ = true; | |
125 } | |
126 | |
127 void GamepadProvider::Initialize(std::unique_ptr<GamepadDataFetcher> fetcher) { | |
128 size_t data_size = sizeof(GamepadHardwareBuffer); | |
129 base::SystemMonitor* monitor = base::SystemMonitor::Get(); | |
130 if (monitor) | |
131 monitor->AddDevicesChangedObserver(this); | |
132 bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size); | |
133 CHECK(res); | |
134 GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); | |
135 memset(hwbuf, 0, sizeof(GamepadHardwareBuffer)); | |
136 pad_states_.reset(new PadState[WebGamepads::itemsLengthCap]); | |
137 | |
138 polling_thread_.reset(new base::Thread("Gamepad polling thread")); | |
139 #if defined(OS_LINUX) | |
140 // On Linux, the data fetcher needs to watch file descriptors, so the message | |
141 // loop needs to be a libevent loop. | |
142 const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO; | |
143 #elif defined(OS_ANDROID) | |
144 // On Android, keeping a message loop of default type. | |
145 const base::MessageLoop::Type kMessageLoopType = | |
146 base::MessageLoop::TYPE_DEFAULT; | |
147 #else | |
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 | |
150 // to properly pump the MessageWindow that captures device state. | |
151 const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI; | |
152 #endif | |
153 polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); | |
154 | |
155 polling_thread_->task_runner()->PostTask( | |
156 FROM_HERE, base::Bind(&GamepadProvider::DoInitializePollingThread, | |
157 base::Unretained(this), base::Passed(&fetcher))); | |
158 } | |
159 | |
160 void GamepadProvider::DoInitializePollingThread( | |
161 std::unique_ptr<GamepadDataFetcher> fetcher) { | |
162 DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); | |
163 DCHECK(!data_fetcher_.get()); // Should only initialize once. | |
164 | |
165 if (!fetcher) | |
166 fetcher.reset(new GamepadPlatformDataFetcher); | |
167 data_fetcher_ = std::move(fetcher); | |
168 } | |
169 | |
170 void GamepadProvider::SendPauseHint(bool paused) { | |
171 DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); | |
172 if (data_fetcher_) | |
173 data_fetcher_->PauseHint(paused); | |
174 } | |
175 | |
176 bool GamepadProvider::PadState::Match(const WebGamepad& pad) const { | |
177 return connected_ == pad.connected && | |
178 axes_length_ == pad.axesLength && | |
179 buttons_length_ == pad.buttonsLength && | |
180 memcmp(id_, pad.id, sizeof(id_)) == 0 && | |
181 memcmp(mapping_, pad.mapping, sizeof(mapping_)) == 0; | |
182 } | |
183 | |
184 void GamepadProvider::PadState::SetPad(const WebGamepad& pad) { | |
185 connected_ = pad.connected; | |
186 axes_length_ = pad.axesLength; | |
187 buttons_length_ = pad.buttonsLength; | |
188 memcpy(id_, pad.id, sizeof(id_)); | |
189 memcpy(mapping_, pad.mapping, sizeof(mapping_)); | |
190 } | |
191 | |
192 void GamepadProvider::PadState::SetDisconnected() { | |
193 connected_ = false; | |
194 axes_length_ = 0; | |
195 buttons_length_ = 0; | |
196 memset(id_, 0, sizeof(id_)); | |
197 memset(mapping_, 0, sizeof(mapping_)); | |
198 } | |
199 | |
200 void GamepadProvider::PadState::AsWebGamepad(WebGamepad* pad) { | |
201 pad->connected = connected_; | |
202 pad->axesLength = axes_length_; | |
203 pad->buttonsLength = buttons_length_; | |
204 memcpy(pad->id, id_, sizeof(id_)); | |
205 memcpy(pad->mapping, mapping_, sizeof(mapping_)); | |
206 memset(pad->axes, 0, sizeof(pad->axes)); | |
207 memset(pad->buttons, 0, sizeof(pad->buttons)); | |
208 } | |
209 | |
210 void GamepadProvider::DoPoll() { | |
211 DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); | |
212 DCHECK(have_scheduled_do_poll_); | |
213 have_scheduled_do_poll_ = false; | |
214 | |
215 bool changed; | |
216 GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); | |
217 | |
218 ANNOTATE_BENIGN_RACE_SIZED( | |
219 &hwbuf->buffer, | |
220 sizeof(WebGamepads), | |
221 "Racey reads are discarded"); | |
222 | |
223 { | |
224 base::AutoLock lock(devices_changed_lock_); | |
225 changed = devices_changed_; | |
226 devices_changed_ = false; | |
227 } | |
228 | |
229 { | |
230 base::AutoLock lock(shared_memory_lock_); | |
231 | |
232 // Acquire the SeqLock. There is only ever one writer to this data. | |
233 // See gamepad_hardware_buffer.h. | |
234 hwbuf->sequence.WriteBegin(); | |
235 data_fetcher_->GetGamepadData(&hwbuf->buffer, changed); | |
236 hwbuf->sequence.WriteEnd(); | |
237 } | |
238 | |
239 if (ever_had_user_gesture_) { | |
240 for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
241 WebGamepad& pad = hwbuf->buffer.items[i]; | |
242 PadState& state = pad_states_.get()[i]; | |
243 if (pad.connected && !state.connected()) { | |
244 OnGamepadConnectionChange(true, i, pad); | |
245 } else if (!pad.connected && state.connected()) { | |
246 OnGamepadConnectionChange(false, i, pad); | |
247 } else if (pad.connected && state.connected() && !state.Match(pad)) { | |
248 WebGamepad old_pad; | |
249 state.AsWebGamepad(&old_pad); | |
250 OnGamepadConnectionChange(false, i, old_pad); | |
251 OnGamepadConnectionChange(true, i, pad); | |
252 } | |
253 } | |
254 } | |
255 | |
256 CheckForUserGesture(); | |
257 | |
258 // Schedule our next interval of polling. | |
259 ScheduleDoPoll(); | |
260 } | |
261 | |
262 void GamepadProvider::ScheduleDoPoll() { | |
263 DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); | |
264 if (have_scheduled_do_poll_) | |
265 return; | |
266 | |
267 { | |
268 base::AutoLock lock(is_paused_lock_); | |
269 if (is_paused_) | |
270 return; | |
271 } | |
272 | |
273 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
274 FROM_HERE, base::Bind(&GamepadProvider::DoPoll, Unretained(this)), | |
275 base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs)); | |
276 have_scheduled_do_poll_ = true; | |
277 } | |
278 | |
279 void GamepadProvider::OnGamepadConnectionChange( | |
280 bool connected, int index, const WebGamepad& pad) { | |
281 PadState& state = pad_states_.get()[index]; | |
282 if (connected) | |
283 state.SetPad(pad); | |
284 else | |
285 state.SetDisconnected(); | |
286 | |
287 BrowserThread::PostTask( | |
288 BrowserThread::IO, | |
289 FROM_HERE, | |
290 base::Bind(&GamepadProvider::DispatchGamepadConnectionChange, | |
291 base::Unretained(this), | |
292 connected, | |
293 index, | |
294 pad)); | |
295 } | |
296 | |
297 void GamepadProvider::DispatchGamepadConnectionChange( | |
298 bool connected, int index, const WebGamepad& pad) { | |
299 if (connected) | |
300 GamepadService::GetInstance()->OnGamepadConnected(index, pad); | |
301 else | |
302 GamepadService::GetInstance()->OnGamepadDisconnected(index, pad); | |
303 } | |
304 | |
305 GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() { | |
306 void* mem = gamepad_shared_memory_.memory(); | |
307 CHECK(mem); | |
308 return static_cast<GamepadHardwareBuffer*>(mem); | |
309 } | |
310 | |
311 void GamepadProvider::CheckForUserGesture() { | |
312 base::AutoLock lock(user_gesture_lock_); | |
313 if (user_gesture_observers_.empty() && ever_had_user_gesture_) | |
314 return; | |
315 | |
316 bool had_gesture_before = ever_had_user_gesture_; | |
317 const WebGamepads& pads = SharedMemoryAsHardwareBuffer()->buffer; | |
318 if (GamepadsHaveUserGesture(pads)) { | |
319 ever_had_user_gesture_ = true; | |
320 for (size_t i = 0; i < user_gesture_observers_.size(); i++) { | |
321 user_gesture_observers_[i].task_runner->PostTask( | |
322 FROM_HERE, user_gesture_observers_[i].closure); | |
323 } | |
324 user_gesture_observers_.clear(); | |
325 } | |
326 if (!had_gesture_before && ever_had_user_gesture_) { | |
327 // Initialize pad_states_ for the first time. | |
328 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
329 pad_states_.get()[i].SetPad(pads.items[i]); | |
330 } | |
331 } | |
332 } | |
333 | |
334 } // namespace content | |
OLD | NEW |