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_platform_data_fetcher_win.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <string.h> | |
9 | |
10 #include "base/strings/stringprintf.h" | |
11 #include "base/trace_event/trace_event.h" | |
12 #include "base/win/windows_version.h" | |
13 #include "content/common/gamepad_hardware_buffer.h" | |
14 #include "content/common/gamepad_messages.h" | |
15 | |
16 namespace content { | |
17 | |
18 using namespace blink; | |
19 | |
20 namespace { | |
21 | |
22 // See http://goo.gl/5VSJR. These are not available in all versions of the | |
23 // header, but they can be returned from the driver, so we define our own | |
24 // versions here. | |
25 static const BYTE kDeviceSubTypeGamepad = 1; | |
26 static const BYTE kDeviceSubTypeWheel = 2; | |
27 static const BYTE kDeviceSubTypeArcadeStick = 3; | |
28 static const BYTE kDeviceSubTypeFlightStick = 4; | |
29 static const BYTE kDeviceSubTypeDancePad = 5; | |
30 static const BYTE kDeviceSubTypeGuitar = 6; | |
31 static const BYTE kDeviceSubTypeGuitarAlternate = 7; | |
32 static const BYTE kDeviceSubTypeDrumKit = 8; | |
33 static const BYTE kDeviceSubTypeGuitarBass = 11; | |
34 static const BYTE kDeviceSubTypeArcadePad = 19; | |
35 | |
36 float NormalizeXInputAxis(SHORT value) { | |
37 return ((value + 32768.f) / 32767.5f) - 1.f; | |
38 } | |
39 | |
40 const WebUChar* GamepadSubTypeName(BYTE sub_type) { | |
41 switch (sub_type) { | |
42 case kDeviceSubTypeGamepad: return L"GAMEPAD"; | |
43 case kDeviceSubTypeWheel: return L"WHEEL"; | |
44 case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK"; | |
45 case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK"; | |
46 case kDeviceSubTypeDancePad: return L"DANCE_PAD"; | |
47 case kDeviceSubTypeGuitar: return L"GUITAR"; | |
48 case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE"; | |
49 case kDeviceSubTypeDrumKit: return L"DRUM_KIT"; | |
50 case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS"; | |
51 case kDeviceSubTypeArcadePad: return L"ARCADE_PAD"; | |
52 default: return L"<UNKNOWN>"; | |
53 } | |
54 } | |
55 | |
56 const WebUChar* XInputDllFileName() { | |
57 // Xinput.h defines filename (XINPUT_DLL) on different Windows versions, but | |
58 // Xinput.h specifies it in build time. Approach here uses the same values | |
59 // and it is resolving dll filename based on Windows version it is running on. | |
60 if (base::win::GetVersion() >= base::win::VERSION_WIN8) { | |
61 // For Windows 8 and 10, XINPUT_DLL is xinput1_4.dll. | |
62 return FILE_PATH_LITERAL("xinput1_4.dll"); | |
63 } else if (base::win::GetVersion() >= base::win::VERSION_WIN7) { | |
64 return FILE_PATH_LITERAL("xinput9_1_0.dll"); | |
65 } else { | |
66 NOTREACHED(); | |
67 return nullptr; | |
68 } | |
69 } | |
70 | |
71 } // namespace | |
72 | |
73 GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin() | |
74 : xinput_dll_(base::FilePath(XInputDllFileName())), | |
75 xinput_available_(GetXInputDllFunctions()) { | |
76 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
77 platform_pad_state_[i].status = DISCONNECTED; | |
78 pad_state_[i].mapper = NULL; | |
79 pad_state_[i].axis_mask = 0; | |
80 pad_state_[i].button_mask = 0; | |
81 } | |
82 | |
83 raw_input_fetcher_.reset(new RawInputDataFetcher()); | |
84 raw_input_fetcher_->StartMonitor(); | |
85 } | |
86 | |
87 GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() { | |
88 raw_input_fetcher_->StopMonitor(); | |
89 } | |
90 | |
91 int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const { | |
92 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
93 if (platform_pad_state_[i].status == DISCONNECTED) | |
94 return i; | |
95 } | |
96 return -1; | |
97 } | |
98 | |
99 bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const { | |
100 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
101 if (platform_pad_state_[i].status == XINPUT_CONNECTED && | |
102 platform_pad_state_[i].xinput_index == index) | |
103 return true; | |
104 } | |
105 return false; | |
106 } | |
107 | |
108 bool GamepadPlatformDataFetcherWin::HasRawInputGamepad( | |
109 const HANDLE handle) const { | |
110 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
111 if (platform_pad_state_[i].status == RAWINPUT_CONNECTED && | |
112 platform_pad_state_[i].raw_input_handle == handle) | |
113 return true; | |
114 } | |
115 return false; | |
116 } | |
117 | |
118 void GamepadPlatformDataFetcherWin::EnumerateDevices() { | |
119 TRACE_EVENT0("GAMEPAD", "EnumerateDevices"); | |
120 | |
121 // Mark all disconnected pads DISCONNECTED. | |
122 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
123 if (!pad_state_[i].data.connected) | |
124 platform_pad_state_[i].status = DISCONNECTED; | |
125 } | |
126 | |
127 for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) { | |
128 if (HasXInputGamepad(i)) | |
129 continue; | |
130 int pad_index = FirstAvailableGamepadId(); | |
131 if (pad_index == -1) | |
132 return; // We can't add any more gamepads. | |
133 WebGamepad& pad = pad_state_[pad_index].data; | |
134 if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) { | |
135 platform_pad_state_[pad_index].status = XINPUT_CONNECTED; | |
136 platform_pad_state_[pad_index].xinput_index = i; | |
137 pad_state_[pad_index].mapper = NULL; | |
138 pad_state_[pad_index].axis_mask = 0; | |
139 pad_state_[pad_index].button_mask = 0; | |
140 } | |
141 } | |
142 | |
143 if (raw_input_fetcher_->Available()) { | |
144 std::vector<RawGamepadInfo*> raw_inputs = | |
145 raw_input_fetcher_->EnumerateDevices(); | |
146 for (size_t i = 0; i < raw_inputs.size(); ++i) { | |
147 RawGamepadInfo* gamepad = raw_inputs[i]; | |
148 if (gamepad->buttons_length == 0 && gamepad->axes_length == 0) | |
149 continue; | |
150 if (HasRawInputGamepad(gamepad->handle)) | |
151 continue; | |
152 int pad_index = FirstAvailableGamepadId(); | |
153 if (pad_index == -1) | |
154 return; | |
155 WebGamepad& pad = pad_state_[pad_index].data; | |
156 pad.connected = true; | |
157 PadState& state = pad_state_[pad_index]; | |
158 PlatformPadState& platform_state = platform_pad_state_[pad_index]; | |
159 platform_state.status = RAWINPUT_CONNECTED; | |
160 platform_state.raw_input_handle = gamepad->handle; | |
161 | |
162 std::string vendor = base::StringPrintf("%04x", gamepad->vendor_id); | |
163 std::string product = base::StringPrintf("%04x", gamepad->product_id); | |
164 state.mapper = GetGamepadStandardMappingFunction(vendor, product); | |
165 state.axis_mask = 0; | |
166 state.button_mask = 0; | |
167 | |
168 swprintf(pad.id, WebGamepad::idLengthCap, | |
169 L"%ls (%lsVendor: %04x Product: %04x)", | |
170 gamepad->id, state.mapper ? L"STANDARD GAMEPAD " : L"", | |
171 gamepad->vendor_id, gamepad->product_id); | |
172 | |
173 if (state.mapper) | |
174 swprintf(pad.mapping, WebGamepad::mappingLengthCap, L"standard"); | |
175 else | |
176 pad.mapping[0] = 0; | |
177 } | |
178 } | |
179 } | |
180 | |
181 void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads, | |
182 bool devices_changed_hint) { | |
183 TRACE_EVENT0("GAMEPAD", "GetGamepadData"); | |
184 | |
185 if (!xinput_available_ && | |
186 !raw_input_fetcher_->Available()) { | |
187 pads->length = 0; | |
188 return; | |
189 } | |
190 | |
191 // A note on XInput devices: | |
192 // If we got notification that system devices have been updated, then | |
193 // run GetCapabilities to update the connected status and the device | |
194 // identifier. It can be slow to do to both GetCapabilities and | |
195 // GetState on unconnected devices, so we want to avoid a 2-5ms pause | |
196 // here by only doing this when the devices are updated (despite | |
197 // documentation claiming it's OK to call it any time). | |
198 if (devices_changed_hint) | |
199 EnumerateDevices(); | |
200 | |
201 pads->length = 0; | |
202 | |
203 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { | |
204 // We rely on device_changed and GetCapabilities to tell us that | |
205 // something's been connected, but we will mark as disconnected if | |
206 // Get___PadState returns that we've lost the pad. | |
207 if (!pad_state_[i].data.connected) | |
208 continue; | |
209 | |
210 if (platform_pad_state_[i].status == XINPUT_CONNECTED) | |
211 GetXInputPadData(i, &pad_state_[i].data); | |
212 else if (platform_pad_state_[i].status == RAWINPUT_CONNECTED) | |
213 GetRawInputPadData(i, &pad_state_[i].data); | |
214 | |
215 MapAndSanitizeGamepadData(&pad_state_[i], &pads->items[i]); | |
216 | |
217 if (pads->items[i].connected) | |
218 pads->length++; | |
219 } | |
220 } | |
221 | |
222 void GamepadPlatformDataFetcherWin::PauseHint(bool pause) { | |
223 if (pause) | |
224 raw_input_fetcher_->StopMonitor(); | |
225 else | |
226 raw_input_fetcher_->StartMonitor(); | |
227 } | |
228 | |
229 bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity( | |
230 int i, | |
231 WebGamepad* pad) const { | |
232 DCHECK(pad); | |
233 TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i); | |
234 XINPUT_CAPABILITIES caps; | |
235 DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); | |
236 if (res == ERROR_DEVICE_NOT_CONNECTED) { | |
237 pad->connected = false; | |
238 return false; | |
239 } else { | |
240 pad->connected = true; | |
241 swprintf(pad->id, | |
242 WebGamepad::idLengthCap, | |
243 L"Xbox 360 Controller (XInput STANDARD %ls)", | |
244 GamepadSubTypeName(caps.SubType)); | |
245 swprintf(pad->mapping, WebGamepad::mappingLengthCap, L"standard"); | |
246 return true; | |
247 } | |
248 } | |
249 | |
250 void GamepadPlatformDataFetcherWin::GetXInputPadData( | |
251 int i, | |
252 WebGamepad* pad) { | |
253 XINPUT_STATE state; | |
254 memset(&state, 0, sizeof(XINPUT_STATE)); | |
255 TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i); | |
256 DWORD dwResult = xinput_get_state_(platform_pad_state_[i].xinput_index, | |
257 &state); | |
258 TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i); | |
259 | |
260 if (dwResult == ERROR_SUCCESS) { | |
261 pad->timestamp = state.dwPacketNumber; | |
262 pad->buttonsLength = 0; | |
263 WORD val = state.Gamepad.wButtons; | |
264 #define ADD(b) pad->buttons[pad->buttonsLength].pressed = (val & (b)) != 0; \ | |
265 pad->buttons[pad->buttonsLength++].value = ((val & (b)) ? 1.f : 0.f); | |
266 ADD(XINPUT_GAMEPAD_A); | |
267 ADD(XINPUT_GAMEPAD_B); | |
268 ADD(XINPUT_GAMEPAD_X); | |
269 ADD(XINPUT_GAMEPAD_Y); | |
270 ADD(XINPUT_GAMEPAD_LEFT_SHOULDER); | |
271 ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER); | |
272 | |
273 pad->buttons[pad->buttonsLength].pressed = | |
274 state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; | |
275 pad->buttons[pad->buttonsLength++].value = | |
276 state.Gamepad.bLeftTrigger / 255.f; | |
277 | |
278 pad->buttons[pad->buttonsLength].pressed = | |
279 state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; | |
280 pad->buttons[pad->buttonsLength++].value = | |
281 state.Gamepad.bRightTrigger / 255.f; | |
282 | |
283 ADD(XINPUT_GAMEPAD_BACK); | |
284 ADD(XINPUT_GAMEPAD_START); | |
285 ADD(XINPUT_GAMEPAD_LEFT_THUMB); | |
286 ADD(XINPUT_GAMEPAD_RIGHT_THUMB); | |
287 ADD(XINPUT_GAMEPAD_DPAD_UP); | |
288 ADD(XINPUT_GAMEPAD_DPAD_DOWN); | |
289 ADD(XINPUT_GAMEPAD_DPAD_LEFT); | |
290 ADD(XINPUT_GAMEPAD_DPAD_RIGHT); | |
291 #undef ADD | |
292 pad->axesLength = 0; | |
293 | |
294 float value = 0.0; | |
295 #define ADD(a, factor) value = factor * NormalizeXInputAxis(a); \ | |
296 pad->axes[pad->axesLength++] = value; | |
297 | |
298 // XInput are +up/+right, -down/-left, we want -up/-left. | |
299 ADD(state.Gamepad.sThumbLX, 1); | |
300 ADD(state.Gamepad.sThumbLY, -1); | |
301 ADD(state.Gamepad.sThumbRX, 1); | |
302 ADD(state.Gamepad.sThumbRY, -1); | |
303 #undef ADD | |
304 } else { | |
305 pad->connected = false; | |
306 } | |
307 } | |
308 | |
309 void GamepadPlatformDataFetcherWin::GetRawInputPadData( | |
310 int index, | |
311 WebGamepad* pad) { | |
312 RawGamepadInfo* gamepad = raw_input_fetcher_->GetGamepadInfo( | |
313 platform_pad_state_[index].raw_input_handle); | |
314 if (!gamepad) { | |
315 pad->connected = false; | |
316 return; | |
317 } | |
318 | |
319 pad->timestamp = gamepad->report_id; | |
320 pad->buttonsLength = gamepad->buttons_length; | |
321 pad->axesLength = gamepad->axes_length; | |
322 | |
323 for (unsigned int i = 0; i < pad->buttonsLength; i++) { | |
324 pad->buttons[i].pressed = gamepad->buttons[i]; | |
325 pad->buttons[i].value = gamepad->buttons[i] ? 1.0 : 0.0; | |
326 } | |
327 | |
328 for (unsigned int i = 0; i < pad->axesLength; i++) | |
329 pad->axes[i] = gamepad->axes[i].value; | |
330 } | |
331 | |
332 bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() { | |
333 xinput_get_capabilities_ = NULL; | |
334 xinput_get_state_ = NULL; | |
335 XInputEnableFunc xinput_enable = reinterpret_cast<XInputEnableFunc>( | |
336 xinput_dll_.GetFunctionPointer("XInputEnable")); | |
337 xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>( | |
338 xinput_dll_.GetFunctionPointer("XInputGetCapabilities")); | |
339 if (!xinput_get_capabilities_) | |
340 return false; | |
341 xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>( | |
342 xinput_dll_.GetFunctionPointer("XInputGetState")); | |
343 if (!xinput_get_state_) | |
344 return false; | |
345 if (xinput_enable) { | |
346 // XInputEnable is unavailable before Win8 and deprecated in Win10. | |
347 xinput_enable(true); | |
348 } | |
349 return true; | |
350 } | |
351 | |
352 } // namespace content | |
OLD | NEW |