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.
5 #include "content/browser/gamepad/xbox_data_fetcher_mac.h"
11 #include <CoreFoundation/CoreFoundation.h>
12 #include <IOKit/IOCFPlugIn.h>
13 #include <IOKit/IOKitLib.h>
14 #include <IOKit/usb/IOUSBLib.h>
15 #include <IOKit/usb/USB.h>
17 #include "base/logging.h"
18 #include "base/mac/foundation_util.h"
21 const int kVendorMicrosoft
= 0x045e;
22 const int kProductXbox360Controller
= 0x028e;
23 const int kProductXboxOneController
= 0x02d1;
25 const int kXbox360ReadEndpoint
= 1;
26 const int kXbox360ControlEndpoint
= 2;
28 const int kXboxOneReadEndpoint
= 2;
29 const int kXboxOneControlEndpoint
= 1;
32 STATUS_MESSAGE_BUTTONS
= 0,
33 STATUS_MESSAGE_LED
= 1,
35 // Apparently this message tells you if the rumble pack is disabled in the
36 // controller. If the rumble pack is disabled, vibration control messages
38 STATUS_MESSAGE_RUMBLE
= 3,
42 XBOX_ONE_STATUS_MESSAGE_BUTTONS
= 0x20,
46 CONTROL_MESSAGE_SET_RUMBLE
= 0,
47 CONTROL_MESSAGE_SET_LED
= 1,
51 struct Xbox360ButtonData
{
59 bool stick_left_click
: 1;
60 bool stick_right_click
: 1;
63 bool bumper_right
: 1;
65 bool dummy1
: 1; // Always 0.
85 struct XboxOneButtonData
{
87 bool dummy1
: 1; // Always 0.
101 bool bumper_left
: 1;
102 bool bumper_right
: 1;
103 bool stick_left_click
: 1;
104 bool stick_right_click
: 1;
107 uint16 trigger_right
;
116 COMPILE_ASSERT(sizeof(Xbox360ButtonData
) == 18, xbox_button_data_wrong_size
);
117 COMPILE_ASSERT(sizeof(XboxOneButtonData
) == 14, xbox_button_data_wrong_size
);
120 // http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).aspx#dead_zone
121 const int16 kLeftThumbDeadzone
= 7849;
122 const int16 kRightThumbDeadzone
= 8689;
123 const uint8 kXbox360TriggerDeadzone
= 30;
124 const uint16 kXboxOneTriggerMax
= 1023;
125 const uint16 kXboxOneTriggerDeadzone
= 120;
127 void NormalizeAxis(int16 x
,
135 // Determine how far the stick is pushed.
136 float real_magnitude
= std::sqrt(x_val
* x_val
+ y_val
* y_val
);
138 // Check if the controller is outside a circular dead zone.
139 if (real_magnitude
> deadzone
) {
140 // Clip the magnitude at its expected maximum value.
141 float magnitude
= std::min(32767.0f
, real_magnitude
);
143 // Adjust magnitude relative to the end of the dead zone.
144 magnitude
-= deadzone
;
146 // Normalize the magnitude with respect to its expected range giving a
147 // magnitude value of 0.0 to 1.0
148 float ratio
= (magnitude
/ (32767 - deadzone
)) / real_magnitude
;
150 // Y is negated because xbox controllers have an opposite sign from
151 // the 'standard controller' recommendations.
152 *x_out
= x_val
* ratio
;
153 *y_out
= -y_val
* ratio
;
155 // If the controller is in the deadzone zero out the magnitude.
156 *x_out
= *y_out
= 0.0f
;
160 float NormalizeTrigger(uint8 value
) {
161 return value
< kXbox360TriggerDeadzone
? 0 :
162 static_cast<float>(value
- kXbox360TriggerDeadzone
) /
163 (std::numeric_limits
<uint8
>::max() - kXbox360TriggerDeadzone
);
166 float NormalizeXboxOneTrigger(uint16 value
) {
167 return value
< kXboxOneTriggerDeadzone
? 0 :
168 static_cast<float>(value
- kXboxOneTriggerDeadzone
) /
169 (kXboxOneTriggerMax
- kXboxOneTriggerDeadzone
);
172 void NormalizeXbox360ButtonData(const Xbox360ButtonData
& data
,
173 XboxController::Data
* normalized_data
) {
174 normalized_data
->buttons
[0] = data
.a
;
175 normalized_data
->buttons
[1] = data
.b
;
176 normalized_data
->buttons
[2] = data
.x
;
177 normalized_data
->buttons
[3] = data
.y
;
178 normalized_data
->buttons
[4] = data
.bumper_left
;
179 normalized_data
->buttons
[5] = data
.bumper_right
;
180 normalized_data
->buttons
[6] = data
.back
;
181 normalized_data
->buttons
[7] = data
.start
;
182 normalized_data
->buttons
[8] = data
.stick_left_click
;
183 normalized_data
->buttons
[9] = data
.stick_right_click
;
184 normalized_data
->buttons
[10] = data
.dpad_up
;
185 normalized_data
->buttons
[11] = data
.dpad_down
;
186 normalized_data
->buttons
[12] = data
.dpad_left
;
187 normalized_data
->buttons
[13] = data
.dpad_right
;
188 normalized_data
->buttons
[14] = data
.guide
;
189 normalized_data
->triggers
[0] = NormalizeTrigger(data
.trigger_left
);
190 normalized_data
->triggers
[1] = NormalizeTrigger(data
.trigger_right
);
191 NormalizeAxis(data
.stick_left_x
,
194 &normalized_data
->axes
[0],
195 &normalized_data
->axes
[1]);
196 NormalizeAxis(data
.stick_right_x
,
199 &normalized_data
->axes
[2],
200 &normalized_data
->axes
[3]);
203 void NormalizeXboxOneButtonData(const XboxOneButtonData
& data
,
204 XboxController::Data
* normalized_data
) {
205 normalized_data
->buttons
[0] = data
.a
;
206 normalized_data
->buttons
[1] = data
.b
;
207 normalized_data
->buttons
[2] = data
.x
;
208 normalized_data
->buttons
[3] = data
.y
;
209 normalized_data
->buttons
[4] = data
.bumper_left
;
210 normalized_data
->buttons
[5] = data
.bumper_right
;
211 normalized_data
->buttons
[6] = data
.back
;
212 normalized_data
->buttons
[7] = data
.start
;
213 normalized_data
->buttons
[8] = data
.stick_left_click
;
214 normalized_data
->buttons
[9] = data
.stick_right_click
;
215 normalized_data
->buttons
[10] = data
.dpad_up
;
216 normalized_data
->buttons
[11] = data
.dpad_down
;
217 normalized_data
->buttons
[12] = data
.dpad_left
;
218 normalized_data
->buttons
[13] = data
.dpad_right
;
219 normalized_data
->buttons
[14] = data
.sync
;
220 normalized_data
->triggers
[0] = NormalizeXboxOneTrigger(data
.trigger_left
);
221 normalized_data
->triggers
[1] = NormalizeXboxOneTrigger(data
.trigger_right
);
222 NormalizeAxis(data
.stick_left_x
,
225 &normalized_data
->axes
[0],
226 &normalized_data
->axes
[1]);
227 NormalizeAxis(data
.stick_right_x
,
230 &normalized_data
->axes
[2],
231 &normalized_data
->axes
[3]);
236 XboxController::XboxController(Delegate
* delegate
)
239 device_is_open_(false),
240 interface_is_open_(false),
241 read_buffer_size_(0),
242 led_pattern_(LED_NUM_PATTERNS
),
245 controller_type_(UNKNOWN_CONTROLLER
),
247 control_endpoint_(0) {
250 XboxController::~XboxController() {
252 CFRunLoopSourceInvalidate(source_
);
253 if (interface_
&& interface_is_open_
)
254 (*interface_
)->USBInterfaceClose(interface_
);
255 if (device_
&& device_is_open_
)
256 (*device_
)->USBDeviceClose(device_
);
259 bool XboxController::OpenDevice(io_service_t service
) {
260 IOCFPlugInInterface
**plugin
;
261 SInt32 score
; // Unused, but required for IOCreatePlugInInterfaceForService.
263 IOCreatePlugInInterfaceForService(service
,
264 kIOUSBDeviceUserClientTypeID
,
265 kIOCFPlugInInterfaceID
,
268 if (kr
!= KERN_SUCCESS
)
270 base::mac::ScopedIOPluginInterface
<IOCFPlugInInterface
> plugin_ref(plugin
);
273 (*plugin
)->QueryInterface(plugin
,
274 CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID320
),
276 if (!SUCCEEDED(res
) || !device_
)
280 kr
= (*device_
)->GetDeviceVendor(device_
, &vendor_id
);
281 if (kr
!= KERN_SUCCESS
|| vendor_id
!= kVendorMicrosoft
)
285 kr
= (*device_
)->GetDeviceProduct(device_
, &product_id
);
286 if (kr
!= KERN_SUCCESS
)
289 IOUSBFindInterfaceRequest request
;
290 switch (product_id
) {
291 case kProductXbox360Controller
:
292 controller_type_
= XBOX_360_CONTROLLER
;
293 read_endpoint_
= kXbox360ReadEndpoint
;
294 control_endpoint_
= kXbox360ControlEndpoint
;
295 request
.bInterfaceClass
= 255;
296 request
.bInterfaceSubClass
= 93;
297 request
.bInterfaceProtocol
= 1;
298 request
.bAlternateSetting
= kIOUSBFindInterfaceDontCare
;
300 case kProductXboxOneController
:
301 controller_type_
= XBOX_ONE_CONTROLLER
;
302 read_endpoint_
= kXboxOneReadEndpoint
;
303 control_endpoint_
= kXboxOneControlEndpoint
;
304 request
.bInterfaceClass
= 255;
305 request
.bInterfaceSubClass
= 71;
306 request
.bInterfaceProtocol
= 208;
307 request
.bAlternateSetting
= kIOUSBFindInterfaceDontCare
;
313 // Open the device and configure it.
314 kr
= (*device_
)->USBDeviceOpen(device_
);
315 if (kr
!= KERN_SUCCESS
)
317 device_is_open_
= true;
319 // Xbox controllers have one configuration option which has configuration
320 // value 1. Try to set it and fail if it couldn't be configured.
321 IOUSBConfigurationDescriptorPtr config_desc
;
322 kr
= (*device_
)->GetConfigurationDescriptorPtr(device_
, 0, &config_desc
);
323 if (kr
!= KERN_SUCCESS
)
325 kr
= (*device_
)->SetConfiguration(device_
, config_desc
->bConfigurationValue
);
326 if (kr
!= KERN_SUCCESS
)
329 // The device has 4 interfaces. They are as follows:
331 // - Endpoint 1 (in) : Controller events, including button presses.
332 // - Endpoint 2 (out): Rumble pack and LED control
333 // Protocol 2 has a single endpoint to read from a connected ChatPad device.
334 // Protocol 3 is used by a connected headset device.
335 // The device also has an interface on subclass 253, protocol 10 with no
336 // endpoints. It is unused.
338 // We don't currently support the ChatPad or headset, so protocol 1 is the
339 // only protocol we care about.
341 // For more detail, see
342 // https://github.com/Grumbel/xboxdrv/blob/master/PROTOCOL
344 kr
= (*device_
)->CreateInterfaceIterator(device_
, &request
, &iter
);
345 if (kr
!= KERN_SUCCESS
)
347 base::mac::ScopedIOObject
<io_iterator_t
> iter_ref(iter
);
349 // There should be exactly one USB interface which matches the requested
351 io_service_t usb_interface
= IOIteratorNext(iter
);
355 // We need to make an InterfaceInterface to communicate with the device
356 // endpoint. This is the same process as earlier: first make a
357 // PluginInterface from the io_service then make the InterfaceInterface from
359 IOCFPlugInInterface
**plugin_interface
;
360 kr
= IOCreatePlugInInterfaceForService(usb_interface
,
361 kIOUSBInterfaceUserClientTypeID
,
362 kIOCFPlugInInterfaceID
,
365 if (kr
!= KERN_SUCCESS
|| !plugin_interface
)
367 base::mac::ScopedIOPluginInterface
<IOCFPlugInInterface
> interface_ref(
370 // Release the USB interface, and any subsequent interfaces returned by the
371 // iterator. (There shouldn't be any, but in case a future device does
372 // contain more interfaces, this will serve to avoid memory leaks.)
374 IOObjectRelease(usb_interface
);
375 } while ((usb_interface
= IOIteratorNext(iter
)));
377 // Actually create the interface.
378 res
= (*plugin_interface
)->QueryInterface(
380 CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300
),
381 (LPVOID
*)&interface_
);
383 if (!SUCCEEDED(res
) || !interface_
)
386 // Actually open the interface.
387 kr
= (*interface_
)->USBInterfaceOpen(interface_
);
388 if (kr
!= KERN_SUCCESS
)
390 interface_is_open_
= true;
392 CFRunLoopSourceRef source_ref
;
393 kr
= (*interface_
)->CreateInterfaceAsyncEventSource(interface_
, &source_ref
);
394 if (kr
!= KERN_SUCCESS
|| !source_ref
)
396 source_
.reset(source_ref
);
397 CFRunLoopAddSource(CFRunLoopGetCurrent(), source_
, kCFRunLoopDefaultMode
);
399 // The interface should have two pipes. Pipe 1 with direction kUSBIn and pipe
400 // 2 with direction kUSBOut. Both pipes should have type kUSBInterrupt.
402 kr
= (*interface_
)->GetNumEndpoints(interface_
, &num_endpoints
);
403 if (kr
!= KERN_SUCCESS
|| num_endpoints
< 2)
406 for (int i
= 1; i
<= 2; i
++) {
410 uint16 max_packet_size
;
413 kr
= (*interface_
)->GetPipeProperties(interface_
,
420 if (kr
!= KERN_SUCCESS
|| transfer_type
!= kUSBInterrupt
) {
423 if (i
== read_endpoint_
) {
424 if (direction
!= kUSBIn
)
426 read_buffer_
.reset(new uint8
[max_packet_size
]);
427 read_buffer_size_
= max_packet_size
;
429 } else if (i
== control_endpoint_
) {
430 if (direction
!= kUSBOut
)
432 if (controller_type_
== XBOX_ONE_CONTROLLER
)
437 // The location ID is unique per controller, and can be used to track
438 // controllers through reconnections (though if a controller is detached from
439 // one USB hub and attached to another, the location ID will change).
440 kr
= (*device_
)->GetLocationID(device_
, &location_id_
);
441 if (kr
!= KERN_SUCCESS
)
447 void XboxController::SetLEDPattern(LEDPattern pattern
) {
448 led_pattern_
= pattern
;
449 const UInt8 length
= 3;
451 // This buffer will be released in WriteComplete when WritePipeAsync
453 UInt8
* buffer
= new UInt8
[length
];
454 buffer
[0] = static_cast<UInt8
>(CONTROL_MESSAGE_SET_LED
);
456 buffer
[2] = static_cast<UInt8
>(pattern
);
457 kern_return_t kr
= (*interface_
)->WritePipeAsync(interface_
,
463 if (kr
!= KERN_SUCCESS
) {
470 int XboxController::GetVendorId() const {
471 return kVendorMicrosoft
;
474 int XboxController::GetProductId() const {
475 if (controller_type_
== XBOX_360_CONTROLLER
)
476 return kProductXbox360Controller
;
478 return kProductXboxOneController
;
481 XboxController::ControllerType
XboxController::GetControllerType() const {
482 return controller_type_
;
485 void XboxController::WriteComplete(void* context
, IOReturn result
, void* arg0
) {
486 UInt8
* buffer
= static_cast<UInt8
*>(context
);
489 // Ignoring any errors sending data, because they will usually only occur
490 // when the device is disconnected, in which case it really doesn't matter if
491 // the data got to the controller or not.
492 if (result
!= kIOReturnSuccess
)
496 void XboxController::GotData(void* context
, IOReturn result
, void* arg0
) {
497 size_t bytes_read
= reinterpret_cast<size_t>(arg0
);
498 XboxController
* controller
= static_cast<XboxController
*>(context
);
500 if (result
!= kIOReturnSuccess
) {
501 // This will happen if the device was disconnected. The gamepad has
502 // probably been destroyed by a meteorite.
503 controller
->IOError();
507 if (controller
->GetControllerType() == XBOX_360_CONTROLLER
)
508 controller
->ProcessXbox360Packet(bytes_read
);
510 controller
->ProcessXboxOnePacket(bytes_read
);
512 // Queue up another read.
513 controller
->QueueRead();
516 void XboxController::ProcessXbox360Packet(size_t length
) {
519 DCHECK(length
<= read_buffer_size_
);
520 if (length
> read_buffer_size_
) {
524 uint8
* buffer
= read_buffer_
.get();
526 if (buffer
[1] != length
)
527 // Length in packet doesn't match length reported by USB.
530 uint8 type
= buffer
[0];
534 case STATUS_MESSAGE_BUTTONS
: {
535 if (length
!= sizeof(Xbox360ButtonData
))
537 Xbox360ButtonData
* data
= reinterpret_cast<Xbox360ButtonData
*>(buffer
);
538 Data normalized_data
;
539 NormalizeXbox360ButtonData(*data
, &normalized_data
);
540 delegate_
->XboxControllerGotData(this, normalized_data
);
543 case STATUS_MESSAGE_LED
:
546 // The controller sends one of these messages every time the LED pattern
547 // is set, as well as once when it is plugged in.
548 if (led_pattern_
== LED_NUM_PATTERNS
&& buffer
[0] < LED_NUM_PATTERNS
)
549 led_pattern_
= static_cast<LEDPattern
>(buffer
[0]);
552 // Unknown packet: ignore!
557 void XboxController::ProcessXboxOnePacket(size_t length
) {
560 DCHECK(length
<= read_buffer_size_
);
561 if (length
> read_buffer_size_
) {
565 uint8
* buffer
= read_buffer_
.get();
567 uint8 type
= buffer
[0];
571 case XBOX_ONE_STATUS_MESSAGE_BUTTONS
: {
572 if (length
!= sizeof(XboxOneButtonData
))
574 XboxOneButtonData
* data
= reinterpret_cast<XboxOneButtonData
*>(buffer
);
575 Data normalized_data
;
576 NormalizeXboxOneButtonData(*data
, &normalized_data
);
577 delegate_
->XboxControllerGotData(this, normalized_data
);
581 // Unknown packet: ignore!
586 void XboxController::QueueRead() {
587 kern_return_t kr
= (*interface_
)->ReadPipeAsync(interface_
,
593 if (kr
!= KERN_SUCCESS
)
597 void XboxController::IOError() {
598 delegate_
->XboxControllerError(this);
601 void XboxController::WriteXboxOneInit() {
602 const UInt8 length
= 2;
604 // This buffer will be released in WriteComplete when WritePipeAsync
606 UInt8
* buffer
= new UInt8
[length
];
609 kern_return_t kr
= (*interface_
)->WritePipeAsync(interface_
,
615 if (kr
!= KERN_SUCCESS
) {
622 //-----------------------------------------------------------------------------
624 XboxDataFetcher::XboxDataFetcher(Delegate
* delegate
)
625 : delegate_(delegate
),
631 XboxDataFetcher::~XboxDataFetcher() {
632 while (!controllers_
.empty()) {
633 RemoveController(*controllers_
.begin());
635 UnregisterFromNotifications();
638 void XboxDataFetcher::DeviceAdded(void* context
, io_iterator_t iterator
) {
640 XboxDataFetcher
* fetcher
= static_cast<XboxDataFetcher
*>(context
);
642 while ((ref
= IOIteratorNext(iterator
))) {
643 base::mac::ScopedIOObject
<io_service_t
> scoped_ref(ref
);
644 XboxController
* controller
= new XboxController(fetcher
);
645 if (controller
->OpenDevice(ref
)) {
646 fetcher
->AddController(controller
);
653 void XboxDataFetcher::DeviceRemoved(void* context
, io_iterator_t iterator
) {
655 XboxDataFetcher
* fetcher
= static_cast<XboxDataFetcher
*>(context
);
657 while ((ref
= IOIteratorNext(iterator
))) {
658 base::mac::ScopedIOObject
<io_service_t
> scoped_ref(ref
);
659 base::ScopedCFTypeRef
<CFNumberRef
> number(
660 base::mac::CFCastStrict
<CFNumberRef
>(
661 IORegistryEntryCreateCFProperty(ref
,
662 CFSTR(kUSBDevicePropertyLocationID
),
665 UInt32 location_id
= 0;
666 CFNumberGetValue(number
, kCFNumberSInt32Type
, &location_id
);
667 fetcher
->RemoveControllerByLocationID(location_id
);
671 bool XboxDataFetcher::RegisterForNotifications() {
674 port_
= IONotificationPortCreate(kIOMasterPortDefault
);
677 source_
= IONotificationPortGetRunLoopSource(port_
);
680 CFRunLoopAddSource(CFRunLoopGetCurrent(), source_
, kCFRunLoopDefaultMode
);
684 if (!RegisterForDeviceNotifications(
685 kVendorMicrosoft
, kProductXboxOneController
,
686 &xbox_one_device_added_iter_
,
687 &xbox_one_device_removed_iter_
))
690 if (!RegisterForDeviceNotifications(
691 kVendorMicrosoft
, kProductXbox360Controller
,
692 &xbox_360_device_added_iter_
,
693 &xbox_360_device_removed_iter_
))
699 bool XboxDataFetcher::RegisterForDeviceNotifications(
702 base::mac::ScopedIOObject
<io_iterator_t
>* added_iter
,
703 base::mac::ScopedIOObject
<io_iterator_t
>* removed_iter
) {
704 base::ScopedCFTypeRef
<CFNumberRef
> vendor_cf(CFNumberCreate(
705 kCFAllocatorDefault
, kCFNumberSInt32Type
, &vendor_id
));
706 base::ScopedCFTypeRef
<CFNumberRef
> product_cf(CFNumberCreate(
707 kCFAllocatorDefault
, kCFNumberSInt32Type
, &product_id
));
708 base::ScopedCFTypeRef
<CFMutableDictionaryRef
> matching_dict(
709 IOServiceMatching(kIOUSBDeviceClassName
));
712 CFDictionarySetValue(matching_dict
, CFSTR(kUSBVendorID
), vendor_cf
);
713 CFDictionarySetValue(matching_dict
, CFSTR(kUSBProductID
), product_cf
);
715 // IOServiceAddMatchingNotification() releases the dictionary when it's done.
716 // Retain it before each call to IOServiceAddMatchingNotification to keep
718 CFRetain(matching_dict
);
719 io_iterator_t device_added_iter
;
721 ret
= IOServiceAddMatchingNotification(port_
,
722 kIOFirstMatchNotification
,
727 added_iter
->reset(device_added_iter
);
728 if (ret
!= kIOReturnSuccess
) {
729 LOG(ERROR
) << "Error listening for Xbox controller add events: " << ret
;
732 DeviceAdded(this, added_iter
->get());
734 CFRetain(matching_dict
);
735 io_iterator_t device_removed_iter
;
736 ret
= IOServiceAddMatchingNotification(port_
,
737 kIOTerminatedNotification
,
741 &device_removed_iter
);
742 removed_iter
->reset(device_removed_iter
);
743 if (ret
!= kIOReturnSuccess
) {
744 LOG(ERROR
) << "Error listening for Xbox controller remove events: " << ret
;
747 DeviceRemoved(this, removed_iter
->get());
751 void XboxDataFetcher::UnregisterFromNotifications() {
756 CFRunLoopSourceInvalidate(source_
);
758 IONotificationPortDestroy(port_
);
762 XboxController
* XboxDataFetcher::ControllerForLocation(UInt32 location_id
) {
763 for (std::set
<XboxController
*>::iterator i
= controllers_
.begin();
764 i
!= controllers_
.end();
766 if ((*i
)->location_id() == location_id
)
772 void XboxDataFetcher::AddController(XboxController
* controller
) {
773 DCHECK(!ControllerForLocation(controller
->location_id()))
774 << "Controller with location ID " << controller
->location_id()
775 << " already exists in the set of controllers.";
776 controllers_
.insert(controller
);
777 delegate_
->XboxDeviceAdd(controller
);
780 void XboxDataFetcher::RemoveController(XboxController
* controller
) {
781 delegate_
->XboxDeviceRemove(controller
);
782 controllers_
.erase(controller
);
786 void XboxDataFetcher::RemoveControllerByLocationID(uint32 location_id
) {
787 XboxController
* controller
= NULL
;
788 for (std::set
<XboxController
*>::iterator i
= controllers_
.begin();
789 i
!= controllers_
.end();
791 if ((*i
)->location_id() == location_id
) {
797 RemoveController(controller
);
800 void XboxDataFetcher::XboxControllerGotData(XboxController
* controller
,
801 const XboxController::Data
& data
) {
802 delegate_
->XboxValueChanged(controller
, data
);
805 void XboxDataFetcher::XboxControllerError(XboxController
* controller
) {
806 RemoveController(controller
);