From: Zebediah Figura zfigura@codeweavers.com
This should more closely match the algorithm used by native, discovered through extensive manual testing.
This does not include any automated tests, both because I could not get winmm to recognize a second plugged joystick, and (as I eventually discovered while testing) the number of tests that would be necessary to conclusively prove this algorithm turned out to be impractical.
--
I discovered that a certain joystick, an XBox clone (Logitech F310) was failing to report all of its axes to a winmm game (Super Smash Flash 2). The joystick reports axes X, Y, Z, RX, RY, RZ, but currently winmm does not map RY.
Checking Windows's joy.cpl implied that our HID usages were correct, so I instead started checking whether our mapping algorithm was correct for a synthetic joystick with the same usages.
It turned out that it was not. Native winmm appears to work in a way that is relatively intuitive to understand: for each axis, it runs through a list of candidate usages. If the candidate usage is present on the joystick, and wasn't already mapped to a different axis, then the axis is mapped. --- dlls/winmm/joystick.c | 166 ++++++++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 70 deletions(-)
diff --git a/dlls/winmm/joystick.c b/dlls/winmm/joystick.c index 56ff61582ba..6f0c1208f47 100644 --- a/dlls/winmm/joystick.c +++ b/dlls/winmm/joystick.c @@ -34,6 +34,7 @@ #include "winbase.h" #include "mmsystem.h"
+#include "hidusage.h" #include "initguid.h" #include "dinput.h"
@@ -52,69 +53,21 @@ static CRITICAL_SECTION_DEBUG critsect_debug = }; static CRITICAL_SECTION joystick_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
-struct joystick_state +enum axis { - LONG x; - LONG y; - LONG z; - LONG u; - LONG v; - LONG r; - LONG pov; - BYTE buttons[32]; + AXIS_X, + AXIS_Y, + AXIS_Z, + AXIS_R, + AXIS_U, + AXIS_V, };
-static const DIOBJECTDATAFORMAT object_formats[] = -{ - { &GUID_XAxis, offsetof(struct joystick_state, x), DIDFT_OPTIONAL|DIDFT_AXIS|DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, - { &GUID_YAxis, offsetof(struct joystick_state, y), DIDFT_OPTIONAL|DIDFT_AXIS|DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, - { &GUID_ZAxis, offsetof(struct joystick_state, z), DIDFT_OPTIONAL|DIDFT_AXIS|DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, - { &GUID_RzAxis, offsetof(struct joystick_state, r), DIDFT_OPTIONAL|DIDFT_AXIS|DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, - { &GUID_Slider, offsetof(struct joystick_state, u), DIDFT_OPTIONAL|DIDFT_AXIS|DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, - { &GUID_RxAxis, offsetof(struct joystick_state, v), DIDFT_OPTIONAL|DIDFT_AXIS|DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, - { &GUID_POV, offsetof(struct joystick_state, pov), DIDFT_OPTIONAL|DIDFT_POV|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[0]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[1]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[2]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[3]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[4]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[5]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[6]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[7]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[8]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[9]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[10]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[11]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[12]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[13]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[14]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[15]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[16]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[17]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[18]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[19]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[20]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[21]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[22]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[23]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[24]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[25]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[26]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[27]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[28]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[29]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[30]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, - { NULL, offsetof(struct joystick_state, buttons[31]), DIDFT_OPTIONAL|DIDFT_BUTTON|DIDFT_ANYINSTANCE, 0 }, -}; - -static const DIDATAFORMAT data_format = +struct joystick_state { - .dwSize = sizeof(DIDATAFORMAT), - .dwObjSize = sizeof(DIOBJECTDATAFORMAT), - .dwFlags = DIDF_ABSAXIS, - .dwDataSize = sizeof(struct joystick_state), - .dwNumObjs = ARRAY_SIZE(object_formats), - .rgodf = (DIOBJECTDATAFORMAT *)object_formats, + LONG axes[6]; + LONG pov; + BYTE buttons[32]; };
#define JOY_PERIOD_MIN (10) /* min Capture time period */ @@ -176,20 +129,94 @@ void joystick_unload(void) IDirectInput8_Release( dinput ); }
+static bool is_already_mapped( const DIOBJECTDATAFORMAT *object_formats, DWORD count, + const DIDEVICEOBJECTINSTANCEW *instance ) +{ + DWORD i; + + for (i = 0; i < count; ++i) + { + if (object_formats[i].dwType == instance->dwType) + return true; + } + + return false; +} + static bool acquire_joystick( DWORD index ) { + DIOBJECTDATAFORMAT object_formats[6 + 1 + 32] = {{0}}; /* 6 axes, 1 hat switch, 32 buttons */ const DIDEVICEINSTANCEW *instance = &instances[index]; struct joystick *joystick = &joysticks[index]; + DIOBJECTDATAFORMAT *object_format; + DIDATAFORMAT data_format = {0}; IDirectInputDevice8W *device; + unsigned int i, j; HANDLE event; HRESULT hr;
+ static const struct + { + WORD usages[5]; + } + usage_mappings[6] = + { + [AXIS_X] = {{HID_USAGE_GENERIC_X, HID_USAGE_GENERIC_RY}}, + [AXIS_Y] = {{HID_USAGE_GENERIC_Y, HID_USAGE_GENERIC_RX}}, + [AXIS_Z] = {{HID_USAGE_GENERIC_WHEEL, HID_USAGE_GENERIC_Z, HID_USAGE_GENERIC_SLIDER, HID_USAGE_GENERIC_DIAL}}, + [AXIS_R] = {{HID_USAGE_GENERIC_RZ, HID_USAGE_GENERIC_SLIDER, HID_USAGE_GENERIC_DIAL, HID_USAGE_GENERIC_RY, HID_USAGE_GENERIC_RX}}, + [AXIS_U] = {{HID_USAGE_GENERIC_SLIDER, HID_USAGE_GENERIC_DIAL, HID_USAGE_GENERIC_RY, HID_USAGE_GENERIC_RX}}, + [AXIS_V] = {{HID_USAGE_GENERIC_RX}}, + }; + + memset( joystick, 0, sizeof(*joystick) ); + if (FAILED(hr = IDirectInput8_CreateDevice( dinput, &instance->guidInstance, &device, NULL ))) { WARN( "failed to create dinput device %s, hr %#lx\n", debugstr_guid( &instance->guidInstance ), hr ); return false; }
+ for (i = 0; i < 6; ++i) + { + for (j = 0; j < ARRAY_SIZE(usage_mappings[i].usages) && usage_mappings[i].usages[j]; ++j) + { + DIDEVICEOBJECTINSTANCEW instance; + + instance.dwSize = sizeof(DIDEVICEOBJECTINSTANCEW); + if (FAILED(IDirectInputDevice8_GetObjectInfo( device, &instance, + DIMAKEUSAGEDWORD(HID_USAGE_PAGE_GENERIC, usage_mappings[i].usages[j]), DIPH_BYUSAGE ))) + continue; + if (!(instance.dwFlags & DIDOI_ASPECTPOSITION)) + continue; + if (is_already_mapped( object_formats, data_format.dwNumObjs, &instance )) + continue; + + object_format = &object_formats[data_format.dwNumObjs++]; + object_format->dwOfs = offsetof(struct joystick_state, axes[i]); + object_format->dwType = instance.dwType; + break; + } + } + + object_format = &object_formats[data_format.dwNumObjs++]; + object_format->pguid = &GUID_POV; + object_format->dwOfs = offsetof(struct joystick_state, pov); + object_format->dwType = DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE; + + for (i = 0; i < 32; ++i) + { + object_format = &object_formats[data_format.dwNumObjs++]; + object_format->dwOfs = offsetof(struct joystick_state, buttons[i]); + object_format->dwType = DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE; + } + + data_format.dwSize = sizeof(DIDATAFORMAT); + data_format.dwObjSize = sizeof(DIOBJECTDATAFORMAT); + data_format.dwFlags = DIDF_ABSAXIS; + data_format.dwDataSize = sizeof(struct joystick_state); + data_format.rgodf = object_formats; + if (!(event = CreateEventW( NULL, FALSE, FALSE, NULL ))) ERR( "failed to create event, error %lu\n", GetLastError() ); if (FAILED(hr = IDirectInputDevice8_SetEventNotification( device, event ))) @@ -206,7 +233,6 @@ static bool acquire_joystick( DWORD index ) return false; }
- memset( joystick, 0, sizeof(*joystick) ); joystick->instance = *instance; joystick->device = device; joystick->event = event; @@ -401,13 +427,13 @@ MMRESULT WINAPI DECLSPEC_HOTPATCH joyGetDevCapsW( UINT_PTR id, JOYCAPSW *caps, U caps->wNumAxes = min( dicaps.dwAxes, caps->wMaxAxes ); caps->wMaxButtons = 32;
- hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, z), DIPH_BYOFFSET ); + hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, axes[AXIS_Z]), DIPH_BYOFFSET ); if (SUCCEEDED(hr)) caps->wCaps |= JOYCAPS_HASZ; - hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, r), DIPH_BYOFFSET ); + hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, axes[AXIS_R]), DIPH_BYOFFSET ); if (SUCCEEDED(hr)) caps->wCaps |= JOYCAPS_HASR; - hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, u), DIPH_BYOFFSET ); + hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, axes[AXIS_U]), DIPH_BYOFFSET ); if (SUCCEEDED(hr)) caps->wCaps |= JOYCAPS_HASU; - hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, v), DIPH_BYOFFSET ); + hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, axes[AXIS_V]), DIPH_BYOFFSET ); if (SUCCEEDED(hr)) caps->wCaps |= JOYCAPS_HASV; hr = IDirectInputDevice8_GetObjectInfo( device, &instance, offsetof(struct joystick_state, pov), DIPH_BYOFFSET ); if (SUCCEEDED(hr)) caps->wCaps |= JOYCAPS_HASPOV|JOYCAPS_POV4DIR; @@ -509,12 +535,12 @@ MMRESULT WINAPI DECLSPEC_HOTPATCH joyGetPosEx( UINT id, JOYINFOEX *info ) } else { - if (info->dwFlags & JOY_RETURNX) info->dwXpos = state.x; - if (info->dwFlags & JOY_RETURNY) info->dwYpos = state.y; - if (info->dwFlags & JOY_RETURNZ) info->dwZpos = state.z; - if (info->dwFlags & JOY_RETURNR) info->dwRpos = state.r; - if (info->dwFlags & JOY_RETURNU) info->dwUpos = state.u; - if (info->dwFlags & JOY_RETURNV) info->dwVpos = state.v; + if (info->dwFlags & JOY_RETURNX) info->dwXpos = state.axes[AXIS_X]; + if (info->dwFlags & JOY_RETURNY) info->dwYpos = state.axes[AXIS_Y]; + if (info->dwFlags & JOY_RETURNZ) info->dwZpos = state.axes[AXIS_Z]; + if (info->dwFlags & JOY_RETURNR) info->dwRpos = state.axes[AXIS_R]; + if (info->dwFlags & JOY_RETURNU) info->dwUpos = state.axes[AXIS_U]; + if (info->dwFlags & JOY_RETURNV) info->dwVpos = state.axes[AXIS_V]; if (info->dwFlags & JOY_RETURNPOV) { if (state.pov == ~0) info->dwPOV = 0xffff;