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