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 } 68 69 // Linux-specific joystick API data 70 // 71 struct _GLFWlibraryLinux { 72 int inotify; 73 int watch; 74 version(none) { 75 regex_t regex; 76 } 77 GLFWbool dropped; 78 } 79 80 version(none) { // < v2.6.39 kernel headers 81 // Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32 82 enum SYN_DROPPED = 3; 83 } 84 85 // Apply an EV_KEY event to the specified joystick 86 // 87 private void handleKeyEvent(_GLFWjoystick* js, int code, int value) { 88 _glfwInputJoystickButton(js, 89 js.linjs.keyMap[code - BTN_MISC], 90 value ? GLFW_PRESS : GLFW_RELEASE); 91 } 92 93 // Apply an EV_ABS event to the specified joystick 94 // 95 static void handleAbsEvent(_GLFWjoystick* js, int code, int value) { 96 const(int) index = js.linjs.absMap[code]; 97 98 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) 99 { 100 static const(char)[3][3] stateMap = [ 101 [ GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN ], 102 [ GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN ], 103 [ GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN ], 104 ]; 105 106 const(int) hat = (code - ABS_HAT0X) / 2; 107 const(int) axis = (code - ABS_HAT0X) % 2; 108 int* state = js.linjs.hats[hat].ptr; 109 110 // NOTE: Looking at several input drivers, it seems all hat events use 111 // -1 for left / up, 0 for centered and 1 for right / down 112 if (value == 0) 113 state[axis] = 0; 114 else if (value < 0) 115 state[axis] = 1; 116 else if (value > 0) 117 state[axis] = 2; 118 119 _glfwInputJoystickHat(js, index, stateMap[state[0]][state[1]]); 120 } 121 else 122 { 123 input_absinfo* info = &js.linjs.absInfo[code]; 124 float normalized = value; 125 126 const(int) range = info.maximum - info.minimum; 127 if (range) 128 { 129 // Normalize to 0.0 -> 1.0 130 normalized = (normalized - info.minimum) / range; 131 // Normalize to -1.0 -> 1.0 132 normalized = normalized * 2.0f - 1.0f; 133 } 134 135 _glfwInputJoystickAxis(js, index, normalized); 136 } 137 } 138 139 // Poll state of absolute axes 140 // 141 private void pollAbsState(_GLFWjoystick* js) { 142 for (int code = 0; code < ABS_CNT; code++) 143 { 144 if (js.linjs.absMap[code] < 0) 145 continue; 146 147 input_absinfo* info = &js.linjs.absInfo[code]; 148 149 if (ioctl(js.linjs.fd, EVIOCGABS(code), info) < 0) 150 continue; 151 152 handleAbsEvent(js, code, info.value); 153 } 154 } 155 156 // #define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) 157 enum string isBitSet(string bit, string arr) = ` (`~arr~`[(`~bit~`) / 8] & (1 << ((`~bit~`) % 8)))`; 158 159 // Attempt to open the specified joystick device 160 // 161 private GLFWbool openJoystickDevice(const(char)* path) { 162 for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 163 { 164 if (!_glfw.joysticks[jid].present) 165 continue; 166 if (strcmp(_glfw.joysticks[jid].linjs.path.ptr, path) == 0) 167 return GLFW_FALSE; 168 } 169 170 _GLFWjoystickLinux linjs = _GLFWjoystickLinux(0); 171 linjs.fd = open(path, O_RDONLY | O_NONBLOCK); 172 if (linjs.fd == -1) 173 return GLFW_FALSE; 174 175 char[(EV_CNT + 7) / 8] evBits = '\0'; 176 char[(KEY_CNT + 7) / 8] keyBits = '\0'; 177 char[(ABS_CNT + 7) / 8] absBits = '\0'; 178 input_id id; 179 180 if (ioctl(linjs.fd, EVIOCGBIT!(typeof(evBits) )( 0), evBits.ptr) < 0 || 181 ioctl(linjs.fd, EVIOCGBIT!(typeof(keyBits))(EV_KEY), keyBits.ptr) < 0 || 182 ioctl(linjs.fd, EVIOCGBIT!(typeof(absBits))(EV_ABS), absBits.ptr) < 0 || 183 ioctl(linjs.fd, EVIOCGID, &id) < 0) 184 { 185 _glfwInputError(GLFW_PLATFORM_ERROR, 186 "Linux: Failed to query input device: %s", 187 strerror(errno)); 188 close(linjs.fd); 189 return GLFW_FALSE; 190 } 191 192 // Ensure this device supports the events expected of a joystick 193 if (!mixin(isBitSet!("EV_KEY", "evBits")) || !mixin(isBitSet!("EV_ABS", "evBits"))) 194 { 195 close(linjs.fd); 196 return GLFW_FALSE; 197 } 198 199 char[256] name = ""; 200 201 if (ioctl(linjs.fd, EVIOCGNAME!(typeof(name))(), name.ptr) < 0) 202 strncpy(name.ptr, "Unknown", name.length); 203 204 char[33] guid = ""; 205 206 // Generate a joystick GUID that matches the SDL 2.0.5+ one 207 if (id.vendor && id.product && id.version_) 208 { 209 sprintf(guid.ptr, "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000", 210 id.bustype & 0xff, id.bustype >> 8, 211 id.vendor & 0xff, id.vendor >> 8, 212 id.product & 0xff, id.product >> 8, 213 id.version_ & 0xff, id.version_ >> 8); 214 } 215 else 216 { 217 sprintf(guid.ptr, "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", 218 id.bustype & 0xff, id.bustype >> 8, 219 name[0], name[1], name[2], name[3], 220 name[4], name[5], name[6], name[7], 221 name[8], name[9], name[10]); 222 } 223 224 int axisCount = 0;int buttonCount = 0;int hatCount = 0; 225 226 for (int code = BTN_MISC; code < KEY_CNT; code++) 227 { 228 if (!mixin(isBitSet!("code", "keyBits"))) 229 continue; 230 231 linjs.keyMap[code - BTN_MISC] = buttonCount; 232 buttonCount++; 233 } 234 235 for (int code = 0; code < ABS_CNT; code++) 236 { 237 linjs.absMap[code] = -1; 238 if (!mixin(isBitSet!("code", "absBits"))) 239 continue; 240 241 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) 242 { 243 linjs.absMap[code] = hatCount; 244 hatCount++; 245 // Skip the Y axis 246 code++; 247 } 248 else 249 { 250 if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0) 251 continue; 252 253 linjs.absMap[code] = axisCount; 254 axisCount++; 255 } 256 } 257 258 _GLFWjoystick* js = _glfwAllocJoystick(name.ptr, guid.ptr, axisCount, buttonCount, hatCount); 259 if (!js) 260 { 261 close(linjs.fd); 262 return GLFW_FALSE; 263 } 264 265 strncpy(linjs.path.ptr, path, linjs.path.length - 1); 266 memcpy(&js.linjs, &linjs, linjs.sizeof); // twab: wrong size, linjs.path.length 267 268 pollAbsState(js); 269 270 _glfwInputJoystick(js, GLFW_CONNECTED); 271 return GLFW_TRUE; 272 } 273 274 // Frees all resources associated with the specified joystick 275 // 276 private void closeJoystick(_GLFWjoystick* js) { 277 close(js.linjs.fd); 278 _glfwFreeJoystick(js); 279 _glfwInputJoystick(js, GLFW_DISCONNECTED); 280 } 281 282 // Lexically compare joysticks by name; used by qsort 283 // 284 private int compareJoysticks(const(void)* fp, const(void)* sp) { 285 auto fj = cast(const(_GLFWjoystick)*) fp; 286 auto sj = cast(const(_GLFWjoystick)*) sp; 287 return strcmp(fj.linjs.path.ptr, sj.linjs.path.ptr); 288 } 289 290 291 ////////////////////////////////////////////////////////////////////////// 292 ////// GLFW internal API ////// 293 ////////////////////////////////////////////////////////////////////////// 294 295 /// Returns: `true` if `str` matches the regex `^event[0-9]\\+$` 296 private extern(D) bool isEventFile(const(char)* str) { 297 import core.stdc.string: strlen; 298 const len = strlen(str); 299 if (len < "event0".length) { 300 return false; 301 } 302 if (str[0..5] != "event") { 303 return false; 304 } 305 foreach(i; 5..len) { 306 if (str[i] < '0' || str[i] > '9') { 307 return false; 308 } 309 } 310 return true; 311 } 312 313 @("is event file") unittest { 314 assert(isEventFile("event0")); 315 assert(isEventFile("event1234567890")); 316 assert(!isEventFile("event")); 317 assert(!isEventFile("even0")); 318 assert(!isEventFile("event0A")); 319 } 320 321 static if (__VERSION__ < 2094) { 322 // workaround for missing @nogc attribute in core.sys.linux.sys.inotify 323 // should be fixed in dmd 2.094 324 extern(C) nothrow @nogc int inotify_init(); 325 extern(C) nothrow @nogc int inotify_init1(int flags); 326 extern(C) nothrow @nogc int inotify_add_watch(int fd, const(char)* name, uint mask); 327 extern(C) nothrow @nogc int inotify_rm_watch(int fd, uint wd); 328 } 329 330 // Initialize joystick interface 331 // 332 GLFWbool _glfwInitJoysticksLinux() { 333 const(char)* dirname = "/dev/input"; 334 335 _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 336 if (_glfw.linjs.inotify > 0) 337 { 338 // HACK: Register for IN_ATTRIB to get notified when udev is done 339 // This works well in practice but the true way is libudev 340 341 _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify, 342 dirname, 343 IN_CREATE | IN_ATTRIB | IN_DELETE); 344 } 345 346 // Continue without device connection notifications if inotify fails 347 version(none) { 348 // remove regex dependency 349 if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0) 350 { 351 _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex"); 352 return GLFW_FALSE; 353 } 354 } 355 356 int count = 0; 357 358 DIR* dir = opendir(dirname); 359 if (dir) 360 { 361 dirent* entry; 362 363 while (true) 364 { 365 entry = readdir(dir); 366 if (!entry) { 367 break; 368 } 369 370 version(none) { 371 regmatch_t match; 372 if (regexec(&_glfw.linjs.regex, entry.d_name, 1, &match, 0) != 0) 373 continue; 374 } else { 375 // remove regex dependency 376 if (!isEventFile(entry.d_name.ptr)) { 377 continue; 378 } 379 } 380 381 char[PATH_MAX] path; 382 383 snprintf(path.ptr, path.length, "%s/%s", dirname, entry.d_name.ptr); 384 385 if (openJoystickDevice(path.ptr)) 386 count++; 387 } 388 389 closedir(dir); 390 } 391 392 // Continue with no joysticks if enumeration fails 393 394 qsort(_glfw.joysticks.ptr, count, _GLFWjoystick.sizeof, &compareJoysticks); 395 return GLFW_TRUE; 396 } 397 398 // Close all opened joystick handles 399 // 400 void _glfwTerminateJoysticksLinux() { 401 int jid; 402 403 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 404 { 405 _GLFWjoystick* js = _glfw.joysticks.ptr + jid; 406 if (js.present) 407 closeJoystick(js); 408 } 409 410 version(none) { 411 regfree(&_glfw.linjs.regex); 412 } 413 414 if (_glfw.linjs.inotify > 0) 415 { 416 if (_glfw.linjs.watch > 0) 417 inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch); 418 419 close(_glfw.linjs.inotify); 420 } 421 } 422 423 void _glfwDetectJoystickConnectionLinux() { 424 if (_glfw.linjs.inotify <= 0) 425 return; 426 427 ssize_t offset = 0; 428 char[16384] buffer; 429 const(ssize_t) size = read(_glfw.linjs.inotify, buffer.ptr, typeof(buffer).sizeof); 430 431 while (size > offset) 432 { 433 const(inotify_event)* e = cast(inotify_event*) (buffer.ptr + offset); 434 435 offset += typeof(cast(inotify_event) + e.len).sizeof; 436 437 version(none) { 438 regmatch_t match; 439 if (regexec(&_glfw.linjs.regex, e.name, 1, &match, 0) != 0) 440 continue; 441 } else { 442 if (!isEventFile(e.name.ptr)) { 443 continue; 444 } 445 } 446 447 char[PATH_MAX] path; 448 snprintf(path.ptr, path.length, "/dev/input/%s", e.name.ptr); 449 450 if (e.mask & (IN_CREATE | IN_ATTRIB)) 451 openJoystickDevice(path.ptr); 452 else if (e.mask & IN_DELETE) 453 { 454 for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 455 { 456 if (strcmp(_glfw.joysticks[jid].linjs.path.ptr, path.ptr) == 0) 457 { 458 closeJoystick(_glfw.joysticks.ptr + jid); 459 break; 460 } 461 } 462 } 463 } 464 } 465 466 467 ////////////////////////////////////////////////////////////////////////// 468 ////// GLFW platform API ////// 469 ////////////////////////////////////////////////////////////////////////// 470 471 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { 472 // Read all queued events (non-blocking) 473 for (;;) 474 { 475 input_event e; 476 477 errno = 0; 478 if (read(js.linjs.fd, &e, typeof(e).sizeof) < 0) 479 { 480 // Reset the joystick slot if the device was disconnected 481 if (errno == ENODEV) 482 closeJoystick(js); 483 484 break; 485 } 486 487 if (e.type == EV_SYN) 488 { 489 if (e.code == SYN_DROPPED) 490 _glfw.linjs.dropped = GLFW_TRUE; 491 else if (e.code == SYN_REPORT) 492 { 493 _glfw.linjs.dropped = GLFW_FALSE; 494 pollAbsState(js); 495 } 496 } 497 498 if (_glfw.linjs.dropped) 499 continue; 500 501 if (e.type == EV_KEY) 502 handleKeyEvent(js, e.code, e.value); 503 else if (e.type == EV_ABS) 504 handleAbsEvent(js, e.code, e.value); 505 } 506 507 return js.present; 508 } 509 510 void _glfwPlatformUpdateGamepadGUID(char* guid) { 511 }