diff --git a/README.md b/README.md index 0481934f..84fe3191 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ information on what to include when reporting a bug. - [X11] Bugfix: IM-duplicated key events would leak at low polling rates (#747) - [X11] Bugfix: Gamma ramp setting via RandR did not validate ramp size - [X11] Bugfix: Key name string encoding depended on current locale (#981,#983) +- [Linux] Moved to evdev for joystick input (#906,#1005) - [Linux] Bugfix: Event processing did not detect joystick disconnection (#932) - [Linux] Bugfix: The joystick device path could be truncated (#1025) - [Cocoa] Added support for Vulkan window surface creation via @@ -314,6 +315,7 @@ skills. - Peoro - Braden Pellett - Arturo J. PĂ©rez + - Anthony Pesch - Orson Peters - Emmanuel Gil Peyrot - Cyril Pichard diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index fb18edec..3e09eacf 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -4234,8 +4234,6 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count); * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * - * @bug @linux Joystick hats are currently unimplemented. - * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected, this function is called again for that joystick or the library diff --git a/src/linux_joystick.c b/src/linux_joystick.c index c0a951ff..360e7a28 100644 --- a/src/linux_joystick.c +++ b/src/linux_joystick.c @@ -38,36 +38,37 @@ #include #include -#define TEST_BIT(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) - +// Apply an EV_KEY event to the specified joystick +// static void handleKeyEvent(_GLFWjoystick* js, int code, int value) { - int jid = js - _glfw.joysticks; - int button = js->linjs.keyMap[code]; - - _glfwInputJoystickButton(jid, button, value ? 1 : 0); + _glfwInputJoystickButton(_GLFW_JOYSTICK_ID(js), + js->linjs.keyMap[code - BTN_MISC], + value ? GLFW_PRESS : GLFW_RELEASE); } +// Apply an EV_ABS event to the specified joystick +// static void handleAbsEvent(_GLFWjoystick* js, int code, int value) { - int jid = js - _glfw.joysticks; - int index = js->linjs.absMap[code]; + const int jid = _GLFW_JOYSTICK_ID(js); + const int index = js->linjs.absMap[code]; if (code >= ABS_HAT0X && code <= ABS_HAT3Y) { static const char stateMap[3][3] = { - {GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN}, - {GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN}, - {GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN}, + { GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN }, + { GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN }, + { GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN }, }; - int hat = (code - ABS_HAT0X) / 2; - int axis = (code - ABS_HAT0X) % 2; - int *state = js->linjs.hats[hat]; + const int hat = (code - ABS_HAT0X) / 2; + const int axis = (code - ABS_HAT0X) % 2; + int* state = js->linjs.hats[hat]; - // Looking at several input drivers, it seems all hat events use - // -1 for left / up, 0 for centered and 1 for right / down + // NOTE: Looking at several input drivers, it seems all hat events use + // -1 for left / up, 0 for centered and 1 for right / down if (value == 0) state[axis] = 0; else if (value < 0) @@ -79,15 +80,15 @@ static void handleAbsEvent(_GLFWjoystick* js, int code, int value) } else { - struct input_absinfo *info = &js->linjs.absInfo[code]; - int range = info->maximum - info->minimum; + const struct input_absinfo* info = &js->linjs.absInfo[code]; float normalized = value; - if (range != 0) + const int range = info->maximum - info->minimum; + if (range) { - // Normalize from 0.0 -> 1.0 + // Normalize to 0.0 -> 1.0 normalized = (normalized - info->minimum) / range; - // Normalize from -1.0 -> 1.0 + // Normalize to -1.0 -> 1.0 normalized = normalized * 2.0f - 1.0f; } @@ -95,36 +96,38 @@ static void handleAbsEvent(_GLFWjoystick* js, int code, int value) } } -static void pollJoystick(_GLFWjoystick* js) +// Poll state of absolute axes +// +static void pollAbsState(_GLFWjoystick* js) { - int i; + int code; - for (i = 0; i < ABS_CNT; i++) + for (code = 0; code < ABS_CNT; code++) { - if (js->linjs.absMap[i] < 0) + if (js->linjs.absMap[code] < 0) continue; - struct input_absinfo *info = &js->linjs.absInfo[i]; + struct input_absinfo* info = &js->linjs.absInfo[code]; - if (ioctl(js->linjs.fd, EVIOCGABS(i), info) < 0) + if (ioctl(js->linjs.fd, EVIOCGABS(code), info) < 0) continue; - handleAbsEvent(js, i, info->value); + handleAbsEvent(js, code, info->value); } } +#define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) + // Attempt to open the specified joystick device // static GLFWbool openJoystickDevice(const char* path) { - int jid, fd, i; + int jid, code; char name[256] = ""; char evBits[(EV_CNT + 7) / 8] = {0}; char keyBits[(KEY_CNT + 7) / 8] = {0}; char absBits[(ABS_CNT + 7) / 8] = {0}; - int axisCount = 0; - int buttonCount = 0; - int hatCount = 0; + int axisCount = 0, buttonCount = 0, hatCount = 0; _GLFWjoystickLinux linjs = {0}; _GLFWjoystick* js = NULL; @@ -136,71 +139,81 @@ static GLFWbool openJoystickDevice(const char* path) return GLFW_FALSE; } - fd = open(path, O_RDONLY | O_NONBLOCK); - if (fd == -1) + linjs.fd = open(path, O_RDONLY | O_NONBLOCK); + if (linjs.fd == -1) return GLFW_FALSE; + if (ioctl(linjs.fd, EVIOCGBIT(0, sizeof(evBits)), evBits) < 0 || + ioctl(linjs.fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 || + ioctl(linjs.fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Linux: Failed to query input device elements: %s", + strerror(errno)); + close(linjs.fd); + return GLFW_FALSE; + } + // Ensure this device supports the events expected of a joystick - if (ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits) < 0 || - ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 || - ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0 || - !TEST_BIT(EV_KEY, evBits) || !TEST_BIT(EV_ABS, evBits)) + if (!isBitSet(EV_KEY, evBits) || !isBitSet(EV_ABS, evBits)) { - close(fd); + close(linjs.fd); return GLFW_FALSE; } - if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) + if (ioctl(linjs.fd, EVIOCGNAME(sizeof(name)), name) < 0) strncpy(name, "Unknown", sizeof(name)); - for (i = BTN_MISC; i < KEY_CNT; i++) + for (code = BTN_MISC; code < KEY_CNT; code++) { - if (!TEST_BIT(i, keyBits)) + if (!isBitSet(code, keyBits)) continue; - linjs.keyMap[i] = buttonCount++; + linjs.keyMap[code - BTN_MISC] = buttonCount; + buttonCount++; } - for (i = 0; i < ABS_CNT; i++) + for (code = 0; code < ABS_CNT; code++) { - linjs.absMap[i] = -1; - if (!TEST_BIT(i, absBits)) + linjs.absMap[code] = -1; + if (!isBitSet(code, absBits)) continue; - if (i >= ABS_HAT0X && i <= ABS_HAT3Y) + if (code >= ABS_HAT0X && code <= ABS_HAT3Y) { - linjs.absMap[i] = hatCount++; - + linjs.absMap[code] = hatCount; + hatCount++; // Skip the Y axis - i++; + code++; } else { - if (ioctl(fd, EVIOCGABS(i), &linjs.absInfo[i]) < 0) + if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0) continue; - linjs.absMap[i] = axisCount++; + linjs.absMap[code] = axisCount; + axisCount++; } } js = _glfwAllocJoystick(name, axisCount, buttonCount, hatCount); if (!js) { - close(fd); + close(linjs.fd); return GLFW_FALSE; } - linjs.fd = fd; strncpy(linjs.path, path, sizeof(linjs.path)); memcpy(&js->linjs, &linjs, sizeof(linjs)); - // Set initial values for absolute axes - pollJoystick(js); + pollAbsState(js); _glfwInputJoystick(_GLFW_JOYSTICK_ID(js), GLFW_CONNECTED); return GLFW_TRUE; } +#undef isBitSet + // Frees all resources associated with the specified joystick // static void closeJoystick(_GLFWjoystick* js) @@ -391,8 +404,7 @@ int _glfwPlatformPollJoystick(int jid, int mode) else if (e.type == EV_ABS) handleAbsEvent(js, e.code, e.value); else if (e.type == EV_SYN && e.code == SYN_DROPPED) - // Refresh axes - pollJoystick(js); + pollAbsState(js); } return js->present; diff --git a/src/linux_joystick.h b/src/linux_joystick.h index 127a4485..c364cf76 100644 --- a/src/linux_joystick.h +++ b/src/linux_joystick.h @@ -35,21 +35,21 @@ // typedef struct _GLFWjoystickLinux { - int fd; - char path[PATH_MAX]; - int keyMap[KEY_CNT]; - int absMap[ABS_CNT]; - struct input_absinfo absInfo[ABS_CNT]; - int hats[4][2]; + int fd; + char path[PATH_MAX]; + int keyMap[KEY_CNT - BTN_MISC]; + int absMap[ABS_CNT]; + struct input_absinfo absInfo[ABS_CNT]; + int hats[4][2]; } _GLFWjoystickLinux; // Linux-specific joystick API data // typedef struct _GLFWlibraryLinux { - int inotify; - int watch; - regex_t regex; + int inotify; + int watch; + regex_t regex; } _GLFWlibraryLinux;