diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 3e23968ef..172d355d5 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1140,6 +1140,42 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); */ typedef void (* GLFWjoystickfun)(int,int); +/*! @brief The function signature for joystick button callbacks. + * + * This is the function signature for joystick button callback + * functions. + * + * @param[in] joy The joystick that had a button pressed or released. + * @param[in] button The button that was pressed or released. + * @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`. + * + * @sa @ref joystick_event + * @sa glfwSetJoystickButtonCallback + * + * @since Added in version 3.2. + * + * @ingroup input + */ +typedef void (* GLFWjoystickbuttonfun)(int,int,int); + +/*! @brief The function signature for joystick axis callbacks. + * + * This is the function signature for joystick axis callback + * functions. + * + * @param[in] joy The joystick that had an axis moved. + * @param[in] axis The axis that was moved. + * @param[in] value The axis value. + * + * @sa @ref joystick_event + * @sa glfwSetJoystickAxisCallback + * + * @since Added in version 3.2. + * + * @ingroup input + */ +typedef void (* GLFWjoystickaxisfun)(int,int,float); + /*! @brief Video mode type. * * This describes a single video mode. @@ -3724,6 +3760,50 @@ GLFWAPI const char* glfwGetJoystickName(int joy); */ GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun); +/*! @brief Sets the joystick button callback. + * + * This function sets the joystick button callback, or removes the currently + * set callback. This is called when a joystick button is pressed or released. + * + * @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 GLFWjoystickbuttonfun glfwSetJoystickButtonCallback(GLFWjoystickbuttonfun cbfun); + +/*! @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 is moved. + * + * @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 GLFWjoystickaxisfun glfwSetJoystickAxisCallback(GLFWjoystickaxisfun 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 439918fd7..8a8404474 100644 --- a/src/cocoa_joystick.m +++ b/src/cocoa_joystick.m @@ -37,7 +37,6 @@ #include #include - // Joystick element information // typedef struct _GLFWjoyelementNS @@ -193,75 +192,96 @@ static void removeJoystick(_GLFWjoystickNS* js) _glfwInputJoystickChange(js - _glfw.ns_js, GLFW_DISCONNECTED); } -// Polls for joystick axis events and updates GLFW state +// Input value callback // -static GLFWbool pollJoystickAxisEvents(_GLFWjoystickNS* js) +static void inputValueCallback (void* context, + IOReturn valueResult, + void* deviceRef, + IOHIDValueRef valueRef) { + _GLFWjoystickNS* js; CFIndex i; + int buttonIndex = 0; + if (kIOReturnSuccess != valueResult) + return; + + js = (_GLFWjoystickNS*)context; + if (!js) + return; if (!js->present) - return GLFW_FALSE; + return; + + IOHIDElementRef elementRef = IOHIDValueGetElement(valueRef); for (i = 0; i < CFArrayGetCount(js->axisElements); i++) { _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*) CFArrayGetValueAtIndex(js->axisElements, i); - long value = getElementValue(js, axis); - long readScale = axis->maxReport - axis->minReport; + if (elementRef == axis->elementRef) + { + long value = getElementValue(js, axis); + long readScale = axis->maxReport - axis->minReport; - if (readScale == 0) - js->axes[i] = value; - else - js->axes[i] = (2.f * (value - axis->minReport) / readScale) - 1.f; + if (readScale == 0) + js->axes[i] = value; + else + js->axes[i] = (2.f * (value - axis->minReport) / readScale) - 1.f; + + _glfwInputJoystickAxisMoved(js-_glfw.ns_js,i,js->axes[i]); + return; + } } - return GLFW_TRUE; -} - -// Polls for joystick button events and updates GLFW state -// -static GLFWbool pollJoystickButtonEvents(_GLFWjoystickNS* js) -{ - CFIndex i; - int buttonIndex = 0; - - if (!js->present) - return GLFW_FALSE; - - for (i = 0; i < CFArrayGetCount(js->buttonElements); i++) + for (i = 0; i < CFArrayGetCount(js->buttonElements); i++, buttonIndex++) { _GLFWjoyelementNS* button = (_GLFWjoyelementNS*) CFArrayGetValueAtIndex(js->buttonElements, i); - if (getElementValue(js, button)) - js->buttons[buttonIndex++] = GLFW_PRESS; - else - js->buttons[buttonIndex++] = GLFW_RELEASE; + if (elementRef == button->elementRef) + { + if (getElementValue(js, button)) + js->buttons[buttonIndex] = GLFW_PRESS; + else + js->buttons[buttonIndex] = GLFW_RELEASE; + + _glfwInputJoystickButtonState(js-_glfw.ns_js,buttonIndex,js->buttons[buttonIndex]); + return; + } } - for (i = 0; i < CFArrayGetCount(js->hatElements); i++) + for (i = 0; i < CFArrayGetCount(js->hatElements); i++, buttonIndex += 4) { _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*) CFArrayGetValueAtIndex(js->hatElements, i); - // Bit fields of button presses for each direction, including nil - const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 }; - - long j, value = getElementValue(js, hat); - if (value < 0 || value > 8) - value = 8; - - for (j = 0; j < 4; j++) + if (elementRef == hat->elementRef) { - if (directions[value] & (1 << j)) - js->buttons[buttonIndex++] = GLFW_PRESS; - else - js->buttons[buttonIndex++] = GLFW_RELEASE; - } - } + // Bit fields of button presses for each direction, including nil + const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 }; - return GLFW_TRUE; + long j, value = getElementValue(js, hat); + if (value < 0 || value > 8) + value = 8; + + for (j = 0; j < 4; j++) + { + const unsigned char oldValue = js->buttons[buttonIndex+j]; + + if (directions[value] & (1 << j)) + js->buttons[buttonIndex+j] = GLFW_PRESS; + else + js->buttons[buttonIndex+j] = GLFW_RELEASE; + + // Since we're treating hat switches as buttons, only send events for + // the values that have actually changed. + if (oldValue != js->buttons[buttonIndex+j]) + _glfwInputJoystickButtonState(js-_glfw.ns_js,buttonIndex+j,js->buttons[buttonIndex+j]); + } + } + return; + } } // Callback for user-initiated joystick addition @@ -324,6 +344,10 @@ static void matchCallback(void* context, js->buttons = calloc(CFArrayGetCount(js->buttonElements) + CFArrayGetCount(js->hatElements) * 4, 1); + IOHIDDeviceRegisterInputValueCallback(deviceRef, + inputValueCallback, + (void*)js); + _glfwInputJoystickChange(joy, GLFW_CONNECTED); } @@ -399,7 +423,6 @@ void _glfwInitJoysticksNS(void) _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - matchingCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); @@ -482,7 +505,8 @@ int _glfwPlatformJoystickPresent(int joy) const float* _glfwPlatformGetJoystickAxes(int joy, int* count) { _GLFWjoystickNS* js = _glfw.ns_js + joy; - if (!pollJoystickAxisEvents(js)) + + if (!js->present) return NULL; *count = (int) CFArrayGetCount(js->axisElements); @@ -492,7 +516,8 @@ const float* _glfwPlatformGetJoystickAxes(int joy, int* count) const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count) { _GLFWjoystickNS* js = _glfw.ns_js + joy; - if (!pollJoystickButtonEvents(js)) + + if (!js->present) return NULL; *count = (int) CFArrayGetCount(js->buttonElements) + diff --git a/src/input.c b/src/input.c index c09599212..af4e49151 100644 --- a/src/input.c +++ b/src/input.c @@ -130,6 +130,18 @@ void _glfwInputJoystickChange(int joy, int event) _glfw.callbacks.joystick(joy, event); } +void _glfwInputJoystickButtonState(int joy, int button, int action) +{ + if (_glfw.callbacks.joystick_button) + _glfw.callbacks.joystick_button(joy, button, action); +} + +void _glfwInputJoystickAxisMoved(int joy, int axis, float value) +{ + if (_glfw.callbacks.joystick_axis) + _glfw.callbacks.joystick_axis(joy, axis, value); +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// @@ -618,6 +630,20 @@ GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun 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 void glfwSetClipboardString(GLFWwindow* handle, const char* string) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/internal.h b/src/internal.h index 4d92c554a..6878d4cae 100644 --- a/src/internal.h +++ b/src/internal.h @@ -469,8 +469,10 @@ struct _GLFWlibrary } vk; struct { - GLFWmonitorfun monitor; - GLFWjoystickfun joystick; + GLFWmonitorfun monitor; + GLFWjoystickfun joystick; + GLFWjoystickbuttonfun joystick_button; + GLFWjoystickaxisfun joystick_axis; } callbacks; // This is defined in the window API's platform.h @@ -967,6 +969,22 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** names); */ void _glfwInputJoystickChange(int joy, int event); +/*! @brief Notifies shared code of a joystick button pressed/release action. + * @param[in] joy The joystick that had a button pressed or released. + * @param[in] button The button that was pressed or released. + * @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`. + * @ingroup event + */ +void _glfwInputJoystickButtonState(int joy, int button, int action); + +/*! @brief Notifies shared code of joystick axis motion. + * @param[in] joy The joystick that had an axis moved. + * @param[in] axis The axis that was moved. + * @param[in] value The current position of the axis. + * @ingroup event + */ +void _glfwInputJoystickAxisMoved(int joy, int axis, float value); + //======================================================================== // Utility functions diff --git a/tests/events.c b/tests/events.c index 09da5657d..844940a7a 100644 --- a/tests/events.c +++ b/tests/events.c @@ -482,6 +482,18 @@ static void joystick_callback(int joy, int event) } } +static void joystick_button_callback(int joy, int button, int action) +{ + printf("%08x at %0.3f: Joystick %i button %i was %s\n", + counter++, glfwGetTime(), joy, button, get_action_name(action)); +} + +static void joystick_axis_callback(int joy, int axis, float value) +{ + printf("%08x at %0.3f: Joystick %i axis %i was moved to %f\n", + counter++, glfwGetTime(), joy, axis, value); +} + int main(int argc, char** argv) { Slot* slots; @@ -499,6 +511,8 @@ int main(int argc, char** argv) glfwSetMonitorCallback(monitor_callback); glfwSetJoystickCallback(joystick_callback); + glfwSetJoystickButtonCallback(joystick_button_callback); + glfwSetJoystickAxisCallback(joystick_axis_callback); while ((ch = getopt(argc, argv, "hfn:")) != -1) {