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

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

Issue 8899017: Add gamepad data fetcher for Linux (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: better so load Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/data_fetcher_linux.h"
6
7 #include "base/debug/trace_event.h"
8 #include "base/string_util.h"
9 #include "base/utf_string_conversions.h"
10 #include "content/common/gamepad_hardware_buffer.h"
11
12 #include <dlfcn.h>
13 #include <fcntl.h>
14 #include <linux/joystick.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17
18 namespace content {
19
20 using WebKit::WebGamepad;
21 using WebKit::WebGamepads;
22
23 // We rely on libudev to be available to notify us about device
24 // insertions/removals, and to get device USB vendor/product information. We
25 // try to dynamically load it here because it may not always be available. If
26 // it isn't, we simply disable all gamepad functionality. This late-binding
27 // apparatus is based on a simplified version of libjingle's
28 // linuxdevicefetcher.cc. We declare the symbols we'll use up front, load the
29 // .so and the symbols out of it at initialization time, and then retrieve
30 // them by index using the LATE(...) macro for (fairly) tidy callsites.
31
32 #define LIBUDEV_SYMBOLS_IN_USE \
33 X(udev_device_get_action) \
34 X(udev_device_get_devnode) \
35 X(udev_device_get_devtype) \
36 X(udev_device_get_parent_with_subsystem_devtype) \
37 X(udev_device_get_property_value) \
38 X(udev_device_get_subsystem) \
39 X(udev_device_get_sysattr_value) \
40 X(udev_device_new_from_syspath) \
41 X(udev_device_unref) \
42 X(udev_enumerate_add_match_subsystem) \
43 X(udev_enumerate_get_list_entry) \
44 X(udev_enumerate_new) \
45 X(udev_enumerate_scan_devices) \
46 X(udev_enumerate_unref) \
47 X(udev_list_entry_get_name) \
48 X(udev_list_entry_get_next) \
49 X(udev_monitor_enable_receiving) \
50 X(udev_monitor_filter_add_match_subsystem_devtype) \
51 X(udev_monitor_get_fd) \
52 X(udev_monitor_new_from_netlink) \
53 X(udev_monitor_receive_device) \
54 X(udev_monitor_unref) \
55 X(udev_new) \
56 X(udev_unref)
57
58 enum LibUdevSymbolTable {
59 #define X(sym) LibUdevSymbolTable_INDEX_##sym,
60 LIBUDEV_SYMBOLS_IN_USE
61 #undef X
62 LibUdevSymbolTable_SIZE
63 };
64
65 class LibUdevLateBound {
66 public:
67 static LibUdevLateBound* Load() {
68 void* handle = dlopen("libudev.so.0", RTLD_NOW);
69 if (!handle)
70 return NULL;
71 LibUdevLateBound* result = new LibUdevLateBound(handle);
72 #define X(sym) result->symbols_[LibUdevSymbolTable_INDEX_##sym] = \
73 dlsym(result->handle_, #sym); \
74 if (!result->symbols_[LibUdevSymbolTable_INDEX_##sym]) { \
75 delete result; \
76 return NULL; \
77 }
78 LIBUDEV_SYMBOLS_IN_USE
79 #undef X
80 return result;
81 }
82
83 void* GetByIndex(size_t index) {
84 return symbols_[index];
85 }
86
87 private:
88 friend class scoped_ptr<LibUdevLateBound>;
89
90 ~LibUdevLateBound() {
91 dlclose(handle_);
92 }
93
94 LibUdevLateBound(void* handle) : handle_(handle) {
95 }
96
97 void* handle_;
98 void* symbols_[LibUdevSymbolTable_SIZE];
99
100 DISALLOW_COPY_AND_ASSIGN(LibUdevLateBound);
101 };
102
103 #define LATE(sym) (*reinterpret_cast<typeof(&sym)>(udevlib_->GetByIndex( \
104 LibUdevSymbolTable_INDEX_##sym)))
105
106 GamepadDataFetcherLinux::GamepadDataFetcherLinux()
107 : udevlib_(LibUdevLateBound::Load()) {
108 if (!udevlib_.get())
109 return;
110
111 for (size_t i = 0; i < arraysize(device_fds_); ++i)
112 device_fds_[i] = -1;
113
114 udev_ = LATE(udev_new)();
115
116 monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
117 LATE(udev_monitor_filter_add_match_subsystem_devtype)(
118 monitor_,
119 "input",
120 NULL);
121 LATE(udev_monitor_enable_receiving)(monitor_);
122 monitor_fd_ = LATE(udev_monitor_get_fd)(monitor_);
123
124 EnumerateDevices();
125 }
126
127 bool GamepadDataFetcherLinux::IsGamepad(
128 udev_device* dev,
129 size_t& index,
130 std::string& path) {
131 if (!LATE(udev_device_get_property_value)(dev, "ID_INPUT_JOYSTICK"))
132 return false;
133
134 const char* node_path = LATE(udev_device_get_devnode)(dev);
135 if (!node_path)
136 return false;
137
138 static const char kJoystickRoot[] = "/dev/input/js";
139 bool is = strncmp(kJoystickRoot, node_path, sizeof(kJoystickRoot) - 1) == 0;
140 if (is) {
141 index = node_path[sizeof(kJoystickRoot) - 1] - '0';
142 if (index < 0 || index >= WebGamepads::itemsLengthCap)
143 return false;
144 path = std::string(node_path);
145 }
146 return is;
147 }
148
149 // Used during enumeration, and monitor notifications.
150 void GamepadDataFetcherLinux::RefreshDevice(udev_device* dev) {
151 size_t index;
152 std::string node_path;
153 if (IsGamepad(dev, index, node_path)) {
154 int& device_fd = device_fds_[index];
155 WebGamepad& pad = data_.items[index];
156 GamepadStandardMappingFunction& mapper = mappers_[index];
157
158 // The device pointed to by dev contains information about the input
159 // device. In order to get the information about the USB device, get the
160 // parent device with the subsystem/devtype pair of "usb"/"usb_device".
161 // This function walks up the tree several levels.
162 dev = LATE(udev_device_get_parent_with_subsystem_devtype)(
163 dev,
164 "usb",
165 "usb_device");
166 if (!dev) {
167 // Unable to get device information, don't use this device.
168 device_fd = -1;
169 pad.connected = false;
170 return;
171 }
172
173 device_fd = open(node_path.c_str(), O_RDONLY | O_NONBLOCK);
174 if (device_fd < 0) {
175 // Unable to open device, don't use.
176 pad.connected = false;
177 return;
178 }
179
180 const char* vendor_id =
181 LATE(udev_device_get_sysattr_value)(dev, "idVendor");
182 const char* product_id =
183 LATE(udev_device_get_sysattr_value)(dev, "idProduct");
184 mapper = GetGamepadStandardMappingFunction(vendor_id, product_id);
185
186 const char* manufacturer =
187 LATE(udev_device_get_sysattr_value)(dev, "manufacturer");
188 const char* product = LATE(udev_device_get_sysattr_value)(dev, "product");
189
190 // Driver returns utf-8 strings here, so combine in utf-8 and then convert
191 // to WebUChar to build the id string.
192 char tmp[WebGamepad::idLengthCap];
193 if (mapper) {
194 base::snprintf(tmp,
195 sizeof(tmp),
196 "%s %s (STANDARD GAMEPAD)",
197 manufacturer,
198 product);
199 } else {
200 base::snprintf(tmp,
201 sizeof(tmp),
202 "%s %s (Vendor: %s Product: %s)",
203 manufacturer,
204 product,
205 vendor_id,
206 product_id);
207 }
208 string16 tmp16 = UTF8ToUTF16(tmp);
209 memset(pad.id, 0, sizeof(pad.id));
210 tmp16.copy(pad.id, sizeof(pad.id) - 1);
211
212 pad.connected = true;
213 }
214 }
215
216 void GamepadDataFetcherLinux::EnumerateDevices() {
217 udev_enumerate* enumerate = LATE(udev_enumerate_new)(udev_);
218 LATE(udev_enumerate_add_match_subsystem)(enumerate, "input");
219 LATE(udev_enumerate_scan_devices)(enumerate);
220
221 udev_list_entry* devices = LATE(udev_enumerate_get_list_entry)(enumerate);
222 for (udev_list_entry* dev_list_entry = devices;
223 dev_list_entry != NULL;
224 dev_list_entry = LATE(udev_list_entry_get_next)(dev_list_entry)) {
225 // Get the filename of the /sys entry for the device and create a
226 // udev_device object (dev) representing it
227 const char* path = LATE(udev_list_entry_get_name)(dev_list_entry);
228 udev_device* dev = LATE(udev_device_new_from_syspath)(udev_, path);
229 RefreshDevice(dev);
230 LATE(udev_device_unref)(dev);
231 }
232 // Free the enumerator object
233 LATE(udev_enumerate_unref)(enumerate);
234 }
235
236 GamepadDataFetcherLinux::~GamepadDataFetcherLinux() {
237 LATE(udev_unref)(udev_);
238 }
239
240 void GamepadDataFetcherLinux::CheckForAddRemoveEvents() {
241 // Poll for udev events. Events occur when devices attached to the system
242 // are added, removed, or change state. udev_monitor_receive_device() will
243 // return a device object representing the device which changed and what
244 // type of change occured.
245 //
246 // The select() system call is used to ensure that the call to
247 // udev_monitor_receive_device() will not block.
248 fd_set fds;
249 struct timeval tv;
250 FD_ZERO(&fds);
251 FD_SET(monitor_fd_, &fds);
252 tv.tv_sec = 0;
253 tv.tv_usec = 0;
254
255 // Check if our file descriptor has received data.
256 if (select(monitor_fd_ + 1, &fds, NULL, NULL, &tv) > 0
257 && FD_ISSET(monitor_fd_, &fds)) {
258 // Make the call to receive the device.
259 // select() ensured that this will not block.
260 udev_device* dev = LATE(udev_monitor_receive_device)(monitor_);
261 RefreshDevice(dev);
262 LATE(udev_device_unref)(dev);
263 }
264 }
265
266 void GamepadDataFetcherLinux::ReadDeviceData(size_t index) {
267 int& fd = device_fds_[index];
268 WebGamepad& pad = data_.items[index];
269 DCHECK(fd >= 0);
270
271 js_event event;
272 while (read(fd, &event, sizeof(struct js_event)) > 0) {
273 size_t item = event.number;
274 if (event.type & JS_EVENT_AXIS) {
275 if (item >= WebGamepad::axesLengthCap)
276 continue;
277 pad.axes[item] = event.value / 32767.f;
278 if (item >= pad.axesLength)
279 pad.axesLength = item + 1;
280 } else if (event.type & JS_EVENT_BUTTON) {
281 if (item >= WebGamepad::buttonsLengthCap)
282 continue;
283 pad.buttons[item] = event.value ? 1.0 : 0.0;
284 if (item >= pad.buttonsLength)
285 pad.buttonsLength = item + 1;
286 }
287 pad.timestamp = event.time;
288 }
289 }
290
291 void GamepadDataFetcherLinux::GetGamepadData(WebGamepads* pads, bool) {
292 TRACE_EVENT0("GAMEPAD", "GetGamepadData");
293
294 // If we were unable to load libudev then we are disabled, simply return no
295 // data.
296 if (!udevlib_.get()) {
297 pads->length = 0;
298 return;
299 }
300
301 data_.length = WebGamepads::itemsLengthCap;
302
303 CheckForAddRemoveEvents();
304
305 // Update our internal state.
306 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
307 if (device_fds_[i] >= 0) {
308 ReadDeviceData(i);
309 }
310 }
311
312 // Copy to the current state to the output buffer, using the mapping
313 // function, if there is one available.
314 pads->length = data_.length;
315 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
316 if (mappers_[i])
317 mappers_[i](data_.items[i], &pads->items[i]);
318 else
319 pads->items[i] = data_.items[i];
320 }
321 }
322
323 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698