Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(275)

Side by Side Diff: content/browser/gamepad/xbox_data_fetcher_mac.cc

Issue 14328036: Implement support for USB Xbox360 controllers without a driver on Mac. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix compile Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « content/browser/gamepad/xbox_data_fetcher_mac.h ('k') | content/content_browser.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2013 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/xbox_data_fetcher_mac.h"
6
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <IOKit/IOCFPlugIn.h>
9 #include <IOKit/IOKitLib.h>
10 #include <IOKit/usb/IOUSBLib.h>
11 #include <IOKit/usb/USB.h>
12
13 #include "base/logging.h"
14 #include "base/mac/scoped_cftyperef.h"
15
16 namespace {
17 const int kVendorMicrosoft = 0x045e;
18 const int kProduct360Controller = 0x028e;
19
20 const int kReadEndpoint = 1;
21 const int kControlEndpoint = 2;
22
23 #pragma pack(push, 1)
24 struct ButtonData {
25 bool dpad_up : 1;
26 bool dpad_down : 1;
27 bool dpad_left : 1;
28 bool dpad_right : 1;
29
30 bool start : 1;
31 bool back : 1;
32 bool stick_left_click : 1;
33 bool stick_right_click : 1;
34
35 bool bumper_left : 1;
36 bool bumper_right : 1;
37 bool guide : 1;
38 bool dummy1 : 1; // Always 0.
39
40 bool a : 1;
41 bool b : 1;
42 bool x : 1;
43 bool y : 1;
44
45 uint8 trigger_left;
46 uint8 trigger_right;
47
48 int16 stick_left_x;
49 int16 stick_left_y;
50 int16 stick_right_x;
51 int16 stick_right_y;
52
53 // Always 0.
54 uint32 dummy2;
55 uint16 dummy3;
56 };
57 #pragma pack(pop)
58
59 COMPILE_ASSERT(sizeof(ButtonData) == 0x12, xbox_button_data_wrong_size);
60
61 enum {
62 CONTROL_MESSAGE_SET_RUMBLE = 0,
63 CONTROL_MESSAGE_SET_LED = 1,
64 };
65
66 // From MSDN:
67 // http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).asp x#dead_zone
68 const int16 kLeftThumbDeadzone = 7849;
69 const int16 kRightThumbDeadzone = 8689;
70 const uint8 kTriggerDeadzone = 30;
71
72 void NormalizeAxis(int16 x,
73 int16 y,
74 int16 deadzone,
75 float* x_out,
76 float* y_out) {
77 float x_val = (float)x;
78 float y_val = (float)y;
79
80 // Determine how far the stick is pushed.
81 float real_magnitude = sqrtf(x_val * x_val + y_val * y_val);
82
83 // Check if the controller is outside a circular dead zone.
84 if (real_magnitude > deadzone) {
85 // Clip the magnitude at its expected maximum value.
86 float magnitude = real_magnitude > 32767 ? 32767 : real_magnitude;
87
88 // Adjust magnitude relative to the end of the dead zone.
89 magnitude -= deadzone;
90
91 // Normalize the magnitude with respect to its expected range giving a
92 // magnitude value of 0.0 to 1.0
93 float ratio = (magnitude / (32767 - deadzone)) / real_magnitude;
94
95 // Y is negated because xbox controllers have an opposite sign from
96 // the 'standard controller' recommendations.
97 *x_out = x_val * ratio;
98 *y_out = -y_val * ratio;
99 } else {
100 // If the controller is in the deadzone zero out the magnitude.
101 *x_out = *y_out = 0.0f;
102 }
103 }
104
105 static float NormalizeTrigger(uint8 value) {
106 return value < kTriggerDeadzone ? 0 :
107 (float)(value - kTriggerDeadzone) / (kuint8max - kTriggerDeadzone);
108 }
109
110 void NormalizeButtonData(const ButtonData& data,
111 XboxController::Data* normalized_data) {
112 normalized_data->buttons[0] = data.a ? 1.f : 0.f;
113 normalized_data->buttons[1] = data.b ? 1.f : 0.f;
114 normalized_data->buttons[2] = data.x ? 1.f : 0.f;
115 normalized_data->buttons[3] = data.y ? 1.f : 0.f;
116 normalized_data->buttons[4] = data.bumper_left ? 1.f : 0.f;
117 normalized_data->buttons[5] = data.bumper_right ? 1.f : 0.f;
118 normalized_data->buttons[6] = NormalizeTrigger(data.trigger_left);
119 normalized_data->buttons[7] = NormalizeTrigger(data.trigger_right);
120 normalized_data->buttons[8] = data.back ? 1.f : 0.f;
121 normalized_data->buttons[9] = data.start ? 1.f : 0.f;
122 normalized_data->buttons[10] = data.stick_left_click ? 1.f : 0.f;
123 normalized_data->buttons[11] = data.stick_right_click ? 1.f : 0.f;
124 normalized_data->buttons[12] = data.dpad_up ? 1.f : 0.f;
125 normalized_data->buttons[13] = data.dpad_down ? 1.f : 0.f;
126 normalized_data->buttons[14] = data.dpad_left ? 1.f : 0.f;
127 normalized_data->buttons[15] = data.dpad_right ? 1.f : 0.f;
128 normalized_data->buttons[16] = data.guide ? 1.f : 0.f;
129 NormalizeAxis(data.stick_left_x,
130 data.stick_left_y,
131 kLeftThumbDeadzone,
132 &normalized_data->axes[0],
133 &normalized_data->axes[1]);
134 NormalizeAxis(data.stick_right_x,
135 data.stick_right_y,
136 kRightThumbDeadzone,
137 &normalized_data->axes[2],
138 &normalized_data->axes[3]);
139 }
140
141 } // namespace
142
143 XboxController::XboxController(Delegate* delegate)
144 : device_(NULL),
145 interface_(NULL),
146 device_is_open_(false),
147 interface_is_open_(false),
148 source_(NULL),
149 read_buffer_size_(0),
150 led_pattern_(LED_NUM_PATTERNS),
151 location_id_(0),
152 delegate_(delegate) {
153 }
154
155 XboxController::~XboxController() {
156 if (source_)
157 CFRunLoopSourceInvalidate(source_);
158 if (interface_ && interface_is_open_)
159 (*interface_)->USBInterfaceClose(interface_);
160 if (device_ && device_is_open_)
161 (*device_)->USBDeviceClose(device_);
162 }
163
164 bool XboxController::OpenDevice(io_service_t service) {
165 kern_return_t kr;
166 HRESULT res;
167
168 IOCFPlugInInterface **plugin;
169 SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService.
170 kr = IOCreatePlugInInterfaceForService(service,
171 kIOUSBDeviceUserClientTypeID,
172 kIOCFPlugInInterfaceID,
173 &plugin,
174 &score);
175 if (kr != KERN_SUCCESS)
176 return false;
177 base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_ref(plugin);
178
179 // IOUSBDeviceStruct320 is the latest version of the device API that is
180 // supported on Mac OS 10.6.
181 res = (*plugin)->QueryInterface(
182 plugin,
183 CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID320),
184 (LPVOID *)&device_);
185 if (res || !device_)
186 return false;
187
188 // Open the device and configure it.
189 kr = (*device_)->USBDeviceOpen(device_);
190 if (kr != KERN_SUCCESS)
191 return false;
192 device_is_open_ = true;
193
194 // Xbox controllers have one configuration option which has configuration
195 // value 1. Try to set it and fail out if it couldn't be configured.
196 IOUSBConfigurationDescriptorPtr config_desc;
197 kr = (*device_)->GetConfigurationDescriptorPtr(device_, 0, &config_desc);
198 if (kr != KERN_SUCCESS)
199 return false;
200 kr = (*device_)->SetConfiguration(device_, config_desc->bConfigurationValue);
201 if (kr != KERN_SUCCESS)
202 return false;
203
204 // The device has 4 interfaces. They are as follows:
205 // Protocol 1:
206 // - Endpoint 1 (in) : Controller events, including button presses.
207 // - Endpoint 2 (out): Rumble pack and LED control
208 // Protocol 2 has a single endpoint to read from a connected ChatPad device.
209 // Protocol 3 is used by a connected headset device.
210 // The device also has an interface on subclass 253, protocol 10 with no
211 // endpoints. It is unused.
212 //
213 // We don't currently support the ChatPad or headset, so protocol 1 is the
214 // only protocol we care about.
215 //
216 // For more detail, see https://github.com/Grumbel/xboxdrv/blob/master/PROTOCO L
217 IOUSBFindInterfaceRequest request;
218 request.bInterfaceClass = 255;
219 request.bInterfaceSubClass = 93;
220 request.bInterfaceProtocol = 1;
221 request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
222 io_iterator_t iter;
223 kr = (*device_)->CreateInterfaceIterator(device_, &request, &iter);
224 if (kr != KERN_SUCCESS)
225 return false;
226
227 // There should be exactly one usb interface which matches the requested
228 // settings.
229 io_service_t usb_interface = IOIteratorNext(iter);
230 if (!usb_interface)
231 return false;
232
233 // We need to make an InterfaceInterface to communicate with the device
234 // endpoint. This is the same process as earlier: first make a
235 // PluginInterface from the io_service then make the InterfaceInterface from
236 // that.
237 IOCFPlugInInterface **plugin_interface;
238 kr = IOCreatePlugInInterfaceForService(usb_interface,
239 kIOUSBInterfaceUserClientTypeID,
240 kIOCFPlugInInterfaceID,
241 &plugin_interface,
242 &score);
243 if (kr != KERN_SUCCESS || !plugin_interface)
244 return false;
245 base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> interface_ref(
246 plugin_interface);
247
248 // Release the usb interface, and any subsequent interfaces returned by the
249 // iterator. (There shouldn't be any, but in case a future device does
250 // contain more interfaces, this will serve to avoid memory leaks.)
251 do {
252 IOObjectRelease(usb_interface);
253 } while ((usb_interface = IOIteratorNext(iter)));
254
255 // Actually create the interface.
256 kr = (*plugin_interface)->QueryInterface(
257 plugin_interface,
258 CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
259 (LPVOID *)&interface_);
260
261 if (kr != KERN_SUCCESS || !interface_)
262 return false;
263
264 // Actually open the interface.
265 kr = (*interface_)->USBInterfaceOpen(interface_);
266 if (kr != KERN_SUCCESS)
267 return false;
268 interface_is_open_ = true;
269
270 kr = (*interface_)->CreateInterfaceAsyncEventSource(interface_, &source_);
271 if (kr != KERN_SUCCESS || !source_)
272 return false;
273 CFRunLoopAddSource(CFRunLoopGetMain(), source_, kCFRunLoopDefaultMode);
274 // The CFRunLoop retains the source.
275 CFRelease(source_);
276
277 // The interface should have two pipes. Pipe 1 with direction kUSBIn and pipe
278 // 2 with direction kUSBOut. Both pipes should have type kUSBInterrupt.
279 uint8 num_endpoints;
280 kr = (*interface_)->GetNumEndpoints(interface_, &num_endpoints);
281 if (kr != KERN_SUCCESS || num_endpoints < 2)
282 return false;
283
284 for (int i = 1; i <= num_endpoints; i++) {
285 uint8 direction;
286 uint8 number;
287 uint8 transfer_type;
288 uint16 max_packet_size;
289 uint8 interval;
290
291 kr = (*interface_)->GetPipeProperties(interface_,
292 i,
293 &direction,
294 &number,
295 &transfer_type,
296 &max_packet_size,
297 &interval);
298 if (kr != KERN_SUCCESS || transfer_type != kUSBInterrupt)
299 return false;
300 if (i == kReadEndpoint) {
301 if (direction != kUSBIn)
302 return false;
303 if (max_packet_size > 32)
304 return false;
305 read_buffer_.reset(new uint8[max_packet_size]);
306 read_buffer_size_ = max_packet_size;
307 QueueRead();
308 } else if (i == kControlEndpoint) {
309 if (direction != kUSBOut)
310 return false;
311 }
312 }
313
314 // The location ID is unique per controller, and can be used to track
315 // controllers through reconnections (though if a controller is detached from
316 // one USB hub and attached to another, the location ID will change).
317 kr = (*device_)->GetLocationID(device_, &location_id_);
318 if (kr != KERN_SUCCESS)
319 return false;
320
321 return true;
322 }
323
324 void XboxController::SetLEDPattern(LEDPattern pattern) {
325 led_pattern_ = pattern;
326 const UInt8 length = 3;
327 // This buffer will be released in WriteComplete when WritePipeAsync
328 // finishes.
329 UInt8* buffer = new UInt8[length];
330 buffer[0] = (UInt8)CONTROL_MESSAGE_SET_LED;
331 buffer[1] = length;
332 buffer[2] = (UInt8)pattern;
333 kern_return_t kr = (*interface_)->WritePipeAsync(interface_,
334 kControlEndpoint,
335 buffer,
336 (UInt32)length,
337 WriteComplete,
338 buffer);
339 if (kr != KERN_SUCCESS) {
340 IOError();
341 return;
342 }
343 }
344
345 int XboxController::GetVendorId() const {
346 return kVendorMicrosoft;
347 }
348
349 int XboxController::GetProductId() const {
350 return kProduct360Controller;
351 }
352
353 void XboxController::WriteComplete(void* context, IOReturn result, void* arg0) {
354 // Ignoring any errors sending data, because they will usually only occur
355 // when the device is disconnected, in which case it really doesn't matter if
356 // the data got to the controller or not.
357 if (result != KERN_SUCCESS)
358 return;
359
360 UInt8* buffer = (UInt8*)context;
361 delete[] buffer;
362 }
363
364 void XboxController::GotData(void* context, IOReturn result, void* arg0) {
365 uint32 bytesRead = (uint32)arg0;
366 XboxController* controller = static_cast<XboxController*>(context);
367
368 if (result != kIOReturnSuccess) {
369 // This will happen if the device was disconnected. The gamepad has
370 // probably been destroyed by a meteorite.
371 controller->IOError();
372 return;
373 }
374
375 controller->ProcessPacket(bytesRead);
376
377 // Queue up another read.
378 controller->QueueRead();
379 }
380
381 void XboxController::ProcessPacket(uint32 length) {
382 if (length < 3) return;
383 DCHECK(length <= read_buffer_size_);
384 if (length > read_buffer_size_) {
385 IOError();
386 return;
387 }
388 uint8* buffer = read_buffer_.get();
389
390 if (buffer[1] != length)
391 // Length in packet doesn't match length reported by USB.
392 return;
393
394 uint8 type = buffer[0];
395 buffer += 2;
396 length -= 2;
397 switch (type) {
398 case STATUS_MESSAGE_BUTTONS: {
399 if (length != sizeof(ButtonData))
400 return;
401 ButtonData data;
402 memcpy(&data, buffer, sizeof(data));
403 Data normalized_data;
404 NormalizeButtonData(data, &normalized_data);
405 delegate_->XboxControllerGotData(this, normalized_data);
406 break;
407 }
408 case STATUS_MESSAGE_LED:
409 // The controller sends one of these messages every time the LED pattern
410 // is set, as well as once when it is plugged in.
411 if (led_pattern_ == LED_NUM_PATTERNS && buffer[0] < LED_NUM_PATTERNS)
412 led_pattern_ = (LEDPattern)buffer[0];
413 break;
414 default:
415 // Unknown packet: ignore!
416 break;
417 }
418 }
419
420 void XboxController::QueueRead() {
421 kern_return_t kr = (*interface_)->ReadPipeAsync(interface_,
422 kReadEndpoint,
423 read_buffer_.get(),
424 read_buffer_size_,
425 GotData,
426 this);
427 if (kr != KERN_SUCCESS)
428 IOError();
429 }
430
431 void XboxController::IOError() {
432 delegate_->XboxControllerError(this);
433 }
434
435 //-----------------------------------------------------------------------------
436
437 XboxDataFetcher::XboxDataFetcher(Delegate* delegate)
438 : delegate_(delegate),
439 listening_(false),
440 port_(NULL),
441 source_(NULL) {
442 }
443
444 XboxDataFetcher::~XboxDataFetcher() {
445 while (!controllers_.empty()) {
446 RemoveController(*controllers_.begin());
447 }
448 UnregisterFromNotifications();
449 }
450
451 void XboxDataFetcher::DeviceAdded(void* context, io_iterator_t iterator) {
452 DCHECK(context);
453 XboxDataFetcher* fetcher = static_cast<XboxDataFetcher*>(context);
454 io_service_t ref;
455 while ((ref = IOIteratorNext(iterator))) {
456 base::mac::ScopedIOObject<io_service_t> scoped_ref(ref);
457 XboxController* controller = new XboxController(fetcher);
458 if (controller->OpenDevice(ref)) {
459 fetcher->AddController(controller);
460 } else {
461 delete controller;
462 }
463 }
464 }
465
466 void XboxDataFetcher::DeviceRemoved(void* context, io_iterator_t iterator) {
467 DCHECK(context);
468 XboxDataFetcher* fetcher = static_cast<XboxDataFetcher*>(context);
469 io_service_t ref;
470 while ((ref = IOIteratorNext(iterator))) {
471 base::mac::ScopedIOObject<io_service_t> scoped_ref(ref);
472 base::mac::ScopedCFTypeRef<CFNumberRef> number(
473 (CFNumberRef)IORegistryEntryCreateCFProperty(
Avi (use Gerrit) 2013/04/24 03:30:12 CFCast<CFNumberRef>, probably CFCastStrict.
jeremya 2013/04/24 03:34:15 Done.
474 ref,
475 CFSTR(kUSBDevicePropertyLocationID),
476 kCFAllocatorDefault,
477 kNilOptions));
478 UInt32 location_id = 0;
479 CFNumberGetValue(number, kCFNumberSInt32Type, &location_id);
480 fetcher->RemoveControllerByLocationID(location_id);
481 }
482 }
483
484 void XboxDataFetcher::RegisterForNotifications() {
485 if (listening_)
486 return;
487 base::mac::ScopedCFTypeRef<CFNumberRef> vendor_cf(
488 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
489 &kVendorMicrosoft));
490 base::mac::ScopedCFTypeRef<CFNumberRef> product_cf(
491 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
492 &kProduct360Controller));
493 base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> matching_dict(
494 IOServiceMatching(kIOUSBDeviceClassName));
495 if (!matching_dict)
496 return;
497 CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID), vendor_cf);
498 CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID), product_cf);
499 port_ = IONotificationPortCreate(kIOMasterPortDefault);
500 if (!port_)
501 return;
502 source_ = IONotificationPortGetRunLoopSource(port_);
503 if (!source_)
504 return;
505 CFRunLoopAddSource(CFRunLoopGetMain(), source_, kCFRunLoopDefaultMode);
506 // The CFRunLoop retains the source.
507 CFRelease(source_);
508
509 listening_ = true;
510
511 IOReturn ret;
512
513 // IOServiceAddMatchingNotification() releases the dictionary when it's done.
514 // Retain it before each call to IOServiceAddMatchingNotification to keep
515 // things balanced.
516 CFRetain(matching_dict);
517 io_iterator_t device_added_iter;
518 ret = IOServiceAddMatchingNotification(port_,
519 kIOFirstMatchNotification,
520 matching_dict,
521 DeviceAdded,
522 this,
523 &device_added_iter);
524 device_added_iter_.reset(device_added_iter);
525 if (ret != kIOReturnSuccess) {
526 LOG(ERROR) << "Error listening for Xbox controller add events: " << ret;
527 return;
528 }
529 DeviceAdded(this, device_added_iter_.get());
530
531 CFRetain(matching_dict);
532 io_iterator_t device_removed_iter;
533 ret = IOServiceAddMatchingNotification(port_,
534 kIOTerminatedNotification,
535 matching_dict,
536 DeviceRemoved,
537 this,
538 &device_removed_iter);
539 device_removed_iter_.reset(device_removed_iter);
540 if (ret != kIOReturnSuccess) {
541 LOG(ERROR) << "Error listening for Xbox controller remove events: " << ret;
542 return;
543 }
544 DeviceRemoved(this, device_removed_iter_.get());
545 }
546
547 void XboxDataFetcher::UnregisterFromNotifications() {
548 if (!listening_)
549 return;
550 listening_ = false;
551 if (source_)
552 CFRunLoopSourceInvalidate(source_);
553 source_ = NULL;
554 if (port_)
555 IONotificationPortDestroy(port_);
556 port_ = NULL;
557 }
558
559 void XboxDataFetcher::AddController(XboxController* controller) {
560 controllers_.insert(controller);
561 delegate_->XboxDeviceAdd(controller);
562 }
563
564 void XboxDataFetcher::RemoveController(XboxController* controller) {
565 controllers_.erase(controller);
566 delegate_->XboxDeviceRemove(controller);
567 delete controller;
568 }
569
570 void XboxDataFetcher::RemoveControllerByLocationID(uint32 location_id) {
571 XboxController* controller = NULL;
572 for (std::set<XboxController*>::iterator i = controllers_.begin();
573 i != controllers_.end();
574 ++i) {
575 if ((*i)->location_id() == location_id) {
576 controller = *i;
577 break;
578 }
579 }
580 if (controller)
581 RemoveController(controller);
582 }
583
584 void XboxDataFetcher::XboxControllerGotData(XboxController* controller,
585 const XboxController::Data& data) {
586 delegate_->XboxValueChanged(controller, data);
587 }
588
589 void XboxDataFetcher::XboxControllerError(XboxController* controller) {
590 RemoveController(controller);
591 }
OLDNEW
« no previous file with comments | « content/browser/gamepad/xbox_data_fetcher_mac.h ('k') | content/content_browser.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698