1 /// Translated from C to D 2 module glfw3.linux_joystick; 3 4 extern(C): @nogc: nothrow: __gshared: 5 6 //======================================================================== 7 // GLFW 3.3 Linux - www.glfw.org 8 //------------------------------------------------------------------------ 9 // Copyright (c) 2002-2006 Marcus Geelnard 10 // Copyright (c) 2006-2017 Camilla Löwy <elmindreda@glfw.org> 11 // 12 // This software is provided 'as-is', without any express or implied 13 // warranty. In no event will the authors be held liable for any damages 14 // arising from the use of this software. 15 // 16 // Permission is granted to anyone to use this software for any purpose, 17 // including commercial applications, and to alter it and redistribute it 18 // freely, subject to the following restrictions: 19 // 20 // 1. The origin of this software must not be misrepresented; you must not 21 // claim that you wrote the original software. If you use this software 22 // in a product, an acknowledgment in the product documentation would 23 // be appreciated but is not required. 24 // 25 // 2. Altered source versions must be plainly marked as such, and must not 26 // be misrepresented as being the original software. 27 // 28 // 3. This notice may not be removed or altered from any source 29 // distribution. 30 // 31 //======================================================================== 32 // It is fine to use C99 in this file because it will not be built with VS 33 //======================================================================== 34 35 import glfw3.internal; 36 37 import core.sys.posix.sys.types; 38 import core.sys.posix.sys.stat; 39 import core.sys.linux.sys.inotify; 40 import core.sys.posix.fcntl; 41 import core.stdc.errno; 42 import core.sys.posix.dirent; 43 import core.stdc.stdio; 44 import core.stdc.stdlib; 45 import core.stdc.string; 46 import core.sys.posix.unistd; 47 48 public import glfw3.linuxinput; 49 import core.stdc.limits; 50 51 import core.sys.posix.sys.ioctl: ioctl; 52 53 mixin template _GLFW_PLATFORM_JOYSTICK_STATE() { _GLFWjoystickLinux linjs; } 54 mixin template _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE() {_GLFWlibraryLinux linjs; } 55 56 enum _GLFW_PLATFORM_MAPPING_NAME = "Linux"; 57 58 // Linux-specific joystick data 59 // 60 struct _GLFWjoystickLinux { 61 int fd; 62 char[PATH_MAX] path = '\0'; 63 int[KEY_CNT - BTN_MISC] keyMap; 64 int[ABS_CNT] absMap; 65 input_absinfo[ABS_CNT] absInfo; 66 int[2][4] hats; 67 bool hasRumble = false; 68 ff_effect rumble; 69 } 70 71 // Linux-specific joystick API data 72 // 73 struct _GLFWlibraryLinux { 74 int inotify; 75 int watch; 76 version(none) { 77 regex_t regex; 78 } 79 GLFWbool dropped; 80 } 81 82 version(none) { // < v2.6.39 kernel headers 83 // Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32 84 enum SYN_DROPPED = 3; 85 } 86 87 // Apply an EV_KEY event to the specified joystick 88 // 89 private void handleKeyEvent(_GLFWjoystick* js, int code, int value) { 90 _glfwInputJoystickButton(js, 91 js.linjs.keyMap[code - BTN_MISC], 92 value ? GLFW_PRESS : GLFW_RELEASE); 93 } 94 95 // Apply an EV_ABS event to the specified joystick 96 // 97 private void handleAbsEvent(_GLFWjoystick* js, int code, int value) { 98 const(int) index = js.linjs.absMap[code]; 99 100 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) 101 { 102 static const(char)[3][3] stateMap = [ 103 [ GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN ], 104 [ GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN ], 105 [ GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN ], 106 ]; 107 108 const(int) hat = (code - ABS_HAT0X) / 2; 109 const(int) axis = (code - ABS_HAT0X) % 2; 110 int* state = js.linjs.hats[hat].ptr; 111 112 // NOTE: Looking at several input drivers, it seems all hat events use 113 // -1 for left / up, 0 for centered and 1 for right / down 114 if (value == 0) 115 state[axis] = 0; 116 else if (value < 0) 117 state[axis] = 1; 118 else if (value > 0) 119 state[axis] = 2; 120 121 _glfwInputJoystickHat(js, index, stateMap[state[0]][state[1]]); 122 } 123 else 124 { 125 input_absinfo* info = &js.linjs.absInfo[code]; 126 float normalized = value; 127 128 const(int) range = info.maximum - info.minimum; 129 if (range) 130 { 131 // Normalize to 0.0 -> 1.0 132 normalized = (normalized - info.minimum) / range; 133 // Normalize to -1.0 -> 1.0 134 normalized = normalized * 2.0f - 1.0f; 135 } 136 137 _glfwInputJoystickAxis(js, index, normalized); 138 } 139 } 140 141 // Poll state of absolute axes 142 // 143 private void pollAbsState(_GLFWjoystick* js) { 144 for (int code = 0; code < ABS_CNT; code++) 145 { 146 if (js.linjs.absMap[code] < 0) 147 continue; 148 149 input_absinfo* info = &js.linjs.absInfo[code]; 150 151 if (ioctl(js.linjs.fd, EVIOCGABS(code), info) < 0) 152 continue; 153 154 handleAbsEvent(js, code, info.value); 155 } 156 } 157 158 // #define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) 159 // enum string isBitSet(string bit, string arr) = ` (`~arr~`[(`~bit~`) / 8] & (1 << ((`~bit~`) % 8)))`; 160 private bool isBitSet(int bit, scope const ubyte[] arr) {return cast(bool) (arr[bit / 8] & (1 << (bit % 8)));} 161 162 private void initJoystickForceFeedback(_GLFWjoystickLinux *linjs) 163 { 164 linjs.hasRumble = false; 165 166 ubyte[(FF_CNT + 7) / 8] ffBits = 0; 167 if (ioctl(linjs.fd, EVIOCGBIT!(typeof(ffBits))(EV_FF), ffBits.ptr) < 0) 168 { 169 return; 170 } 171 172 if (isBitSet(FF_RUMBLE, ffBits)) 173 { 174 linjs.rumble.type = FF_RUMBLE; 175 linjs.rumble.id = -1; 176 linjs.rumble.direction = 0; 177 linjs.rumble.trigger = ff_trigger(/*.button*/ 0, /*.interval*/ 0); 178 linjs.rumble.replay = ff_replay(/*length*/ 2000, /*delay*/ 0); 179 linjs.rumble.u.rumble = ff_rumble_effect(/*strong_magnitude*/ 0, /*weak_magnitude*/ 0); // xinput rumble lasts ~2 seconds 180 181 linjs.hasRumble = (ioctl(linjs.fd, EVIOCSFF, &linjs.rumble) >= 0); 182 } 183 } 184 185 // Attempt to open the specified joystick device 186 // 187 private GLFWbool openJoystickDevice(const(char)* path) { 188 for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 189 { 190 if (!_glfw.joysticks[jid].present) 191 continue; 192 if (strcmp(_glfw.joysticks[jid].linjs.path.ptr, path) == 0) 193 return GLFW_FALSE; 194 } 195 196 _GLFWjoystickLinux linjs = _GLFWjoystickLinux(0); 197 linjs.fd = open(path, O_RDWR | O_NONBLOCK); 198 if (linjs.fd == -1) 199 return GLFW_FALSE; 200 201 ubyte[(EV_CNT + 7) / 8] evBits = 0; 202 ubyte[(KEY_CNT + 7) / 8] keyBits = 0; 203 ubyte[(ABS_CNT + 7) / 8] absBits = 0; 204 input_id id; 205 206 if (ioctl(linjs.fd, EVIOCGBIT!(typeof(evBits) )( 0), evBits.ptr) < 0 || 207 ioctl(linjs.fd, EVIOCGBIT!(typeof(keyBits))(EV_KEY), keyBits.ptr) < 0 || 208 ioctl(linjs.fd, EVIOCGBIT!(typeof(absBits))(EV_ABS), absBits.ptr) < 0 || 209 ioctl(linjs.fd, EVIOCGID, &id) < 0) 210 { 211 _glfwInputError(GLFW_PLATFORM_ERROR, 212 "Linux: Failed to query input device: %s", 213 strerror(errno)); 214 close(linjs.fd); 215 return GLFW_FALSE; 216 } 217 218 // Ensure this device supports the events expected of a joystick 219 if (!isBitSet(EV_KEY, evBits) || !isBitSet(EV_ABS, evBits)) 220 { 221 close(linjs.fd); 222 return GLFW_FALSE; 223 } 224 225 char[256] name = ""; 226 227 if (ioctl(linjs.fd, EVIOCGNAME!(typeof(name))(), name.ptr) < 0) 228 strncpy(name.ptr, "Unknown", name.length); 229 230 char[33] guid = ""; 231 232 // Generate a joystick GUID that matches the SDL 2.0.5+ one 233 if (id.vendor && id.product && id.version_) 234 { 235 sprintf(guid.ptr, "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000", 236 id.bustype & 0xff, id.bustype >> 8, 237 id.vendor & 0xff, id.vendor >> 8, 238 id.product & 0xff, id.product >> 8, 239 id.version_ & 0xff, id.version_ >> 8); 240 } 241 else 242 { 243 sprintf(guid.ptr, "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", 244 id.bustype & 0xff, id.bustype >> 8, 245 name[0], name[1], name[2], name[3], 246 name[4], name[5], name[6], name[7], 247 name[8], name[9], name[10]); 248 } 249 250 int axisCount = 0;int buttonCount = 0;int hatCount = 0; 251 252 for (int code = BTN_MISC; code < KEY_CNT; code++) 253 { 254 if (!isBitSet(code, keyBits)) 255 continue; 256 257 linjs.keyMap[code - BTN_MISC] = buttonCount; 258 buttonCount++; 259 } 260 261 for (int code = 0; code < ABS_CNT; code++) 262 { 263 linjs.absMap[code] = -1; 264 if (!isBitSet(code, absBits)) 265 continue; 266 267 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) 268 { 269 linjs.absMap[code] = hatCount; 270 hatCount++; 271 // Skip the Y axis 272 code++; 273 } 274 else 275 { 276 if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0) 277 continue; 278 279 linjs.absMap[code] = axisCount; 280 axisCount++; 281 } 282 } 283 284 initJoystickForceFeedback(&linjs); 285 286 _GLFWjoystick* js = _glfwAllocJoystick(name.ptr, guid.ptr, axisCount, buttonCount, hatCount); 287 if (!js) 288 { 289 close(linjs.fd); 290 return GLFW_FALSE; 291 } 292 293 strncpy(linjs.path.ptr, path, linjs.path.length - 1); 294 memcpy(&js.linjs, &linjs, linjs.sizeof); // twab: wrong size, linjs.path.length 295 296 pollAbsState(js); 297 298 _glfwInputJoystick(js, GLFW_CONNECTED); 299 return GLFW_TRUE; 300 } 301 302 // Frees all resources associated with the specified joystick 303 // 304 private void closeJoystick(_GLFWjoystick* js) { 305 close(js.linjs.fd); 306 _glfwFreeJoystick(js); 307 _glfwInputJoystick(js, GLFW_DISCONNECTED); 308 } 309 310 // Lexically compare joysticks by name; used by qsort 311 // 312 private int compareJoysticks(const(void)* fp, const(void)* sp) { 313 auto fj = cast(const(_GLFWjoystick)*) fp; 314 auto sj = cast(const(_GLFWjoystick)*) sp; 315 return strcmp(fj.linjs.path.ptr, sj.linjs.path.ptr); 316 } 317 318 319 ////////////////////////////////////////////////////////////////////////// 320 ////// GLFW internal API ////// 321 ////////////////////////////////////////////////////////////////////////// 322 323 /// Returns: `true` if `str` matches the regex `^event[0-9]\\+$` 324 private extern(D) bool isEventFile(const(char)* str) { 325 import core.stdc.string: strlen; 326 const len = strlen(str); 327 if (len < "event0".length) { 328 return false; 329 } 330 if (str[0..5] != "event") { 331 return false; 332 } 333 foreach(i; 5..len) { 334 if (str[i] < '0' || str[i] > '9') { 335 return false; 336 } 337 } 338 return true; 339 } 340 341 @("is event file") unittest { 342 assert(isEventFile("event0")); 343 assert(isEventFile("event1234567890")); 344 assert(!isEventFile("event")); 345 assert(!isEventFile("even0")); 346 assert(!isEventFile("event0A")); 347 } 348 349 static if (__VERSION__ < 2094) { 350 // workaround for missing @nogc attribute in core.sys.linux.sys.inotify 351 // should be fixed in dmd 2.094 352 extern(C) nothrow @nogc int inotify_init(); 353 extern(C) nothrow @nogc int inotify_init1(int flags); 354 extern(C) nothrow @nogc int inotify_add_watch(int fd, const(char)* name, uint mask); 355 extern(C) nothrow @nogc int inotify_rm_watch(int fd, uint wd); 356 } 357 358 // Initialize joystick interface 359 // 360 GLFWbool _glfwInitJoysticksLinux() { 361 const(char)* dirname = "/dev/input"; 362 363 _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 364 if (_glfw.linjs.inotify > 0) 365 { 366 // HACK: Register for IN_ATTRIB to get notified when udev is done 367 // This works well in practice but the true way is libudev 368 369 _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify, 370 dirname, 371 IN_CREATE | IN_ATTRIB | IN_DELETE); 372 } 373 374 // Continue without device connection notifications if inotify fails 375 version(none) { 376 // remove regex dependency 377 if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0) 378 { 379 _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex"); 380 return GLFW_FALSE; 381 } 382 } 383 384 int count = 0; 385 386 DIR* dir = opendir(dirname); 387 if (dir) 388 { 389 dirent* entry; 390 391 while (true) 392 { 393 entry = readdir(dir); 394 if (!entry) { 395 break; 396 } 397 398 version(none) { 399 regmatch_t match; 400 if (regexec(&_glfw.linjs.regex, entry.d_name, 1, &match, 0) != 0) 401 continue; 402 } else { 403 // remove regex dependency 404 if (!isEventFile(entry.d_name.ptr)) { 405 continue; 406 } 407 } 408 409 char[PATH_MAX] path; 410 411 snprintf(path.ptr, path.length, "%s/%s", dirname, entry.d_name.ptr); 412 413 if (openJoystickDevice(path.ptr)) 414 count++; 415 } 416 417 closedir(dir); 418 } 419 420 // Continue with no joysticks if enumeration fails 421 422 qsort(_glfw.joysticks.ptr, count, _GLFWjoystick.sizeof, &compareJoysticks); 423 return GLFW_TRUE; 424 } 425 426 // Close all opened joystick handles 427 // 428 void _glfwTerminateJoysticksLinux() { 429 int jid; 430 431 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 432 { 433 _GLFWjoystick* js = _glfw.joysticks.ptr + jid; 434 if (js.present) 435 closeJoystick(js); 436 } 437 438 version(none) { 439 regfree(&_glfw.linjs.regex); 440 } 441 442 if (_glfw.linjs.inotify > 0) 443 { 444 if (_glfw.linjs.watch > 0) 445 inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch); 446 447 close(_glfw.linjs.inotify); 448 } 449 } 450 451 void _glfwDetectJoystickConnectionLinux() { 452 if (_glfw.linjs.inotify <= 0) 453 return; 454 455 ssize_t offset = 0; 456 char[16384] buffer; 457 const(ssize_t) size = read(_glfw.linjs.inotify, buffer.ptr, typeof(buffer).sizeof); 458 459 while (size > offset) 460 { 461 const(inotify_event)* e = cast(inotify_event*) (buffer.ptr + offset); 462 463 offset += typeof(cast(inotify_event) + e.len).sizeof; 464 465 version(none) { 466 regmatch_t match; 467 if (regexec(&_glfw.linjs.regex, e.name, 1, &match, 0) != 0) 468 continue; 469 } else { 470 if (!isEventFile(e.name.ptr)) { 471 continue; 472 } 473 } 474 475 char[PATH_MAX] path; 476 snprintf(path.ptr, path.length, "/dev/input/%s", e.name.ptr); 477 478 if (e.mask & (IN_CREATE | IN_ATTRIB)) 479 openJoystickDevice(path.ptr); 480 else if (e.mask & IN_DELETE) 481 { 482 for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 483 { 484 if (strcmp(_glfw.joysticks[jid].linjs.path.ptr, path.ptr) == 0) 485 { 486 closeJoystick(_glfw.joysticks.ptr + jid); 487 break; 488 } 489 } 490 } 491 } 492 } 493 494 495 ////////////////////////////////////////////////////////////////////////// 496 ////// GLFW platform API ////// 497 ////////////////////////////////////////////////////////////////////////// 498 499 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { 500 // Read all queued events (non-blocking) 501 for (;;) 502 { 503 input_event e; 504 505 errno = 0; 506 if (read(js.linjs.fd, &e, typeof(e).sizeof) < 0) 507 { 508 // Reset the joystick slot if the device was disconnected 509 if (errno == ENODEV) 510 closeJoystick(js); 511 512 break; 513 } 514 515 if (e.type == EV_SYN) 516 { 517 if (e.code == SYN_DROPPED) 518 _glfw.linjs.dropped = GLFW_TRUE; 519 else if (e.code == SYN_REPORT) 520 { 521 _glfw.linjs.dropped = GLFW_FALSE; 522 pollAbsState(js); 523 } 524 } 525 526 if (_glfw.linjs.dropped) 527 continue; 528 529 if (e.type == EV_KEY) 530 handleKeyEvent(js, e.code, e.value); 531 else if (e.type == EV_ABS) 532 handleAbsEvent(js, e.code, e.value); 533 } 534 535 return js.present; 536 } 537 538 void _glfwPlatformUpdateGamepadGUID(char* guid) { 539 } 540 541 int _glfwPlatformSetJoystickRumble(_GLFWjoystick* js, float slowMotorIntensity, float fastMotorIntensity) 542 { 543 _GLFWjoystickLinux *linjs = &js.linjs; 544 545 if (!js.linjs.hasRumble) 546 return GLFW_FALSE; 547 548 js.linjs.rumble.u.rumble = ff_rumble_effect( 549 /*strong_magnitude*/ cast(ushort) (65_535 * slowMotorIntensity), 550 /*weak_magnitude*/ cast(ushort) (65_535 * fastMotorIntensity) 551 ); 552 553 input_event play; 554 play.type = EV_FF; 555 play.code = linjs.rumble.id; 556 play.value = 1; 557 558 if (ioctl(linjs.fd, EVIOCSFF, &linjs.rumble) < 0) { 559 return GLFW_FALSE; 560 } 561 if (write(linjs.fd, &play, play.sizeof) < 0) { 562 return GLFW_FALSE; 563 } 564 565 return GLFW_TRUE; 566 }