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