1 /*
2 * Copyright (C) 2014-2020 Garrett Brown
3 * Copyright (C) 2014-2020 Team Kodi
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSE.md for more information.
7 */
8
9 #include "JoystickDirectInput.h"
10 #include "JoystickInterfaceDirectInput.h"
11 #include "api/JoystickTypes.h"
12 #include "log/Log.h"
13 #include "utils/CommonMacros.h"
14 #include "utils/windows/CharsetConverter.h"
15
16 using namespace JOYSTICK;
17
18 #define AXIS_MIN -32768 /* minimum value for axis coordinate */
19 #define AXIS_MAX 32767 /* maximum value for axis coordinate */
20
21 #define JOY_POV_360 JOY_POVBACKWARD * 2
22 #define JOY_POV_NE (JOY_POVFORWARD + JOY_POVRIGHT) / 2
23 #define JOY_POV_SE (JOY_POVRIGHT + JOY_POVBACKWARD) / 2
24 #define JOY_POV_SW (JOY_POVBACKWARD + JOY_POVLEFT) / 2
25 #define JOY_POV_NW (JOY_POVLEFT + JOY_POV_360) / 2
26
CJoystickDirectInput(GUID deviceGuid,LPDIRECTINPUTDEVICE8 joystickDevice,const TCHAR * strName)27 CJoystickDirectInput::CJoystickDirectInput(GUID deviceGuid,
28 LPDIRECTINPUTDEVICE8 joystickDevice,
29 const TCHAR *strName)
30 : CJoystick(EJoystickInterface::DIRECTINPUT),
31 m_deviceGuid(deviceGuid),
32 m_joystickDevice(joystickDevice)
33 {
34 #if !defined(_UNICODE)
35 SetName(strName);
36 #else
37 SetName(KODI::PLATFORM::WINDOWS::FromW(strName));
38 #endif
39 }
40
~CJoystickDirectInput()41 CJoystickDirectInput::~CJoystickDirectInput()
42 {
43 if (m_bAcquired)
44 m_joystickDevice->Unacquire();
45
46 SAFE_RELEASE(m_joystickDevice);
47 }
48
Equals(const CJoystick * rhs) const49 bool CJoystickDirectInput::Equals(const CJoystick* rhs) const
50 {
51 if (rhs == nullptr)
52 return false;
53
54 const CJoystickDirectInput* rhsDirectInput = dynamic_cast<const CJoystickDirectInput*>(rhs);
55 if (rhsDirectInput == nullptr)
56 return false;
57
58 return m_deviceGuid == rhsDirectInput->m_deviceGuid;
59 }
60
Initialize(void)61 bool CJoystickDirectInput::Initialize(void)
62 {
63 HRESULT hr;
64
65 // This will be done automatically when we're in the foreground but
66 // let's do it here to check that we can acquire it and that no other
67 // app has it in exclusive mode
68 m_bAcquired = !(FAILED(m_joystickDevice->Acquire()));
69 if (!m_bAcquired)
70 {
71 esyslog("%s: Failed to acquire device on: %s", __FUNCTION__, Name().c_str());
72 return false;
73 }
74
75 // Get capabilities
76 DIDEVCAPS diDevCaps;
77 diDevCaps.dwSize = sizeof(DIDEVCAPS);
78 hr = m_joystickDevice->GetCapabilities(&diDevCaps);
79 if (FAILED(hr))
80 {
81 esyslog("%s: Failed to GetCapabilities for: %s", __FUNCTION__, Name().c_str());
82 return false;
83 }
84
85 SetButtonCount(diDevCaps.dwButtons);
86 SetHatCount(diDevCaps.dwPOVs);
87 SetAxisCount(diDevCaps.dwAxes);
88
89 // Get vendor and product ID
90 DIPROPDWORD diDevProperty;
91 diDevProperty.diph.dwSize = sizeof(DIPROPDWORD);
92 diDevProperty.diph.dwHeaderSize = sizeof(DIPROPHEADER);
93 diDevProperty.diph.dwObj = 0; // device property
94 diDevProperty.diph.dwHow = DIPH_DEVICE;
95
96 hr = m_joystickDevice->GetProperty(DIPROP_VIDPID, &diDevProperty.diph);
97 if (FAILED(hr))
98 {
99 esyslog("%s: Failed to GetProperty for: %s", __FUNCTION__, Name().c_str());
100 return false;
101 }
102
103 SetVendorID(LOWORD(diDevProperty.dwData));
104 SetProductID(HIWORD(diDevProperty.dwData));
105
106 // Initialize axes
107 // Enumerate the joystick objects. The callback function enables user
108 // interface elements for objects that are found, and sets the min/max
109 // values properly for discovered axes.
110 hr = m_joystickDevice->EnumObjects(EnumObjectsCallback, m_joystickDevice, DIDFT_ALL);
111 if (FAILED(hr))
112 {
113 esyslog("%s: Failed to enumerate objects", __FUNCTION__);
114 return false;
115 }
116
117 return CJoystick::Initialize();
118 }
119
120 //-----------------------------------------------------------------------------
121 // Name: EnumObjectsCallback()
122 // Desc: Callback function for enumerating objects (axes, buttons, POVs) on a
123 // joystick. This function enables user interface elements for objects
124 // that are found to exist, and scales axes min/max values.
125 //-----------------------------------------------------------------------------
EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE * pdidoi,VOID * pContext)126 BOOL CALLBACK CJoystickDirectInput::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext)
127 {
128 LPDIRECTINPUTDEVICE8 pJoy = static_cast<LPDIRECTINPUTDEVICE8>(pContext);
129
130 // For axes that are returned, set the DIPROP_RANGE property for the
131 // enumerated axis in order to scale min/max values.
132 if (pdidoi->dwType & DIDFT_AXIS)
133 {
134 DIPROPRANGE diprg;
135 diprg.diph.dwSize = sizeof(DIPROPRANGE);
136 diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
137 diprg.diph.dwHow = DIPH_BYID;
138 diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis
139 diprg.lMin = AXIS_MIN;
140 diprg.lMax = AXIS_MAX;
141
142 // Set the range for the axis
143 HRESULT hr = pJoy->SetProperty(DIPROP_RANGE, &diprg.diph);
144 if (FAILED(hr))
145 esyslog("%s : Failed to set property on %s", __FUNCTION__, pdidoi->tszName);
146 }
147 return DIENUM_CONTINUE;
148 }
149
ScanEvents(void)150 bool CJoystickDirectInput::ScanEvents(void)
151 {
152 HRESULT hr;
153 DIJOYSTATE2 js; // DInput joystick state
154
155 hr = m_joystickDevice->Poll();
156
157 if (FAILED(hr))
158 {
159 int i = 0;
160 // DInput is telling us that the input stream has been interrupted. We
161 // aren't tracking any state between polls, so we don't have any special
162 // reset that needs to be done. We just re-acquire and try again 10 times.
163 do
164 {
165 hr = m_joystickDevice->Acquire();
166 } while (hr == DIERR_INPUTLOST && i++ < 10);
167
168 // hr may be DIERR_OTHERAPPHASPRIO or other errors. This may occur when the
169 // app is minimized or in the process of switching, so just try again later.
170 return false;
171 }
172
173 // Get the input's device state
174 hr = m_joystickDevice->GetDeviceState(sizeof(DIJOYSTATE2), &js);
175 if (FAILED(hr))
176 return false; // The device should have been acquired during the Poll()
177
178 // Gamepad buttons
179 for (unsigned int b = 0; b < ButtonCount(); b++)
180 SetButtonValue(b, (js.rgbButtons[b] & 0x80) ? JOYSTICK_STATE_BUTTON_PRESSED : JOYSTICK_STATE_BUTTON_UNPRESSED);
181
182 // Gamepad hats
183 for (unsigned int h = 0; h < HatCount(); h++)
184 {
185 JOYSTICK_STATE_HAT hatState = JOYSTICK_STATE_HAT_UNPRESSED;
186
187 const bool bCentered = ((js.rgdwPOV[h] & 0xFFFF) == 0xFFFF);
188 if (!bCentered)
189 {
190 if ((JOY_POV_NW <= js.rgdwPOV[h] && js.rgdwPOV[h] <= JOY_POV_360) || js.rgdwPOV[h] <= JOY_POV_NE)
191 hatState = JOYSTICK_STATE_HAT_UP;
192 else if (JOY_POV_SE <= js.rgdwPOV[h] && js.rgdwPOV[h] <= JOY_POV_SW)
193 hatState = JOYSTICK_STATE_HAT_DOWN;
194
195 if (JOY_POV_NE <= js.rgdwPOV[h] && js.rgdwPOV[h] <= JOY_POV_SE)
196 hatState = (JOYSTICK_STATE_HAT)(hatState | JOYSTICK_STATE_HAT_RIGHT);
197 else if (JOY_POV_SW <= js.rgdwPOV[h] && js.rgdwPOV[h] <= JOY_POV_NW)
198 hatState = (JOYSTICK_STATE_HAT)(hatState | JOYSTICK_STATE_HAT_LEFT);
199 }
200
201 SetHatValue(h, hatState);
202 }
203
204 // Gamepad axes
205 const long amounts[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz, js.rglSlider[0], js.rglSlider[1] };
206 for (unsigned int a = 0; a < ARRAY_SIZE(amounts); a++)
207 SetAxisValue(a, amounts[a], AXIS_MAX);
208
209 return true;
210 }
211