From 679147eead574d186ebf3069647b4c23e8ccace6 Mon Sep 17 00:00:00 2001 From: Zeno Albisser Date: Thu, 15 Aug 2013 21:46:11 +0200 Subject: Initial import. --- chromium/content/browser/gamepad/OWNERS | 1 + .../content/browser/gamepad/gamepad_data_fetcher.h | 26 + .../gamepad/gamepad_platform_data_fetcher.cc | 19 + .../gamepad/gamepad_platform_data_fetcher.h | 55 ++ .../gamepad/gamepad_platform_data_fetcher_linux.cc | 257 +++++++++ .../gamepad/gamepad_platform_data_fetcher_linux.h | 56 ++ .../gamepad/gamepad_platform_data_fetcher_mac.h | 106 ++++ .../gamepad/gamepad_platform_data_fetcher_mac.mm | 441 +++++++++++++++ .../gamepad/gamepad_platform_data_fetcher_win.cc | 498 ++++++++++++++++ .../gamepad/gamepad_platform_data_fetcher_win.h | 100 ++++ .../content/browser/gamepad/gamepad_provider.cc | 220 ++++++++ .../content/browser/gamepad/gamepad_provider.h | 130 +++++ .../browser/gamepad/gamepad_provider_unittest.cc | 158 ++++++ .../content/browser/gamepad/gamepad_service.cc | 69 +++ chromium/content/browser/gamepad/gamepad_service.h | 77 +++ .../browser/gamepad/gamepad_standard_mappings.h | 61 ++ .../gamepad/gamepad_standard_mappings_linux.cc | 164 ++++++ .../gamepad/gamepad_standard_mappings_mac.mm | 219 ++++++++ .../gamepad/gamepad_standard_mappings_win.cc | 124 ++++ .../browser/gamepad/gamepad_test_helpers.cc | 55 ++ .../content/browser/gamepad/gamepad_test_helpers.h | 84 +++ .../browser/gamepad/xbox_data_fetcher_mac.cc | 625 +++++++++++++++++++++ .../browser/gamepad/xbox_data_fetcher_mac.h | 167 ++++++ 23 files changed, 3712 insertions(+) create mode 100644 chromium/content/browser/gamepad/OWNERS create mode 100644 chromium/content/browser/gamepad/gamepad_data_fetcher.h create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc create mode 100644 chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h create mode 100644 chromium/content/browser/gamepad/gamepad_provider.cc create mode 100644 chromium/content/browser/gamepad/gamepad_provider.h create mode 100644 chromium/content/browser/gamepad/gamepad_provider_unittest.cc create mode 100644 chromium/content/browser/gamepad/gamepad_service.cc create mode 100644 chromium/content/browser/gamepad/gamepad_service.h create mode 100644 chromium/content/browser/gamepad/gamepad_standard_mappings.h create mode 100644 chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc create mode 100644 chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm create mode 100644 chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc create mode 100644 chromium/content/browser/gamepad/gamepad_test_helpers.cc create mode 100644 chromium/content/browser/gamepad/gamepad_test_helpers.h create mode 100644 chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc create mode 100644 chromium/content/browser/gamepad/xbox_data_fetcher_mac.h (limited to 'chromium/content/browser/gamepad') diff --git a/chromium/content/browser/gamepad/OWNERS b/chromium/content/browser/gamepad/OWNERS new file mode 100644 index 00000000000..3f809e82b19 --- /dev/null +++ b/chromium/content/browser/gamepad/OWNERS @@ -0,0 +1 @@ +scottmg@chromium.org diff --git a/chromium/content/browser/gamepad/gamepad_data_fetcher.h b/chromium/content/browser/gamepad/gamepad_data_fetcher.h new file mode 100644 index 00000000000..e5e009dc42b --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_data_fetcher.h @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_DATA_FETCHER_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_DATA_FETCHER_H_ + +namespace WebKit { +class WebGamepads; +} + +namespace content { + +// Abstract interface for imlementing platform- (and test-) specific behaviro +// for getting the gamepad data. +class GamepadDataFetcher { + public: + virtual ~GamepadDataFetcher() {} + virtual void GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) = 0; + virtual void PauseHint(bool paused) {} +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_DATA_FETCHER_H_ diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc new file mode 100644 index 00000000000..b4499d6fd41 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_platform_data_fetcher.h" + +#include "third_party/WebKit/public/platform/WebGamepads.h" + +namespace content { + +GamepadDataFetcherEmpty::GamepadDataFetcherEmpty() { +} + +void GamepadDataFetcherEmpty::GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) { + pads->length = 0; +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h new file mode 100644 index 00000000000..f073a538d6f --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Define the default data fetcher that GamepadProvider will use if none is +// supplied. (GamepadPlatformDataFetcher). + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" + +#if defined(OS_WIN) +#include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h" +#elif defined(OS_MACOSX) +#include "content/browser/gamepad/gamepad_platform_data_fetcher_mac.h" +#elif defined(OS_LINUX) +#include "content/browser/gamepad/gamepad_platform_data_fetcher_linux.h" +#endif + +namespace content { + +#if defined(OS_WIN) + +typedef GamepadPlatformDataFetcherWin GamepadPlatformDataFetcher; + +#elif defined(OS_MACOSX) + +typedef GamepadPlatformDataFetcherMac GamepadPlatformDataFetcher; + +#elif defined(OS_LINUX) + +typedef GamepadPlatformDataFetcherLinux GamepadPlatformDataFetcher; + +#else + +class GamepadDataFetcherEmpty : public GamepadDataFetcher { + public: + GamepadDataFetcherEmpty(); + + virtual void GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(GamepadDataFetcherEmpty); +}; +typedef GamepadDataFetcherEmpty GamepadPlatformDataFetcher; + +#endif + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_ diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc new file mode 100644 index 00000000000..c82997e22a1 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_platform_data_fetcher_linux.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "base/debug/trace_event.h" +#include "base/message_loop/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/udev_linux.h" + +namespace { + +const char kInputSubsystem[] = "input"; +const char kUsbSubsystem[] = "usb"; +const char kUsbDeviceType[] = "usb_device"; +const float kMaxLinuxAxisValue = 32767.0; + +void CloseFileDescriptorIfValid(int fd) { + if (fd >= 0) + close(fd); +} + +bool IsGamepad(udev_device* dev, int* index, std::string* path) { + if (!udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK")) + return false; + + const char* node_path = udev_device_get_devnode(dev); + if (!node_path) + return false; + + static const char kJoystickRoot[] = "/dev/input/js"; + bool is_gamepad = StartsWithASCII(node_path, kJoystickRoot, true); + if (!is_gamepad) + return false; + + int tmp_idx = -1; + const int base_len = sizeof(kJoystickRoot) - 1; + base::StringPiece str(&node_path[base_len], strlen(node_path) - base_len); + if (!base::StringToInt(str, &tmp_idx)) + return false; + if (tmp_idx < 0 || + tmp_idx >= static_cast(WebKit::WebGamepads::itemsLengthCap)) { + return false; + } + *index = tmp_idx; + *path = node_path; + return true; +} + +} // namespace + +namespace content { + +using WebKit::WebGamepad; +using WebKit::WebGamepads; + +GamepadPlatformDataFetcherLinux::GamepadPlatformDataFetcherLinux() { + for (size_t i = 0; i < arraysize(device_fds_); ++i) + device_fds_[i] = -1; + memset(mappers_, 0, sizeof(mappers_)); + + std::vector filters; + filters.push_back(UdevLinux::UdevMonitorFilter(kInputSubsystem, NULL)); + udev_.reset( + new UdevLinux(filters, + base::Bind(&GamepadPlatformDataFetcherLinux::RefreshDevice, + base::Unretained(this)))); + + EnumerateDevices(); +} + +GamepadPlatformDataFetcherLinux::~GamepadPlatformDataFetcherLinux() { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) + CloseFileDescriptorIfValid(device_fds_[i]); +} + +void GamepadPlatformDataFetcherLinux::GetGamepadData(WebGamepads* pads, bool) { + TRACE_EVENT0("GAMEPAD", "GetGamepadData"); + + data_.length = WebGamepads::itemsLengthCap; + + // Update our internal state. + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (device_fds_[i] >= 0) { + ReadDeviceData(i); + } + } + + // Copy to the current state to the output buffer, using the mapping + // function, if there is one available. + pads->length = data_.length; + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (mappers_[i]) + mappers_[i](data_.items[i], &pads->items[i]); + else + pads->items[i] = data_.items[i]; + } +} + +// Used during enumeration, and monitor notifications. +void GamepadPlatformDataFetcherLinux::RefreshDevice(udev_device* dev) { + int index; + std::string node_path; + if (IsGamepad(dev, &index, &node_path)) { + int& device_fd = device_fds_[index]; + WebGamepad& pad = data_.items[index]; + GamepadStandardMappingFunction& mapper = mappers_[index]; + + CloseFileDescriptorIfValid(device_fd); + + // The device pointed to by dev contains information about the logical + // joystick device. In order to get the information about the physical + // hardware, get the parent device that is also in the "input" subsystem. + // This function should just walk up the tree one level. + dev = udev_device_get_parent_with_subsystem_devtype( + dev, + kInputSubsystem, + NULL); + if (!dev) { + // Unable to get device information, don't use this device. + device_fd = -1; + pad.connected = false; + return; + } + + device_fd = HANDLE_EINTR(open(node_path.c_str(), O_RDONLY | O_NONBLOCK)); + if (device_fd < 0) { + // Unable to open device, don't use. + pad.connected = false; + return; + } + + const char* vendor_id = udev_device_get_sysattr_value(dev, "id/vendor"); + const char* product_id = udev_device_get_sysattr_value(dev, "id/product"); + mapper = GetGamepadStandardMappingFunction(vendor_id, product_id); + + // Driver returns utf-8 strings here, so combine in utf-8 first and + // convert to WebUChar later once we've picked an id string. + const char* name = udev_device_get_sysattr_value(dev, "name"); + std::string name_string = base::StringPrintf("%s", name); + + // In many cases the information the input subsystem contains isn't + // as good as the information that the device bus has, walk up further + // to the subsystem/device type "usb"/"usb_device" and if this device + // has the same vendor/product id, prefer the description from that. + struct udev_device *usb_dev = udev_device_get_parent_with_subsystem_devtype( + dev, + kUsbSubsystem, + kUsbDeviceType); + if (usb_dev) { + const char* usb_vendor_id = + udev_device_get_sysattr_value(usb_dev, "idVendor"); + const char* usb_product_id = + udev_device_get_sysattr_value(usb_dev, "idProduct"); + + if (strcmp(vendor_id, usb_vendor_id) == 0 && + strcmp(product_id, usb_product_id) == 0) { + const char* manufacturer = + udev_device_get_sysattr_value(usb_dev, "manufacturer"); + const char* product = udev_device_get_sysattr_value(usb_dev, "product"); + + // Replace the previous name string with one containing the better + // information, again driver returns utf-8 strings here so combine + // in utf-8 for conversion to WebUChar below. + name_string = base::StringPrintf("%s %s", manufacturer, product); + } + } + + // Append the vendor and product information then convert the utf-8 + // id string to WebUChar. + std::string id = name_string + base::StringPrintf( + " (%sVendor: %s Product: %s)", + mapper ? "STANDARD GAMEPAD " : "", + vendor_id, + product_id); + TruncateUTF8ToByteSize(id, WebGamepad::idLengthCap - 1, &id); + string16 tmp16 = UTF8ToUTF16(id); + memset(pad.id, 0, sizeof(pad.id)); + tmp16.copy(pad.id, arraysize(pad.id) - 1); + + pad.connected = true; + } +} + +void GamepadPlatformDataFetcherLinux::EnumerateDevices() { + udev_enumerate* enumerate = udev_enumerate_new(udev_->udev_handle()); + if (!enumerate) + return; + int ret = udev_enumerate_add_match_subsystem(enumerate, kInputSubsystem); + if (ret != 0) + return; + ret = udev_enumerate_scan_devices(enumerate); + if (ret != 0) + return; + + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate); + for (udev_list_entry* dev_list_entry = devices; + dev_list_entry != NULL; + dev_list_entry = udev_list_entry_get_next(dev_list_entry)) { + // Get the filename of the /sys entry for the device and create a + // udev_device object (dev) representing it + const char* path = udev_list_entry_get_name(dev_list_entry); + udev_device* dev = udev_device_new_from_syspath(udev_->udev_handle(), path); + if (!dev) + continue; + RefreshDevice(dev); + udev_device_unref(dev); + } + // Free the enumerator object + udev_enumerate_unref(enumerate); +} + +void GamepadPlatformDataFetcherLinux::ReadDeviceData(size_t index) { + // Linker does not like CHECK_LT(index, WebGamepads::itemsLengthCap). =/ + if (index >= WebGamepads::itemsLengthCap) { + CHECK(false); + return; + } + + const int& fd = device_fds_[index]; + WebGamepad& pad = data_.items[index]; + DCHECK_GE(fd, 0); + + js_event event; + while (HANDLE_EINTR(read(fd, &event, sizeof(struct js_event))) > 0) { + size_t item = event.number; + if (event.type & JS_EVENT_AXIS) { + if (item >= WebGamepad::axesLengthCap) + continue; + pad.axes[item] = event.value / kMaxLinuxAxisValue; + if (item >= pad.axesLength) + pad.axesLength = item + 1; + } else if (event.type & JS_EVENT_BUTTON) { + if (item >= WebGamepad::buttonsLengthCap) + continue; + pad.buttons[item] = event.value ? 1.0 : 0.0; + if (item >= pad.buttonsLength) + pad.buttonsLength = item + 1; + } + pad.timestamp = event.time; + } +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h new file mode 100644 index 00000000000..916eda311f4 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_linux.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_LINUX_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_LINUX_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_standard_mappings.h" +#include "third_party/WebKit/public/platform/WebGamepads.h" + +extern "C" { +struct udev_device; +} + +namespace content { + +class UdevLinux; + +class GamepadPlatformDataFetcherLinux : public GamepadDataFetcher { + public: + GamepadPlatformDataFetcherLinux(); + virtual ~GamepadPlatformDataFetcherLinux(); + + // GamepadDataFetcher implementation. + virtual void GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) OVERRIDE; + + private: + void RefreshDevice(udev_device* dev); + void EnumerateDevices(); + void ReadDeviceData(size_t index); + + // File descriptors for the /dev/input/js* devices. -1 if not in use. + int device_fds_[WebKit::WebGamepads::itemsLengthCap]; + + // Functions to map from device data to standard layout, if available. May + // be null if no mapping is available. + GamepadStandardMappingFunction mappers_[WebKit::WebGamepads::itemsLengthCap]; + + // Data that's returned to the consumer. + WebKit::WebGamepads data_; + + scoped_ptr udev_; + + DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherLinux); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_LINUX_H_ diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h new file mode 100644 index 00000000000..3b22b69350c --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.h @@ -0,0 +1,106 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_MAC_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_MAC_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_standard_mappings.h" +#include "content/browser/gamepad/xbox_data_fetcher_mac.h" +#include "content/common/gamepad_hardware_buffer.h" +#include "third_party/WebKit/public/platform/WebGamepads.h" + +#include +#include + +#if defined(__OBJC__) +@class NSArray; +#else +class NSArray; +#endif + +namespace content { + +class GamepadPlatformDataFetcherMac : public GamepadDataFetcher, + public XboxDataFetcher::Delegate { + public: + GamepadPlatformDataFetcherMac(); + virtual ~GamepadPlatformDataFetcherMac(); + virtual void GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) OVERRIDE; + virtual void PauseHint(bool paused) OVERRIDE; + + private: + bool enabled_; + base::ScopedCFTypeRef hid_manager_ref_; + + static GamepadPlatformDataFetcherMac* InstanceFromContext(void* context); + static void DeviceAddCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref); + static void DeviceRemoveCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref); + static void ValueChangedCallback(void* context, + IOReturn result, + void* sender, + IOHIDValueRef ref); + + size_t GetEmptySlot(); + size_t GetSlotForDevice(IOHIDDeviceRef device); + size_t GetSlotForXboxDevice(XboxController* device); + + void DeviceAdd(IOHIDDeviceRef device); + void AddButtonsAndAxes(NSArray* elements, size_t slot); + void DeviceRemove(IOHIDDeviceRef device); + void ValueChanged(IOHIDValueRef value); + + virtual void XboxDeviceAdd(XboxController* device) OVERRIDE; + virtual void XboxDeviceRemove(XboxController* device) OVERRIDE; + virtual void XboxValueChanged(XboxController* device, + const XboxController::Data& data) OVERRIDE; + + void RegisterForNotifications(); + void UnregisterFromNotifications(); + + scoped_ptr xbox_fetcher_; + + WebKit::WebGamepads data_; + + // Side-band data that's not passed to the consumer, but we need to maintain + // to update data_. + struct AssociatedData { + bool is_xbox; + union { + struct { + IOHIDDeviceRef device_ref; + IOHIDElementRef button_elements[WebKit::WebGamepad::buttonsLengthCap]; + IOHIDElementRef axis_elements[WebKit::WebGamepad::buttonsLengthCap]; + CFIndex axis_minimums[WebKit::WebGamepad::axesLengthCap]; + CFIndex axis_maximums[WebKit::WebGamepad::axesLengthCap]; + // Function to map from device data to standard layout, if available. + // May be null if no mapping is available. + GamepadStandardMappingFunction mapper; + } hid; + struct { + XboxController* device; + UInt32 location_id; + } xbox; + }; + }; + AssociatedData associated_[WebKit::WebGamepads::itemsLengthCap]; + + DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherMac); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_MAC_H_ diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm new file mode 100644 index 00000000000..51524d7f523 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_mac.mm @@ -0,0 +1,441 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_platform_data_fetcher_mac.h" + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_nsobject.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" + +#import +#include + +using WebKit::WebGamepad; +using WebKit::WebGamepads; + +namespace content { + +namespace { + +NSDictionary* DeviceMatching(uint32_t usage_page, uint32_t usage) { + return [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:usage_page], + base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsagePageKey)), + [NSNumber numberWithUnsignedInt:usage], + base::mac::CFToNSCast(CFSTR(kIOHIDDeviceUsageKey)), + nil]; +} + +float NormalizeAxis(CFIndex value, CFIndex min, CFIndex max) { + return (2.f * (value - min) / static_cast(max - min)) - 1.f; +} + +// http://www.usb.org/developers/hidpage +const uint32_t kGenericDesktopUsagePage = 0x01; +const uint32_t kButtonUsagePage = 0x09; +const uint32_t kJoystickUsageNumber = 0x04; +const uint32_t kGameUsageNumber = 0x05; +const uint32_t kMultiAxisUsageNumber = 0x08; +const uint32_t kAxisMinimumUsageNumber = 0x30; +const uint32_t kAxisMaximumUsageNumber = 0x35; + +} // namespace + +GamepadPlatformDataFetcherMac::GamepadPlatformDataFetcherMac() + : enabled_(true) { + memset(associated_, 0, sizeof(associated_)); + + xbox_fetcher_.reset(new XboxDataFetcher(this)); + if (!xbox_fetcher_->RegisterForNotifications()) + xbox_fetcher_.reset(); + + hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDOptionsTypeNone)); + if (CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) { + enabled_ = false; + return; + } + + base::scoped_nsobject criteria([[NSArray alloc] initWithObjects: + DeviceMatching(kGenericDesktopUsagePage, kJoystickUsageNumber), + DeviceMatching(kGenericDesktopUsagePage, kGameUsageNumber), + DeviceMatching(kGenericDesktopUsagePage, kMultiAxisUsageNumber), + nil]); + IOHIDManagerSetDeviceMatchingMultiple( + hid_manager_ref_, + base::mac::NSToCFCast(criteria)); + + RegisterForNotifications(); +} + +void GamepadPlatformDataFetcherMac::RegisterForNotifications() { + // Register for plug/unplug notifications. + IOHIDManagerRegisterDeviceMatchingCallback( + hid_manager_ref_, + &DeviceAddCallback, + this); + IOHIDManagerRegisterDeviceRemovalCallback( + hid_manager_ref_, + DeviceRemoveCallback, + this); + + // Register for value change notifications. + IOHIDManagerRegisterInputValueCallback( + hid_manager_ref_, + ValueChangedCallback, + this); + + IOHIDManagerScheduleWithRunLoop( + hid_manager_ref_, + CFRunLoopGetMain(), + kCFRunLoopDefaultMode); + + enabled_ = IOHIDManagerOpen(hid_manager_ref_, + kIOHIDOptionsTypeNone) == kIOReturnSuccess; + + if (xbox_fetcher_) + xbox_fetcher_->RegisterForNotifications(); +} + +void GamepadPlatformDataFetcherMac::UnregisterFromNotifications() { + IOHIDManagerUnscheduleFromRunLoop( + hid_manager_ref_, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone); + if (xbox_fetcher_) + xbox_fetcher_->UnregisterFromNotifications(); +} + +void GamepadPlatformDataFetcherMac::PauseHint(bool pause) { + if (pause) + UnregisterFromNotifications(); + else + RegisterForNotifications(); +} + +GamepadPlatformDataFetcherMac::~GamepadPlatformDataFetcherMac() { + UnregisterFromNotifications(); +} + +GamepadPlatformDataFetcherMac* +GamepadPlatformDataFetcherMac::InstanceFromContext(void* context) { + return reinterpret_cast(context); +} + +void GamepadPlatformDataFetcherMac::DeviceAddCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref) { + InstanceFromContext(context)->DeviceAdd(ref); +} + +void GamepadPlatformDataFetcherMac::DeviceRemoveCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref) { + InstanceFromContext(context)->DeviceRemove(ref); +} + +void GamepadPlatformDataFetcherMac::ValueChangedCallback(void* context, + IOReturn result, + void* sender, + IOHIDValueRef ref) { + InstanceFromContext(context)->ValueChanged(ref); +} + +void GamepadPlatformDataFetcherMac::AddButtonsAndAxes(NSArray* elements, + size_t slot) { + WebGamepad& pad = data_.items[slot]; + AssociatedData& associated = associated_[slot]; + CHECK(!associated.is_xbox); + + pad.axesLength = 0; + pad.buttonsLength = 0; + pad.timestamp = 0; + memset(pad.axes, 0, sizeof(pad.axes)); + memset(pad.buttons, 0, sizeof(pad.buttons)); + + for (id elem in elements) { + IOHIDElementRef element = reinterpret_cast(elem); + uint32_t usagePage = IOHIDElementGetUsagePage(element); + uint32_t usage = IOHIDElementGetUsage(element); + if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button && + usagePage == kButtonUsagePage) { + uint32_t button_index = usage - 1; + if (button_index < WebGamepad::buttonsLengthCap) { + associated.hid.button_elements[button_index] = element; + pad.buttonsLength = std::max(pad.buttonsLength, button_index + 1); + } + } + else if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Misc) { + uint32_t axis_index = usage - kAxisMinimumUsageNumber; + if (axis_index < WebGamepad::axesLengthCap) { + associated.hid.axis_minimums[axis_index] = + IOHIDElementGetLogicalMin(element); + associated.hid.axis_maximums[axis_index] = + IOHIDElementGetLogicalMax(element); + associated.hid.axis_elements[axis_index] = element; + pad.axesLength = std::max(pad.axesLength, axis_index + 1); + } + } + } +} + +size_t GamepadPlatformDataFetcherMac::GetEmptySlot() { + // Find a free slot for this device. + for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { + if (!data_.items[slot].connected) + return slot; + } + return WebGamepads::itemsLengthCap; +} + +size_t GamepadPlatformDataFetcherMac::GetSlotForDevice(IOHIDDeviceRef device) { + for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { + // If we already have this device, and it's already connected, don't do + // anything now. + if (data_.items[slot].connected && + !associated_[slot].is_xbox && + associated_[slot].hid.device_ref == device) + return WebGamepads::itemsLengthCap; + } + return GetEmptySlot(); +} + +size_t GamepadPlatformDataFetcherMac::GetSlotForXboxDevice( + XboxController* device) { + for (size_t slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { + if (associated_[slot].is_xbox && + associated_[slot].xbox.location_id == device->location_id()) { + if (data_.items[slot].connected) { + // The device is already connected. No idea why we got a second "device + // added" call, but let's not add it twice. + DCHECK_EQ(associated_[slot].xbox.device, device); + return WebGamepads::itemsLengthCap; + } else { + // A device with the same location ID was previously connected, so put + // it in the same slot. + return slot; + } + } + } + return GetEmptySlot(); +} + +void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) { + using base::mac::CFToNSCast; + using base::mac::CFCastStrict; + + if (!enabled_) + return; + + // Find an index for this device. + size_t slot = GetSlotForDevice(device); + + // We can't handle this many connected devices. + if (slot == WebGamepads::itemsLengthCap) + return; + + NSNumber* vendor_id = CFToNSCast(CFCastStrict( + IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)))); + NSNumber* product_id = CFToNSCast(CFCastStrict( + IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)))); + NSString* product = CFToNSCast(CFCastStrict( + IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)))); + int vendor_int = [vendor_id intValue]; + int product_int = [product_id intValue]; + + char vendor_as_str[5], product_as_str[5]; + snprintf(vendor_as_str, sizeof(vendor_as_str), "%04x", vendor_int); + snprintf(product_as_str, sizeof(product_as_str), "%04x", product_int); + associated_[slot].hid.mapper = + GetGamepadStandardMappingFunction(vendor_as_str, product_as_str); + + NSString* ident = [NSString stringWithFormat: + @"%@ (%sVendor: %04x Product: %04x)", + product, + associated_[slot].hid.mapper ? "STANDARD GAMEPAD " : "", + vendor_int, + product_int]; + NSData* as16 = [ident dataUsingEncoding:NSUTF16LittleEndianStringEncoding]; + + const size_t kOutputLengthBytes = sizeof(data_.items[slot].id); + memset(&data_.items[slot].id, 0, kOutputLengthBytes); + [as16 getBytes:data_.items[slot].id + length:kOutputLengthBytes - sizeof(WebKit::WebUChar)]; + + base::ScopedCFTypeRef elements( + IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone)); + AddButtonsAndAxes(CFToNSCast(elements), slot); + + associated_[slot].hid.device_ref = device; + data_.items[slot].connected = true; + if (slot >= data_.length) + data_.length = slot + 1; +} + +void GamepadPlatformDataFetcherMac::DeviceRemove(IOHIDDeviceRef device) { + if (!enabled_) + return; + + // Find the index for this device. + size_t slot; + for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { + if (data_.items[slot].connected && + !associated_[slot].is_xbox && + associated_[slot].hid.device_ref == device) + break; + } + DCHECK(slot < WebGamepads::itemsLengthCap); + // Leave associated device_ref so that it will be reconnected in the same + // location. Simply mark it as disconnected. + data_.items[slot].connected = false; +} + +void GamepadPlatformDataFetcherMac::ValueChanged(IOHIDValueRef value) { + if (!enabled_) + return; + + IOHIDElementRef element = IOHIDValueGetElement(value); + IOHIDDeviceRef device = IOHIDElementGetDevice(element); + + // Find device slot. + size_t slot; + for (slot = 0; slot < data_.length; ++slot) { + if (data_.items[slot].connected && + !associated_[slot].is_xbox && + associated_[slot].hid.device_ref == device) + break; + } + if (slot == data_.length) + return; + + WebGamepad& pad = data_.items[slot]; + AssociatedData& associated = associated_[slot]; + + // Find and fill in the associated button event, if any. + for (size_t i = 0; i < pad.buttonsLength; ++i) { + if (associated.hid.button_elements[i] == element) { + pad.buttons[i] = IOHIDValueGetIntegerValue(value) ? 1.f : 0.f; + pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value)); + return; + } + } + + // Find and fill in the associated axis event, if any. + for (size_t i = 0; i < pad.axesLength; ++i) { + if (associated.hid.axis_elements[i] == element) { + pad.axes[i] = NormalizeAxis(IOHIDValueGetIntegerValue(value), + associated.hid.axis_minimums[i], + associated.hid.axis_maximums[i]); + pad.timestamp = std::max(pad.timestamp, IOHIDValueGetTimeStamp(value)); + return; + } + } +} + +void GamepadPlatformDataFetcherMac::XboxDeviceAdd(XboxController* device) { + if (!enabled_) + return; + + size_t slot = GetSlotForXboxDevice(device); + + // We can't handle this many connected devices. + if (slot == WebGamepads::itemsLengthCap) + return; + + device->SetLEDPattern( + (XboxController::LEDPattern)(XboxController::LED_FLASH_TOP_LEFT + slot)); + + NSString* ident = + [NSString stringWithFormat: + @"Xbox 360 Controller (STANDARD GAMEPAD Vendor: %04x Product: %04x)", + device->GetProductId(), device->GetVendorId()]; + NSData* as16 = [ident dataUsingEncoding:NSUTF16StringEncoding]; + const size_t kOutputLengthBytes = sizeof(data_.items[slot].id); + memset(&data_.items[slot].id, 0, kOutputLengthBytes); + [as16 getBytes:data_.items[slot].id + length:kOutputLengthBytes - sizeof(WebKit::WebUChar)]; + + associated_[slot].is_xbox = true; + associated_[slot].xbox.device = device; + associated_[slot].xbox.location_id = device->location_id(); + data_.items[slot].connected = true; + data_.items[slot].axesLength = 4; + data_.items[slot].buttonsLength = 17; + data_.items[slot].timestamp = 0; + if (slot >= data_.length) + data_.length = slot + 1; +} + +void GamepadPlatformDataFetcherMac::XboxDeviceRemove(XboxController* device) { + if (!enabled_) + return; + + // Find the index for this device. + size_t slot; + for (slot = 0; slot < WebGamepads::itemsLengthCap; ++slot) { + if (data_.items[slot].connected && + associated_[slot].is_xbox && + associated_[slot].xbox.device == device) + break; + } + DCHECK(slot < WebGamepads::itemsLengthCap); + // Leave associated location id so that the controller will be reconnected in + // the same slot if it is plugged in again. Simply mark it as disconnected. + data_.items[slot].connected = false; +} + +void GamepadPlatformDataFetcherMac::XboxValueChanged( + XboxController* device, const XboxController::Data& data) { + // Find device slot. + size_t slot; + for (slot = 0; slot < data_.length; ++slot) { + if (data_.items[slot].connected && + associated_[slot].is_xbox && + associated_[slot].xbox.device == device) + break; + } + if (slot == data_.length) + return; + + WebGamepad& pad = data_.items[slot]; + + for (size_t i = 0; i < 6; i++) { + pad.buttons[i] = data.buttons[i] ? 1.0f : 0.0f; + } + pad.buttons[6] = data.triggers[0]; + pad.buttons[7] = data.triggers[1]; + for (size_t i = 8; i < 17; i++) { + pad.buttons[i] = data.buttons[i - 2] ? 1.0f : 0.0f; + } + for (size_t i = 0; i < arraysize(data.axes); i++) { + pad.axes[i] = data.axes[i]; + } + + pad.timestamp = base::TimeTicks::Now().ToInternalValue(); +} + +void GamepadPlatformDataFetcherMac::GetGamepadData(WebGamepads* pads, bool) { + if (!enabled_ && !xbox_fetcher_) { + pads->length = 0; + return; + } + + // Copy to the current state to the output buffer, using the mapping + // function, if there is one available. + pads->length = WebGamepads::itemsLengthCap; + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (!associated_[i].is_xbox && associated_[i].hid.mapper) + associated_[i].hid.mapper(data_.items[i], &pads->items[i]); + else + pads->items[i] = data_.items[i]; + } +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc new file mode 100644 index 00000000000..d59ac180aa8 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc @@ -0,0 +1,498 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h" + +#include +#include + +#include "base/debug/trace_event.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" +#include "content/common/gamepad_hardware_buffer.h" +#include "content/common/gamepad_messages.h" + +// This was removed from the Windows 8 SDK for some reason. +// We need it so we can get state for axes without worrying if they +// exist. +#ifndef DIDFT_OPTIONAL +#define DIDFT_OPTIONAL 0x80000000 +#endif + +namespace content { + +using namespace WebKit; + +namespace { + +// See http://goo.gl/5VSJR. These are not available in all versions of the +// header, but they can be returned from the driver, so we define our own +// versions here. +static const BYTE kDeviceSubTypeGamepad = 1; +static const BYTE kDeviceSubTypeWheel = 2; +static const BYTE kDeviceSubTypeArcadeStick = 3; +static const BYTE kDeviceSubTypeFlightStick = 4; +static const BYTE kDeviceSubTypeDancePad = 5; +static const BYTE kDeviceSubTypeGuitar = 6; +static const BYTE kDeviceSubTypeGuitarAlternate = 7; +static const BYTE kDeviceSubTypeDrumKit = 8; +static const BYTE kDeviceSubTypeGuitarBass = 11; +static const BYTE kDeviceSubTypeArcadePad = 19; + +float NormalizeXInputAxis(SHORT value) { + return ((value + 32768.f) / 32767.5f) - 1.f; +} + +const WebUChar* const GamepadSubTypeName(BYTE sub_type) { + switch (sub_type) { + case kDeviceSubTypeGamepad: return L"GAMEPAD"; + case kDeviceSubTypeWheel: return L"WHEEL"; + case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK"; + case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK"; + case kDeviceSubTypeDancePad: return L"DANCE_PAD"; + case kDeviceSubTypeGuitar: return L"GUITAR"; + case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE"; + case kDeviceSubTypeDrumKit: return L"DRUM_KIT"; + case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS"; + case kDeviceSubTypeArcadePad: return L"ARCADE_PAD"; + default: return L""; + } +} + +bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad, + std::string* vendor, + std::string* product) { + DIPROPDWORD prop; + prop.diph.dwSize = sizeof(DIPROPDWORD); + prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop.diph.dwObj = 0; + prop.diph.dwHow = DIPH_DEVICE; + + if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph))) + return false; + *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData)); + *product = base::StringPrintf("%04x", HIWORD(prop.dwData)); + return true; +} + +// Sets the deadzone value for all axes of a gamepad. +// deadzone values range from 0 (no deadzone) to 10,000 (entire range +// is dead). +bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad, + int deadzone) { + DIPROPDWORD prop; + prop.diph.dwSize = sizeof(DIPROPDWORD); + prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop.diph.dwObj = 0; + prop.diph.dwHow = DIPH_DEVICE; + prop.dwData = deadzone; + return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph)); +} + +struct InternalDirectInputDevice { + IDirectInputDevice8* gamepad; + GamepadStandardMappingFunction mapper; + wchar_t id[WebGamepad::idLengthCap]; + GUID guid; +}; + +struct EnumDevicesContext { + IDirectInput8* directinput_interface; + std::vector* directinput_devices; +}; + +// We define our own data format structure to attempt to get as many +// axes as possible. +struct JoyData { + long axes[10]; + char buttons[24]; + DWORD pov; // Often used for D-pads. +}; + +BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance, + void* context) { + EnumDevicesContext* ctxt = reinterpret_cast(context); + IDirectInputDevice8* gamepad; + + if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance, + &gamepad, + NULL))) + return DIENUM_CONTINUE; + + gamepad->Acquire(); + +#define MAKE_AXIS(i) \ + {0, FIELD_OFFSET(JoyData, axes) + 4 * i, \ + DIDFT_AXIS | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0} +#define MAKE_BUTTON(i) \ + {&GUID_Button, FIELD_OFFSET(JoyData, buttons) + i, \ + DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0} +#define MAKE_POV() \ + {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0} + DIOBJECTDATAFORMAT rgodf[] = { + MAKE_AXIS(0), + MAKE_AXIS(1), + MAKE_AXIS(2), + MAKE_AXIS(3), + MAKE_AXIS(4), + MAKE_AXIS(5), + MAKE_AXIS(6), + MAKE_AXIS(7), + MAKE_AXIS(8), + MAKE_AXIS(9), + MAKE_BUTTON(0), + MAKE_BUTTON(1), + MAKE_BUTTON(2), + MAKE_BUTTON(3), + MAKE_BUTTON(4), + MAKE_BUTTON(5), + MAKE_BUTTON(6), + MAKE_BUTTON(7), + MAKE_BUTTON(8), + MAKE_BUTTON(9), + MAKE_BUTTON(10), + MAKE_BUTTON(11), + MAKE_BUTTON(12), + MAKE_BUTTON(13), + MAKE_BUTTON(14), + MAKE_BUTTON(15), + MAKE_BUTTON(16), + MAKE_POV(), + }; +#undef MAKE_AXIS +#undef MAKE_BUTTON +#undef MAKE_POV + + DIDATAFORMAT df = { + sizeof (DIDATAFORMAT), + sizeof (DIOBJECTDATAFORMAT), + DIDF_ABSAXIS, + sizeof (JoyData), + sizeof (rgodf) / sizeof (rgodf[0]), + rgodf + }; + + // If we can't set the data format on the device, don't add it to our + // list, since we won't know how to read data from it. + if (FAILED(gamepad->SetDataFormat(&df))) { + gamepad->Release(); + return DIENUM_CONTINUE; + } + + InternalDirectInputDevice device; + device.guid = instance->guidInstance; + device.gamepad = gamepad; + std::string vendor; + std::string product; + if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) { + gamepad->Release(); + return DIENUM_CONTINUE; + } + + // Set the dead zone to 10% of the axis length for all axes. This + // gives us a larger space for what's "neutral" so the controls don't + // slowly drift. + SetDirectInputDeadZone(gamepad, 1000); + device.mapper = GetGamepadStandardMappingFunction(vendor, product); + if (device.mapper) { + base::swprintf(device.id, + WebGamepad::idLengthCap, + L"STANDARD GAMEPAD (%ls)", + instance->tszProductName); + ctxt->directinput_devices->push_back(device); + } else { + gamepad->Release(); + } + return DIENUM_CONTINUE; +} + +} // namespace + +GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin() + : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))), + xinput_available_(GetXInputDllFunctions()) { + // TODO(teravest): http://crbug.com/260187 + if (base::win::GetVersion() > base::win::VERSION_XP) { + directinput_available_ = SUCCEEDED(DirectInput8Create( + GetModuleHandle(NULL), + DIRECTINPUT_VERSION, + IID_IDirectInput8, + reinterpret_cast(&directinput_interface_), + NULL)); + } else { + directinput_available_ = false; + } + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) + pad_state_[i].status = DISCONNECTED; +} + +GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == DIRECTINPUT_CONNECTED) + pad_state_[i].directinput_gamepad->Release(); + } +} + +int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == DISCONNECTED) + return i; + } + return -1; +} + +bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == XINPUT_CONNECTED && + pad_state_[i].xinput_index == index) + return true; + } + return false; +} + +bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad( + const GUID& guid) const { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == DIRECTINPUT_CONNECTED && + pad_state_[i].guid == guid) + return true; + } + return false; +} + +void GamepadPlatformDataFetcherWin::EnumerateDevices( + WebGamepads* pads) { + TRACE_EVENT0("GAMEPAD", "EnumerateDevices"); + + // Mark all disconnected pads DISCONNECTED. + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (!pads->items[i].connected) + pad_state_[i].status = DISCONNECTED; + } + + for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) { + if (HasXInputGamepad(i)) + continue; + int pad_index = FirstAvailableGamepadId(); + if (pad_index == -1) + return; // We can't add any more gamepads. + WebGamepad& pad = pads->items[pad_index]; + if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) { + pad_state_[pad_index].status = XINPUT_CONNECTED; + pad_state_[pad_index].xinput_index = i; + } + } + + if (directinput_available_) { + struct EnumDevicesContext context; + std::vector directinput_gamepads; + context.directinput_interface = directinput_interface_; + context.directinput_devices = &directinput_gamepads; + + directinput_interface_->EnumDevices( + DI8DEVCLASS_GAMECTRL, + &DirectInputEnumDevicesCallback, + &context, + DIEDFL_ATTACHEDONLY); + for (size_t i = 0; i < directinput_gamepads.size(); ++i) { + if (HasDirectInputGamepad(directinput_gamepads[i].guid)) { + directinput_gamepads[i].gamepad->Release(); + continue; + } + int pad_index = FirstAvailableGamepadId(); + if (pad_index == -1) + return; + WebGamepad& pad = pads->items[pad_index]; + pad.connected = true; + wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id); + PadState& state = pad_state_[pad_index]; + state.status = DIRECTINPUT_CONNECTED; + state.guid = directinput_gamepads[i].guid; + state.directinput_gamepad = directinput_gamepads[i].gamepad; + state.mapper = directinput_gamepads[i].mapper; + } + } +} + + +void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads, + bool devices_changed_hint) { + TRACE_EVENT0("GAMEPAD", "GetGamepadData"); + + if (!xinput_available_ && !directinput_available_) { + pads->length = 0; + return; + } + + // A note on XInput devices: + // If we got notification that system devices have been updated, then + // run GetCapabilities to update the connected status and the device + // identifier. It can be slow to do to both GetCapabilities and + // GetState on unconnected devices, so we want to avoid a 2-5ms pause + // here by only doing this when the devices are updated (despite + // documentation claiming it's OK to call it any time). + if (devices_changed_hint) + EnumerateDevices(pads); + + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + WebGamepad& pad = pads->items[i]; + if (pad_state_[i].status == XINPUT_CONNECTED) + GetXInputPadData(i, &pad); + else if (pad_state_[i].status == DIRECTINPUT_CONNECTED) + GetDirectInputPadData(i, &pad); + } + pads->length = WebGamepads::itemsLengthCap; +} + +bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity( + int i, + WebGamepad* pad) const { + DCHECK(pad); + TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i); + XINPUT_CAPABILITIES caps; + DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); + if (res == ERROR_DEVICE_NOT_CONNECTED) { + pad->connected = false; + return false; + } else { + pad->connected = true; + base::swprintf(pad->id, + WebGamepad::idLengthCap, + L"Xbox 360 Controller (XInput STANDARD %ls)", + GamepadSubTypeName(caps.SubType)); + return true; + } +} + +void GamepadPlatformDataFetcherWin::GetXInputPadData( + int i, + WebGamepad* pad) { + // We rely on device_changed and GetCapabilities to tell us that + // something's been connected, but we will mark as disconnected if + // GetState returns that we've lost the pad. + if (!pad->connected) + return; + + XINPUT_STATE state; + memset(&state, 0, sizeof(XINPUT_STATE)); + TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i); + DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state); + TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i); + + if (dwResult == ERROR_SUCCESS) { + pad->timestamp = state.dwPacketNumber; + pad->buttonsLength = 0; +#define ADD(b) pad->buttons[pad->buttonsLength++] = \ + ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0); + ADD(XINPUT_GAMEPAD_A); + ADD(XINPUT_GAMEPAD_B); + ADD(XINPUT_GAMEPAD_X); + ADD(XINPUT_GAMEPAD_Y); + ADD(XINPUT_GAMEPAD_LEFT_SHOULDER); + ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER); + pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0; + pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0; + ADD(XINPUT_GAMEPAD_BACK); + ADD(XINPUT_GAMEPAD_START); + ADD(XINPUT_GAMEPAD_LEFT_THUMB); + ADD(XINPUT_GAMEPAD_RIGHT_THUMB); + ADD(XINPUT_GAMEPAD_DPAD_UP); + ADD(XINPUT_GAMEPAD_DPAD_DOWN); + ADD(XINPUT_GAMEPAD_DPAD_LEFT); + ADD(XINPUT_GAMEPAD_DPAD_RIGHT); +#undef ADD + pad->axesLength = 0; + // XInput are +up/+right, -down/-left, we want -up/-left. + pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX); + pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY); + pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX); + pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY); + } else { + pad->connected = false; + } +} + +void GamepadPlatformDataFetcherWin::GetDirectInputPadData( + int index, + WebGamepad* pad) { + if (!pad->connected) + return; + + IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad; + if (FAILED(gamepad->Poll())) { + // Polling didn't work, try acquiring the gamepad. + if (FAILED(gamepad->Acquire())) { + pad->buttonsLength = 0; + pad->axesLength = 0; + return; + } + // Try polling again. + if (FAILED(gamepad->Poll())) { + pad->buttonsLength = 0; + pad->axesLength = 0; + return; + } + } + JoyData state; + if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) { + pad->connected = false; + return; + } + + WebGamepad raw; + raw.connected = true; + for (int i = 0; i < 16; i++) + raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0; + + // We map the POV (often a D-pad) into the buttons 16-19. + // DirectInput gives pov measurements in hundredths of degrees, + // clockwise from "North". + // We use 22.5 degree slices so we can handle diagonal D-raw presses. + static const int arc_segment = 2250; // 22.5 degrees = 1/16 circle + if (state.pov > arc_segment && state.pov < 7 * arc_segment) + raw.buttons[19] = 1.0; + else + raw.buttons[19] = 0.0; + + if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment) + raw.buttons[17] = 1.0; + else + raw.buttons[17] = 0.0; + + if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment) + raw.buttons[18] = 1.0; + else + raw.buttons[18] = 0.0; + + if (state.pov < 3 * arc_segment || + (state.pov > 13 * arc_segment && state.pov < 36000)) + raw.buttons[16] = 1.0; + else + raw.buttons[16] = 0.0; + + for (int i = 0; i < 10; i++) + raw.axes[i] = state.axes[i]; + pad_state_[index].mapper(raw, pad); +} + +bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() { + xinput_get_capabilities_ = NULL; + xinput_get_state_ = NULL; + xinput_enable_ = reinterpret_cast( + xinput_dll_.GetFunctionPointer("XInputEnable")); + if (!xinput_enable_) + return false; + xinput_get_capabilities_ = reinterpret_cast( + xinput_dll_.GetFunctionPointer("XInputGetCapabilities")); + if (!xinput_get_capabilities_) + return false; + xinput_get_state_ = reinterpret_cast( + xinput_dll_.GetFunctionPointer("XInputGetState")); + if (!xinput_get_state_) + return false; + xinput_enable_(true); + return true; +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h new file mode 100644 index 00000000000..7551ee91c4c --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_platform_data_fetcher_win.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_WIN_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_WIN_H_ + +#include "build/build_config.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#define DIRECTINPUT_VERSION 0x0800 +#include +#include +#include +#include +#include +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/scoped_native_library.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_standard_mappings.h" +#include "third_party/WebKit/public/platform/WebGamepads.h" + +namespace content { + +class GamepadPlatformDataFetcherWin : public GamepadDataFetcher { + public: + GamepadPlatformDataFetcherWin(); + virtual ~GamepadPlatformDataFetcherWin(); + virtual void GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) OVERRIDE; + private: + // XInput-specific implementation for GetGamepadData. + bool GetXInputGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint); + bool GetDirectInputGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint); + + // The three function types we use from xinput1_3.dll. + typedef void (WINAPI *XInputEnableFunc)(BOOL enable); + typedef DWORD (WINAPI *XInputGetCapabilitiesFunc)( + DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES* pCapabilities); + typedef DWORD (WINAPI *XInputGetStateFunc)( + DWORD dwUserIndex, XINPUT_STATE* pState); + + // Get functions from dynamically loaded xinput1_3.dll. We don't use + // DELAYLOAD because the import library for Win8 SDK pulls xinput1_4 which + // isn't redistributable. Returns true if loading was successful. We include + // xinput1_3.dll with Chrome. + bool GetXInputDllFunctions(); + + // Scan for connected XInput and DirectInput gamepads. + void EnumerateDevices(WebKit::WebGamepads* pads); + bool GetXInputPadConnectivity(int i, WebKit::WebGamepad* pad) const; + + void GetXInputPadData(int i, WebKit::WebGamepad* pad); + void GetDirectInputPadData(int i, WebKit::WebGamepad* pad); + + int FirstAvailableGamepadId() const; + bool HasXInputGamepad(int index) const; + bool HasDirectInputGamepad(const GUID &guid) const; + + base::ScopedNativeLibrary xinput_dll_; + bool xinput_available_; + bool directinput_available_; + + // Function pointers to XInput functionality, retrieved in + // |GetXinputDllFunctions|. + XInputEnableFunc xinput_enable_; + XInputGetCapabilitiesFunc xinput_get_capabilities_; + XInputGetStateFunc xinput_get_state_; + + IDirectInput8* directinput_interface_; + + enum PadConnectionStatus { + DISCONNECTED, + XINPUT_CONNECTED, + DIRECTINPUT_CONNECTED + }; + + struct PadState { + PadConnectionStatus status; + int xinput_index; // XInput-only. + // Fields below are for DirectInput devices only. + GUID guid; + IDirectInputDevice8* directinput_gamepad; + GamepadStandardMappingFunction mapper; + }; + PadState pad_state_[WebKit::WebGamepads::itemsLengthCap]; + + DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherWin); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_WIN_H_ diff --git a/chromium/content/browser/gamepad/gamepad_provider.cc b/chromium/content/browser/gamepad/gamepad_provider.cc new file mode 100644 index 00000000000..7e8e6dee132 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_provider.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_platform_data_fetcher.h" +#include "content/browser/gamepad/gamepad_provider.h" +#include "content/common/gamepad_hardware_buffer.h" +#include "content/common/gamepad_messages.h" +#include "content/common/gamepad_user_gesture.h" + +namespace content { + +GamepadProvider::ClosureAndThread::ClosureAndThread( + const base::Closure& c, + const scoped_refptr& m) + : closure(c), + message_loop(m) { +} + +GamepadProvider::ClosureAndThread::~ClosureAndThread() { +} + +GamepadProvider::GamepadProvider() + : is_paused_(true), + have_scheduled_do_poll_(false), + devices_changed_(true) { + Initialize(scoped_ptr()); +} + +GamepadProvider::GamepadProvider(scoped_ptr fetcher) + : is_paused_(true), + have_scheduled_do_poll_(false), + devices_changed_(true) { + Initialize(fetcher.Pass()); +} + +GamepadProvider::~GamepadProvider() { + base::SystemMonitor* monitor = base::SystemMonitor::Get(); + if (monitor) + monitor->RemoveDevicesChangedObserver(this); + + // Use Stop() to join the polling thread, as there may be pending callbacks + // which dereference |polling_thread_|. + polling_thread_->Stop(); + data_fetcher_.reset(); +} + +base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess( + base::ProcessHandle process) { + base::SharedMemoryHandle renderer_handle; + gamepad_shared_memory_.ShareToProcess(process, &renderer_handle); + return renderer_handle; +} + +void GamepadProvider::Pause() { + { + base::AutoLock lock(is_paused_lock_); + is_paused_ = true; + } + base::MessageLoop* polling_loop = polling_thread_->message_loop(); + polling_loop->PostTask( + FROM_HERE, + base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), true)); +} + +void GamepadProvider::Resume() { + { + base::AutoLock lock(is_paused_lock_); + if (!is_paused_) + return; + is_paused_ = false; + } + + base::MessageLoop* polling_loop = polling_thread_->message_loop(); + polling_loop->PostTask( + FROM_HERE, + base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false)); + polling_loop->PostTask( + FROM_HERE, + base::Bind(&GamepadProvider::ScheduleDoPoll, Unretained(this))); +} + +void GamepadProvider::RegisterForUserGesture(const base::Closure& closure) { + base::AutoLock lock(user_gesture_lock_); + user_gesture_observers_.push_back(ClosureAndThread( + closure, base::MessageLoop::current()->message_loop_proxy())); +} + +void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) { + base::AutoLock lock(devices_changed_lock_); + devices_changed_ = true; +} + +void GamepadProvider::Initialize(scoped_ptr fetcher) { + size_t data_size = sizeof(GamepadHardwareBuffer); + base::SystemMonitor* monitor = base::SystemMonitor::Get(); + if (monitor) + monitor->AddDevicesChangedObserver(this); + bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size); + CHECK(res); + GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); + memset(hwbuf, 0, sizeof(GamepadHardwareBuffer)); + + polling_thread_.reset(new base::Thread("Gamepad polling thread")); +#if defined(OS_MACOSX) + // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the + // message loop needs to be a UI-type loop. + const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI; +#else + // On Linux, the data fetcher needs to watch file descriptors, so the message + // loop needs to be a libevent loop. On Windows it doesn't matter what the + // loop is. + const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO; +#endif + polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0)); + + polling_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&GamepadProvider::DoInitializePollingThread, + base::Unretained(this), + base::Passed(&fetcher))); +} + +void GamepadProvider::DoInitializePollingThread( + scoped_ptr fetcher) { + DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); + DCHECK(!data_fetcher_.get()); // Should only initialize once. + + if (!fetcher) + fetcher.reset(new GamepadPlatformDataFetcher); + data_fetcher_ = fetcher.Pass(); +} + +void GamepadProvider::SendPauseHint(bool paused) { + DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); + if (data_fetcher_) + data_fetcher_->PauseHint(paused); +} + +void GamepadProvider::DoPoll() { + DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); + DCHECK(have_scheduled_do_poll_); + have_scheduled_do_poll_ = false; + + bool changed; + GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer(); + + ANNOTATE_BENIGN_RACE_SIZED( + &hwbuf->buffer, + sizeof(WebKit::WebGamepads), + "Racey reads are discarded"); + + { + base::AutoLock lock(devices_changed_lock_); + changed = devices_changed_; + devices_changed_ = false; + } + + // Acquire the SeqLock. There is only ever one writer to this data. + // See gamepad_hardware_buffer.h. + hwbuf->sequence.WriteBegin(); + data_fetcher_->GetGamepadData(&hwbuf->buffer, changed); + hwbuf->sequence.WriteEnd(); + + CheckForUserGesture(); + + // Schedule our next interval of polling. + ScheduleDoPoll(); +} + +void GamepadProvider::ScheduleDoPoll() { + DCHECK(base::MessageLoop::current() == polling_thread_->message_loop()); + if (have_scheduled_do_poll_) + return; + + { + base::AutoLock lock(is_paused_lock_); + if (is_paused_) + return; + } + + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&GamepadProvider::DoPoll, Unretained(this)), + base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs)); + have_scheduled_do_poll_ = true; +} + +GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() { + void* mem = gamepad_shared_memory_.memory(); + CHECK(mem); + return static_cast(mem); +} + +void GamepadProvider::CheckForUserGesture() { + base::AutoLock lock(user_gesture_lock_); + if (user_gesture_observers_.empty()) + return; // Don't need to check if nobody is listening. + + if (GamepadsHaveUserGesture(SharedMemoryAsHardwareBuffer()->buffer)) { + for (size_t i = 0; i < user_gesture_observers_.size(); i++) { + user_gesture_observers_[i].message_loop->PostTask(FROM_HERE, + user_gesture_observers_[i].closure); + } + user_gesture_observers_.clear(); + } +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_provider.h b/chromium/content/browser/gamepad/gamepad_provider.h new file mode 100644 index 00000000000..5f10f137a14 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_provider.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PROVIDER_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PROVIDER_H_ + +#include +#include + +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/synchronization/lock.h" +#include "base/system_monitor/system_monitor.h" +#include "content/common/content_export.h" + +namespace base { +class MessageLoopProxy; +class Thread; +} + +namespace content { + +class GamepadDataFetcher; +struct GamepadHardwareBuffer; + +class CONTENT_EXPORT GamepadProvider : + public base::SystemMonitor::DevicesChangedObserver { + public: + GamepadProvider(); + + // Manually specifies the data fetcher. Used for testing. + explicit GamepadProvider(scoped_ptr fetcher); + + virtual ~GamepadProvider(); + + // Returns the shared memory handle of the gamepad data duplicated into the + // given process. + base::SharedMemoryHandle GetSharedMemoryHandleForProcess( + base::ProcessHandle renderer_process); + + // Pause and resume the background polling thread. Can be called from any + // thread. + void Pause(); + void Resume(); + + // Registers the given closure for calling when the user has interacted with + // the device. This callback will only be issued once. + void RegisterForUserGesture(const base::Closure& closure); + + // base::SystemMonitor::DevicesChangedObserver implementation. + virtual void OnDevicesChanged(base::SystemMonitor::DeviceType type) OVERRIDE; + + private: + void Initialize(scoped_ptr fetcher); + + // Method for setting up the platform-specific data fetcher. Takes ownership + // of |fetcher|. + void DoInitializePollingThread(scoped_ptr fetcher); + + // Method for sending pause hints to the low-level data fetcher. Runs on + // polling_thread_. + void SendPauseHint(bool paused); + + // Method for polling a GamepadDataFetcher. Runs on the polling_thread_. + void DoPoll(); + void ScheduleDoPoll(); + + GamepadHardwareBuffer* SharedMemoryAsHardwareBuffer(); + + // Checks the gamepad state to see if the user has interacted with it. + void CheckForUserGesture(); + + enum { kDesiredSamplingIntervalMs = 16 }; + + // Keeps track of when the background thread is paused. Access to is_paused_ + // must be guarded by is_paused_lock_. + base::Lock is_paused_lock_; + bool is_paused_; + + // Keep track of when a polling task is schedlued, so as to prevent us from + // accidentally scheduling more than one at any time, when rapidly toggling + // |is_paused_|. + bool have_scheduled_do_poll_; + + // Lists all observers registered for user gestures, and the thread which + // to issue the callbacks on. Since we always issue the callback on the + // thread which the registration happened, and this class lives on the I/O + // thread, the message loop proxies will normally just be the I/O thread. + // However, this will be the main thread for unit testing. + base::Lock user_gesture_lock_; + struct ClosureAndThread { + ClosureAndThread(const base::Closure& c, + const scoped_refptr& m); + ~ClosureAndThread(); + + base::Closure closure; + scoped_refptr message_loop; + }; + typedef std::vector UserGestureObserverVector; + UserGestureObserverVector user_gesture_observers_; + + // Updated based on notification from SystemMonitor when the system devices + // have been updated, and this notification is passed on to the data fetcher + // to enable it to avoid redundant (and possibly expensive) is-connected + // tests. Access to devices_changed_ must be guarded by + // devices_changed_lock_. + base::Lock devices_changed_lock_; + bool devices_changed_; + + // When polling_thread_ is running, members below are only to be used + // from that thread. + scoped_ptr data_fetcher_; + base::SharedMemory gamepad_shared_memory_; + + // Polling is done on this background thread. + scoped_ptr polling_thread_; + + static GamepadProvider* instance_; + + DISALLOW_COPY_AND_ASSIGN(GamepadProvider); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PROVIDER_H_ diff --git a/chromium/content/browser/gamepad/gamepad_provider_unittest.cc b/chromium/content/browser/gamepad/gamepad_provider_unittest.cc new file mode 100644 index 00000000000..0e5785cc598 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_provider_unittest.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_provider.h" +#include "content/browser/gamepad/gamepad_test_helpers.h" +#include "content/common/gamepad_hardware_buffer.h" +#include "content/common/gamepad_messages.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +namespace { + +using WebKit::WebGamepads; + +// Helper class to generate and record user gesture callbacks. +class UserGestureListener { + public: + UserGestureListener() + : weak_factory_(this), + has_user_gesture_(false) { + } + + base::Closure GetClosure() { + return base::Bind(&UserGestureListener::GotUserGesture, + weak_factory_.GetWeakPtr()); + } + + bool has_user_gesture() const { return has_user_gesture_; } + + private: + void GotUserGesture() { + has_user_gesture_ = true; + } + + base::WeakPtrFactory weak_factory_; + bool has_user_gesture_; +}; + +// Main test fixture +class GamepadProviderTest : public testing::Test, public GamepadTestHelper { + public: + GamepadProvider* CreateProvider(const WebGamepads& test_data) { + mock_data_fetcher_ = new MockGamepadDataFetcher(test_data); + provider_.reset(new GamepadProvider( + scoped_ptr(mock_data_fetcher_))); + return provider_.get(); + } + + protected: + GamepadProviderTest() { + } + + scoped_ptr provider_; + + // Pointer owned by the provider. + MockGamepadDataFetcher* mock_data_fetcher_; + + DISALLOW_COPY_AND_ASSIGN(GamepadProviderTest); +}; + +// Crashes. http://crbug.com/106163 +TEST_F(GamepadProviderTest, PollingAccess) { + WebGamepads test_data; + test_data.length = 1; + test_data.items[0].connected = true; + test_data.items[0].timestamp = 0; + test_data.items[0].buttonsLength = 1; + test_data.items[0].axesLength = 2; + test_data.items[0].buttons[0] = 1.f; + test_data.items[0].axes[0] = -1.f; + test_data.items[0].axes[1] = .5f; + + GamepadProvider* provider = CreateProvider(test_data); + provider->Resume(); + + message_loop().RunUntilIdle(); + + mock_data_fetcher_->WaitForDataRead(); + + // Renderer-side, pull data out of poll buffer. + base::SharedMemoryHandle handle = provider->GetSharedMemoryHandleForProcess( + base::GetCurrentProcessHandle()); + scoped_ptr shared_memory( + new base::SharedMemory(handle, true)); + EXPECT_TRUE(shared_memory->Map(sizeof(GamepadHardwareBuffer))); + void* mem = shared_memory->memory(); + + GamepadHardwareBuffer* hwbuf = static_cast(mem); + // See gamepad_hardware_buffer.h for details on the read discipline. + WebGamepads output; + + base::subtle::Atomic32 version; + do { + version = hwbuf->sequence.ReadBegin(); + memcpy(&output, &hwbuf->buffer, sizeof(output)); + } while (hwbuf->sequence.ReadRetry(version)); + + EXPECT_EQ(1u, output.length); + EXPECT_EQ(1u, output.items[0].buttonsLength); + EXPECT_EQ(1.f, output.items[0].buttons[0]); + EXPECT_EQ(2u, output.items[0].axesLength); + EXPECT_EQ(-1.f, output.items[0].axes[0]); + EXPECT_EQ(0.5f, output.items[0].axes[1]); +} + +// Tests that waiting for a user gesture works properly. +TEST_F(GamepadProviderTest, UserGesture) { + WebGamepads no_button_data; + no_button_data.length = 1; + no_button_data.items[0].connected = true; + no_button_data.items[0].timestamp = 0; + no_button_data.items[0].buttonsLength = 1; + no_button_data.items[0].axesLength = 2; + no_button_data.items[0].buttons[0] = 0.f; + no_button_data.items[0].axes[0] = -1.f; + no_button_data.items[0].axes[1] = .5f; + + WebGamepads button_down_data = no_button_data; + button_down_data.items[0].buttons[0] = 1.f; + + UserGestureListener listener; + GamepadProvider* provider = CreateProvider(no_button_data); + provider->Resume(); + + // Register for a user gesture and make sure the provider reads it twice + // see below for why). + provider->RegisterForUserGesture(listener.GetClosure()); + mock_data_fetcher_->WaitForDataRead(); + mock_data_fetcher_->WaitForDataRead(); + + // It should not have issued our callback. + message_loop().RunUntilIdle(); + EXPECT_FALSE(listener.has_user_gesture()); + + // Set a button down and wait for it to be read twice. + // + // We wait for two reads before calling RunAllPending because the provider + // will read the data on the background thread (setting the event) and *then* + // will issue the callback on our thread. Waiting for it to read twice + // ensures that it was able to issue callbacks for the first read (if it + // issued one) before we try to check for it. + mock_data_fetcher_->SetTestData(button_down_data); + mock_data_fetcher_->WaitForDataRead(); + mock_data_fetcher_->WaitForDataRead(); + + // It should have issued our callback. + message_loop().RunUntilIdle(); + EXPECT_TRUE(listener.has_user_gesture()); +} + +} // namespace + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_service.cc b/chromium/content/browser/gamepad/gamepad_service.cc new file mode 100644 index 00000000000..11a6964b3ab --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_service.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_service.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_provider.h" +#include "content/public/browser/render_process_host.h" + +namespace content { + +GamepadService::GamepadService() : num_readers_(0) { +} + +GamepadService::GamepadService(scoped_ptr fetcher) + : num_readers_(0), + provider_(new GamepadProvider(fetcher.Pass())) { + thread_checker_.DetachFromThread(); +} + +GamepadService::~GamepadService() { +} + +GamepadService* GamepadService::GetInstance() { + return Singleton >::get(); +} + +void GamepadService::AddConsumer() { + DCHECK(thread_checker_.CalledOnValidThread()); + + num_readers_++; + DCHECK(num_readers_ > 0); + if (!provider_) + provider_.reset(new GamepadProvider); + provider_->Resume(); +} + +void GamepadService::RemoveConsumer() { + DCHECK(thread_checker_.CalledOnValidThread()); + + --num_readers_; + DCHECK(num_readers_ >= 0); + + if (num_readers_ == 0) + provider_->Pause(); +} + +void GamepadService::RegisterForUserGesture(const base::Closure& closure) { + DCHECK(num_readers_ > 0); + DCHECK(thread_checker_.CalledOnValidThread()); + provider_->RegisterForUserGesture(closure); +} + +void GamepadService::Terminate() { + provider_.reset(); +} + +base::SharedMemoryHandle GamepadService::GetSharedMemoryHandleForProcess( + base::ProcessHandle handle) { + DCHECK(thread_checker_.CalledOnValidThread()); + return provider_->GetSharedMemoryHandleForProcess(handle); +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_service.h b/chromium/content/browser/gamepad/gamepad_service.h new file mode 100644 index 00000000000..94620b34b60 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_service.h @@ -0,0 +1,77 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_SERVICE_H +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_SERVICE_H + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "base/memory/singleton.h" +#include "base/threading/thread_checker.h" +#include "content/common/content_export.h" + +namespace content { + +class GamepadDataFetcher; +class GamepadProvider; +class GamepadServiceTestConstructor; +class RenderProcessHost; + +// Owns the GamepadProvider (the background polling thread) and keeps track of +// the number of consumers currently using the data (and pausing the provider +// when not in use). +class CONTENT_EXPORT GamepadService { + public: + // Returns the GamepadService singleton. + static GamepadService* GetInstance(); + + // Increments the number of users of the provider. The Provider is running + // when there's > 0 users, and is paused when the count drops to 0. + // + // Must be called on the I/O thread. + void AddConsumer(); + + // Removes a consumer. Should be matched with an AddConsumer call. + // + // Must be called on the I/O thread. + void RemoveConsumer(); + + // Registers the given closure for calling when the user has interacted with + // the device. This callback will only be issued once. Should only be called + // while a consumer is active. + void RegisterForUserGesture(const base::Closure& closure); + + // Returns the shared memory handle of the gamepad data duplicated into the + // given process. + base::SharedMemoryHandle GetSharedMemoryHandleForProcess( + base::ProcessHandle handle); + + // Stop/join with the background thread in GamepadProvider |provider_|. + void Terminate(); + + private: + friend struct DefaultSingletonTraits; + friend class GamepadServiceTestConstructor; + + GamepadService(); + + // Constructor for testing. This specifies the data fetcher to use for a + // provider, bypassing the default platform one. + GamepadService(scoped_ptr fetcher); + + virtual ~GamepadService(); + + int num_readers_; + scoped_ptr provider_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(GamepadService); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_SERVICE_H_ diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings.h b/chromium/content/browser/gamepad/gamepad_standard_mappings.h new file mode 100644 index 00000000000..6f5d72fc0a7 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_standard_mappings.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_STANDARD_MAPPINGS_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_STANDARD_MAPPINGS_H_ + +#include "base/strings/string_piece.h" + +namespace WebKit { +class WebGamepad; +} + +namespace content { + +typedef void (*GamepadStandardMappingFunction)( + const WebKit::WebGamepad& original, + WebKit::WebGamepad* mapped); + +GamepadStandardMappingFunction GetGamepadStandardMappingFunction( + const base::StringPiece& vendor_id, + const base::StringPiece& product_id); + +// This defines our canonical mapping order for gamepad-like devices. If these +// items cannot all be satisfied, it is a case-by-case judgement as to whether +// it is better to leave the device unmapped, or to partially map it. In +// general, err towards leaving it *unmapped* so that content can handle +// appropriately. + +enum CanonicalButtonIndex { + kButtonPrimary, + kButtonSecondary, + kButtonTertiary, + kButtonQuaternary, + kButtonLeftShoulder, + kButtonRightShoulder, + kButtonLeftTrigger, + kButtonRightTrigger, + kButtonBackSelect, + kButtonStart, + kButtonLeftThumbstick, + kButtonRightThumbstick, + kButtonDpadUp, + kButtonDpadDown, + kButtonDpadLeft, + kButtonDpadRight, + kButtonMeta, + kNumButtons +}; + +enum CanonicalAxisIndex { + kAxisLeftStickX, + kAxisLeftStickY, + kAxisRightStickX, + kAxisRightStickY, + kNumAxes +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_STANDARD_MAPPINGS_H_ diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc b/chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc new file mode 100644 index 00000000000..e9e7094c532 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_standard_mappings_linux.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_standard_mappings.h" + +#include "content/common/gamepad_hardware_buffer.h" + +namespace content { + +namespace { + +float AxisToButton(float input) { + return (input + 1.f) / 2.f; +} + +float AxisNegativeAsButton(float input) { + return (input < -0.5f) ? 1.f : 0.f; +} + +float AxisPositiveAsButton(float input) { + return (input > 0.5f) ? 1.f : 0.f; +} + +void MapperXInputStyleGamepad( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonLeftTrigger] = AxisToButton(input.axes[2]); + mapped->buttons[kButtonRightTrigger] = AxisToButton(input.axes[5]); + mapped->buttons[kButtonBackSelect] = input.buttons[6]; + mapped->buttons[kButtonStart] = input.buttons[7]; + mapped->buttons[kButtonLeftThumbstick] = input.buttons[9]; + mapped->buttons[kButtonRightThumbstick] = input.buttons[10]; + mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[7]); + mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[7]); + mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[6]); + mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[6]); + mapped->buttons[kButtonMeta] = input.buttons[8]; + mapped->axes[kAxisRightStickX] = input.axes[3]; + mapped->axes[kAxisRightStickY] = input.axes[4]; + mapped->buttonsLength = kNumButtons; + mapped->axesLength = kNumAxes; +} + +void MapperLakeviewResearch( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[2]; + mapped->buttons[kButtonTertiary] = input.buttons[3]; + mapped->buttons[kButtonQuaternary] = input.buttons[0]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[6]; + mapped->buttons[kButtonRightShoulder] = input.buttons[7]; + mapped->buttons[kButtonLeftTrigger] = input.buttons[4]; + mapped->buttons[kButtonRightTrigger] = input.buttons[5]; + mapped->buttons[kButtonBackSelect] = input.buttons[9]; + mapped->buttons[kButtonStart] = input.buttons[8]; + mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[5]); + mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[5]); + mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[4]); + mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[4]); + mapped->buttonsLength = kNumButtons - 1; // no Meta on this device + mapped->axesLength = kNumAxes; +} + +void MapperPlaystationSixAxis( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[14]; + mapped->buttons[kButtonSecondary] = input.buttons[13]; + mapped->buttons[kButtonTertiary] = input.buttons[15]; + mapped->buttons[kButtonQuaternary] = input.buttons[12]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[10]; + mapped->buttons[kButtonRightShoulder] = input.buttons[11]; + mapped->buttons[kButtonLeftTrigger] = AxisToButton(input.axes[12]); + mapped->buttons[kButtonRightTrigger] = AxisToButton(input.axes[13]); + mapped->buttons[kButtonBackSelect] = input.buttons[0]; + mapped->buttons[kButtonStart] = input.buttons[3]; + mapped->buttons[kButtonLeftThumbstick] = input.buttons[1]; + mapped->buttons[kButtonRightThumbstick] = input.buttons[2]; + mapped->buttons[kButtonDpadUp] = AxisToButton(input.axes[8]); + mapped->buttons[kButtonDpadDown] = AxisToButton(input.axes[10]); + mapped->buttons[kButtonDpadLeft] = input.buttons[7]; + mapped->buttons[kButtonDpadRight] = AxisToButton(input.axes[9]); + mapped->buttons[kButtonMeta] = input.buttons[16]; + + mapped->buttonsLength = kNumButtons; + mapped->axesLength = kNumAxes; +} + +void MapperXGEAR( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[2]; + mapped->buttons[kButtonSecondary] = input.buttons[1]; + mapped->buttons[kButtonTertiary] = input.buttons[3]; + mapped->buttons[kButtonQuaternary] = input.buttons[0]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[6]; + mapped->buttons[kButtonRightShoulder] = input.buttons[7]; + mapped->buttons[kButtonLeftTrigger] = input.buttons[4]; + mapped->buttons[kButtonRightTrigger] = input.buttons[5]; + mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[5]); + mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[5]); + mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[4]); + mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[4]); + mapped->axes[kAxisRightStickX] = input.axes[3]; + mapped->axes[kAxisRightStickY] = input.axes[2]; + mapped->buttonsLength = kNumButtons - 1; // no Meta on this device + mapped->axesLength = kNumAxes; +} + + +void MapperDragonRiseGeneric( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonDpadUp] = AxisNegativeAsButton(input.axes[6]); + mapped->buttons[kButtonDpadDown] = AxisPositiveAsButton(input.axes[6]); + mapped->buttons[kButtonDpadLeft] = AxisNegativeAsButton(input.axes[5]); + mapped->buttons[kButtonDpadRight] = AxisPositiveAsButton(input.axes[5]); + mapped->axes[kAxisLeftStickX] = input.axes[0]; + mapped->axes[kAxisLeftStickY] = input.axes[1]; + mapped->axes[kAxisRightStickX] = input.axes[3]; + mapped->axes[kAxisRightStickY] = input.axes[4]; + mapped->buttonsLength = kNumButtons - 1; // no Meta on this device + mapped->axesLength = kNumAxes; +} + + +struct MappingData { + const char* const vendor_id; + const char* const product_id; + GamepadStandardMappingFunction function; +} AvailableMappings[] = { + // http://www.linux-usb.org/usb.ids + { "0079", "0006", MapperDragonRiseGeneric }, // DragonRise Generic USB + { "045e", "028e", MapperXInputStyleGamepad }, // Xbox 360 Controller + { "045e", "028f", MapperXInputStyleGamepad }, // Xbox 360 Wireless Controller + { "046d", "c21d", MapperXInputStyleGamepad }, // Logitech F310 + { "046d", "c21e", MapperXInputStyleGamepad }, // Logitech F510 + { "046d", "c21f", MapperXInputStyleGamepad }, // Logitech F710 + { "054c", "0268", MapperPlaystationSixAxis }, // Playstation SIXAXIS + { "0925", "0005", MapperLakeviewResearch }, // SmartJoy PLUS Adapter + { "0925", "8866", MapperLakeviewResearch }, // WiseGroup MP-8866 + { "0e8f", "0003", MapperXGEAR }, // XFXforce XGEAR PS2 Controller +}; + +} // namespace + +GamepadStandardMappingFunction GetGamepadStandardMappingFunction( + const base::StringPiece& vendor_id, + const base::StringPiece& product_id) { + for (size_t i = 0; i < arraysize(AvailableMappings); ++i) { + MappingData& item = AvailableMappings[i]; + if (vendor_id == item.vendor_id && product_id == item.product_id) + return item.function; + } + return NULL; +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm b/chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm new file mode 100644 index 00000000000..0ffb35f8fec --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_standard_mappings_mac.mm @@ -0,0 +1,219 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_standard_mappings.h" + +#include "content/common/gamepad_hardware_buffer.h" + +namespace content { + +namespace { + +float AxisToButton(float input) { + return (input + 1.f) / 2.f; +} + +void DpadFromAxis(WebKit::WebGamepad* mapped, float dir) { + // Dpad is mapped as a direction on one axis, where -1 is up and it + // increases clockwise to 1, which is up + left. It's set to a large (> 1.f) + // number when nothing is depressed, except on start up, sometimes it's 0.0 + // for no data, rather than the large number. + if (dir == 0.0f) { + mapped->buttons[kButtonDpadUp] = 0.f; + mapped->buttons[kButtonDpadDown] = 0.f; + mapped->buttons[kButtonDpadLeft] = 0.f; + mapped->buttons[kButtonDpadRight] = 0.f; + } else { + mapped->buttons[kButtonDpadUp] = (dir >= -1.f && dir < -0.7f) || + (dir >= .95f && dir <= 1.f); + mapped->buttons[kButtonDpadRight] = dir >= -.75f && dir < -.1f; + mapped->buttons[kButtonDpadDown] = dir >= -.2f && dir < .45f; + mapped->buttons[kButtonDpadLeft] = dir >= .4f && dir <= 1.f; + } +} + +void MapperXbox360Gamepad( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonLeftTrigger] = AxisToButton(input.axes[2]); + mapped->buttons[kButtonRightTrigger] = AxisToButton(input.axes[5]); + mapped->buttons[kButtonBackSelect] = input.buttons[9]; + mapped->buttons[kButtonStart] = input.buttons[8]; + mapped->buttons[kButtonLeftThumbstick] = input.buttons[6]; + mapped->buttons[kButtonRightThumbstick] = input.buttons[7]; + mapped->buttons[kButtonDpadUp] = input.buttons[11]; + mapped->buttons[kButtonDpadDown] = input.buttons[12]; + mapped->buttons[kButtonDpadLeft] = input.buttons[13]; + mapped->buttons[kButtonDpadRight] = input.buttons[14]; + mapped->buttons[kButtonMeta] = input.buttons[10]; + mapped->axes[kAxisRightStickX] = input.axes[3]; + mapped->axes[kAxisRightStickY] = input.axes[4]; + mapped->buttonsLength = kNumButtons; + mapped->axesLength = kNumAxes; +} + +void MapperPlaystationSixAxis( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[14]; + mapped->buttons[kButtonSecondary] = input.buttons[13]; + mapped->buttons[kButtonTertiary] = input.buttons[15]; + mapped->buttons[kButtonQuaternary] = input.buttons[12]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[10]; + mapped->buttons[kButtonRightShoulder] = input.buttons[11]; + mapped->buttons[kButtonLeftTrigger] = input.buttons[8]; + mapped->buttons[kButtonRightTrigger] = input.buttons[9]; + mapped->buttons[kButtonBackSelect] = input.buttons[0]; + mapped->buttons[kButtonStart] = input.buttons[3]; + mapped->buttons[kButtonLeftThumbstick] = input.buttons[1]; + mapped->buttons[kButtonRightThumbstick] = input.buttons[2]; + mapped->buttons[kButtonDpadUp] = input.buttons[4]; + mapped->buttons[kButtonDpadDown] = input.buttons[6]; + mapped->buttons[kButtonDpadLeft] = input.buttons[7]; + mapped->buttons[kButtonDpadRight] = input.buttons[5]; + mapped->buttons[kButtonMeta] = input.buttons[16]; + mapped->axes[kAxisRightStickY] = input.axes[5]; + + mapped->buttonsLength = kNumButtons; + mapped->axesLength = kNumAxes; +} + +void MapperDirectInputStyle( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[1]; + mapped->buttons[kButtonSecondary] = input.buttons[2]; + mapped->buttons[kButtonTertiary] = input.buttons[0]; + mapped->axes[kAxisRightStickY] = input.axes[5]; + DpadFromAxis(mapped, input.axes[9]); + mapped->buttonsLength = kNumButtons - 1; /* no meta */ + mapped->axesLength = kNumAxes; +} + +void MapperMacallyIShock( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + enum IShockButtons { + kButtonC = kNumButtons, + kButtonD, + kButtonE, + kNumIShockButtons + }; + + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[6]; + mapped->buttons[kButtonSecondary] = input.buttons[5]; + mapped->buttons[kButtonTertiary] = input.buttons[7]; + mapped->buttons[kButtonQuaternary] = input.buttons[4]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[14]; + mapped->buttons[kButtonRightShoulder] = input.buttons[12]; + mapped->buttons[kButtonLeftTrigger] = input.buttons[15]; + mapped->buttons[kButtonRightTrigger] = input.buttons[13]; + mapped->buttons[kButtonBackSelect] = input.buttons[9]; + mapped->buttons[kButtonStart] = input.buttons[10]; + mapped->buttons[kButtonLeftThumbstick] = input.buttons[16]; + mapped->buttons[kButtonRightThumbstick] = input.buttons[17]; + mapped->buttons[kButtonDpadUp] = input.buttons[0]; + mapped->buttons[kButtonDpadDown] = input.buttons[1]; + mapped->buttons[kButtonDpadLeft] = input.buttons[2]; + mapped->buttons[kButtonDpadRight] = input.buttons[3]; + mapped->buttons[kButtonMeta] = input.buttons[11]; + mapped->buttons[kButtonC] = input.buttons[8]; + mapped->buttons[kButtonD] = input.buttons[18]; + mapped->buttons[kButtonE] = input.buttons[19]; + mapped->axes[kAxisLeftStickX] = input.axes[0]; + mapped->axes[kAxisLeftStickY] = input.axes[1]; + mapped->axes[kAxisRightStickX] = -input.axes[5]; + mapped->axes[kAxisRightStickY] = input.axes[6]; + + mapped->buttonsLength = kNumIShockButtons; + mapped->axesLength = kNumAxes; +} + +void MapperXGEAR( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[2]; + mapped->buttons[kButtonTertiary] = input.buttons[3]; + mapped->buttons[kButtonQuaternary] = input.buttons[0]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[6]; + mapped->buttons[kButtonRightShoulder] = input.buttons[7]; + mapped->buttons[kButtonLeftTrigger] = input.buttons[4]; + mapped->buttons[kButtonRightTrigger] = input.buttons[5]; + DpadFromAxis(mapped, input.axes[9]); + mapped->axes[kAxisRightStickX] = input.axes[5]; + mapped->axes[kAxisRightStickY] = input.axes[2]; + mapped->buttonsLength = kNumButtons - 1; /* no meta */ + mapped->axesLength = kNumAxes; +} + +void MapperSmartJoyPLUS( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonPrimary] = input.buttons[2]; + mapped->buttons[kButtonTertiary] = input.buttons[3]; + mapped->buttons[kButtonQuaternary] = input.buttons[0]; + mapped->buttons[kButtonStart] = input.buttons[8]; + mapped->buttons[kButtonBackSelect] = input.buttons[9]; + mapped->buttons[kButtonLeftShoulder] = input.buttons[6]; + mapped->buttons[kButtonRightShoulder] = input.buttons[7]; + mapped->buttons[kButtonLeftTrigger] = input.buttons[4]; + mapped->buttons[kButtonRightTrigger] = input.buttons[5]; + DpadFromAxis(mapped, input.axes[9]); + mapped->axes[kAxisRightStickY] = input.axes[5]; + mapped->buttonsLength = kNumButtons - 1; /* no meta */ + mapped->axesLength = kNumAxes; +} + +void MapperDragonRiseGeneric( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + DpadFromAxis(mapped, input.axes[9]); + mapped->axes[kAxisLeftStickX] = input.axes[0]; + mapped->axes[kAxisLeftStickY] = input.axes[1]; + mapped->axes[kAxisRightStickX] = input.axes[2]; + mapped->axes[kAxisRightStickY] = input.axes[5]; + mapped->buttonsLength = kNumButtons - 1; /* no meta */ + mapped->axesLength = kNumAxes; +} + +struct MappingData { + const char* const vendor_id; + const char* const product_id; + GamepadStandardMappingFunction function; +} AvailableMappings[] = { + // http://www.linux-usb.org/usb.ids + { "0079", "0006", MapperDragonRiseGeneric }, // DragonRise Generic USB + { "045e", "028e", MapperXbox360Gamepad }, // Xbox 360 Controller + { "045e", "028f", MapperXbox360Gamepad }, // Xbox 360 Wireless Controller + { "046d", "c216", MapperDirectInputStyle }, // Logitech F310, D mode + { "046d", "c218", MapperDirectInputStyle }, // Logitech F510, D mode + { "046d", "c219", MapperDirectInputStyle }, // Logitech F710, D mode + { "054c", "0268", MapperPlaystationSixAxis }, // Playstation SIXAXIS + { "0925", "0005", MapperSmartJoyPLUS }, // SmartJoy PLUS Adapter + { "0e8f", "0003", MapperXGEAR }, // XFXforce XGEAR PS2 Controller + { "2222", "0060", MapperDirectInputStyle }, // Macally iShockX, analog mode + { "2222", "4010", MapperMacallyIShock }, // Macally iShock +}; + +} // namespace + +GamepadStandardMappingFunction GetGamepadStandardMappingFunction( + const base::StringPiece& vendor_id, + const base::StringPiece& product_id) { + for (size_t i = 0; i < arraysize(AvailableMappings); ++i) { + MappingData& item = AvailableMappings[i]; + if (vendor_id == item.vendor_id && product_id == item.product_id) + return item.function; + } + return NULL; +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc b/chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc new file mode 100644 index 00000000000..fa57c032c77 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_standard_mappings_win.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_standard_mappings.h" + +#include "content/common/gamepad_hardware_buffer.h" + +namespace content { + +namespace { + +// Maps 0..65535 to -1..1. +float NormalizeDirectInputAxis(long value) { + return (value * 1.f / 32767.5f) - 1.f; +} + +float AxisNegativeAsButton(long value) { + return (value < 32767) ? 1.f : 0.f; +} + +float AxisPositiveAsButton(long value) { + return (value > 32767) ? 1.f : 0.f; +} + +void MapperDragonRiseGeneric( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[0] = input.buttons[1]; + mapped->buttons[1] = input.buttons[2]; + mapped->buttons[2] = input.buttons[0]; + mapped->buttons[12] = input.buttons[16]; + mapped->buttons[13] = input.buttons[17]; + mapped->buttons[14] = input.buttons[18]; + mapped->buttons[15] = input.buttons[19]; + mapped->buttonsLength = 16; + mapped->axes[0] = NormalizeDirectInputAxis(input.axes[0]); + mapped->axes[1] = NormalizeDirectInputAxis(input.axes[1]); + mapped->axes[2] = NormalizeDirectInputAxis(input.axes[2]); + mapped->axes[3] = NormalizeDirectInputAxis(input.axes[5]); + mapped->axesLength = 4; +} + +void MapperLogitechDualAction( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[0] = input.buttons[1]; + mapped->buttons[1] = input.buttons[2]; + mapped->buttons[2] = input.buttons[0]; + mapped->buttons[12] = input.buttons[16]; + mapped->buttons[13] = input.buttons[17]; + mapped->buttons[14] = input.buttons[18]; + mapped->buttons[15] = input.buttons[19]; + mapped->buttonsLength = 16; + mapped->axes[0] = NormalizeDirectInputAxis(input.axes[0]); + mapped->axes[1] = NormalizeDirectInputAxis(input.axes[1]); + mapped->axes[2] = NormalizeDirectInputAxis(input.axes[2]); + mapped->axes[3] = NormalizeDirectInputAxis(input.axes[5]); + mapped->axesLength = 4; +} + +void MapperLogitechPrecision( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[0] = input.buttons[1]; + mapped->buttons[1] = input.buttons[2]; + mapped->buttons[2] = input.buttons[0]; + mapped->buttons[kButtonLeftThumbstick] = 0; // Not present + mapped->buttons[kButtonRightThumbstick] = 0; // Not present + mapped->buttons[12] = AxisNegativeAsButton(input.axes[1]); + mapped->buttons[13] = AxisPositiveAsButton(input.axes[1]); + mapped->buttons[14] = AxisNegativeAsButton(input.axes[0]); + mapped->buttons[15] = AxisPositiveAsButton(input.axes[0]); + mapped->buttonsLength = 16; + mapped->axesLength = 0; +} + +void Mapper2Axes8Keys( + const WebKit::WebGamepad& input, + WebKit::WebGamepad* mapped) { + *mapped = input; + mapped->buttons[kButtonLeftTrigger] = 0; // Not present + mapped->buttons[kButtonRightTrigger] = 0; // Not present + mapped->buttons[8] = input.buttons[6]; + mapped->buttons[9] = input.buttons[7]; + mapped->buttons[kButtonLeftThumbstick] = 0; // Not present + mapped->buttons[kButtonRightThumbstick] = 0; // Not present + mapped->buttons[12] = AxisNegativeAsButton(input.axes[1]); + mapped->buttons[13] = AxisPositiveAsButton(input.axes[1]); + mapped->buttons[14] = AxisNegativeAsButton(input.axes[0]); + mapped->buttons[15] = AxisPositiveAsButton(input.axes[0]); + mapped->buttonsLength = 16; + mapped->axesLength = 0; +} + +struct MappingData { + const char* const vendor_id; + const char* const product_id; + GamepadStandardMappingFunction function; +} AvailableMappings[] = { + // http://www.linux-usb.org/usb.ids + { "0079", "0006", MapperDragonRiseGeneric }, // DragonRise Generic USB + { "046d", "c216", MapperLogitechDualAction }, // Logitech DualAction + { "046d", "c21a", MapperLogitechPrecision }, // Logitech Precision + { "12bd", "d012", Mapper2Axes8Keys }, // 2Axes 8Keys Game Pad +}; + +} // namespace + +GamepadStandardMappingFunction GetGamepadStandardMappingFunction( + const base::StringPiece& vendor_id, + const base::StringPiece& product_id) { + for (size_t i = 0; i < arraysize(AvailableMappings); ++i) { + MappingData& item = AvailableMappings[i]; + if (vendor_id == item.vendor_id && product_id == item.product_id) + return item.function; + } + return NULL; +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_test_helpers.cc b/chromium/content/browser/gamepad/gamepad_test_helpers.cc new file mode 100644 index 00000000000..e4db14ce080 --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_test_helpers.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_test_helpers.h" + +#include "content/browser/gamepad/gamepad_service.h" + +namespace content { + +MockGamepadDataFetcher::MockGamepadDataFetcher( + const WebKit::WebGamepads& test_data) + : test_data_(test_data), + read_data_(false, false) { +} + +MockGamepadDataFetcher::~MockGamepadDataFetcher() { +} + +void MockGamepadDataFetcher::GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) { + { + base::AutoLock lock(lock_); + *pads = test_data_; + } + read_data_.Signal(); +} + +void MockGamepadDataFetcher::WaitForDataRead() { + return read_data_.Wait(); +} + +void MockGamepadDataFetcher::SetTestData(const WebKit::WebGamepads& new_data) { + base::AutoLock lock(lock_); + test_data_ = new_data; +} + +GamepadTestHelper::GamepadTestHelper() { +} + +GamepadTestHelper::~GamepadTestHelper() { +} + +GamepadServiceTestConstructor::GamepadServiceTestConstructor( + const WebKit::WebGamepads& test_data) { + data_fetcher_ = new MockGamepadDataFetcher(test_data); + gamepad_service_ = + new GamepadService(scoped_ptr(data_fetcher_)); +} + +GamepadServiceTestConstructor::~GamepadServiceTestConstructor() { + delete gamepad_service_; +} + +} // namespace content diff --git a/chromium/content/browser/gamepad/gamepad_test_helpers.h b/chromium/content/browser/gamepad/gamepad_test_helpers.h new file mode 100644 index 00000000000..3b43f7787ff --- /dev/null +++ b/chromium/content/browser/gamepad/gamepad_test_helpers.h @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_TEST_HELPERS_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_TEST_HELPERS_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "third_party/WebKit/public/platform/WebGamepads.h" + +namespace content { + +class GamepadService; + +// Data fetcher that returns canned data for the gamepad provider. +class MockGamepadDataFetcher : public GamepadDataFetcher { + public: + // Initializes the fetcher with the given gamepad data, which will be + // returned when the provider queries us. + explicit MockGamepadDataFetcher(const WebKit::WebGamepads& test_data); + + virtual ~MockGamepadDataFetcher(); + + // GamepadDataFetcher. + virtual void GetGamepadData(WebKit::WebGamepads* pads, + bool devices_changed_hint) OVERRIDE; + + // Blocks the current thread until the GamepadProvider reads from this + // fetcher on the background thread. + void WaitForDataRead(); + + // Updates the test data. + void SetTestData(const WebKit::WebGamepads& new_data); + + private: + base::Lock lock_; + WebKit::WebGamepads test_data_; + base::WaitableEvent read_data_; + + DISALLOW_COPY_AND_ASSIGN(MockGamepadDataFetcher); +}; + +// Base class for the other test helpers. This just sets up the system monitor. +class GamepadTestHelper { + public: + GamepadTestHelper(); + virtual ~GamepadTestHelper(); + + base::MessageLoop& message_loop() { return message_loop_; } + + private: + // This must be constructed before the system monitor. + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(GamepadTestHelper); +}; + +// Constructs a GamepadService with a mock data source. This bypasses the +// global singleton for the gamepad service. +class GamepadServiceTestConstructor : public GamepadTestHelper { + public: + explicit GamepadServiceTestConstructor(const WebKit::WebGamepads& test_data); + virtual ~GamepadServiceTestConstructor(); + + GamepadService* gamepad_service() { return gamepad_service_; } + MockGamepadDataFetcher* data_fetcher() { return data_fetcher_; } + + private: + // Owning pointer (can't be a scoped_ptr due to private destructor). + GamepadService* gamepad_service_; + + // Pointer owned by the provider (which is owned by the gamepad service). + MockGamepadDataFetcher* data_fetcher_; + + DISALLOW_COPY_AND_ASSIGN(GamepadServiceTestConstructor); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_TEST_HELPERS_H_ diff --git a/chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc new file mode 100644 index 00000000000..6619b9b48f7 --- /dev/null +++ b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.cc @@ -0,0 +1,625 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/xbox_data_fetcher_mac.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/mac/foundation_util.h" + +namespace { +const int kVendorMicrosoft = 0x045e; +const int kProduct360Controller = 0x028e; + +const int kReadEndpoint = 1; +const int kControlEndpoint = 2; + +enum { + STATUS_MESSAGE_BUTTONS = 0, + STATUS_MESSAGE_LED = 1, + + // Apparently this message tells you if the rumble pack is disabled in the + // controller. If the rumble pack is disabled, vibration control messages + // have no effect. + STATUS_MESSAGE_RUMBLE = 3, +}; + +enum { + CONTROL_MESSAGE_SET_RUMBLE = 0, + CONTROL_MESSAGE_SET_LED = 1, +}; + +#pragma pack(push, 1) +struct ButtonData { + bool dpad_up : 1; + bool dpad_down : 1; + bool dpad_left : 1; + bool dpad_right : 1; + + bool start : 1; + bool back : 1; + bool stick_left_click : 1; + bool stick_right_click : 1; + + bool bumper_left : 1; + bool bumper_right : 1; + bool guide : 1; + bool dummy1 : 1; // Always 0. + + bool a : 1; + bool b : 1; + bool x : 1; + bool y : 1; + + uint8 trigger_left; + uint8 trigger_right; + + int16 stick_left_x; + int16 stick_left_y; + int16 stick_right_x; + int16 stick_right_y; + + // Always 0. + uint32 dummy2; + uint16 dummy3; +}; +#pragma pack(pop) + +COMPILE_ASSERT(sizeof(ButtonData) == 0x12, xbox_button_data_wrong_size); + +// From MSDN: +// http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).aspx#dead_zone +const int16 kLeftThumbDeadzone = 7849; +const int16 kRightThumbDeadzone = 8689; +const uint8 kTriggerDeadzone = 30; + +void NormalizeAxis(int16 x, + int16 y, + int16 deadzone, + float* x_out, + float* y_out) { + float x_val = x; + float y_val = y; + + // Determine how far the stick is pushed. + float real_magnitude = std::sqrt(x_val * x_val + y_val * y_val); + + // Check if the controller is outside a circular dead zone. + if (real_magnitude > deadzone) { + // Clip the magnitude at its expected maximum value. + float magnitude = std::min(32767.0f, real_magnitude); + + // Adjust magnitude relative to the end of the dead zone. + magnitude -= deadzone; + + // Normalize the magnitude with respect to its expected range giving a + // magnitude value of 0.0 to 1.0 + float ratio = (magnitude / (32767 - deadzone)) / real_magnitude; + + // Y is negated because xbox controllers have an opposite sign from + // the 'standard controller' recommendations. + *x_out = x_val * ratio; + *y_out = -y_val * ratio; + } else { + // If the controller is in the deadzone zero out the magnitude. + *x_out = *y_out = 0.0f; + } +} + +float NormalizeTrigger(uint8 value) { + return value < kTriggerDeadzone ? 0 : + static_cast(value - kTriggerDeadzone) / + (std::numeric_limits::max() - kTriggerDeadzone); +} + +void NormalizeButtonData(const ButtonData& data, + XboxController::Data* normalized_data) { + normalized_data->buttons[0] = data.a; + normalized_data->buttons[1] = data.b; + normalized_data->buttons[2] = data.x; + normalized_data->buttons[3] = data.y; + normalized_data->buttons[4] = data.bumper_left; + normalized_data->buttons[5] = data.bumper_right; + normalized_data->buttons[6] = data.back; + normalized_data->buttons[7] = data.start; + normalized_data->buttons[8] = data.stick_left_click; + normalized_data->buttons[9] = data.stick_right_click; + normalized_data->buttons[10] = data.dpad_up; + normalized_data->buttons[11] = data.dpad_down; + normalized_data->buttons[12] = data.dpad_left; + normalized_data->buttons[13] = data.dpad_right; + normalized_data->buttons[14] = data.guide; + normalized_data->triggers[0] = NormalizeTrigger(data.trigger_left); + normalized_data->triggers[1] = NormalizeTrigger(data.trigger_right); + NormalizeAxis(data.stick_left_x, + data.stick_left_y, + kLeftThumbDeadzone, + &normalized_data->axes[0], + &normalized_data->axes[1]); + NormalizeAxis(data.stick_right_x, + data.stick_right_y, + kRightThumbDeadzone, + &normalized_data->axes[2], + &normalized_data->axes[3]); +} + +} // namespace + +XboxController::XboxController(Delegate* delegate) + : device_(NULL), + interface_(NULL), + device_is_open_(false), + interface_is_open_(false), + read_buffer_size_(0), + led_pattern_(LED_NUM_PATTERNS), + location_id_(0), + delegate_(delegate) { +} + +XboxController::~XboxController() { + if (source_) + CFRunLoopSourceInvalidate(source_); + if (interface_ && interface_is_open_) + (*interface_)->USBInterfaceClose(interface_); + if (device_ && device_is_open_) + (*device_)->USBDeviceClose(device_); +} + +bool XboxController::OpenDevice(io_service_t service) { + IOCFPlugInInterface **plugin; + SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService. + kern_return_t kr = + IOCreatePlugInInterfaceForService(service, + kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugin, + &score); + if (kr != KERN_SUCCESS) + return false; + base::mac::ScopedIOPluginInterface plugin_ref(plugin); + + HRESULT res = + (*plugin)->QueryInterface(plugin, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID320), + (LPVOID *)&device_); + if (!SUCCEEDED(res) || !device_) + return false; + + UInt16 vendor_id; + kr = (*device_)->GetDeviceVendor(device_, &vendor_id); + if (kr != KERN_SUCCESS) + return false; + UInt16 product_id; + kr = (*device_)->GetDeviceProduct(device_, &product_id); + if (kr != KERN_SUCCESS) + return false; + if (vendor_id != kVendorMicrosoft || product_id != kProduct360Controller) + return false; + + // Open the device and configure it. + kr = (*device_)->USBDeviceOpen(device_); + if (kr != KERN_SUCCESS) + return false; + device_is_open_ = true; + + // Xbox controllers have one configuration option which has configuration + // value 1. Try to set it and fail if it couldn't be configured. + IOUSBConfigurationDescriptorPtr config_desc; + kr = (*device_)->GetConfigurationDescriptorPtr(device_, 0, &config_desc); + if (kr != KERN_SUCCESS) + return false; + kr = (*device_)->SetConfiguration(device_, config_desc->bConfigurationValue); + if (kr != KERN_SUCCESS) + return false; + + // The device has 4 interfaces. They are as follows: + // Protocol 1: + // - Endpoint 1 (in) : Controller events, including button presses. + // - Endpoint 2 (out): Rumble pack and LED control + // Protocol 2 has a single endpoint to read from a connected ChatPad device. + // Protocol 3 is used by a connected headset device. + // The device also has an interface on subclass 253, protocol 10 with no + // endpoints. It is unused. + // + // We don't currently support the ChatPad or headset, so protocol 1 is the + // only protocol we care about. + // + // For more detail, see + // https://github.com/Grumbel/xboxdrv/blob/master/PROTOCOL + IOUSBFindInterfaceRequest request; + request.bInterfaceClass = 255; + request.bInterfaceSubClass = 93; + request.bInterfaceProtocol = 1; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + io_iterator_t iter; + kr = (*device_)->CreateInterfaceIterator(device_, &request, &iter); + if (kr != KERN_SUCCESS) + return false; + base::mac::ScopedIOObject iter_ref(iter); + + // There should be exactly one USB interface which matches the requested + // settings. + io_service_t usb_interface = IOIteratorNext(iter); + if (!usb_interface) + return false; + + // We need to make an InterfaceInterface to communicate with the device + // endpoint. This is the same process as earlier: first make a + // PluginInterface from the io_service then make the InterfaceInterface from + // that. + IOCFPlugInInterface **plugin_interface; + kr = IOCreatePlugInInterfaceForService(usb_interface, + kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugin_interface, + &score); + if (kr != KERN_SUCCESS || !plugin_interface) + return false; + base::mac::ScopedIOPluginInterface interface_ref( + plugin_interface); + + // Release the USB interface, and any subsequent interfaces returned by the + // iterator. (There shouldn't be any, but in case a future device does + // contain more interfaces, this will serve to avoid memory leaks.) + do { + IOObjectRelease(usb_interface); + } while ((usb_interface = IOIteratorNext(iter))); + + // Actually create the interface. + res = (*plugin_interface)->QueryInterface( + plugin_interface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300), + (LPVOID *)&interface_); + + if (!SUCCEEDED(res) || !interface_) + return false; + + // Actually open the interface. + kr = (*interface_)->USBInterfaceOpen(interface_); + if (kr != KERN_SUCCESS) + return false; + interface_is_open_ = true; + + CFRunLoopSourceRef source_ref; + kr = (*interface_)->CreateInterfaceAsyncEventSource(interface_, &source_ref); + if (kr != KERN_SUCCESS || !source_ref) + return false; + source_.reset(source_ref); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopDefaultMode); + + // The interface should have two pipes. Pipe 1 with direction kUSBIn and pipe + // 2 with direction kUSBOut. Both pipes should have type kUSBInterrupt. + uint8 num_endpoints; + kr = (*interface_)->GetNumEndpoints(interface_, &num_endpoints); + if (kr != KERN_SUCCESS || num_endpoints < 2) + return false; + + for (int i = 1; i <= 2; i++) { + uint8 direction; + uint8 number; + uint8 transfer_type; + uint16 max_packet_size; + uint8 interval; + + kr = (*interface_)->GetPipeProperties(interface_, + i, + &direction, + &number, + &transfer_type, + &max_packet_size, + &interval); + if (kr != KERN_SUCCESS || transfer_type != kUSBInterrupt) + return false; + if (i == kReadEndpoint) { + if (direction != kUSBIn) + return false; + if (max_packet_size > 32) + return false; + read_buffer_.reset(new uint8[max_packet_size]); + read_buffer_size_ = max_packet_size; + QueueRead(); + } else if (i == kControlEndpoint) { + if (direction != kUSBOut) + return false; + } + } + + // The location ID is unique per controller, and can be used to track + // controllers through reconnections (though if a controller is detached from + // one USB hub and attached to another, the location ID will change). + kr = (*device_)->GetLocationID(device_, &location_id_); + if (kr != KERN_SUCCESS) + return false; + + return true; +} + +void XboxController::SetLEDPattern(LEDPattern pattern) { + led_pattern_ = pattern; + const UInt8 length = 3; + + // This buffer will be released in WriteComplete when WritePipeAsync + // finishes. + UInt8* buffer = new UInt8[length]; + buffer[0] = static_cast(CONTROL_MESSAGE_SET_LED); + buffer[1] = length; + buffer[2] = static_cast(pattern); + kern_return_t kr = (*interface_)->WritePipeAsync(interface_, + kControlEndpoint, + buffer, + (UInt32)length, + WriteComplete, + buffer); + if (kr != KERN_SUCCESS) { + delete[] buffer; + IOError(); + return; + } +} + +int XboxController::GetVendorId() const { + return kVendorMicrosoft; +} + +int XboxController::GetProductId() const { + return kProduct360Controller; +} + +void XboxController::WriteComplete(void* context, IOReturn result, void* arg0) { + UInt8* buffer = static_cast(context); + delete[] buffer; + + // Ignoring any errors sending data, because they will usually only occur + // when the device is disconnected, in which case it really doesn't matter if + // the data got to the controller or not. + if (result != kIOReturnSuccess) + return; +} + +void XboxController::GotData(void* context, IOReturn result, void* arg0) { + size_t bytes_read = reinterpret_cast(arg0); + XboxController* controller = static_cast(context); + + if (result != kIOReturnSuccess) { + // This will happen if the device was disconnected. The gamepad has + // probably been destroyed by a meteorite. + controller->IOError(); + return; + } + + controller->ProcessPacket(bytes_read); + + // Queue up another read. + controller->QueueRead(); +} + +void XboxController::ProcessPacket(size_t length) { + if (length < 2) return; + DCHECK(length <= read_buffer_size_); + if (length > read_buffer_size_) { + IOError(); + return; + } + uint8* buffer = read_buffer_.get(); + + if (buffer[1] != length) + // Length in packet doesn't match length reported by USB. + return; + + uint8 type = buffer[0]; + buffer += 2; + length -= 2; + switch (type) { + case STATUS_MESSAGE_BUTTONS: { + if (length != sizeof(ButtonData)) + return; + ButtonData* data = reinterpret_cast(buffer); + Data normalized_data; + NormalizeButtonData(*data, &normalized_data); + delegate_->XboxControllerGotData(this, normalized_data); + break; + } + case STATUS_MESSAGE_LED: + if (length != 3) + return; + // The controller sends one of these messages every time the LED pattern + // is set, as well as once when it is plugged in. + if (led_pattern_ == LED_NUM_PATTERNS && buffer[0] < LED_NUM_PATTERNS) + led_pattern_ = static_cast(buffer[0]); + break; + default: + // Unknown packet: ignore! + break; + } +} + +void XboxController::QueueRead() { + kern_return_t kr = (*interface_)->ReadPipeAsync(interface_, + kReadEndpoint, + read_buffer_.get(), + read_buffer_size_, + GotData, + this); + if (kr != KERN_SUCCESS) + IOError(); +} + +void XboxController::IOError() { + delegate_->XboxControllerError(this); +} + +//----------------------------------------------------------------------------- + +XboxDataFetcher::XboxDataFetcher(Delegate* delegate) + : delegate_(delegate), + listening_(false), + source_(NULL), + port_(NULL) { +} + +XboxDataFetcher::~XboxDataFetcher() { + while (!controllers_.empty()) { + RemoveController(*controllers_.begin()); + } + UnregisterFromNotifications(); +} + +void XboxDataFetcher::DeviceAdded(void* context, io_iterator_t iterator) { + DCHECK(context); + XboxDataFetcher* fetcher = static_cast(context); + io_service_t ref; + while ((ref = IOIteratorNext(iterator))) { + base::mac::ScopedIOObject scoped_ref(ref); + XboxController* controller = new XboxController(fetcher); + if (controller->OpenDevice(ref)) { + fetcher->AddController(controller); + } else { + delete controller; + } + } +} + +void XboxDataFetcher::DeviceRemoved(void* context, io_iterator_t iterator) { + DCHECK(context); + XboxDataFetcher* fetcher = static_cast(context); + io_service_t ref; + while ((ref = IOIteratorNext(iterator))) { + base::mac::ScopedIOObject scoped_ref(ref); + base::ScopedCFTypeRef number( + base::mac::CFCastStrict( + IORegistryEntryCreateCFProperty(ref, + CFSTR(kUSBDevicePropertyLocationID), + kCFAllocatorDefault, + kNilOptions))); + UInt32 location_id = 0; + CFNumberGetValue(number, kCFNumberSInt32Type, &location_id); + fetcher->RemoveControllerByLocationID(location_id); + } +} + +bool XboxDataFetcher::RegisterForNotifications() { + if (listening_) + return true; + base::ScopedCFTypeRef vendor_cf(CFNumberCreate( + kCFAllocatorDefault, kCFNumberSInt32Type, &kVendorMicrosoft)); + base::ScopedCFTypeRef product_cf(CFNumberCreate( + kCFAllocatorDefault, kCFNumberSInt32Type, &kProduct360Controller)); + base::ScopedCFTypeRef matching_dict( + IOServiceMatching(kIOUSBDeviceClassName)); + if (!matching_dict) + return false; + CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID), vendor_cf); + CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID), product_cf); + port_ = IONotificationPortCreate(kIOMasterPortDefault); + if (!port_) + return false; + source_ = IONotificationPortGetRunLoopSource(port_); + if (!source_) + return false; + CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopDefaultMode); + + listening_ = true; + + // IOServiceAddMatchingNotification() releases the dictionary when it's done. + // Retain it before each call to IOServiceAddMatchingNotification to keep + // things balanced. + CFRetain(matching_dict); + io_iterator_t device_added_iter; + IOReturn ret; + ret = IOServiceAddMatchingNotification(port_, + kIOFirstMatchNotification, + matching_dict, + DeviceAdded, + this, + &device_added_iter); + device_added_iter_.reset(device_added_iter); + if (ret != kIOReturnSuccess) { + LOG(ERROR) << "Error listening for Xbox controller add events: " << ret; + return false; + } + DeviceAdded(this, device_added_iter_.get()); + + CFRetain(matching_dict); + io_iterator_t device_removed_iter; + ret = IOServiceAddMatchingNotification(port_, + kIOTerminatedNotification, + matching_dict, + DeviceRemoved, + this, + &device_removed_iter); + device_removed_iter_.reset(device_removed_iter); + if (ret != kIOReturnSuccess) { + LOG(ERROR) << "Error listening for Xbox controller remove events: " << ret; + return false; + } + DeviceRemoved(this, device_removed_iter_.get()); + return true; +} + +void XboxDataFetcher::UnregisterFromNotifications() { + if (!listening_) + return; + listening_ = false; + if (source_) + CFRunLoopSourceInvalidate(source_); + if (port_) + IONotificationPortDestroy(port_); + port_ = NULL; +} + +XboxController* XboxDataFetcher::ControllerForLocation(UInt32 location_id) { + for (std::set::iterator i = controllers_.begin(); + i != controllers_.end(); + ++i) { + if ((*i)->location_id() == location_id) + return *i; + } + return NULL; +} + +void XboxDataFetcher::AddController(XboxController* controller) { + DCHECK(!ControllerForLocation(controller->location_id())) + << "Controller with location ID " << controller->location_id() + << " already exists in the set of controllers."; + controllers_.insert(controller); + delegate_->XboxDeviceAdd(controller); +} + +void XboxDataFetcher::RemoveController(XboxController* controller) { + delegate_->XboxDeviceRemove(controller); + controllers_.erase(controller); + delete controller; +} + +void XboxDataFetcher::RemoveControllerByLocationID(uint32 location_id) { + XboxController* controller = NULL; + for (std::set::iterator i = controllers_.begin(); + i != controllers_.end(); + ++i) { + if ((*i)->location_id() == location_id) { + controller = *i; + break; + } + } + if (controller) + RemoveController(controller); +} + +void XboxDataFetcher::XboxControllerGotData(XboxController* controller, + const XboxController::Data& data) { + delegate_->XboxValueChanged(controller, data); +} + +void XboxDataFetcher::XboxControllerError(XboxController* controller) { + RemoveController(controller); +} diff --git a/chromium/content/browser/gamepad/xbox_data_fetcher_mac.h b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.h new file mode 100644 index 00000000000..ca8f7fcd88c --- /dev/null +++ b/chromium/content/browser/gamepad/xbox_data_fetcher_mac.h @@ -0,0 +1,167 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_GAMEPAD_XBOX_DATA_FETCHER_MAC_H_ +#define CONTENT_BROWSER_GAMEPAD_XBOX_DATA_FETCHER_MAC_H_ + +#include +#include + +#include + +#include "base/basictypes.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_ioobject.h" +#include "base/mac/scoped_ioplugininterface.h" +#include "base/memory/scoped_ptr.h" + +class XboxController { + public: + enum LEDPattern { + LED_OFF = 0, + + // 2 quick flashes, then a series of slow flashes (about 1 per second). + LED_FLASH = 1, + + // Flash three times then hold the LED on. This is the standard way to tell + // the player which player number they are. + LED_FLASH_TOP_LEFT = 2, + LED_FLASH_TOP_RIGHT = 3, + LED_FLASH_BOTTOM_LEFT = 4, + LED_FLASH_BOTTOM_RIGHT = 5, + + // Simply turn on the specified LED and turn all other LEDs off. + LED_HOLD_TOP_LEFT = 6, + LED_HOLD_TOP_RIGHT = 7, + LED_HOLD_BOTTOM_LEFT = 8, + LED_HOLD_BOTTOM_RIGHT = 9, + + LED_ROTATE = 10, + + LED_FLASH_FAST = 11, + LED_FLASH_SLOW = 12, // Flash about once per 3 seconds + + // Flash alternating LEDs for a few seconds, then flash all LEDs about once + // per second + LED_ALTERNATE_PATTERN = 13, + + // 14 is just another boring flashing speed. + + // Flash all LEDs once then go black. + LED_FLASH_ONCE = 15, + + LED_NUM_PATTERNS + }; + + struct Data { + bool buttons[15]; + float triggers[2]; + float axes[4]; + }; + + class Delegate { + public: + virtual void XboxControllerGotData(XboxController* controller, + const Data& data) = 0; + virtual void XboxControllerError(XboxController* controller) = 0; + }; + + explicit XboxController(Delegate* delegate_); + virtual ~XboxController(); + + bool OpenDevice(io_service_t service); + + void SetLEDPattern(LEDPattern pattern); + + UInt32 location_id() { return location_id_; } + int GetVendorId() const; + int GetProductId() const; + + private: + static void WriteComplete(void* context, IOReturn result, void* arg0); + static void GotData(void* context, IOReturn result, void* arg0); + + void ProcessPacket(size_t length); + void QueueRead(); + + void IOError(); + + // Handle for the USB device. IOUSBDeviceStruct320 is the latest version of + // the device API that is supported on Mac OS 10.6. + base::mac::ScopedIOPluginInterface device_; + + // Handle for the interface on the device which sends button and analog data. + // The other interfaces (for the ChatPad and headset) are ignored. + base::mac::ScopedIOPluginInterface interface_; + + bool device_is_open_; + bool interface_is_open_; + + base::ScopedCFTypeRef source_; + + // This will be set to the max packet size reported by the interface, which + // is 32 bytes. I would have expected USB to do message framing itself, but + // somehow we still sometimes (rarely!) get packets off the interface which + // aren't correctly framed. The 360 controller frames its packets with a 2 + // byte header (type, total length) so we can reframe the packet data + // ourselves. + uint16 read_buffer_size_; + scoped_ptr read_buffer_; + + // The pattern that the LEDs on the device are currently displaying, or + // LED_NUM_PATTERNS if unknown. + LEDPattern led_pattern_; + + UInt32 location_id_; + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(XboxController); +}; + +class XboxDataFetcher : public XboxController::Delegate { + public: + class Delegate { + public: + virtual void XboxDeviceAdd(XboxController* device) = 0; + virtual void XboxDeviceRemove(XboxController* device) = 0; + virtual void XboxValueChanged(XboxController* device, + const XboxController::Data& data) = 0; + }; + + explicit XboxDataFetcher(Delegate* delegate); + virtual ~XboxDataFetcher(); + + bool RegisterForNotifications(); + void UnregisterFromNotifications(); + + XboxController* ControllerForLocation(UInt32 location_id); + + private: + static void DeviceAdded(void* context, io_iterator_t iterator); + static void DeviceRemoved(void* context, io_iterator_t iterator); + void AddController(XboxController* controller); + void RemoveController(XboxController* controller); + void RemoveControllerByLocationID(uint32 id); + virtual void XboxControllerGotData(XboxController* controller, + const XboxController::Data& data) OVERRIDE; + virtual void XboxControllerError(XboxController* controller) OVERRIDE; + + Delegate* delegate_; + + std::set controllers_; + + bool listening_; + + // port_ owns source_, so this doesn't need to be a ScopedCFTypeRef, but we + // do need to maintain a reference to it so we can invalidate it. + CFRunLoopSourceRef source_; + IONotificationPortRef port_; + base::mac::ScopedIOObject device_added_iter_; + base::mac::ScopedIOObject device_removed_iter_; + + DISALLOW_COPY_AND_ASSIGN(XboxDataFetcher); +}; + +#endif // CONTENT_BROWSER_GAMEPAD_XBOX_DATA_FETCHER_MAC_H_ -- cgit v1.2.1