diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 95aac6d9..94ae786c 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1900,6 +1900,107 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor* monitor, int event); */ typedef void (* GLFWjoystickfun)(int jid, int event); +/*! @brief The function pointer type for joystick button callbacks. + * + * This is the function pointer type for joystick button callbacks. A joystick + * button callback function has the following signature: + * @code + * void function_name(int jid, int button, int action) + * @endcode + * + * @param[in] jid The joystick that had a button pressed or released. + * @param[in] button The [joystick button](@ref buttons) that was pressed or released. + * @param[in] action `GLFW_PRESS` or `GLFW_RELEASE`. Future + * releases may add more actions. + * + * @sa @ref input_joystick_button + * @sa @ref glfwSetJoystickButonCallback + * + * @since Added in version 3.4. + * @ingroup input + */ +typedef void (* GLFWjoystickbuttonfun)(int,int,int); + +/*! @brief The function pointer type for joystick axis movement callbacks. + * + * This is the function pointer type for joystick axis movement callbacks. A joystick + * axis movement callback function has the following signature: + * @code + * void function_name(int jid, int axis, float position) + * @endcode + * + * @param[in] jid The joystick that had an axis moved. + * @param[in] axis The [joystick axis](@ref gamepad axes) that was moved. + * @param[in] position A value between -1.0 and 1.0 that indicates the position of the axis. + * + * @sa @ref input_gamepad_axis + * @sa @ref glfwSetJoystickAxisCallback + * + * @since Added in version 3.4. + * @ingroup input + */ +typedef void (* GLFWjoystickaxisfun)(int,int,float); + +/*! @brief The function pointer type for joystick hat movement callbacks. + * + * This is the function pointer type for joystick hat movement callbacks. A joystick + * hat movement callback function has the following signature: + * @code + * void function_name(int jid, int hat, int position) + * @endcode + * + * @param[in] jid The joystick that had an axis moved. + * @param[in] hat The [joystick hat](@ref joystick hats) that was moved. + * @param[in] position A value that indicates the position of the hat. + * The position parameter is one of the following values: + * + * Name | Value + * ---- | ----- + * `GLFW_HAT_CENTERED` | 0 + * `GLFW_HAT_UP` | 1 + * `GLFW_HAT_RIGHT` | 2 + * `GLFW_HAT_DOWN` | 4 + * `GLFW_HAT_LEFT` | 8 + * `GLFW_HAT_RIGHT_UP` | `GLFW_HAT_RIGHT` \| `GLFW_HAT_UP` + * `GLFW_HAT_RIGHT_DOWN` | `GLFW_HAT_RIGHT` \| `GLFW_HAT_DOWN` + * `GLFW_HAT_LEFT_UP` | `GLFW_HAT_LEFT` \| `GLFW_HAT_UP` + * `GLFW_HAT_LEFT_DOWN` | `GLFW_HAT_LEFT` \| `GLFW_HAT_DOWN` + * + * The diagonal directions are bitwise combinations of the primary (up, right, + * down and left) directions and you can test for these individually by ANDing + * it with the corresponding direction. + * + * @sa @ref input_joystick_hat + * @sa @ref glfwSetJoystickHatCallback + * + * @since Added in version 3.4. + * @ingroup input + */ +typedef void (* GLFWjoystickhatfun)(int,int,int); + +/*! @brief The function pointer type for game pad state changes. + * + * This is the function pointer type for game pad state change callbacks. + * A game pad state change callback function has the following signature: + * @code + * void function_name(int jid, GLFWgamepadstate* state) + * @endcode + * + * @param[in] jid The ID of the game pad that changed state. + * @param[in] buttons The states of each + * [gamepad button](@ref gamepad_buttons), + * `GLFW_PRESS` or `GLFW_RELEASE`. + * @param[in] axes The states of each [gamepad axis](@ref gamepad_axes), + * in the range -1.0 to 1.0 inclusive. + * + * @sa @ref input_gamepad + * @sa @ref glfwSetGamepadStateCallback + * + * @since Added in version 3.4. + * @ingroup input + */ +typedef void (* GLFWgamepadstatefun)(int, unsigned char buttons[15], + float axes[6]); /*! @brief Video mode type. * * This describes a single video mode. @@ -1995,6 +2096,7 @@ typedef struct GLFWimage * * @sa @ref gamepad * @sa @ref glfwGetGamepadState + * @sa @ref glfwSetGamepadStateCallback * * @since Added in version 3.3. * @@ -5509,6 +5611,140 @@ GLFWAPI int glfwJoystickIsGamepad(int jid); */ GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun callback); +/*! @brief Sets the joystick button callback. + * + * This function sets the joystick configuration callback, or removes the + * currently set callback. This is called when a joystick button is pressed + * or released. + * + * For joystick button events to be delivered on all platforms, + * you need to call one of the [event processing](@ref events) + * functions. + * + * @param[in] callback 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). + * + * @callback_signature + * @code + * void function_name(int jid, int button, int state) + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWjoystickbuttonfun). + * + * @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 GLFWjoystickbuttonfun glfwSetJoystickButtonCallback(GLFWjoystickbuttonfun callback); + +/*! @brief Sets the joystick axis callback. + * + * This function sets the joystick axis callback, or removes the + * currently set callback. This is called when a joystick axis moved. + * + * For joystick axis events to be delivered on all platforms, + * you need to call one of the [event processing](@ref events) + * functions. + * + * @param[in] callback 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). + * + * @callback_signature + * @code + * void function_name(int jid, int axis, float state) + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWjoystickaxisfun). + * + * @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 GLFWjoystickaxisfun glfwSetJoystickAxisCallback(GLFWjoystickaxisfun callback); + +/*! @brief Sets the joystick hat callback. + * + * This function sets the joystick hat callback, or removes the + * currently set callback. This is called when a joystick hat moved. + * + * For joystick hat events to be delivered on all platforms, + * you need to call one of the [event processing](@ref events) + * functions. + * + * @param[in] callback 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). + * + * @callback_signature + * @code + * void function_name(int jid, int hat, int state) + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWjoystickhatfun). + * + * @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 GLFWjoystickhatfun glfwSetJoystickHatCallback(GLFWjoystickhatfun callback); + +/*! @brief Sets the game pad state callback. + * + * This function sets the game pad state callback, or removes the + * currently set callback. This is called when a game pad state changes. + * + * For game pad events to be delivered on all platforms, + * you need to call one of the [event processing](@ref events) + * functions. + * + * @param[in] callback 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). + * + * @callback_signature + * @code + * void function_name(int jid, unsigned char buttons[15], float axes[6]) + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWgamepadstatefun). + * + * @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 GLFWgamepadstatefun glfwSetGamepadStateCallback(GLFWgamepadstatefun callback); + + /*! @brief Adds the specified SDL_GameControllerDB gamepad mappings. * * This function parses the specified ASCII encoded string and updates the diff --git a/src/input.c b/src/input.c index 83ef6921..863cacd7 100644 --- a/src/input.c +++ b/src/input.c @@ -370,28 +370,61 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) window->callbacks.drop((GLFWwindow*) window, count, paths); } +// Notifies shared code of a change in the gamepad state. +// Automatically recalculates the state if the gamepad callback is installed. +void _glfwInputGamepad(_GLFWjoystick* js) +{ + const int jid = (int) (js - _glfw.joysticks); + + if (!_glfw.initialized) { + return; + } + + if ((js->mapping != NULL) && (_glfw.callbacks.gamepad_state)) { + GLFWgamepadstate state; + if (glfwGetGamepadState(jid, &state)) { + _glfw.callbacks.gamepad_state(jid, state.buttons, state.axes); + } + } +} + // Notifies shared code of a joystick connection or disconnection // void _glfwInputJoystick(_GLFWjoystick* js, int event) { - const int jid = (int) (js - _glfw.joysticks); - - if (_glfw.callbacks.joystick) + if (_glfw.callbacks.joystick) { + const int jid = (int) (js - _glfw.joysticks); _glfw.callbacks.joystick(jid, event); + } } + // Notifies shared code of the new value of a joystick axis // void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value) { - js->axes[axis] = value; + if (js->axes[axis] != value) { + const int jid = (int) (js - _glfw.joysticks); + + js->axes[axis] = value; + if (_glfw.callbacks.joystick_axis) + _glfw.callbacks.joystick_axis(jid, axis, value); + _glfwInputGamepad(js); + } } // Notifies shared code of the new value of a joystick button // void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value) { - js->buttons[button] = value; + if (js->buttons[button] != value) { + const int jid = (int) (js - _glfw.joysticks); + + js->buttons[button] = value; + if(_glfw.callbacks.joystick_button) + _glfw.callbacks.joystick_button(jid, button, value); + _glfwInputGamepad(js); + } } // Notifies shared code of the new value of a joystick hat @@ -405,7 +438,14 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value) js->buttons[base + 2] = (value & 0x04) ? GLFW_PRESS : GLFW_RELEASE; js->buttons[base + 3] = (value & 0x08) ? GLFW_PRESS : GLFW_RELEASE; - js->hats[hat] = value; + if (js->hats[hat] != value) { + const int jid = (int) (js - _glfw.joysticks); + + js->hats[hat] = value; + if(_glfw.callbacks.joystick_hat) + _glfw.callbacks.joystick_hat(jid, hat, value); + _glfwInputGamepad(js); + } } @@ -484,6 +524,17 @@ void _glfwCenterCursorInContentArea(_GLFWwindow* window) _glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0); } +void _glfwPollAllJoysticks() { + int jid; + + for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) + { + if (_glfw.joysticks[jid].present == GLFW_TRUE) { + _glfwPlatformPollJoystick(_glfw.joysticks + jid, _GLFW_POLL_ALL); + } + } +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// @@ -1157,6 +1208,35 @@ GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) return cbfun; } +GLFWAPI GLFWgamepadstatefun glfwSetGamepadStateCallback(GLFWgamepadstatefun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.gamepad_state, cbfun); + return cbfun; +} + +GLFWAPI GLFWjoystickbuttonfun glfwSetJoystickButtonCallback(GLFWjoystickbuttonfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.joystick_button, cbfun); + return cbfun; +} + +GLFWAPI GLFWjoystickaxisfun glfwSetJoystickAxisCallback(GLFWjoystickaxisfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.joystick_axis, cbfun); + return cbfun; +} + +GLFWAPI GLFWjoystickhatfun glfwSetJoystickHatCallback(GLFWjoystickhatfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.joystick_hat, cbfun); + return cbfun; +} + + GLFWAPI int glfwUpdateGamepadMappings(const char* string) { int jid; diff --git a/src/internal.h b/src/internal.h index f41db3c3..3c19a950 100644 --- a/src/internal.h +++ b/src/internal.h @@ -582,8 +582,12 @@ struct _GLFWlibrary } vk; struct { - GLFWmonitorfun monitor; - GLFWjoystickfun joystick; + GLFWmonitorfun monitor; + GLFWjoystickfun joystick; + GLFWjoystickaxisfun joystick_axis; + GLFWjoystickbuttonfun joystick_button; + GLFWjoystickhatfun joystick_hat; + GLFWgamepadstatefun gamepad_state; } callbacks; // This is defined in the window API's platform.h @@ -790,6 +794,7 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name, int hatCount); void _glfwFreeJoystick(_GLFWjoystick* js); void _glfwCenterCursorInContentArea(_GLFWwindow* window); +void _glfwPollAllJoysticks(); GLFWbool _glfwInitVulkan(int mode); void _glfwTerminateVulkan(void); diff --git a/src/window.c b/src/window.c index 93d8a14c..64dbd54e 100644 --- a/src/window.c +++ b/src/window.c @@ -1081,12 +1081,14 @@ GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* GLFWAPI void glfwPollEvents(void) { _GLFW_REQUIRE_INIT(); + _glfwPollAllJoysticks(); _glfwPlatformPollEvents(); } GLFWAPI void glfwWaitEvents(void) { _GLFW_REQUIRE_INIT(); + _glfwPollAllJoysticks(); _glfwPlatformWaitEvents(); } diff --git a/src/x11_window.c b/src/x11_window.c index 297b975d..7ab8d9df 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -65,21 +65,32 @@ static GLFWbool waitForEvent(double* timeout) { fd_set fds; const int fd = ConnectionNumber(_glfw.x11.display); - int count = fd + 1; - -#if defined(__linux__) - if (_glfw.linjs.inotify > fd) - count = _glfw.linjs.inotify + 1; -#endif for (;;) { + int count = fd + 1; FD_ZERO(&fds); FD_SET(fd, &fds); + #if defined(__linux__) if (_glfw.linjs.inotify > 0) - FD_SET(_glfw.linjs.inotify, &fds); -#endif + { + if (_glfw.linjs.inotify >= count ) + count = _glfw.linjs.inotify + 1; + FD_SET(_glfw.linjs.inotify, &fds); + } + + for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) + { + if (_glfw.joysticks[jid].present) { + if (_glfw.joysticks[jid].linjs.fd >= count) + count = _glfw.joysticks[jid].linjs.fd + 1; + + FD_SET(_glfw.joysticks[jid].linjs.fd, &fds); + } + } + +#endif if (timeout) { const long seconds = (long) *timeout; @@ -2780,8 +2791,10 @@ void _glfwPlatformPollEvents(void) _GLFWwindow* window; #if defined(__linux__) - if (_glfw.joysticksInitialized) - _glfwDetectJoystickConnectionLinux(); + if (_glfw.joysticksInitialized) { + _glfwDetectJoystickConnectionLinux(); + _glfwPollAllJoysticks(); + } #endif XPending(_glfw.x11.display); @@ -2810,23 +2823,35 @@ void _glfwPlatformPollEvents(void) XFlush(_glfw.x11.display); } -void _glfwPlatformWaitEvents(void) +static GLFWbool waitForAndPollEvents(double * timeout) { - while (!XPending(_glfw.x11.display)) - waitForEvent(NULL); + GLFWbool result = GLFW_FALSE; + for (;;) + { + int xpending = XPending(_glfw.x11.display); + GLFWbool event = waitForEvent(timeout); + // We cannot use the boolean shortcut here, since + // waiting for events might have a side effect. + if (event || xpending ) + { + result = GLFW_TRUE; + break; + } + } _glfwPlatformPollEvents(); + return result; +} + + +void _glfwPlatformWaitEvents(void) +{ + waitForAndPollEvents(NULL); } void _glfwPlatformWaitEventsTimeout(double timeout) { - while (!XPending(_glfw.x11.display)) - { - if (!waitForEvent(&timeout)) - break; - } - - _glfwPlatformPollEvents(); + waitForAndPollEvents(&timeout); } void _glfwPlatformPostEmptyEvent(void) diff --git a/tests/events.c b/tests/events.c index a9d4023d..2341a637 100644 --- a/tests/events.c +++ b/tests/events.c @@ -532,6 +532,52 @@ static void joystick_callback(int jid, int event) } } +static void joystick_button_callback(int jid, int button, int state) { + printf("%08x at %0.3f: Joystick %i (%s) button %d state %d\n", + counter++, glfwGetTime(), + jid, + glfwGetJoystickName(jid), + button, + state); +} + +static void joystick_axis_callback(int jid, int axis, float value) { + printf("%08x at %0.3f: Joystick %i (%s) axis %d value %0.4f\n", + counter++, glfwGetTime(), jid, + glfwGetJoystickName(jid), + axis, + value); +} + +static void joystick_hat_callback(int jid, int hat, int value) { + printf("%08x at %0.3f: Joystick %i (%s) hat %d value %d\n", + counter++, glfwGetTime(), + jid, + glfwGetJoystickName(jid), + hat, + value); +} + +static void gamepad_state_callback(int jid, unsigned char buttons[15], float axes[6]) { + int i = 0; + printf("%08x at %0.3f: Gamepad %i (%s) state:", + counter++, glfwGetTime(), + jid, + glfwGetJoystickName(jid)); + + printf("Buttons: "); + for (i= 0 ; i < 15; i++) { + printf(" %d:%d", i, buttons[i]); + } + printf("Axes: "); + for (i= 0 ; i < 6; i++) { + printf(" %d:%0.4f", i, axes[i]); + } + printf("\n"); + +} + + int main(int argc, char** argv) { Slot* slots; @@ -547,6 +593,10 @@ int main(int argc, char** argv) glfwSetMonitorCallback(monitor_callback); glfwSetJoystickCallback(joystick_callback); + glfwSetJoystickAxisCallback(joystick_axis_callback); + glfwSetJoystickButtonCallback(joystick_button_callback); + glfwSetJoystickHatCallback(joystick_hat_callback); + glfwSetGamepadStateCallback(gamepad_state_callback); while ((ch = getopt(argc, argv, "hfn:")) != -1) {