linux_joystick.c (12406B)
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 // It is fine to use C99 in this file because it will not be built with VS 28 //======================================================================== 29 30 #include "internal.h" 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].present) 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); 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_KEY, evBits) || !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 close(js->linjs.fd); 249 _glfwFreeJoystick(js); 250 _glfwInputJoystick(js, GLFW_DISCONNECTED); 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 // Initialize joystick interface 268 // 269 GLFWbool _glfwInitJoysticksLinux(void) 270 { 271 const char* dirname = "/dev/input"; 272 273 _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 274 if (_glfw.linjs.inotify > 0) 275 { 276 // HACK: Register for IN_ATTRIB to get notified when udev is done 277 // This works well in practice but the true way is libudev 278 279 _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify, 280 dirname, 281 IN_CREATE | IN_ATTRIB | IN_DELETE); 282 } 283 284 // Continue without device connection notifications if inotify fails 285 286 if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0) 287 { 288 _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex"); 289 return GLFW_FALSE; 290 } 291 292 int count = 0; 293 294 DIR* dir = opendir(dirname); 295 if (dir) 296 { 297 struct dirent* entry; 298 299 while ((entry = readdir(dir))) 300 { 301 regmatch_t match; 302 303 if (regexec(&_glfw.linjs.regex, entry->d_name, 1, &match, 0) != 0) 304 continue; 305 306 char path[PATH_MAX]; 307 308 snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name); 309 310 if (openJoystickDevice(path)) 311 count++; 312 } 313 314 closedir(dir); 315 } 316 317 // Continue with no joysticks if enumeration fails 318 319 qsort(_glfw.joysticks, count, sizeof(_GLFWjoystick), compareJoysticks); 320 return GLFW_TRUE; 321 } 322 323 // Close all opened joystick handles 324 // 325 void _glfwTerminateJoysticksLinux(void) 326 { 327 int jid; 328 329 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 330 { 331 _GLFWjoystick* js = _glfw.joysticks + jid; 332 if (js->present) 333 closeJoystick(js); 334 } 335 336 regfree(&_glfw.linjs.regex); 337 338 if (_glfw.linjs.inotify > 0) 339 { 340 if (_glfw.linjs.watch > 0) 341 inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch); 342 343 close(_glfw.linjs.inotify); 344 } 345 } 346 347 void _glfwDetectJoystickConnectionLinux(void) 348 { 349 if (_glfw.linjs.inotify <= 0) 350 return; 351 352 ssize_t offset = 0; 353 char buffer[16384]; 354 const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer)); 355 356 while (size > offset) 357 { 358 regmatch_t match; 359 const struct inotify_event* e = (struct inotify_event*) (buffer + offset); 360 361 offset += sizeof(struct inotify_event) + e->len; 362 363 if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0) 364 continue; 365 366 char path[PATH_MAX]; 367 snprintf(path, sizeof(path), "/dev/input/%s", e->name); 368 369 if (e->mask & (IN_CREATE | IN_ATTRIB)) 370 openJoystickDevice(path); 371 else if (e->mask & IN_DELETE) 372 { 373 for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 374 { 375 if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) 376 { 377 closeJoystick(_glfw.joysticks + jid); 378 break; 379 } 380 } 381 } 382 } 383 } 384 385 386 ////////////////////////////////////////////////////////////////////////// 387 ////// GLFW platform API ////// 388 ////////////////////////////////////////////////////////////////////////// 389 390 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) 391 { 392 // Read all queued events (non-blocking) 393 for (;;) 394 { 395 struct input_event e; 396 397 errno = 0; 398 if (read(js->linjs.fd, &e, sizeof(e)) < 0) 399 { 400 // Reset the joystick slot if the device was disconnected 401 if (errno == ENODEV) 402 closeJoystick(js); 403 404 break; 405 } 406 407 if (e.type == EV_SYN) 408 { 409 if (e.code == SYN_DROPPED) 410 _glfw.linjs.dropped = GLFW_TRUE; 411 else if (e.code == SYN_REPORT) 412 { 413 _glfw.linjs.dropped = GLFW_FALSE; 414 pollAbsState(js); 415 } 416 } 417 418 if (_glfw.linjs.dropped) 419 continue; 420 421 if (e.type == EV_KEY) 422 handleKeyEvent(js, e.code, e.value); 423 else if (e.type == EV_ABS) 424 handleAbsEvent(js, e.code, e.value); 425 } 426 427 return js->present; 428 } 429 430 void _glfwPlatformUpdateGamepadGUID(char* guid) 431 { 432 } 433