1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/gamepad/gamepad_platform_data_fetcher_linux.h" 6 7 #include <fcntl.h> 8 #include <libudev.h> 9 #include <linux/joystick.h> 10 #include <string.h> 11 #include <sys/stat.h> 12 #include <sys/types.h> 13 #include <unistd.h> 14 15 #include "base/debug/trace_event.h" 16 #include "base/message_loop/message_loop.h" 17 #include "base/posix/eintr_wrapper.h" 18 #include "base/strings/string_number_conversions.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "content/browser/udev_linux.h" 23 24 namespace { 25 26 const char kInputSubsystem[] = "input"; 27 const char kUsbSubsystem[] = "usb"; 28 const char kUsbDeviceType[] = "usb_device"; 29 const float kMaxLinuxAxisValue = 32767.0; 30 31 void CloseFileDescriptorIfValid(int fd) { 32 if (fd >= 0) 33 close(fd); 34 } 35 36 bool IsGamepad(udev_device* dev, int* index, std::string* path) { 37 if (!udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK")) 38 return false; 39 40 const char* node_path = udev_device_get_devnode(dev); 41 if (!node_path) 42 return false; 43 44 static const char kJoystickRoot[] = "/dev/input/js"; 45 bool is_gamepad = StartsWithASCII(node_path, kJoystickRoot, true); 46 if (!is_gamepad) 47 return false; 48 49 int tmp_idx = -1; 50 const int base_len = sizeof(kJoystickRoot) - 1; 51 base::StringPiece str(&node_path[base_len], strlen(node_path) - base_len); 52 if (!base::StringToInt(str, &tmp_idx)) 53 return false; 54 if (tmp_idx < 0 || 55 tmp_idx >= static_cast<int>(blink::WebGamepads::itemsLengthCap)) { 56 return false; 57 } 58 *index = tmp_idx; 59 *path = node_path; 60 return true; 61 } 62 63 } // namespace 64 65 namespace content { 66 67 using blink::WebGamepad; 68 using blink::WebGamepads; 69 70 GamepadPlatformDataFetcherLinux::GamepadPlatformDataFetcherLinux() { 71 for (size_t i = 0; i < arraysize(device_fds_); ++i) 72 device_fds_[i] = -1; 73 memset(mappers_, 0, sizeof(mappers_)); 74 75 std::vector<UdevLinux::UdevMonitorFilter> filters; 76 filters.push_back(UdevLinux::UdevMonitorFilter(kInputSubsystem, NULL)); 77 udev_.reset( 78 new UdevLinux(filters, 79 base::Bind(&GamepadPlatformDataFetcherLinux::RefreshDevice, 80 base::Unretained(this)))); 81 82 EnumerateDevices(); 83 } 84 85 GamepadPlatformDataFetcherLinux::~GamepadPlatformDataFetcherLinux() { 86 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) 87 CloseFileDescriptorIfValid(device_fds_[i]); 88 } 89 90 void GamepadPlatformDataFetcherLinux::GetGamepadData(WebGamepads* pads, bool) { 91 TRACE_EVENT0("GAMEPAD", "GetGamepadData"); 92 93 data_.length = WebGamepads::itemsLengthCap; 94 95 // Update our internal state. 96 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { 97 if (device_fds_[i] >= 0) { 98 ReadDeviceData(i); 99 } 100 } 101 102 // Copy to the current state to the output buffer, using the mapping 103 // function, if there is one available. 104 pads->length = data_.length; 105 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { 106 if (mappers_[i]) 107 mappers_[i](data_.items[i], &pads->items[i]); 108 else 109 pads->items[i] = data_.items[i]; 110 } 111 } 112 113 // Used during enumeration, and monitor notifications. 114 void GamepadPlatformDataFetcherLinux::RefreshDevice(udev_device* dev) { 115 int index; 116 std::string node_path; 117 if (IsGamepad(dev, &index, &node_path)) { 118 int& device_fd = device_fds_[index]; 119 WebGamepad& pad = data_.items[index]; 120 GamepadStandardMappingFunction& mapper = mappers_[index]; 121 122 CloseFileDescriptorIfValid(device_fd); 123 124 // The device pointed to by dev contains information about the logical 125 // joystick device. In order to get the information about the physical 126 // hardware, get the parent device that is also in the "input" subsystem. 127 // This function should just walk up the tree one level. 128 dev = udev_device_get_parent_with_subsystem_devtype( 129 dev, 130 kInputSubsystem, 131 NULL); 132 if (!dev) { 133 // Unable to get device information, don't use this device. 134 device_fd = -1; 135 pad.connected = false; 136 return; 137 } 138 139 device_fd = HANDLE_EINTR(open(node_path.c_str(), O_RDONLY | O_NONBLOCK)); 140 if (device_fd < 0) { 141 // Unable to open device, don't use. 142 pad.connected = false; 143 return; 144 } 145 146 const char* vendor_id = udev_device_get_sysattr_value(dev, "id/vendor"); 147 const char* product_id = udev_device_get_sysattr_value(dev, "id/product"); 148 mapper = GetGamepadStandardMappingFunction(vendor_id, product_id); 149 150 // Driver returns utf-8 strings here, so combine in utf-8 first and 151 // convert to WebUChar later once we've picked an id string. 152 const char* name = udev_device_get_sysattr_value(dev, "name"); 153 std::string name_string = base::StringPrintf("%s", name); 154 155 // In many cases the information the input subsystem contains isn't 156 // as good as the information that the device bus has, walk up further 157 // to the subsystem/device type "usb"/"usb_device" and if this device 158 // has the same vendor/product id, prefer the description from that. 159 struct udev_device *usb_dev = udev_device_get_parent_with_subsystem_devtype( 160 dev, 161 kUsbSubsystem, 162 kUsbDeviceType); 163 if (usb_dev) { 164 const char* usb_vendor_id = 165 udev_device_get_sysattr_value(usb_dev, "idVendor"); 166 const char* usb_product_id = 167 udev_device_get_sysattr_value(usb_dev, "idProduct"); 168 169 if (strcmp(vendor_id, usb_vendor_id) == 0 && 170 strcmp(product_id, usb_product_id) == 0) { 171 const char* manufacturer = 172 udev_device_get_sysattr_value(usb_dev, "manufacturer"); 173 const char* product = udev_device_get_sysattr_value(usb_dev, "product"); 174 175 // Replace the previous name string with one containing the better 176 // information, again driver returns utf-8 strings here so combine 177 // in utf-8 for conversion to WebUChar below. 178 name_string = base::StringPrintf("%s %s", manufacturer, product); 179 } 180 } 181 182 // Append the vendor and product information then convert the utf-8 183 // id string to WebUChar. 184 std::string id = name_string + base::StringPrintf( 185 " (%sVendor: %s Product: %s)", 186 mapper ? "STANDARD GAMEPAD " : "", 187 vendor_id, 188 product_id); 189 base::TruncateUTF8ToByteSize(id, WebGamepad::idLengthCap - 1, &id); 190 base::string16 tmp16 = base::UTF8ToUTF16(id); 191 memset(pad.id, 0, sizeof(pad.id)); 192 tmp16.copy(pad.id, arraysize(pad.id) - 1); 193 194 if (mapper) { 195 std::string mapping = "standard"; 196 base::TruncateUTF8ToByteSize(mapping, WebGamepad::mappingLengthCap - 1, 197 &mapping); 198 tmp16 = base::UTF8ToUTF16(mapping); 199 memset(pad.mapping, 0, sizeof(pad.mapping)); 200 tmp16.copy(pad.mapping, arraysize(pad.mapping) - 1); 201 } else { 202 pad.mapping[0] = 0; 203 } 204 205 pad.connected = true; 206 } 207 } 208 209 void GamepadPlatformDataFetcherLinux::EnumerateDevices() { 210 udev_enumerate* enumerate = udev_enumerate_new(udev_->udev_handle()); 211 if (!enumerate) 212 return; 213 int ret = udev_enumerate_add_match_subsystem(enumerate, kInputSubsystem); 214 if (ret != 0) 215 return; 216 ret = udev_enumerate_scan_devices(enumerate); 217 if (ret != 0) 218 return; 219 220 udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate); 221 for (udev_list_entry* dev_list_entry = devices; 222 dev_list_entry != NULL; 223 dev_list_entry = udev_list_entry_get_next(dev_list_entry)) { 224 // Get the filename of the /sys entry for the device and create a 225 // udev_device object (dev) representing it 226 const char* path = udev_list_entry_get_name(dev_list_entry); 227 udev_device* dev = udev_device_new_from_syspath(udev_->udev_handle(), path); 228 if (!dev) 229 continue; 230 RefreshDevice(dev); 231 udev_device_unref(dev); 232 } 233 // Free the enumerator object 234 udev_enumerate_unref(enumerate); 235 } 236 237 void GamepadPlatformDataFetcherLinux::ReadDeviceData(size_t index) { 238 // Linker does not like CHECK_LT(index, WebGamepads::itemsLengthCap). =/ 239 if (index >= WebGamepads::itemsLengthCap) { 240 CHECK(false); 241 return; 242 } 243 244 const int& fd = device_fds_[index]; 245 WebGamepad& pad = data_.items[index]; 246 DCHECK_GE(fd, 0); 247 248 js_event event; 249 while (HANDLE_EINTR(read(fd, &event, sizeof(struct js_event))) > 0) { 250 size_t item = event.number; 251 if (event.type & JS_EVENT_AXIS) { 252 if (item >= WebGamepad::axesLengthCap) 253 continue; 254 pad.axes[item] = event.value / kMaxLinuxAxisValue; 255 if (item >= pad.axesLength) 256 pad.axesLength = item + 1; 257 } else if (event.type & JS_EVENT_BUTTON) { 258 if (item >= WebGamepad::buttonsLengthCap) 259 continue; 260 pad.buttons[item].pressed = event.value; 261 pad.buttons[item].value = event.value ? 1.0 : 0.0; 262 if (item >= pad.buttonsLength) 263 pad.buttonsLength = item + 1; 264 } 265 pad.timestamp = event.time; 266 } 267 } 268 269 } // namespace content 270