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