diff --git a/README.md b/README.md index bc6ab59b..292ccecb 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ does not find Doxygen, the documentation will not be generated. - Added `glfwWaitEventsTimeout` for waiting for events for a set amount of time - Added `glfwSetWindowIcon` for setting the icon of a window - Added `glfwGetTimerValue` and `glfwGetTimerFrequency` for raw timer access + - Added `glfwSetJoystickCallback` and `GLFWjoystickfun` for joystick connection + and disconnection events - Added `GLFW_NO_API` for creating window without contexts - Added `GLFW_CONTEXT_NO_ERROR` context hint for `GL_KHR_no_error` support - Added `GLFW_INCLUDE_VULKAN` for including the Vulkan header diff --git a/docs/input.dox b/docs/input.dox index 8e4ff7ea..7ac3f229 100644 --- a/docs/input.dox +++ b/docs/input.dox @@ -547,6 +547,33 @@ and make may have the same name. Only the [joystick token](@ref joysticks) is guaranteed to be unique, and only until that joystick is disconnected. +@subsection joystick_event Joystick configuration changes + +If you wish to be notified when a joystick is connected or disconnected, set +a joystick callback. + +@code +glfwSetJoystickCallback(joystick_callback); +@endcode + +The callback function receives the ID of the joystick that has been connected +and disconnected and the event that occurred. + +@code +void joystick_callback(int joy, int event) +{ + if (event == GLFW_CONNECTED) + { + // The joystick was connected + } + else if (event == GLFW_DISCONNECTED) + { + // The joystick was disconnected + } +} +@endcode + + @section time Time input GLFW provides high-resolution time input, in seconds, with @ref glfwGetTime. diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index ebd7eb26..f8ca3d61 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1097,6 +1097,23 @@ typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**); */ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); +/*! @brief The function signature for joystick configuration callbacks. + * + * This is the function signature for joystick configuration callback + * functions. + * + * @param[in] joy The joystick that was connected or disconnected. + * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. + * + * @sa @ref joystick_event + * @sa glfwSetJoystickCallback + * + * @since Added in version 3.2. + * + * @ingroup input + */ +typedef void (* GLFWjoystickfun)(int,int); + /*! @brief Video mode type. * * This describes a single video mode. @@ -3596,6 +3613,29 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int joy, int* count); */ GLFWAPI const char* glfwGetJoystickName(int joy); +/*! @brief Sets the joystick configuration callback. + * + * This function sets the joystick configuration callback, or removes the + * currently set callback. This is called when a joystick is connected to or + * disconnected from the system. + * + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref joystick_event + * + * @since Added in version 3.2. + * + * @ingroup input + */ +GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun); + /*! @brief Sets the clipboard to the specified string. * * This function sets the system clipboard to the specified, UTF-8 encoded diff --git a/src/cocoa_joystick.m b/src/cocoa_joystick.m index 29264f49..71ead6d1 100644 --- a/src/cocoa_joystick.m +++ b/src/cocoa_joystick.m @@ -190,6 +190,8 @@ static void removeJoystick(_GLFWjoydeviceNS* joystick) free(joystick->buttons); memset(joystick, 0, sizeof(_GLFWjoydeviceNS)); + + _glfwInputJoystickChange(joystick - _glfw.ns_js.devices, GLFW_DISCONNECTED); } // Polls for joystick axis events and updates GLFW state @@ -329,6 +331,8 @@ static void matchCallback(void* context, sizeof(float)); joystick->buttons = calloc(CFArrayGetCount(joystick->buttonElements) + CFArrayGetCount(joystick->hatElements) * 4, 1); + + _glfwInputJoystickChange(joy, GLFW_CONNECTED); } // Callback for user-initiated joystick removal diff --git a/src/input.c b/src/input.c index 4ca83f87..287dab3c 100644 --- a/src/input.c +++ b/src/input.c @@ -220,6 +220,12 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) window->callbacks.drop((GLFWwindow*) window, count, paths); } +void _glfwInputJoystickChange(int joy, int event) +{ + if (_glfw.callbacks.joystick) + _glfw.callbacks.joystick(joy, event); +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// @@ -621,6 +627,13 @@ GLFWAPI const char* glfwGetJoystickName(int joy) return _glfwPlatformGetJoystickName(joy); } +GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun); + return cbfun; +} + GLFWAPI void glfwSetClipboardString(GLFWwindow* handle, const char* string) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/internal.h b/src/internal.h index d9d037c6..e8304770 100644 --- a/src/internal.h +++ b/src/internal.h @@ -448,6 +448,7 @@ struct _GLFWlibrary struct { GLFWmonitorfun monitor; + GLFWjoystickfun joystick; } callbacks; // This is defined in the window API's platform.h @@ -947,6 +948,13 @@ void _glfwInputError(int error, const char* format, ...); */ void _glfwInputDrop(_GLFWwindow* window, int count, const char** names); +/*! @brief Notifies shared code of a joystick connection/disconnection event. + * @param[in] joy The joystick that was connected or disconnected. + * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. + * @ingroup event + */ +void _glfwInputJoystickChange(int joy, int event); + //======================================================================== // Utility functions diff --git a/src/linux_joystick.c b/src/linux_joystick.c index e3f8bb38..3342e0fe 100644 --- a/src/linux_joystick.c +++ b/src/linux_joystick.c @@ -100,6 +100,8 @@ static GLFWbool openJoystickDevice(const char* path) ioctl(fd, JSIOCGBUTTONS, &buttonCount); js->buttonCount = (int) buttonCount; js->buttons = calloc(buttonCount, 1); + + _glfwInputJoystickChange(joy, GLFW_CONNECTED); #endif // __linux__ return GLFW_TRUE; } @@ -109,25 +111,7 @@ static GLFWbool openJoystickDevice(const char* path) static GLFWbool pollJoystickEvents(_GLFWjoystickLinux* js) { #if defined(__linux__) - ssize_t offset = 0; - char buffer[16384]; - - const ssize_t size = read(_glfw.linux_js.inotify, buffer, sizeof(buffer)); - - while (size > offset) - { - regmatch_t match; - const struct inotify_event* e = (struct inotify_event*) (buffer + offset); - - if (regexec(&_glfw.linux_js.regex, e->name, 1, &match, 0) == 0) - { - char path[20]; - snprintf(path, sizeof(path), "/dev/input/%s", e->name); - openJoystickDevice(path); - } - - offset += sizeof(struct inotify_event) + e->len; - } + _glfwPollJoystickEvents(); if (!js->present) return GLFW_FALSE; @@ -149,6 +133,9 @@ static GLFWbool pollJoystickEvents(_GLFWjoystickLinux* js) free(js->path); memset(js, 0, sizeof(_GLFWjoystickLinux)); + + _glfwInputJoystickChange(js - _glfw.linux_js.js, + GLFW_DISCONNECTED); } break; @@ -285,6 +272,29 @@ void _glfwTerminateJoysticksLinux(void) #endif // __linux__ } +void _glfwPollJoystickEvents(void) +{ + ssize_t offset = 0; + char buffer[16384]; + + const ssize_t size = read(_glfw.linux_js.inotify, buffer, sizeof(buffer)); + + while (size > offset) + { + regmatch_t match; + const struct inotify_event* e = (struct inotify_event*) (buffer + offset); + + if (regexec(&_glfw.linux_js.regex, e->name, 1, &match, 0) == 0) + { + char path[20]; + snprintf(path, sizeof(path), "/dev/input/%s", e->name); + openJoystickDevice(path); + } + + offset += sizeof(struct inotify_event) + e->len; + } +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// diff --git a/src/linux_joystick.h b/src/linux_joystick.h index 6f34d576..c949e81d 100644 --- a/src/linux_joystick.h +++ b/src/linux_joystick.h @@ -64,4 +64,6 @@ typedef struct _GLFWjoylistLinux GLFWbool _glfwInitJoysticksLinux(void); void _glfwTerminateJoysticksLinux(void); +void _glfwPollJoystickEvents(void); + #endif // _glfw3_linux_joystick_h_ diff --git a/src/win32_joystick.c b/src/win32_joystick.c index 9184e80a..b3eab707 100644 --- a/src/win32_joystick.c +++ b/src/win32_joystick.c @@ -97,6 +97,8 @@ static GLFWbool openJoystickDevice(DWORD index) js->name = strdup(getDeviceDescription(&xic)); js->index = index; + _glfwInputJoystickChange(joy, GLFW_CONNECTED); + return GLFW_TRUE; } @@ -120,6 +122,7 @@ static GLFWbool pollJoystickEvents(_GLFWjoystickWin32* js, int flags) { free(js->name); memset(js, 0, sizeof(_GLFWjoystickWin32)); + _glfwInputJoystickChange((int) (js - _glfw.win32_js), GLFW_DISCONNECTED); } return GLFW_FALSE; diff --git a/src/x11_window.c b/src/x11_window.c index ff38a82d..0f805324 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -54,11 +54,19 @@ void selectDisplayConnection(struct timeval* timeout) { fd_set fds; - int result; + int result, count; const int fd = ConnectionNumber(_glfw.x11.display); FD_ZERO(&fds); FD_SET(fd, &fds); +#if defined(__linux__) + FD_SET(_glfw.linux_js.inotify, &fds); +#endif + + if (fd > _glfw.linux_js.inotify) + count = fd + 1; + else + count = _glfw.linux_js.inotify + 1; // NOTE: We use select instead of an X function like XNextEvent, as the // wait inside those are guarded by the mutex protecting the display @@ -68,7 +76,7 @@ void selectDisplayConnection(struct timeval* timeout) // TODO: Update timeout value manually do { - result = select(fd + 1, &fds, NULL, NULL, timeout); + result = select(count, &fds, NULL, NULL, timeout); } while (result == -1 && errno == EINTR && timeout == NULL); } @@ -1999,6 +2007,8 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window) void _glfwPlatformPollEvents(void) { + _glfwPollJoystickEvents(); + int count = XPending(_glfw.x11.display); while (count--) { diff --git a/tests/events.c b/tests/events.c index 3d4a8106..5e697f97 100644 --- a/tests/events.c +++ b/tests/events.c @@ -451,6 +451,29 @@ static void monitor_callback(GLFWmonitor* monitor, int event) } } +static void joystick_callback(int joy, int event) +{ + if (event == GLFW_CONNECTED) + { + int axisCount, buttonCount; + + glfwGetJoystickAxes(joy, &axisCount); + glfwGetJoystickButtons(joy, &buttonCount); + + printf("%08x at %0.3f: Joystick %i (%s) was connected with %i axes and %i buttons\n", + counter++, glfwGetTime(), + joy, + glfwGetJoystickName(joy), + axisCount, + buttonCount); + } + else + { + printf("%08x at %0.3f: Joystick %i was disconnected\n", + counter++, glfwGetTime(), joy); + } +} + int main(int argc, char** argv) { Slot* slots; @@ -467,6 +490,7 @@ int main(int argc, char** argv) printf("Library initialized\n"); glfwSetMonitorCallback(monitor_callback); + glfwSetJoystickCallback(joystick_callback); while ((ch = getopt(argc, argv, "hfn:")) != -1) { diff --git a/tests/joysticks.c b/tests/joysticks.c index 5397ba7f..04b578e9 100644 --- a/tests/joysticks.c +++ b/tests/joysticks.c @@ -39,17 +39,7 @@ #define strdup(x) _strdup(x) #endif -typedef struct Joystick -{ - int present; - char* name; - float* axes; - unsigned char* buttons; - int axis_count; - int button_count; -} Joystick; - -static Joystick joysticks[GLFW_JOYSTICK_LAST - GLFW_JOYSTICK_1 + 1]; +static int joysticks[GLFW_JOYSTICK_LAST + 1]; static int joystick_count = 0; static void error_callback(int error, const char* description) @@ -62,19 +52,23 @@ static void framebuffer_size_callback(GLFWwindow* window, int width, int height) glViewport(0, 0, width, height); } -static void draw_joystick(Joystick* j, int x, int y, int width, int height) +static void draw_joystick(int index, int x, int y, int width, int height) { int i; + int axis_count, button_count; + const float* axes; + const unsigned char* buttons; const int axis_height = 3 * height / 4; const int button_height = height / 4; - if (j->axis_count) + axes = glfwGetJoystickAxes(joysticks[index], &axis_count); + if (axis_count) { - const int axis_width = width / j->axis_count; + const int axis_width = width / axis_count; - for (i = 0; i < j->axis_count; i++) + for (i = 0; i < axis_count; i++) { - float value = j->axes[i] / 2.f + 0.5f; + float value = axes[i] / 2.f + 0.5f; glColor3f(0.3f, 0.3f, 0.3f); glRecti(x + i * axis_width, @@ -90,13 +84,14 @@ static void draw_joystick(Joystick* j, int x, int y, int width, int height) } } - if (j->button_count) + buttons = glfwGetJoystickButtons(joysticks[index], &button_count); + if (button_count) { - const int button_width = width / j->button_count; + const int button_width = width / button_count; - for (i = 0; i < j->button_count; i++) + for (i = 0; i < button_count; i++) { - if (j->buttons[i]) + if (buttons[i]) glColor3f(1.f, 1.f, 1.f); else glColor3f(0.3f, 0.3f, 0.3f); @@ -120,79 +115,58 @@ static void draw_joysticks(GLFWwindow* window) glOrtho(0.f, width, height, 0.f, 1.f, -1.f); glMatrixMode(GL_MODELVIEW); - for (i = 0; i < sizeof(joysticks) / sizeof(Joystick); i++) + for (i = 0; i < joystick_count; i++) { - Joystick* j = joysticks + i; - - if (j->present) - { - draw_joystick(j, - 0, offset * height / joystick_count, - width, height / joystick_count); - offset++; - } + draw_joystick(i, + 0, offset * height / joystick_count, + width, height / joystick_count); + offset++; } } -static void refresh_joysticks(void) +static void joystick_callback(int joy, int event) { - int i; - - for (i = 0; i < sizeof(joysticks) / sizeof(Joystick); i++) + if (event == GLFW_CONNECTED) { - Joystick* j = joysticks + i; + int axis_count, button_count; - if (glfwJoystickPresent(GLFW_JOYSTICK_1 + i)) + glfwGetJoystickAxes(joy, &axis_count); + glfwGetJoystickButtons(joy, &button_count); + + printf("Found joystick %i named \'%s\' with %i axes, %i buttons\n", + joy + 1, + glfwGetJoystickName(joy), + axis_count, + button_count); + + joysticks[joystick_count++] = joy; + } + else if (event == GLFW_DISCONNECTED) + { + int i; + + for (i = 0; i < joystick_count; i++) { - const float* axes; - const unsigned char* buttons; - int axis_count, button_count; - - free(j->name); - j->name = strdup(glfwGetJoystickName(GLFW_JOYSTICK_1 + i)); - - axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1 + i, &axis_count); - if (axis_count != j->axis_count) - { - j->axis_count = axis_count; - j->axes = realloc(j->axes, j->axis_count * sizeof(float)); - } - - memcpy(j->axes, axes, axis_count * sizeof(float)); - - buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1 + i, &button_count); - if (button_count != j->button_count) - { - j->button_count = button_count; - j->buttons = realloc(j->buttons, j->button_count); - } - - memcpy(j->buttons, buttons, button_count * sizeof(unsigned char)); - - if (!j->present) - { - printf("Found joystick %i named \'%s\' with %i axes, %i buttons\n", - i + 1, j->name, j->axis_count, j->button_count); - - joystick_count++; - } - - j->present = GLFW_TRUE; + if (joysticks[i] == joy) + break; } - else - { - if (j->present) - { - printf("Lost joystick %i named \'%s\'\n", i + 1, j->name); - free(j->name); - free(j->axes); - free(j->buttons); - memset(j, 0, sizeof(Joystick)); + for (i = i + 1; i < joystick_count; i++) + joysticks[i - 1] = joysticks[i]; - joystick_count--; - } - } + printf("Lost joystick %i\n", joy + 1); + joystick_count--; + } +} + +static void find_joysticks(void) +{ + int joy; + + for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++) + { + if (glfwJoystickPresent(joy)) + joystick_callback(joy, GLFW_CONNECTED); } } @@ -207,6 +181,9 @@ int main(void) if (!glfwInit()) exit(EXIT_FAILURE); + find_joysticks(); + glfwSetJoystickCallback(joystick_callback); + window = glfwCreateWindow(640, 480, "Joystick Test", NULL, NULL); if (!window) { @@ -224,7 +201,6 @@ int main(void) { glClear(GL_COLOR_BUFFER_BIT); - refresh_joysticks(); draw_joysticks(window); glfwSwapBuffers(window);