From d1a2ec4d20d5757e6f0188476993ee8623f9cbaf Mon Sep 17 00:00:00 2001 From: Anthony Pesch Date: Sat, 6 May 2017 16:34:56 -0400 Subject: [PATCH] Linux: Move to evdev for joystick input Closes #1005. --- src/linux_joystick.c | 156 ++++++++++++++++++++++++++++++++++++------- src/linux_joystick.h | 17 +++-- 2 files changed, 143 insertions(+), 30 deletions(-) diff --git a/src/linux_joystick.c b/src/linux_joystick.c index 25bfa65c..38c6a503 100644 --- a/src/linux_joystick.c +++ b/src/linux_joystick.c @@ -27,8 +27,6 @@ #include "internal.h" -#include - #include #include #include @@ -40,15 +38,93 @@ #include #include +#define NUM_BITS(size) ((size) / 8) +#define TEST_BIT(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) + +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); +} + +static void handleAbsEvent(_GLFWjoystick* js, int code, int value) +{ + int jid = js - _glfw.joysticks; + 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}, + }; + + int hat = (code - ABS_HAT0X) / 2; + 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 + if (value == 0) + state[axis] = 0; + else if (value < 0) + state[axis] = 1; + else if (value > 0) + state[axis] = 2; + + _glfwInputJoystickHat(jid, index, stateMap[state[0]][state[1]]); + } + else + { + struct input_absinfo *info = &js->linjs.absInfo[code]; + int range = info->maximum - info->minimum; + float normalized = value; + + if (range != 0) + { + // Normalize from 0.0 -> 1.0 + normalized = (normalized - info->minimum) / range; + // Normalize from -1.0 -> 1.0 + normalized = normalized * 2.0f - 1.0f; + } + + _glfwInputJoystickAxis(jid, index, normalized); + } +} + +static void pollJoystick(_GLFWjoystick* js) +{ + int i; + + for (i = ABS_X; i < ABS_MAX; i++) + { + struct input_absinfo *info = &js->linjs.absInfo[i]; + + if (ioctl(js->linjs.fd, EVIOCGABS(i), info) < 0) + continue; + + handleAbsEvent(js, i, info->value); + } +} // Attempt to open the specified joystick device // static GLFWbool openJoystickDevice(const char* path) { - char axisCount, buttonCount; + int jid, fd, i; char name[256] = ""; - int jid, fd, version; - _GLFWjoystick* js; + char evBits[NUM_BITS(EV_MAX)] = {0}; + char keyBits[NUM_BITS(KEY_MAX)] = {0}; + char absBits[NUM_BITS(ABS_MAX)] = {0}; + int axisCount = 0; + int buttonCount = 0; + int hatCount = 0; + _GLFWjoystickLinux linjs = {0}; + _GLFWjoystick* js = NULL; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { @@ -62,30 +138,61 @@ static GLFWbool openJoystickDevice(const char* path) if (fd == -1) return GLFW_FALSE; - // Verify that the joystick driver version is at least 1.0 - ioctl(fd, JSIOCGVERSION, &version); - if (version < 0x010000) + // 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)) { - // It's an old 0.x interface (we don't support it) close(fd); return GLFW_FALSE; } - if (ioctl(fd, JSIOCGNAME(sizeof(name)), name) < 0) + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) strncpy(name, "Unknown", sizeof(name)); - ioctl(fd, JSIOCGAXES, &axisCount); - ioctl(fd, JSIOCGBUTTONS, &buttonCount); + for (i = BTN_MISC; i < KEY_MAX; i++) + { + if (!TEST_BIT(i, keyBits)) + continue; - js = _glfwAllocJoystick(name, axisCount, buttonCount, 0); + linjs.keyMap[i] = buttonCount++; + } + + for (i = ABS_X; i < ABS_MAX; i++) + { + if (!TEST_BIT(i, absBits)) + continue; + + if (i >= ABS_HAT0X && i <= ABS_HAT3Y) + { + linjs.absMap[i] = hatCount++; + + // Skip the Y axis + i++; + } + else + { + if (ioctl(fd, EVIOCGABS(i), &linjs.absInfo[i]) < 0) + continue; + + linjs.absMap[i] = axisCount++; + } + } + + js = _glfwAllocJoystick(name, axisCount, buttonCount, hatCount); if (!js) { close(fd); return GLFW_FALSE; } - js->linjs.path = strdup(path); - js->linjs.fd = fd; + linjs.fd = fd; + strncpy(linjs.path, path, sizeof(linjs.path)); + memcpy(&js->linjs, &linjs, sizeof(linjs)); + + // Set initial values for absolute axes + pollJoystick(js); _glfwInputJoystick(_GLFW_JOYSTICK_ID(js), GLFW_CONNECTED); return GLFW_TRUE; @@ -96,7 +203,6 @@ static GLFWbool openJoystickDevice(const char* path) static void closeJoystick(_GLFWjoystick* js) { close(js->linjs.fd); - free(js->linjs.path); _glfwFreeJoystick(js); _glfwInputJoystick(_GLFW_JOYSTICK_ID(js), GLFW_DISCONNECTED); } @@ -147,7 +253,7 @@ GLFWbool _glfwInitJoysticksLinux(void) // Continue without device connection notifications } - if (regcomp(&_glfw.linjs.regex, "^js[0-9]\\+$", 0) != 0) + if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex"); return GLFW_FALSE; @@ -265,7 +371,7 @@ int _glfwPlatformPollJoystick(int jid, int mode) // Read all queued events (non-blocking) for (;;) { - struct js_event e; + struct input_event e; errno = 0; if (read(js->linjs.fd, &e, sizeof(e)) < 0) @@ -277,13 +383,13 @@ int _glfwPlatformPollJoystick(int jid, int mode) break; } - // Clear the initial-state bit - e.type &= ~JS_EVENT_INIT; - - if (e.type == JS_EVENT_AXIS) - _glfwInputJoystickAxis(jid, e.number, e.value / 32767.0f); - else if (e.type == JS_EVENT_BUTTON) - _glfwInputJoystickButton(jid, e.number, e.value ? 1 : 0); + if (e.type == EV_KEY) + handleKeyEvent(js, e.code, e.value); + 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); } return js->present; diff --git a/src/linux_joystick.h b/src/linux_joystick.h index 311becf6..73b4819f 100644 --- a/src/linux_joystick.h +++ b/src/linux_joystick.h @@ -24,27 +24,34 @@ // //======================================================================== +#include +#include #include #define _GLFW_PLATFORM_JOYSTICK_STATE _GLFWjoystickLinux linjs #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE _GLFWlibraryLinux linjs +#define HATS_MAX ((ABS_HAT3Y - ABS_HAT0X) / 2) // Linux-specific joystick data // typedef struct _GLFWjoystickLinux { - int fd; - char* path; + int fd; + char path[PATH_MAX]; + int keyMap[KEY_MAX]; + int absMap[ABS_MAX]; + struct input_absinfo absInfo[ABS_MAX]; + int hats[HATS_MAX][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;