From eb3577c1ebca161c7d603d1c757fb62168711106 Mon Sep 17 00:00:00 2001 From: Camilla Berglund Date: Fri, 15 Aug 2014 15:08:09 +0200 Subject: [PATCH] Add initial XInput support --- src/win32_init.c | 53 +++++++- src/win32_joystick.c | 301 +++++++++++++++++++++++++++++-------------- src/win32_joystick.h | 9 +- src/win32_platform.h | 52 ++++++-- src/win32_window.c | 29 ++++- 5 files changed, 323 insertions(+), 121 deletions(-) diff --git a/src/win32_init.c b/src/win32_init.c index f993064f..0a83c0e1 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -30,6 +30,8 @@ #include #include +#include +DEFINE_GUID(GUID_DEVINTERFACE_HID,0x4d1e55b2,0xf16f,0x11cf,0x88,0xcb,0x00,0x11,0x11,0x00,0x00,0x30); #if defined(_GLFW_USE_HYBRID_HPG) || defined(_GLFW_USE_OPTIMUS_HPG) @@ -69,12 +71,6 @@ static GLFWbool loadLibraries(void) return GLFW_FALSE; } - _glfw.win32.winmm.joyGetDevCaps = (JOYGETDEVCAPS_T) - GetProcAddress(_glfw.win32.winmm.instance, "joyGetDevCapsW"); - _glfw.win32.winmm.joyGetPos = (JOYGETPOS_T) - GetProcAddress(_glfw.win32.winmm.instance, "joyGetPos"); - _glfw.win32.winmm.joyGetPosEx = (JOYGETPOSEX_T) - GetProcAddress(_glfw.win32.winmm.instance, "joyGetPosEx"); _glfw.win32.winmm.timeGetTime = (TIMEGETTIME_T) GetProcAddress(_glfw.win32.winmm.instance, "timeGetTime"); @@ -90,6 +86,33 @@ static GLFWbool loadLibraries(void) _glfw.win32.user32.ChangeWindowMessageFilterEx = (CHANGEWINDOWMESSAGEFILTEREX_T) GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx"); + { + int i; + const char* names[] = + { + "xinput1_4.dll", + "xinput1_3.dll", + "xinput9_1_0.dll", + "xinput1_2.dll", + "xinput1_1.dll", + NULL + }; + + for (i = 0; names[i]; i++) + { + _glfw.win32.xinput.instance = LoadLibraryA(names[i]); + if (_glfw.win32.xinput.instance) + { + _glfw.win32.xinput.XInputGetCapabilities = (XINPUTGETCAPABILITIES_T) + GetProcAddress(_glfw.win32.xinput.instance, "XInputGetCapabilities"); + _glfw.win32.xinput.XInputGetState = (XINPUTGETSTATE_T) + GetProcAddress(_glfw.win32.xinput.instance, "XInputGetState"); + + break; + } + } + } + _glfw.win32.dwmapi.instance = LoadLibraryA("dwmapi.dll"); if (_glfw.win32.dwmapi.instance) { @@ -113,6 +136,9 @@ static GLFWbool loadLibraries(void) // static void freeLibraries(void) { + if (_glfw.win32.xinput.instance) + FreeLibrary(_glfw.win32.xinput.instance); + if (_glfw.win32.winmm.instance) FreeLibrary(_glfw.win32.winmm.instance); @@ -283,7 +309,20 @@ static HWND createHelperWindow(void) return NULL; } - return window; + // Register for HID device notifications + { + DEV_BROADCAST_DEVICEINTERFACE_W dbi; + ZeroMemory(&dbi, sizeof(dbi)); + dbi.dbcc_size = sizeof(dbi); + dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + dbi.dbcc_classguid = GUID_DEVINTERFACE_HID; + + RegisterDeviceNotificationW(window, + (DEV_BROADCAST_HDR*) &dbi, + DEVICE_NOTIFY_WINDOW_HANDLE); + } + + return window; } diff --git a/src/win32_joystick.c b/src/win32_joystick.c index 854dfe7d..9184e80a 100644 --- a/src/win32_joystick.c +++ b/src/win32_joystick.c @@ -1,8 +1,8 @@ //======================================================================== -// GLFW 3.2 Win32 - www.glfw.org +// GLFW 3.1 Win32 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard -// Copyright (c) 2006-2010 Camilla Berglund +// Copyright (c) 2006-2015 Camilla Berglund // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages @@ -27,22 +27,169 @@ #include "internal.h" -#include +#include + +#define _GLFW_UPDATE_BUTTONS 1 +#define _GLFW_UPDATE_AXES 2 -////////////////////////////////////////////////////////////////////////// -////// GLFW internal API ////// -////////////////////////////////////////////////////////////////////////// - -// Convert axis value to the [-1,1] range +// Returns a description fitting the specified XInput capabilities // -static float normalizeAxis(DWORD pos, DWORD min, DWORD max) +static const char* getDeviceDescription(const XINPUT_CAPABILITIES* xic) { - float fpos = (float) pos; - float fmin = (float) min; - float fmax = (float) max; + switch (xic->SubType) + { + case XINPUT_DEVSUBTYPE_WHEEL: + return "XInput Wheel"; + case XINPUT_DEVSUBTYPE_ARCADE_STICK: + return "XInput Arcade Stick"; + case XINPUT_DEVSUBTYPE_FLIGHT_STICK: + return "XInput Flight Stick"; + case XINPUT_DEVSUBTYPE_DANCE_PAD: + return "XInput Dance Pad"; + case XINPUT_DEVSUBTYPE_GUITAR: + return "XInput Guitar"; + case XINPUT_DEVSUBTYPE_DRUM_KIT: + return "XInput Drum Kit"; + case XINPUT_DEVSUBTYPE_GAMEPAD: + { + if (xic->Flags & XINPUT_CAPS_WIRELESS) + return "Wireless Xbox 360 Controller"; + else + return "Xbox 360 Controller"; + } + } - return (2.f * (fpos - fmin) / (fmax - fmin)) - 1.f; + return "Unknown XInput Device"; +} + +// Attempt to open the specified joystick device +// TODO: Pack state arrays for non-gamepad devices +// +static GLFWbool openJoystickDevice(DWORD index) +{ + int joy; + XINPUT_CAPABILITIES xic; + _GLFWjoystickWin32* js; + + for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++) + { + if (_glfw.win32_js[joy].present && _glfw.win32_js[joy].index == index) + return GLFW_FALSE; + } + + for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++) + { + if (!_glfw.win32_js[joy].present) + break; + } + + if (joy > GLFW_JOYSTICK_LAST) + return GLFW_FALSE; + + if (_glfw_XInputGetCapabilities(index, 0, &xic) != ERROR_SUCCESS) + return GLFW_FALSE; + + js = _glfw.win32_js + joy; + js->axisCount = 6; + js->buttonCount = 14; + js->present = GLFW_TRUE; + js->name = strdup(getDeviceDescription(&xic)); + js->index = index; + + return GLFW_TRUE; +} + +// Polls for and processes events the specified joystick +// +static GLFWbool pollJoystickEvents(_GLFWjoystickWin32* js, int flags) +{ + XINPUT_STATE xis; + DWORD result; + + if (!_glfw.win32.xinput.instance) + return GLFW_FALSE; + + if (!js->present) + return GLFW_FALSE; + + result = _glfw_XInputGetState(js->index, &xis); + if (result != ERROR_SUCCESS) + { + if (result == ERROR_DEVICE_NOT_CONNECTED) + { + free(js->name); + memset(js, 0, sizeof(_GLFWjoystickWin32)); + } + + return GLFW_FALSE; + } + + if (flags & _GLFW_UPDATE_AXES) + { + if (sqrtf((float) (xis.Gamepad.sThumbLX * xis.Gamepad.sThumbLX + + xis.Gamepad.sThumbLY * xis.Gamepad.sThumbLY)) > + (float) XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) + { + js->axes[0] = (xis.Gamepad.sThumbLX + 0.5f) / 32767.f; + js->axes[1] = (xis.Gamepad.sThumbLY + 0.5f) / 32767.f; + } + else + { + js->axes[0] = 0.f; + js->axes[1] = 0.f; + } + + if (sqrtf((float) (xis.Gamepad.sThumbRX * xis.Gamepad.sThumbRX + + xis.Gamepad.sThumbRY * xis.Gamepad.sThumbRY)) > + (float) XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) + { + js->axes[2] = (xis.Gamepad.sThumbRX + 0.5f) / 32767.f; + js->axes[3] = (xis.Gamepad.sThumbRY + 0.5f) / 32767.f; + } + else + { + js->axes[2] = 0.f; + js->axes[3] = 0.f; + } + + if (xis.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) + js->axes[4] = xis.Gamepad.bLeftTrigger / 127.5f - 1.f; + else + js->axes[4] = -1.f; + + if (xis.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) + js->axes[5] = xis.Gamepad.bRightTrigger / 127.5f - 1.f; + else + js->axes[5] = -1.f; + } + + if (flags & _GLFW_UPDATE_BUTTONS) + { + int i; + const WORD buttons[14] = + { + XINPUT_GAMEPAD_A, + XINPUT_GAMEPAD_B, + XINPUT_GAMEPAD_X, + XINPUT_GAMEPAD_Y, + XINPUT_GAMEPAD_LEFT_SHOULDER, + XINPUT_GAMEPAD_RIGHT_SHOULDER, + XINPUT_GAMEPAD_BACK, + XINPUT_GAMEPAD_START, + XINPUT_GAMEPAD_LEFT_THUMB, + XINPUT_GAMEPAD_RIGHT_THUMB, + XINPUT_GAMEPAD_DPAD_UP, + XINPUT_GAMEPAD_DPAD_RIGHT, + XINPUT_GAMEPAD_DPAD_DOWN, + XINPUT_GAMEPAD_DPAD_LEFT + }; + + for (i = 0; i < 14; i++) + js->buttons[i] = (xis.Gamepad.wButtons & buttons[i]) ? 1 : 0; + } + + return GLFW_TRUE; } @@ -54,16 +201,43 @@ static float normalizeAxis(DWORD pos, DWORD min, DWORD max) // void _glfwInitJoysticksWin32(void) { + _glfwDetectJoystickConnectionWin32(); } // Close all opened joystick handles // void _glfwTerminateJoysticksWin32(void) { - int i; + int joy; - for (i = 0; i < GLFW_JOYSTICK_LAST; i++) - free(_glfw.win32_js[i].name); + for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++) + free(_glfw.win32_js[joy].name); +} + +// Looks for new joysticks +// +void _glfwDetectJoystickConnectionWin32(void) +{ + DWORD i; + + if (!_glfw.win32.xinput.instance) + return; + + for (i = 0; i < XUSER_MAX_COUNT; i++) + openJoystickDevice(i); +} + +// Checks if any current joystick has been disconnected +// +void _glfwDetectJoystickDisconnectionWin32(void) +{ + DWORD i; + + if (!_glfw.win32.xinput.instance) + return; + + for (i = 0; i < XUSER_MAX_COUNT; i++) + pollJoystickEvents(_glfw.win32_js + i, 0); } @@ -73,105 +247,36 @@ void _glfwTerminateJoysticksWin32(void) int _glfwPlatformJoystickPresent(int joy) { - JOYINFO ji; - - if (_glfw_joyGetPos(joy, &ji) != JOYERR_NOERROR) - return GLFW_FALSE; - - return GLFW_TRUE; + _GLFWjoystickWin32* js = _glfw.win32_js + joy; + return pollJoystickEvents(js, 0); } const float* _glfwPlatformGetJoystickAxes(int joy, int* count) { - JOYCAPS jc; - JOYINFOEX ji; - float* axes = _glfw.win32_js[joy].axes; - - if (_glfw_joyGetDevCaps(joy, &jc, sizeof(JOYCAPS)) != JOYERR_NOERROR) + _GLFWjoystickWin32* js = _glfw.win32_js + joy; + if (!pollJoystickEvents(js, _GLFW_UPDATE_AXES)) return NULL; - ji.dwSize = sizeof(JOYINFOEX); - ji.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | - JOY_RETURNR | JOY_RETURNU | JOY_RETURNV; - if (_glfw_joyGetPosEx(joy, &ji) != JOYERR_NOERROR) - return NULL; - - axes[(*count)++] = normalizeAxis(ji.dwXpos, jc.wXmin, jc.wXmax); - axes[(*count)++] = normalizeAxis(ji.dwYpos, jc.wYmin, jc.wYmax); - - if (jc.wCaps & JOYCAPS_HASZ) - axes[(*count)++] = normalizeAxis(ji.dwZpos, jc.wZmin, jc.wZmax); - - if (jc.wCaps & JOYCAPS_HASR) - axes[(*count)++] = normalizeAxis(ji.dwRpos, jc.wRmin, jc.wRmax); - - if (jc.wCaps & JOYCAPS_HASU) - axes[(*count)++] = normalizeAxis(ji.dwUpos, jc.wUmin, jc.wUmax); - - if (jc.wCaps & JOYCAPS_HASV) - axes[(*count)++] = normalizeAxis(ji.dwVpos, jc.wVmin, jc.wVmax); - - return axes; + *count = js->axisCount; + return js->axes; } const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count) { - JOYCAPS jc; - JOYINFOEX ji; - unsigned char* buttons = _glfw.win32_js[joy].buttons; - - if (_glfw_joyGetDevCaps(joy, &jc, sizeof(JOYCAPS)) != JOYERR_NOERROR) + _GLFWjoystickWin32* js = _glfw.win32_js + joy; + if (!pollJoystickEvents(js, _GLFW_UPDATE_BUTTONS)) return NULL; - ji.dwSize = sizeof(JOYINFOEX); - ji.dwFlags = JOY_RETURNBUTTONS | JOY_RETURNPOV; - if (_glfw_joyGetPosEx(joy, &ji) != JOYERR_NOERROR) - return NULL; - - while (*count < (int) jc.wNumButtons) - { - buttons[*count] = (unsigned char) - (ji.dwButtons & (1UL << *count) ? GLFW_PRESS : GLFW_RELEASE); - (*count)++; - } - - // Virtual buttons - Inject data from hats - // Each hat is exposed as 4 buttons which exposes 8 directions with - // concurrent button presses - // NOTE: this API exposes only one hat - - if ((jc.wCaps & JOYCAPS_HASPOV) && (jc.wCaps & JOYCAPS_POV4DIR)) - { - int i, value = ji.dwPOV / 100 / 45; - - // Bit fields of button presses for each direction, including nil - const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 }; - - if (value < 0 || value > 8) - value = 8; - - for (i = 0; i < 4; i++) - { - if (directions[value] & (1 << i)) - buttons[(*count)++] = GLFW_PRESS; - else - buttons[(*count)++] = GLFW_RELEASE; - } - } - - return buttons; + *count = js->buttonCount; + return js->buttons; } const char* _glfwPlatformGetJoystickName(int joy) { - JOYCAPS jc; - - if (_glfw_joyGetDevCaps(joy, &jc, sizeof(JOYCAPS)) != JOYERR_NOERROR) + _GLFWjoystickWin32* js = _glfw.win32_js + joy; + if (!pollJoystickEvents(js, 0)) return NULL; - free(_glfw.win32_js[joy].name); - _glfw.win32_js[joy].name = _glfwCreateUTF8FromWideStringWin32(jc.szPname); - - return _glfw.win32_js[joy].name; + return js->name; } diff --git a/src/win32_joystick.h b/src/win32_joystick.h index ea999cd8..e57d16ea 100644 --- a/src/win32_joystick.h +++ b/src/win32_joystick.h @@ -30,18 +30,23 @@ #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE \ _GLFWjoystickWin32 win32_js[GLFW_JOYSTICK_LAST + 1] - // Win32-specific per-joystick data // typedef struct _GLFWjoystickWin32 { + GLFWbool present; float axes[6]; - unsigned char buttons[36]; // 32 buttons plus one hat + int axisCount; + unsigned char buttons[14]; + int buttonCount; char* name; + DWORD index; } _GLFWjoystickWin32; void _glfwInitJoysticksWin32(void); void _glfwTerminateJoysticksWin32(void); +void _glfwDetectJoystickConnectionWin32(void); +void _glfwDetectJoystickDisconnectionWin32(void); #endif // _glfw3_win32_joystick_h_ diff --git a/src/win32_platform.h b/src/win32_platform.h index b94b0b0d..6ec69c6e 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -63,6 +63,7 @@ #include #include +#include #include #if defined(_MSC_VER) @@ -120,16 +121,42 @@ typedef enum PROCESS_DPI_AWARENESS } PROCESS_DPI_AWARENESS; #endif /*DPI_ENUMS_DECLARED*/ +// HACK: Define macros that some older xinput.h variants don't +#ifndef XINPUT_CAPS_WIRELESS + #define XINPUT_CAPS_WIRELESS 0x0002 +#endif +#ifndef XINPUT_DEVSUBTYPE_WHEEL + #define XINPUT_DEVSUBTYPE_WHEEL 0x02 +#endif +#ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK + #define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03 +#endif +#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK + #define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04 +#endif +#ifndef XINPUT_DEVSUBTYPE_DANCE_PAD + #define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05 +#endif +#ifndef XINPUT_DEVSUBTYPE_GUITAR + #define XINPUT_DEVSUBTYPE_GUITAR 0x06 +#endif +#ifndef XINPUT_DEVSUBTYPE_DRUM_KIT + #define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08 +#endif +#ifndef XINPUT_DEVSUBTYPE_ARCADE_PAD + #define XINPUT_DEVSUBTYPE_ARCADE_PAD 0x13 +#endif + // winmm.dll function pointer typedefs -typedef MMRESULT (WINAPI * JOYGETDEVCAPS_T)(UINT,LPJOYCAPS,UINT); -typedef MMRESULT (WINAPI * JOYGETPOS_T)(UINT,LPJOYINFO); -typedef MMRESULT (WINAPI * JOYGETPOSEX_T)(UINT,LPJOYINFOEX); typedef DWORD (WINAPI * TIMEGETTIME_T)(void); -#define _glfw_joyGetDevCaps _glfw.win32.winmm.joyGetDevCaps -#define _glfw_joyGetPos _glfw.win32.winmm.joyGetPos -#define _glfw_joyGetPosEx _glfw.win32.winmm.joyGetPosEx #define _glfw_timeGetTime _glfw.win32.winmm.timeGetTime +// xinput.dll function pointer typedefs +typedef DWORD (WINAPI * XINPUTGETCAPABILITIES_T)(DWORD,DWORD,XINPUT_CAPABILITIES*); +typedef DWORD (WINAPI * XINPUTGETSTATE_T)(DWORD,XINPUT_STATE*); +#define _glfw_XInputGetCapabilities _glfw.win32.xinput.XInputGetCapabilities +#define _glfw_XInputGetState _glfw.win32.xinput.XInputGetState + // user32.dll function pointer typedefs typedef BOOL (WINAPI * SETPROCESSDPIAWARE_T)(void); typedef BOOL (WINAPI * CHANGEWINDOWMESSAGEFILTEREX_T)(HWND,UINT,DWORD,PCHANGEFILTERSTRUCT); @@ -217,16 +244,19 @@ typedef struct _GLFWlibraryWin32 // winmm.dll struct { HINSTANCE instance; - JOYGETDEVCAPS_T joyGetDevCaps; - JOYGETPOS_T joyGetPos; - JOYGETPOSEX_T joyGetPosEx; TIMEGETTIME_T timeGetTime; } winmm; // user32.dll struct { - HINSTANCE instance; - SETPROCESSDPIAWARE_T SetProcessDPIAware; + HINSTANCE instance; + XINPUTGETCAPABILITIES_T XInputGetCapabilities; + XINPUTGETSTATE_T XInputGetState; + } xinput; + + struct { + HINSTANCE instance; + SETPROCESSDPIAWARE_T SetProcessDPIAware; CHANGEWINDOWMESSAGEFILTEREX_T ChangeWindowMessageFilterEx; } user32; diff --git a/src/win32_window.c b/src/win32_window.c index ed822b42..ed6bbe7f 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -376,10 +376,33 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, switch (uMsg) { - case WM_DISPLAYCHANGE: + case WM_DEVICECHANGE: { - _glfwInputMonitorChange(); - return 0; + if (wParam == DBT_DEVNODES_CHANGED) + { + _glfwInputMonitorChange(); + return TRUE; + } + else if (wParam == DBT_DEVICEARRIVAL) + { + DEV_BROADCAST_HDR* dbh = (DEV_BROADCAST_HDR*) lParam; + if (dbh) + { + if (dbh->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) + _glfwDetectJoystickConnectionWin32(); + } + } + else if (wParam == DBT_DEVICEREMOVECOMPLETE) + { + DEV_BROADCAST_HDR* dbh = (DEV_BROADCAST_HDR*) lParam; + if (dbh) + { + if (dbh->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) + _glfwDetectJoystickDisconnectionWin32(); + } + } + + break; } }