linux_joystick.cc (12147B)
1 //======================================================================== 2 // GLFW 3.3 Linux - www.glfw.org 3 //------------------------------------------------------------------------ 4 // Copyright (c) 2002-2006 Marcus Geelnard 5 // Copyright (c) 2006-2016 Camilla Löwy <elmindreda@glfw.org> 6 // 7 // This software is provided 'as-is', without any express or implied 8 // warranty. In no event will the authors be held liable for any damages 9 // arising from the use of this software. 10 // 11 // Permission is granted to anyone to use this software for any purpose, 12 // including commercial applications, and to alter it and redistribute it 13 // freely, subject to the following restrictions: 14 // 15 // 1. The origin of this software must not be misrepresented; you must not 16 // claim that you wrote the original software. If you use this software 17 // in a product, an acknowledgment in the product documentation would 18 // be appreciated but is not required. 19 // 20 // 2. Altered source versions must be plainly marked as such, and must not 21 // be misrepresented as being the original software. 22 // 23 // 3. This notice may not be removed or altered from any source 24 // distribution. 25 // 26 //======================================================================== 27 28 #include "internal.h" 29 30 #include <sys/types.h> 31 #include <sys/stat.h> 32 #include <sys/inotify.h> 33 #include <fcntl.h> 34 #include <errno.h> 35 #include <dirent.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <unistd.h> 40 41 // Apply an EV_KEY event to the specified joystick 42 // 43 static void handleKeyEvent(_GLFWjoystick* js, int code, int value) 44 { 45 _glfwInputJoystickButton(js, 46 js->linjs.keyMap[code - BTN_MISC], 47 value ? GLFW_PRESS : GLFW_RELEASE); 48 } 49 50 // Apply an EV_ABS event to the specified joystick 51 // 52 static void handleAbsEvent(_GLFWjoystick* js, int code, int value) 53 { 54 const int index = js->linjs.absMap[code]; 55 56 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) 57 { 58 static const char stateMap[3][3] = 59 { 60 { GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN }, 61 { GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN }, 62 { GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN }, 63 }; 64 65 const int hat = (code - ABS_HAT0X) / 2; 66 const int axis = (code - ABS_HAT0X) % 2; 67 int* state = js->linjs.hats[hat]; 68 69 // NOTE: Looking at several input drivers, it seems all hat events use 70 // -1 for left / up, 0 for centered and 1 for right / down 71 if (value == 0) 72 state[axis] = 0; 73 else if (value < 0) 74 state[axis] = 1; 75 else if (value > 0) 76 state[axis] = 2; 77 78 _glfwInputJoystickHat(js, index, stateMap[state[0]][state[1]]); 79 } 80 else 81 { 82 const struct input_absinfo* info = &js->linjs.absInfo[code]; 83 float normalized = value; 84 85 const int range = info->maximum - info->minimum; 86 if (range) 87 { 88 // Normalize to 0.0 -> 1.0 89 normalized = (normalized - info->minimum) / range; 90 // Normalize to -1.0 -> 1.0 91 normalized = normalized * 2.0f - 1.0f; 92 } 93 94 _glfwInputJoystickAxis(js, index, normalized); 95 } 96 } 97 98 // Poll state of absolute axes 99 // 100 static void pollAbsState(_GLFWjoystick* js) 101 { 102 int code; 103 104 for (code = 0; code < ABS_CNT; code++) 105 { 106 if (js->linjs.absMap[code] < 0) 107 continue; 108 109 struct input_absinfo* info = &js->linjs.absInfo[code]; 110 111 if (ioctl(js->linjs.fd, EVIOCGABS(code), info) < 0) 112 continue; 113 114 handleAbsEvent(js, code, info->value); 115 } 116 } 117 118 #define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) 119 120 // Attempt to open the specified joystick device 121 // 122 static GLFWbool openJoystickDevice(const char* path) 123 { 124 int jid, code; 125 char name[256] = ""; 126 char guid[33] = ""; 127 char evBits[(EV_CNT + 7) / 8] = {0}; 128 char keyBits[(KEY_CNT + 7) / 8] = {0}; 129 char absBits[(ABS_CNT + 7) / 8] = {0}; 130 int axisCount = 0, buttonCount = 0, hatCount = 0; 131 struct input_id id; 132 _GLFWjoystickLinux linjs = {0}; 133 _GLFWjoystick* js = NULL; 134 135 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 136 { 137 if (!_glfw.joysticks[jid].present) 138 continue; 139 if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) 140 return GLFW_FALSE; 141 } 142 143 linjs.fd = open(path, O_RDONLY | O_NONBLOCK); 144 if (linjs.fd == -1) 145 return GLFW_FALSE; 146 147 if (ioctl(linjs.fd, EVIOCGBIT(0, sizeof(evBits)), evBits) < 0 || 148 ioctl(linjs.fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 || 149 ioctl(linjs.fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0 || 150 ioctl(linjs.fd, EVIOCGID, &id) < 0) 151 { 152 _glfwInputError(GLFW_PLATFORM_ERROR, 153 "Linux: Failed to query input device: %s", 154 strerror(errno)); 155 close(linjs.fd); 156 return GLFW_FALSE; 157 } 158 159 // Ensure this device supports the events expected of a joystick 160 if (!isBitSet(EV_KEY, evBits) || !isBitSet(EV_ABS, evBits)) 161 { 162 close(linjs.fd); 163 return GLFW_FALSE; 164 } 165 166 if (ioctl(linjs.fd, EVIOCGNAME(sizeof(name)), name) < 0) 167 strncpy(name, "Unknown", sizeof(name)); 168 169 // Generate a joystick GUID that matches the SDL 2.0.5+ one 170 if (id.vendor && id.product && id.version) 171 { 172 sprintf(guid, "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000", 173 id.bustype & 0xff, id.bustype >> 8, 174 id.vendor & 0xff, id.vendor >> 8, 175 id.product & 0xff, id.product >> 8, 176 id.version & 0xff, id.version >> 8); 177 } 178 else 179 { 180 sprintf(guid, "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", 181 id.bustype & 0xff, id.bustype >> 8, 182 name[0], name[1], name[2], name[3], 183 name[4], name[5], name[6], name[7], 184 name[8], name[9], name[10]); 185 } 186 187 for (code = BTN_MISC; code < KEY_CNT; code++) 188 { 189 if (!isBitSet(code, keyBits)) 190 continue; 191 192 linjs.keyMap[code - BTN_MISC] = buttonCount; 193 buttonCount++; 194 } 195 196 for (code = 0; code < ABS_CNT; code++) 197 { 198 linjs.absMap[code] = -1; 199 if (!isBitSet(code, absBits)) 200 continue; 201 202 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) 203 { 204 linjs.absMap[code] = hatCount; 205 hatCount++; 206 // Skip the Y axis 207 code++; 208 } 209 else 210 { 211 if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0) 212 continue; 213 214 linjs.absMap[code] = axisCount; 215 axisCount++; 216 } 217 } 218 219 js = _glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount); 220 if (!js) 221 { 222 close(linjs.fd); 223 return GLFW_FALSE; 224 } 225 226 strncpy(linjs.path, path, sizeof(linjs.path)); 227 memcpy(&js->linjs, &linjs, sizeof(linjs)); 228 229 pollAbsState(js); 230 231 _glfwInputJoystick(js, GLFW_CONNECTED); 232 return GLFW_TRUE; 233 } 234 235 #undef isBitSet 236 237 // Frees all resources associated with the specified joystick 238 // 239 static void closeJoystick(_GLFWjoystick* js) 240 { 241 close(js->linjs.fd); 242 _glfwFreeJoystick(js); 243 _glfwInputJoystick(js, GLFW_DISCONNECTED); 244 } 245 246 // Lexically compare joysticks by name; used by qsort 247 // 248 static int compareJoysticks(const void* fp, const void* sp) 249 { 250 const _GLFWjoystick* fj = fp; 251 const _GLFWjoystick* sj = sp; 252 return strcmp(fj->linjs.path, sj->linjs.path); 253 } 254 255 256 ////////////////////////////////////////////////////////////////////////// 257 ////// GLFW internal API ////// 258 ////////////////////////////////////////////////////////////////////////// 259 260 // Initialize joystick interface 261 // 262 GLFWbool _glfwInitJoysticksLinux(void) 263 { 264 DIR* dir; 265 int count = 0; 266 const char* dirname = "/dev/input"; 267 268 _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 269 if (_glfw.linjs.inotify > 0) 270 { 271 // HACK: Register for IN_ATTRIB to get notified when udev is done 272 // This works well in practice but the true way is libudev 273 274 _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify, 275 dirname, 276 IN_CREATE | IN_ATTRIB | IN_DELETE); 277 } 278 279 // Continue without device connection notifications if inotify fails 280 281 if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0) 282 { 283 _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex"); 284 return GLFW_FALSE; 285 } 286 287 dir = opendir(dirname); 288 if (dir) 289 { 290 struct dirent* entry; 291 292 while ((entry = readdir(dir))) 293 { 294 regmatch_t match; 295 296 if (regexec(&_glfw.linjs.regex, entry->d_name, 1, &match, 0) != 0) 297 continue; 298 299 char path[PATH_MAX]; 300 301 snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name); 302 303 if (openJoystickDevice(path)) 304 count++; 305 } 306 307 closedir(dir); 308 } 309 else 310 311 // Continue with no joysticks if enumeration fails 312 313 qsort(_glfw.joysticks, count, sizeof(_GLFWjoystick), compareJoysticks); 314 return GLFW_TRUE; 315 } 316 317 // Close all opened joystick handles 318 // 319 void _glfwTerminateJoysticksLinux(void) 320 { 321 int jid; 322 323 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 324 { 325 _GLFWjoystick* js = _glfw.joysticks + jid; 326 if (js->present) 327 closeJoystick(js); 328 } 329 330 regfree(&_glfw.linjs.regex); 331 332 if (_glfw.linjs.inotify > 0) 333 { 334 if (_glfw.linjs.watch > 0) 335 inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch); 336 337 close(_glfw.linjs.inotify); 338 } 339 } 340 341 void _glfwDetectJoystickConnectionLinux(void) 342 { 343 ssize_t offset = 0; 344 char buffer[16384]; 345 346 if (_glfw.linjs.inotify <= 0) 347 return; 348 349 const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer)); 350 351 while (size > offset) 352 { 353 regmatch_t match; 354 const struct inotify_event* e = (struct inotify_event*) (buffer + offset); 355 356 offset += sizeof(struct inotify_event) + e->len; 357 358 if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0) 359 continue; 360 361 char path[PATH_MAX]; 362 snprintf(path, sizeof(path), "/dev/input/%s", e->name); 363 364 if (e->mask & (IN_CREATE | IN_ATTRIB)) 365 openJoystickDevice(path); 366 else if (e->mask & IN_DELETE) 367 { 368 int jid; 369 370 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 371 { 372 if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) 373 { 374 closeJoystick(_glfw.joysticks + jid); 375 break; 376 } 377 } 378 } 379 } 380 } 381 382 383 ////////////////////////////////////////////////////////////////////////// 384 ////// GLFW platform API ////// 385 ////////////////////////////////////////////////////////////////////////// 386 387 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) 388 { 389 // Read all queued events (non-blocking) 390 for (;;) 391 { 392 struct input_event e; 393 394 errno = 0; 395 if (read(js->linjs.fd, &e, sizeof(e)) < 0) 396 { 397 // Reset the joystick slot if the device was disconnected 398 if (errno == ENODEV) 399 closeJoystick(js); 400 401 break; 402 } 403 404 if (e.type == EV_SYN) 405 { 406 if (e.code == SYN_DROPPED) 407 _glfw.linjs.dropped = GLFW_TRUE; 408 else if (e.code == SYN_REPORT) 409 { 410 _glfw.linjs.dropped = GLFW_FALSE; 411 pollAbsState(js); 412 } 413 } 414 415 if (_glfw.linjs.dropped) 416 continue; 417 418 if (e.type == EV_KEY) 419 handleKeyEvent(js, e.code, e.value); 420 else if (e.type == EV_ABS) 421 handleAbsEvent(js, e.code, e.value); 422 } 423 424 return js->present; 425 } 426 427 void _glfwPlatformUpdateGamepadGUID(char* guid) 428 { 429 } 430